Browse Source

* Don't check specific html elements from the display code (Fixes #446)
* Renamed and reworked fbuClip to clippingDisplay
* Added tests for clippingDisplay
* Use the a noVNC_container which covers the entire page to get the full size
(Fixes #463)
* Added maxWidth and maxHeight to the canvas which can limit the viewport size
* Only show either the canvas or the logo, hide one when the other is shown
* Always center the canvas (previously it was only centered when not clipping)
* Removed iOS specific "position-fixed" fixes and start calling setBarPosition
on every resize
* Removed the noVNC_screen_pad

samhed 10 years ago
parent
commit
fdedbafb1d
5 changed files with 194 additions and 111 deletions
  1. 14 8
      include/base.css
  2. 65 33
      include/display.js
  3. 80 67
      include/ui.js
  4. 34 0
      tests/test.display.js
  5. 1 3
      vnc.html

+ 14 - 8
include/base.css

@@ -112,13 +112,7 @@ html {
 
 /* Do not set width/height for VNC_screen or VNC_canvas or incorrect
  * scaling will occur. Canvas resizes to remote VNC settings */
-#noVNC_screen_pad {
-  margin: 0px;
-  padding: 0px;
-  height: 36px;
-}
 #noVNC_screen {
-  text-align: center;
   display: table;
   width:100%;
   height:100%;
@@ -127,13 +121,25 @@ html {
   /*border-top-left-radius: 800px 600px;*/
 }
 
-#noVNC_container, #noVNC_canvas {
+#noVNC_container {
+  display: none;
+  position: absolute;
   margin: 0px;
   padding: 0px;
+  bottom: 0px;
+  top: 36px; /* the height of the control bar */
+  left: 0px;
+  right: 0px;
+  width: auto;
+  height: auto;
 }
 
 #noVNC_canvas {
-  left: 0px;
+  position: absolute;
+  left: 0;
+  right: 0;
+  margin-left: auto;
+  margin-right: auto;
 }
 
 #VNC_clipboard_clear_button {

+ 65 - 33
include/display.js

@@ -1,6 +1,7 @@
 /*
  * noVNC: HTML5 VNC client
  * Copyright (C) 2012 Joel Martin
+ * Copyright (C) 2015 Samuel Mannehed for Cendio AB
  * Licensed under MPL 2.0 (see LICENSE.txt)
  *
  * See README.md for usage and integration instructions.
@@ -24,6 +25,10 @@ var Display;
         this._fb_width = 0;
         this._fb_height = 0;
 
+        // the size limit of the viewport (start disabled)
+        this._maxWidth = 0;
+        this._maxHeight = 0;
+
         // the visible "physical canvas" viewport
         this._viewportLoc = { 'x': 0, 'y': 0, 'w': 0, 'h': 0 };
         this._cleanRect = { 'x1': 0, 'y1': 0, 'x2': -1, 'y2': -1 };
@@ -202,8 +207,7 @@ var Display;
 
         viewportChangeSize: function(width, height) {
 
-            if (!this._viewport ||
-                typeof(width) === "undefined" || typeof(height) === "undefined") {
+            if (typeof(width) === "undefined" || typeof(height) === "undefined") {
 
                 Util.Debug("Setting viewport to full display region");
                 width = this._fb_width;
@@ -213,41 +217,49 @@ var Display;
             var vp = this._viewportLoc;
             if (vp.w !== width || vp.h !== height) {
 
+                if (this._viewport) {
+                    if (this._maxWidth !== 0 && width > this._maxWidth) {
+                        width = this._maxWidth;
+                    }
+                    if (this._maxHeight !== 0 && height > this._maxHeight) {
+                        height = this._maxHeight;
+                    }
+                }
+
                 var cr = this._cleanRect;
 
                 if (width < vp.w &&  cr.x2 > vp.x + width - 1) {
                     cr.x2 = vp.x + width - 1;
                 }
-
                 if (height < vp.h &&  cr.y2 > vp.y + height - 1) {
                     cr.y2 = vp.y + height - 1;
                 }
 
-                if (this.fbuClip()) {
-                    // clipping
-                    vp.w = window.innerWidth;
-                    var cb = document.getElementById('noVNC-control-bar');
-                    var controlbar_h = (cb !== null) ? cb.offsetHeight : 0;
-                    vp.h = window.innerHeight - controlbar_h - 5;
-                } else {
-                    // scrollbars
-                    vp.w = width;
-                    vp.h = height;
-                }
+                vp.w = width;
+                vp.h = height;
 
-                var saveImg = null;
                 var canvas = this._target;
-                if (vp.w > 0 && vp.h > 0 && canvas.width > 0 && canvas.height > 0) {
-                    var img_width = canvas.width < vp.w ? canvas.width : vp.w;
-                    var img_height = canvas.height < vp.h ? canvas.height : vp.h;
-                    saveImg = this._drawCtx.getImageData(0, 0, img_width, img_height);
-                }
+                if (canvas.width !== width || canvas.height !== height) {
+
+                    // We have to save the canvas data since changing the size will clear it
+                    var saveImg = null;
+                    if (vp.w > 0 && vp.h > 0 && canvas.width > 0 && canvas.height > 0) {
+                        var img_width = canvas.width < vp.w ? canvas.width : vp.w;
+                        var img_height = canvas.height < vp.h ? canvas.height : vp.h;
+                        saveImg = this._drawCtx.getImageData(0, 0, img_width, img_height);
+                    }
+
+                    if (canvas.width  !== width)  { canvas.width  = width; }
+                    if (canvas.height !== height) { canvas.height = height; }
 
-                canvas.width = vp.w;
-                canvas.height = vp.h;
+                    if (this._viewport) {
+                        canvas.style.height = height + 'px';
+                        canvas.style.width = width + 'px';
+                    }
 
-                if (saveImg) {
-                    this._drawCtx.putImageData(saveImg, 0, 0);
+                    if (saveImg) {
+                        this._drawCtx.putImageData(saveImg, 0, 0);
+                    }
                 }
             }
         },
@@ -487,12 +499,18 @@ var Display;
             this._target.style.cursor = "none";
         },
 
-        fbuClip: function () {
-            var cb = document.getElementById('noVNC-control-bar');
-            var controlbar_h = (cb !== null) ? cb.offsetHeight : 0;
-            return (this._viewport &&
-                    (this._fb_width > window.innerWidth
-                     || this._fb_height > window.innerHeight - controlbar_h - 5));
+        clippingDisplay: function () {
+            var vp = this._viewportLoc;
+
+            var fbClip = this._fb_width > vp.w || this._fb_height > vp.h;
+            var limitedVp = this._maxWidth !== 0 && this._maxHeight !== 0;
+            var clipping = false;
+
+            if (limitedVp) {
+                clipping = vp.w > this._maxWidth || vp.h > this._maxHeight;
+            }
+
+            return fbClip || (limitedVp && clipping);
         },
 
         // Overridden getters/setters
@@ -558,8 +576,20 @@ var Display;
         _rescale: function (factor) {
             this._scale = factor;
 
-            this._target.style.width = Math.round(factor * this._fb_width) + 'px';
-            this._target.style.height = Math.round(factor * this._fb_height) + 'px';
+            var w;
+            var h;
+
+            if (this._viewport &&
+                this._maxWidth !== 0 && this._maxHeight !== 0) {
+                w = Math.min(this._fb_width, this._maxWidth);
+                h = Math.min(this._fb_height, this._maxHeight);
+            } else {
+                w = this._fb_width;
+                h = this._fb_height;
+            }
+
+            this._target.style.width = Math.round(factor * w) + 'px';
+            this._target.style.height = Math.round(factor * h) + 'px';
         },
 
         _setFillColor: function (color) {
@@ -661,9 +691,11 @@ var Display;
         ['true_color', 'rw', 'bool'],  // Use true-color pixel data
         ['colourMap', 'rw', 'arr'],    // Colour map array (when not true-color)
         ['scale', 'rw', 'float'],      // Display area scale factor 0.0 - 1.0
-        ['viewport', 'rw', 'bool'],    // Use a viewport set with viewportChange()
+        ['viewport', 'rw', 'bool'],    // Use viewport clipping
         ['width', 'rw', 'int'],        // Display area width
         ['height', 'rw', 'int'],       // Display area height
+        ['maxWidth', 'rw', 'int'],     // Viewport max width (0 if disabled)
+        ['maxHeight', 'rw', 'int'],    // Viewport max height (0 if disabled)
 
         ['render_mode', 'ro', 'str'],  // Canvas rendering mode (read-only)
 

+ 80 - 67
include/ui.js

@@ -1,7 +1,7 @@
 /*
  * noVNC: HTML5 VNC client
  * Copyright (C) 2012 Joel Martin
- * Copyright (C) 2013 Samuel Mannehed for Cendio AB
+ * Copyright (C) 2015 Samuel Mannehed for Cendio AB
  * Licensed under MPL 2.0 (see LICENSE.txt)
  *
  * See README.md for usage and integration instructions.
@@ -45,33 +45,6 @@ var UI;
             WebUtil.initSettings(UI.start, callback);
         },
 
-        onresize: function (callback) {
-            var innerW = window.innerWidth;
-            var innerH = window.innerHeight;
-            var controlbarH = $D('noVNC-control-bar').offsetHeight;
-            // For some unknown reason the container is higher than the canvas,
-            // 5px higher in Firefox and 4px higher in Chrome
-            var padding = 5;
-            var effectiveH = innerH - controlbarH - padding;
-
-            var display = UI.rfb.get_display();
-
-            if (innerW !== undefined && innerH !== undefined) {
-                var scaleType = UI.getSetting('resize');
-                if (scaleType === 'remote') {
-                    // use remote resizing
-                    Util.Debug('Attempting setDesktopSize(' + innerW + ', ' + effectiveH + ')');
-                    UI.rfb.setDesktopSize(innerW, effectiveH);
-                } else if (scaleType === 'scale' || scaleType === 'downscale') {
-                    // use local scaling
-                    var downscaleOnly = scaleType === 'downscale';
-                    var scaleRatio = display.autoscale(innerW, effectiveH, downscaleOnly);
-                    UI.rfb.get_mouse().set_scale(scaleRatio);
-                    Util.Debug('Scaling by ' + UI.rfb.get_mouse().get_scale());
-                }
-            }
-        },
-
         // Render default UI and initialize settings menu
         start: function(callback) {
             UI.isTouchDevice = 'ontouchstart' in document.documentElement;
@@ -136,6 +109,8 @@ var UI;
 
             UI.updateVisualState();
 
+            $D('noVNC_host').focus();
+
             // Show mouse selector buttons on touch screen devices
             if (UI.isTouchDevice) {
                 // Show mobile buttons
@@ -148,29 +123,14 @@ var UI;
                 UI.initSetting('clip', false);
             }
 
-            //iOS Safari does not support CSS position:fixed.
-            //This detects iOS devices and enables javascript workaround.
-            if ((navigator.userAgent.match(/iPhone/i)) ||
-                (navigator.userAgent.match(/iPod/i)) ||
-                (navigator.userAgent.match(/iPad/i))) {
-                //UI.setOnscroll();
-                //UI.setResize();
-            }
-            UI.setBarPosition();
-
-            $D('noVNC_host').focus();
-
             UI.setViewClip();
+            UI.setBarPosition();
 
             Util.addEvent(window, 'resize', function () {
+                UI.onresize();
                 UI.setViewClip();
-                // When the window has been resized, wait until the size remains
-                // the same for 0.5 seconds before sending the request for changing
-                // the resolution of the session
-                clearTimeout(resizeTimeout);
-                resizeTimeout = setTimeout(function(){
-                    UI.onresize();
-                }, 500);
+                UI.updateViewDragButton();
+                UI.setBarPosition();
             } );
 
             Util.addEvent(window, 'load', UI.keyboardinputReset);
@@ -258,6 +218,55 @@ var UI;
             };
         },
 
+        onresize: function (callback) {
+            var size = UI.getCanvasLimit();
+
+            if (size && UI.rfb_state === 'normal' && UI.rfb.get_display()) {
+                var display = UI.rfb.get_display();
+                var scaleType = UI.getSetting('resize');
+                if (scaleType === 'remote') {
+                    // use remote resizing
+
+                    // When the local window has been resized, wait until the size remains
+                    // the same for 0.5 seconds before sending the request for changing
+                    // the resolution of the session
+                    clearTimeout(resizeTimeout);
+                    resizeTimeout = setTimeout(function(){
+                        display.set_maxWidth(size.w);
+                        display.set_maxHeight(size.h);
+                        Util.Debug('Attempting setDesktopSize(' +
+                                   size.w + ', ' + size.h + ')');
+                        UI.rfb.setDesktopSize(size.w, size.h);
+                    }, 500);
+                } else if (scaleType === 'scale' || scaleType === 'downscale') {
+                    // use local scaling
+
+                    var downscaleOnly = scaleType === 'downscale';
+                    var scaleRatio = display.autoscale(size.w, size.h, downscaleOnly);
+                    UI.rfb.get_mouse().set_scale(scaleRatio);
+                    Util.Debug('Scaling by ' + UI.rfb.get_mouse().get_scale());
+                }
+            }
+        },
+
+        getCanvasLimit: function () {
+            var container = $D('noVNC_container');
+
+            // Hide the scrollbars until the size is calculated
+            container.style.overflow = "hidden";
+
+            var w = Util.getPosition(container).width;
+            var h = Util.getPosition(container).height;
+
+            container.style.overflow = "visible";
+
+            if (isNaN(w) || isNaN(h)) {
+                return false;
+            } else {
+                return {w: w, h: h};
+            }
+        },
+
         // Read form control compatible setting from cookie
         getSetting: function(name) {
             var ctrl = $D('noVNC_' + name);
@@ -613,6 +622,7 @@ var UI;
                     break;
                 case 'disconnected':
                     $D('noVNC_logo').style.display = "block";
+                    $D('noVNC_container').style.display = "none";
                     /* falls through */
                 case 'loaded':
                     klass = "noVNC_status_normal";
@@ -781,6 +791,7 @@ var UI;
             //Close dialog.
             setTimeout(UI.setBarPosition, 100);
             $D('noVNC_logo').style.display = "none";
+            $D('noVNC_container').style.display = "inline";
         },
 
         disconnect: function() {
@@ -791,6 +802,8 @@ var UI;
             UI.rfb.set_onFBUComplete(UI.FBUComplete);
 
             $D('noVNC_logo').style.display = "block";
+            $D('noVNC_container').style.display = "none";
+
             // Don't display the connection settings until we're actually disconnected
         },
 
@@ -839,17 +852,30 @@ var UI;
                 // Turn clipping off
                 UI.updateSetting('clip', false);
                 display.set_viewport(false);
-                $D('noVNC_canvas').style.position = 'static';
+                display.set_maxWidth(0);
+                display.set_maxHeight(0);
                 display.viewportChangeSize();
             }
             if (UI.getSetting('clip')) {
                 // If clipping, update clipping settings
-                $D('noVNC_canvas').style.position = 'absolute';
-                var pos = Util.getPosition($D('noVNC_canvas'));
-                var new_w = window.innerWidth - pos.x;
-                var new_h = window.innerHeight - pos.y;
                 display.set_viewport(true);
-                display.viewportChangeSize(new_w, new_h);
+
+                var size = UI.getCanvasLimit();
+                if (size) {
+                    display.set_maxWidth(size.w);
+                    display.set_maxHeight(size.h);
+
+                    // Hide potential scrollbars that can skew the position
+                    $D('noVNC_container').style.overflow = "hidden";
+
+                    // The x position marks the left margin of the canvas,
+                    // remove the margin from both sides to keep it centered
+                    var new_w = size.w - (2 * Util.getPosition($D('noVNC_canvas')).x);
+
+                    $D('noVNC_container').style.overflow = "visible";
+
+                    display.viewportChangeSize(new_w, size.h);
+                }
             }
         },
 
@@ -878,7 +904,7 @@ var UI;
             var vmb = $D('noVNC_view_drag_button');
             if (UI.rfb_state === 'normal' &&
                 UI.rfb.get_display().get_viewport() &&
-                UI.rfb.get_display().fbuClip()) {
+                UI.rfb.get_display().clippingDisplay()) {
                 vmb.style.display = "inline";
             } else {
                 vmb.style.display = "none";
@@ -1058,19 +1084,6 @@ var UI;
             UI.keyboardVisible = false;
         },
 
-        // iOS < Version 5 does not support position fixed. Javascript workaround:
-        setOnscroll: function() {
-            window.onscroll = function() {
-                UI.setBarPosition();
-            };
-        },
-
-        setResize: function () {
-            window.onResize = function() {
-                UI.setBarPosition();
-            };
-        },
-
         //Helper to add options to dropdown.
         addOption: function(selectbox, text, value) {
             var optn = document.createElement("OPTION");

+ 34 - 0
tests/test.display.js

@@ -134,6 +134,40 @@ describe('Display/Canvas Helper', function () {
         });
     });
 
+    describe('clipping', function () {
+        var display;
+        beforeEach(function () {
+            display = new Display({ target: document.createElement('canvas'), prefer_js: false, viewport: true });
+            display.resize(4, 3);
+        });
+
+        it('should report true when no max-size and framebuffer > viewport', function () {
+            display.viewportChangeSize(2,2);
+            var clipping = display.clippingDisplay();
+            expect(clipping).to.be.true;
+        });
+
+        it('should report false when no max-size and framebuffer = viewport', function () {
+            var clipping = display.clippingDisplay();
+            expect(clipping).to.be.false;
+        });
+
+        it('should report true when viewport > max-size and framebuffer > viewport', function () {
+            display.viewportChangeSize(2,2);
+            display.set_maxWidth(1);
+            display.set_maxHeight(2);
+            var clipping = display.clippingDisplay();
+            expect(clipping).to.be.true;
+        });
+
+        it('should report true when viewport > max-size and framebuffer = viewport', function () {
+            display.set_maxWidth(1);
+            display.set_maxHeight(2);
+            var clipping = display.clippingDisplay();
+            expect(clipping).to.be.true;
+        });
+    });
+
     describe('resizing', function () {
         var display;
         beforeEach(function () {

+ 1 - 3
vnc.html

@@ -203,13 +203,11 @@
 
 
     <div id="noVNC_screen">
-        <div id="noVNC_screen_pad"></div>
-
         <h1 id="noVNC_logo"><span>no</span><br />VNC</h1>
 
         <!-- HTML5 Canvas -->
         <div id="noVNC_container">
-            <canvas id="noVNC_canvas" width="640px" height="20px">
+            <canvas id="noVNC_canvas" width="0" height="0">
                         Canvas not supported.
             </canvas>
         </div>