return this._fb_height;
},
- // Private Methods
- _rescale: function (factor) {
- var canvas = this._target;
- var properties = ['transform', 'WebkitTransform', 'MozTransform'];
- var transform_prop;
- while ((transform_prop = properties.shift())) {
- if (typeof canvas.style[transform_prop] !== 'undefined') {
- break;
- }
- }
+ autoscale: function (containerWidth, containerHeight, downscaleOnly) {
+ var targetAspectRatio = containerWidth / containerHeight;
+ var fbAspectRatio = this._fb_width / this._fb_height;
- if (transform_prop === null) {
- Util.Debug("No scaling support");
- return;
+ var scaleRatio;
+ if (fbAspectRatio >= targetAspectRatio) {
+ scaleRatio = containerWidth / this._fb_width;
+ } else {
+ scaleRatio = containerHeight / this._fb_height;
}
- if (typeof(factor) === "undefined") {
- factor = this._scale;
- } else if (factor > 1.0) {
- factor = 1.0;
- } else if (factor < 0.1) {
- factor = 0.1;
+ var targetW, targetH;
+ if (scaleRatio > 1.0 && downscaleOnly) {
+ targetW = this._fb_width;
+ targetH = this._fb_height;
+ scaleRatio = 1.0;
+ } else if (fbAspectRatio >= targetAspectRatio) {
+ targetW = containerWidth;
+ targetH = Math.round(containerWidth / fbAspectRatio);
+ } else {
+ targetW = Math.round(containerHeight * fbAspectRatio);
+ targetH = containerHeight;
}
- if (this._scale === factor) {
- return;
- }
+ // NB(directxman12): If you set the width directly, or set the
+ // style width to a number, the canvas is cleared.
+ // However, if you set the style width to a string
+ // ('NNNpx'), the canvas is scaled without clearing.
+ this._target.style.width = targetW + 'px';
+ this._target.style.height = targetH + 'px';
+
+ this._scale = scaleRatio;
+
+ return scaleRatio; // so that the mouse, etc scale can be set
+ },
+ // Private Methods
+ _rescale: function (factor) {
this._scale = factor;
- var x = canvas.width - (canvas.width * factor);
- var y = canvas.height - (canvas.height * factor);
- canvas.style[transform_prop] = 'scale(' + this._scale + ') translate(-' + x + 'px, -' + y + 'px)';
+
+ this._target.style.width = Math.round(factor * this._fb_width) + 'px';
+ this._target.style.height = Math.round(factor * this._fb_height) + 'px';
},
_setFillColor: function (color) {
},
onresize: function (callback) {
- if (UI.getSetting('resize') === 'remote') {
- 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;
- if (innerW !== undefined && innerH !== undefined)
- UI.rfb.setDesktopSize(innerW, innerH - controlbarH - padding);
+ 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());
+ }
}
},
$D("noVNC_apply").onclick = UI.settingsApply;
$D("noVNC_connect_button").onclick = UI.connect;
+
+ $D("noVNC_resize").onchange = function () {
+ var connected = UI.rfb_state === 'normal' ? true : false;
+ UI.enableDisableClip(connected);
+ };
},
// Read form control compatible setting from cookie
if (UI.rfb.get_display().get_cursor_uri()) {
UI.saveSetting('cursor');
}
- UI.saveSetting('clip');
+
UI.saveSetting('resize');
+
+ if (UI.getSetting('resize') === 'downscale' || UI.getSetting('resize') === 'scale') {
+ UI.forceSetting('clip', false);
+ }
+
+ UI.saveSetting('clip');
UI.saveSetting('shared');
UI.saveSetting('view_only');
UI.saveSetting('path');
UI.updateSetting('cursor', !UI.isTouchDevice);
$D('noVNC_cursor').disabled = true;
}
- $D('noVNC_clip').disabled = connected || UI.isTouchDevice;
+
+ UI.enableDisableClip(connected);
$D('noVNC_resize').disabled = connected;
$D('noVNC_shared').disabled = connected;
$D('noVNC_view_only').disabled = connected;
}
},
+ enableDisableClip: function (connected) {
+ var resizeElem = $D('noVNC_resize');
+ if (resizeElem.value === 'downscale' || resizeElem.value === 'scale') {
+ UI.forceSetting('clip', false);
+ $D('noVNC_clip').disabled = true;
+ } else {
+ $D('noVNC_clip').disabled = connected || UI.isTouchDevice;
+ if (UI.isTouchDevice) {
+ UI.forceSetting('clip', true);
+ }
+ }
+ },
+
// This resize can not be done until we know from the first Frame Buffer Update
// if it is supported or not.
// The resize is needed to make sure the server desktop size is updated to the
Util.getPosition = function(obj) {
"use strict";
+ // NB(sross): the Mozilla developer reference seems to indicate that
+ // getBoundingClientRect includes border and padding, so the canvas
+ // style should NOT include either.
var objPosition = obj.getBoundingClientRect();
- return {'x': objPosition.left + window.pageXOffset, 'y': objPosition.top + window.pageYOffset};
+ return {'x': objPosition.left + window.pageXOffset, 'y': objPosition.top + window.pageYOffset,
+ 'width': objPosition.width, 'height': objPosition.height};
};
}
var realx = docX - pos.x;
var realy = docY - pos.y;
- var x = Math.max(Math.min(realx, obj.width - 1), 0);
- var y = Math.max(Math.min(realy, obj.height - 1), 0);
+ var x = Math.max(Math.min(realx, pos.width - 1), 0);
+ var y = Math.max(Math.min(realy, pos.height - 1), 0);
return {'x': x / scale, 'y': y / scale, 'realx': realx / scale, 'realy': realy / scale};
};
});
});
+ describe('rescaling', function () {
+ var display;
+ var canvas;
+
+ beforeEach(function () {
+ display = new Display({ target: document.createElement('canvas'), prefer_js: false, viewport: true });
+ display.resize(4, 3);
+ canvas = display.get_target();
+ document.body.appendChild(canvas);
+ });
+
+ afterEach(function () {
+ document.body.removeChild(canvas);
+ });
+
+ it('should not change the bitmap size of the canvas', function () {
+ display.set_scale(0.5);
+ expect(canvas.width).to.equal(4);
+ expect(canvas.height).to.equal(3);
+ });
+
+ it('should change the effective rendered size of the canvas', function () {
+ display.set_scale(0.5);
+ expect(canvas.clientWidth).to.equal(2);
+ expect(canvas.clientHeight).to.equal(2);
+ });
+ });
+
+ describe('autoscaling', function () {
+ var display;
+ var canvas;
+
+ beforeEach(function () {
+ display = new Display({ target: document.createElement('canvas'), prefer_js: false, viewport: true });
+ display.resize(4, 3);
+ canvas = display.get_target();
+ document.body.appendChild(canvas);
+ });
+
+ afterEach(function () {
+ document.body.removeChild(canvas);
+ });
+
+ it('should preserve aspect ratio while autoscaling', function () {
+ display.autoscale(16, 9);
+ expect(canvas.clientWidth / canvas.clientHeight).to.equal(4 / 3);
+ });
+
+ it('should use width to determine scale when the current aspect ratio is wider than the target', function () {
+ expect(display.autoscale(9, 16)).to.equal(9 / 4);
+ expect(canvas.clientWidth).to.equal(9);
+ expect(canvas.clientHeight).to.equal(7); // round 9 / (4 / 3)
+ });
+
+ it('should use height to determine scale when the current aspect ratio is taller than the target', function () {
+ expect(display.autoscale(16, 9)).to.equal(3); // 9 / 3
+ expect(canvas.clientWidth).to.equal(12); // 16 * (4 / 3)
+ expect(canvas.clientHeight).to.equal(9);
+
+ });
+
+ it('should not change the bitmap size of the canvas', function () {
+ display.autoscale(16, 9);
+ expect(canvas.width).to.equal(4);
+ expect(canvas.height).to.equal(3);
+ });
+
+ it('should not upscale when downscaleOnly is true', function () {
+ expect(display.autoscale(2, 2, true)).to.equal(0.5);
+ expect(canvas.clientWidth).to.equal(2);
+ expect(canvas.clientHeight).to.equal(2);
+
+ expect(display.autoscale(16, 9, true)).to.equal(1.0);
+ expect(canvas.clientWidth).to.equal(4);
+ expect(canvas.clientHeight).to.equal(3);
+ });
+ });
+
describe('drawing', function () {
// TODO(directxman12): improve the tests for each of the drawing functions to cover more than just the
<li><label>
<select id="noVNC_resize" name="vncResize">
<option value="off">None</option>
+ <option value="scale">Local Scaling</option>
+ <option value="downscale">Local Downscaling</option>
<option value="remote">Remote Resizing</option>
</select> Scaling Mode</label>
</li>