Commit cefc9a91 authored by Solly's avatar Solly

Merge pull request #464 from kanaka/bug/firefoxresize

Fix resize in Firefox on Android
parents 3b8ec46f fdedbafb
......@@ -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 {
......
/*
* 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)
......
......@@ -37,24 +37,24 @@ var RFB;
// In preference order
this._encodings = [
['COPYRECT', 0x01 ],
['TIGHT', 0x07 ],
['TIGHT_PNG', -260 ],
['HEXTILE', 0x05 ],
['RRE', 0x02 ],
['RAW', 0x00 ],
['DesktopSize', -223 ],
['Cursor', -239 ],
['COPYRECT', 0x01 ],
['TIGHT', 0x07 ],
['TIGHT_PNG', -260 ],
['HEXTILE', 0x05 ],
['RRE', 0x02 ],
['RAW', 0x00 ],
['DesktopSize', -223 ],
['Cursor', -239 ],
// Psuedo-encoding settings
//['JPEG_quality_lo', -32 ],
['JPEG_quality_med', -26 ],
//['JPEG_quality_hi', -23 ],
//['compress_lo', -255 ],
['compress_hi', -247 ],
['last_rect', -224 ],
['xvp', -309 ],
['ext_desktop_size', -308 ]
//['JPEG_quality_lo', -32 ],
['JPEG_quality_med', -26 ],
//['JPEG_quality_hi', -23 ],
//['compress_lo', -255 ],
['compress_hi', -247 ],
['last_rect', -224 ],
['xvp', -309 ],
['ExtendedDesktopSize', -308 ]
];
this._encHandlers = {};
......@@ -1871,15 +1871,27 @@ var RFB;
return true;
},
ext_desktop_size: function () {
handle_FB_resize: function () {
this._fb_width = this._FBU.width;
this._fb_height = this._FBU.height;
this._display.resize(this._fb_width, this._fb_height);
this._onFBResize(this, this._fb_width, this._fb_height);
this._timing.fbu_rt_start = (new Date()).getTime();
this._FBU.bytes = 0;
this._FBU.rects -= 1;
return true;
},
ExtendedDesktopSize: function () {
this._FBU.bytes = 1;
if (this._sock.rQwait("ext_desktop_size", this._FBU.bytes)) { return false; }
if (this._sock.rQwait("ExtendedDesktopSize", this._FBU.bytes)) { return false; }
this._supportsSetDesktopSize = true;
var number_of_screens = this._sock.rQpeek8();
this._FBU.bytes = 4 + (number_of_screens * 16);
if (this._sock.rQwait("ext_desktop_size", this._FBU.bytes)) { return false; }
if (this._sock.rQwait("ExtendedDesktopSize", this._FBU.bytes)) { return false; }
this._sock.rQskipBytes(1); // number-of-screens
this._sock.rQskipBytes(3); // padding
......@@ -1898,31 +1910,42 @@ var RFB;
}
}
if (this._FBU.x == 0 && this._FBU.y != 0) { return true; }
this._fb_width = this._FBU.width;
this._fb_height = this._FBU.height;
this._display.resize(this._fb_width, this._fb_height);
this._onFBResize(this, this._fb_width, this._fb_height);
/*
* The x-position indicates the reason for the change:
*
* 0 - server resized on its own
* 1 - this client requested the resize
* 2 - another client requested the resize
*/
this._FBU.bytes = 0;
this._FBU.rects -= 1;
// We need to handle errors when we requested the resize.
if (this._FBU.x == 1 && this._FBU.y != 0) {
var msg = "";
// The y-position indicates the status code from the server
switch (this._FBU.y) {
case 1:
msg = "Resize is administratively prohibited";
break;
case 2:
msg = "Out of resources";
break;
case 3:
msg = "Invalid screen layout";
break;
default:
msg = "Unknown reason";
break;
}
Util.Info("Server did not accept the resize request: " + msg);
return true;
}
this._encHandlers.handle_FB_resize();
return true;
},
DesktopSize: function () {
Util.Debug(">> set_desktopsize");
this._fb_width = this._FBU.width;
this._fb_height = this._FBU.height;
this._display.resize(this._fb_width, this._fb_height);
this._onFBResize(this, this._fb_width, this._fb_height);
this._timing.fbu_rt_start = (new Date()).getTime();
this._FBU.bytes = 0;
this._FBU.rects--;
Util.Debug("<< set_desktopsize");
this._encHandlers.handle_FB_resize();
return true;
},
......
/*
* 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");
......
......@@ -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 () {
......
......@@ -1584,7 +1584,7 @@ describe('Remote Frame Buffer Protocol Client', function() {
});
it('should not handle a failed request', function () {
var reason_for_change = 0; // non-incremental
var reason_for_change = 1; // requested by this client
var status_code = 1; // Resize is administratively prohibited
send_fbu_msg([{ x: reason_for_change, y: status_code,
......
......@@ -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>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment