Bugfix/kasm 5453 serverside fixed resolutions 2 (#90)

* fix serverside fixed resolutions

* refactor of multi-monitor screen planning

* KASM-5488 mouse up fix

* Fixed display manager not getting updates on primary scren size, static size fixes

* fix phantom screen

* fix mouse off on secondary screen when scaled

* fix scaling off

* fix exception on setting static resolution

* completely disable client set static resolution on secondary displays

---------

Co-authored-by: mattmcclaskey <matt@kasmweb.com>
Co-authored-by: Chris Hunt <chris.hunt@kasmweb.com>
This commit is contained in:
Matt McClaskey 2024-01-26 11:09:56 -05:00 committed by GitHub
parent f6c1d8c668
commit 4825e12f62
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 360 additions and 171 deletions

View File

@ -1745,10 +1745,9 @@ const UI = {
if (UI.rfb) { if (UI.rfb) {
UI.rfb.forcedResolutionX = event.data.value_x; UI.rfb.forcedResolutionX = event.data.value_x;
UI.rfb.forcedResolutionY = event.data.value_y; UI.rfb.forcedResolutionY = event.data.value_y;
UI.forceSetting('forced_resolution_x', event.data.value_x, false);
UI.forceSetting('forced_resolution_y', event.data.value_y, false);
UI.applyResizeMode(); UI.applyResizeMode();
UI.rfb.forcedResolutionX = null;
UI.rfb.forcedResolutionY = null;
UI.rfb._resizeSession = UI.getSetting('resize') === 'remote';
} }
break; break;
case 'set_perf_stats': case 'set_perf_stats':
@ -1861,13 +1860,24 @@ const UI = {
// Apply remote resizing or local scaling // Apply remote resizing or local scaling
applyResizeMode() { applyResizeMode() {
if (!UI.rfb) return; if (!UI.rfb) return;
const resize_setting = UI.getSetting('resize');
UI.rfb.scaleViewport = UI.getSetting('resize') === 'scale'; UI.rfb.clipViewport = resize_setting !== 'off';
UI.rfb.resizeSession = UI.getSetting('resize') === 'remote' || UI.rfb.forcedResolutionX && UI.rfb.forcedResolutionY; UI.rfb.scaleViewport = resize_setting === 'scale';
UI.rfb.resizeSession = resize_setting === 'remote';
UI.rfb.idleDisconnect = UI.getSetting('idle_disconnect'); UI.rfb.idleDisconnect = UI.getSetting('idle_disconnect');
UI.rfb.videoQuality = UI.getSetting('video_quality'); UI.rfb.videoQuality = UI.getSetting('video_quality');
UI.rfb.enableWebP = UI.getSetting('enable_webp'); UI.rfb.enableWebP = UI.getSetting('enable_webp');
UI.rfb.enableHiDpi = UI.getSetting('enable_hidpi'); UI.rfb.enableHiDpi = UI.getSetting('enable_hidpi');
if (UI.rfb.resizeSession) {
UI.rfb.forcedResolutionX = null;
UI.rfb.forcedResolutionY = null;
} else {
UI.rfb.forcedResolutionX = UI.getSetting('forced_resolution_x', false);
UI.rfb.forcedResolutionY = UI.getSetting('forced_resolution_y', false);
}
UI.rfb.updateConnectionSettings();
}, },
/* ------^------- /* ------^-------
@ -2582,6 +2592,7 @@ const UI = {
} else { } else {
UI.rfb.enableHiDpi = false; UI.rfb.enableHiDpi = false;
} }
UI.applyResizeMode();
} }
}, },

View File

@ -142,7 +142,8 @@ const UI = {
} }
} }
UI.rfb = new RFB(document.getElementById('noVNC_container'), if (!UI.rfb) {
UI.rfb = new RFB(document.getElementById('noVNC_container'),
document.getElementById('noVNC_keyboardinput'), document.getElementById('noVNC_keyboardinput'),
"", //URL "", //URL
{ {
@ -153,11 +154,18 @@ const UI = {
}, },
false // Not a primary display false // Not a primary display
); );
}
UI.rfb.addEventListener("connect", UI.connectFinished); UI.rfb.addEventListener("connect", UI.connectFinished);
//UI.rfb.addEventListener("disconnect", UI.disconnectFinished); //UI.rfb.addEventListener("disconnect", UI.disconnectFinished);
UI.rfb.clipViewport = UI.getSetting('view_clip'); //TODO: add support for forced static resolution for multiple monitors
UI.rfb.scaleViewport = UI.getSetting('resize', false, 'remote') === 'scale'; //UI.rfb.forcedResolutionX = UI.getSetting('forced_resolution_x', false);
UI.rfb.resizeSession = UI.getSetting('resize', false, 'remote') === 'remote'; //UI.rfb.forcedResolutionY = UI.getSetting('forced_resolution_y', false);
const resize_setting = UI.getSetting('resize', false, 'remote');
UI.rfb.clipViewport = resize_setting !== 'off';
UI.rfb.scaleViewport = resize_setting === 'scale';
UI.rfb.resizeSession = resize_setting === 'remote';
UI.rfb.qualityLevel = parseInt(UI.getSetting('quality')); UI.rfb.qualityLevel = parseInt(UI.getSetting('quality'));
UI.rfb.dynamicQualityMin = parseInt(UI.getSetting('dynamic_quality_min')); UI.rfb.dynamicQualityMin = parseInt(UI.getSetting('dynamic_quality_min'));
UI.rfb.dynamicQualityMax = parseInt(UI.getSetting('dynamic_quality_max')); UI.rfb.dynamicQualityMax = parseInt(UI.getSetting('dynamic_quality_max'));
@ -194,13 +202,9 @@ const UI = {
} }
//attach this secondary display to the primary display //attach this secondary display to the primary display
if (UI.screenID === null) { const screen = UI.rfb.attachSecondaryDisplay(details);
const screen = UI.rfb.attachSecondaryDisplay(details); UI.screenID = screen.screenID
UI.screenID = screen.screenID UI.screen = screen
UI.screen = screen
} else {
UI.rfb.reattachSecondaryDisplay(UI.screen, details);
}
document.querySelector('title').textContent = 'Display ' + UI.screenID document.querySelector('title').textContent = 'Display ' + UI.screenID
@ -406,6 +410,19 @@ const UI = {
return val; return val;
}, },
// Apply remote resizing or local scaling
applyResizeMode() {
if (!UI.rfb) return;
const resize_setting = UI.getSetting('resize');
UI.rfb.clipViewport = resize_setting !== 'off';
UI.rfb.scaleViewport = resize_setting === 'scale';
UI.rfb.resizeSession = resize_setting === 'remote' || UI.rfb.forcedResolutionX && UI.rfb.forcedResolutionY;
UI.rfb.idleDisconnect = UI.getSetting('idle_disconnect');
UI.rfb.videoQuality = UI.getSetting('video_quality');
UI.rfb.enableWebP = UI.getSetting('enable_webp');
UI.rfb.enableHiDpi = UI.getSetting('enable_hidpi');
},
} }
UI.prime(); UI.prime();

View File

@ -96,15 +96,20 @@ export default class Display {
height: this._target.height, //client height: this._target.height, //client
serverWidth: 0, //calculated serverWidth: 0, //calculated
serverHeight: 0, //calculated serverHeight: 0, //calculated
serverReportedWidth: 0,
serverReportedHeight: 0,
x: 0, x: 0,
y: 0, y: 0,
scale: 1,
relativePosition: 0, //left, right, up, down relative to primary display relativePosition: 0, //left, right, up, down relative to primary display
relativePositionX: 0, //offset relative to primary monitor, always 0 for primary relativePositionX: 0, //offset relative to primary monitor, always 0 for primary
relativePositionY: 0, //offset relative to primary monitor, always 0 for primary relativePositionY: 0, //offset relative to primary monitor, always 0 for primary
pixelRatio: window.devicePixelRatio, pixelRatio: window.devicePixelRatio,
containerHeight: this._target.parentNode.offsetHeight, containerHeight: this._target.parentNode.offsetHeight,
containerWidth: this._target.parentNode.offsetWidth, containerWidth: this._target.parentNode.offsetWidth,
channel: null channel: null,
x2: 0,
y2: 0
}]; }];
//optional offscreen canvas //optional offscreen canvas
@ -224,75 +229,87 @@ export default class Display {
*/ */
getServerRelativeCoordinates(screenIndex, x, y) { getServerRelativeCoordinates(screenIndex, x, y) {
if (screenIndex >= 0 && screenIndex < this._screens.length) { if (screenIndex >= 0 && screenIndex < this._screens.length) {
x += this._screens[screenIndex].x; x = toSigned32bit(x / this._screens[screenIndex].scale + this._screens[screenIndex].x);
y += this._screens[screenIndex].y; y = toSigned32bit(y / this._screens[screenIndex].scale + this._screens[screenIndex].y);
} }
return [x, y]; return [x, y];
} }
getScreenSize(resolutionQuality, max_width, max_height, hiDpi, disableLimit) { getScreenSize(resolutionQuality, max_width, max_height, hiDpi, disableLimit, disableScaling) {
let data = { let data = {
screens: null, screens: null,
serverWidth: 0, serverWidth: 0,
serverHeight: 0 serverHeight: 0
} }
let i = 0;
//recalculate primary display container size //recalculate primary display container size
this._screens[0].containerHeight = this._target.parentNode.offsetHeight; this._screens[i].containerHeight = this._target.parentNode.offsetHeight;
this._screens[0].containerWidth = this._target.parentNode.offsetWidth; this._screens[i].containerWidth = this._target.parentNode.offsetWidth;
this._screens[0].pixelRatio = window.devicePixelRatio; this._screens[i].pixelRatio = window.devicePixelRatio;
this._screens[0].width = this._target.parentNode.offsetWidth; this._screens[i].width = this._target.parentNode.offsetWidth;
this._screens[0].height = this._target.parentNode.offsetHeight; this._screens[i].height = this._target.parentNode.offsetHeight;
//calculate server-side and client-side resolution of each screen //calculate server-side and client-side resolution of each screen
for (let i=0; i<this._screens.length; i++) { let width = max_width || this._screens[i].containerWidth;
let width = max_width || this._screens[i].containerWidth; let height = max_height || this._screens[i].containerHeight;
let height = max_height || this._screens[i].containerHeight; let scale = 1;
let scale = 0;
//max the resolution of a single screen to 1280 //max the resolution of a single screen to 1280
if (width > 1280 && !disableLimit && resolutionQuality == 1) { if (
height = 1280 * (height/width); //keeping the aspect ratio of original resolution, shrink y to match x (this._screens[i].serverReportedWidth > 0 && this._screens[i].serverReportedHeight > 0) &&
width = 1280; (
} disableScaling ||
//hard coded 720p (this._screens[i].serverReportedWidth !== this._screens[i].serverWidth || this._screens[i].serverReportedHeight !== this._screens[i].serverHeight)
else if (resolutionQuality == 0 && !disableLimit) { ) &&
width = 1280; (!max_width && !max_height)
height = 720; ) {
} height = this._screens[i].serverReportedHeight;
//force full resolution on a high DPI monitor where the OS is scaling width = this._screens[i].serverReportedWidth;
else if (hiDpi) { }
width = width * this._screens[i].pixelRatio; else if (width > 1280 && !disableLimit && resolutionQuality == 1) {
height = height * this._screens[i].pixelRatio; height = Math.floor(1280 * (height/width)); //keeping the aspect ratio of original resolution, shrink y to match x
scale = 1 / this._screens[i].pixelRatio; width = 1280;
} }
//physically small device with high DPI //hard coded 720p
else if (this._antiAliasing === 0 && this._screens[i].pixelRatio > 1 && width < 1000 & width > 0) { else if (resolutionQuality == 0 && !disableLimit) {
Log.Info('Device Pixel ratio: ' + this._screens[i].pixelRatio + ' Reported Resolution: ' + width + 'x' + height); width = 1280;
let targetDevicePixelRatio = 1.5; height = 720;
if (this._screens[i].pixelRatio > 2) { targetDevicePixelRatio = 2; } }
let scaledWidth = (width * this._screens[i].pixelRatio) * (1 / targetDevicePixelRatio); //force full resolution on a high DPI monitor where the OS is scaling
let scaleRatio = scaledWidth / width; else if (hiDpi) {
width = width * scaleRatio; width = Math.floor(width * this._screens[i].pixelRatio);
height = height * scaleRatio; height = Math.floor(height * this._screens[i].pixelRatio);
scale = 1 / scaleRatio; scale = 1 / this._screens[i].pixelRatio;
Log.Info('Small device with hDPI screen detected, auto scaling at ' + scaleRatio + ' to ' + width + 'x' + height); }
} //physically small device with high DPI
else if (this._antiAliasing === 0 && this._screens[i].pixelRatio > 1 && width < 1000 & width > 0) {
let clientServerRatioH = this._screens[i].containerHeight / height; Log.Info('Device Pixel ratio: ' + this._screens[i].pixelRatio + ' Reported Resolution: ' + width + 'x' + height);
let clientServerRatioW = this._screens[i].containerWidth / width; let targetDevicePixelRatio = 1.5;
if (this._screens[i].pixelRatio > 2) { targetDevicePixelRatio = 2; }
this._screens[i].height = Math.floor(height * clientServerRatioH); let scaledWidth = (width * this._screens[i].pixelRatio) * (1 / targetDevicePixelRatio);
this._screens[i].width = Math.floor(width * clientServerRatioW); let scaleRatio = scaledWidth / width;
this._screens[i].serverWidth = width; width = width * scaleRatio;
this._screens[i].serverHeight = height; height = height * scaleRatio;
this._screens[i].scale = scale; scale = 1 / scaleRatio;
Log.Info('Small device with hDPI screen detected, auto scaling at ' + scaleRatio + ' to ' + width + 'x' + height);
}
let clientServerRatioH = this._screens[i].containerHeight / height;
let clientServerRatioW = this._screens[i].containerWidth / width;
this._screens[i].height = Math.floor(height * clientServerRatioH);
this._screens[i].width = Math.floor(width * clientServerRatioW);
this._screens[i].serverWidth = width;
this._screens[i].serverHeight = height;
this._screens[i].scale = this._screens[i].width / this._screens[i].serverWidth;
for (i = 0; i < this._screens.length; i++) {
this._screens[i].x2 = this._screens[i].x + this._screens[i].serverWidth; this._screens[i].x2 = this._screens[i].x + this._screens[i].serverWidth;
this._screens[i].y2 = this._screens[i].y + this._screens[i].serverHeight; this._screens[i].y2 = this._screens[i].y + this._screens[i].serverHeight;
}
for (let i = 0; i < this._screens.length; i++) {
data.serverWidth = Math.max(data.serverWidth, this._screens[i].x + this._screens[i].serverWidth); data.serverWidth = Math.max(data.serverWidth, this._screens[i].x + this._screens[i].serverWidth);
data.serverHeight = Math.max(data.serverHeight, this._screens[i].y + this._screens[i].serverHeight); data.serverHeight = Math.max(data.serverHeight, this._screens[i].y + this._screens[i].serverHeight);
} }
@ -302,21 +319,43 @@ export default class Display {
return data; return data;
} }
applyScreenPlan(screenPlan) { applyServerResolution(width, height, screenIndex) {
for (let i = 0; i < screenPlan.screens.length; i++) { for (let z = 0; z < this._screens.length; z++) {
for (let z = 0; z < this._screens.length; z++) { if (screenIndex === this._screens[z].screenIndex) {
if (screenPlan.screens[i].screenID === this._screens[z].screenID) { this._screens[z].serverReportedWidth = width;
this._screens[z].x = screenPlan.screens[i].x; this._screens[z].serverReportedHeight = height;
this._screens[z].y = screenPlan.screens[i].y;
}
} }
} }
} }
addScreen(screenID, width, height, pixelRatio, containerHeight, containerWidth) { applyScreenPlan(screenPlan) {
let changes = false;
for (let i = 0; i < screenPlan.screens.length; i++) {
for (let z = 0; z < this._screens.length; z++) {
if (screenPlan.screens[i].screenID === this._screens[z].screenID) {
if (this._screens[z].x !== screenPlan.screens[i].x || this._screens[z].y !== screenPlan.screens[i].y) {
this._screens[z].x = screenPlan.screens[i].x;
this._screens[z].y = screenPlan.screens[i].y;
changes = true;
}
if (this._screens[z].x2 !== this._screens[z].x + this._screens[z].serverWidth || this._screens[z].y2 !== this._screens[z].y + this._screens[z].serverHeight) {
this._screens[z].x2 = this._screens[z].x + this._screens[z].serverWidth
this._screens[z].y2 = this._screens[z].y + this._screens[z].serverHeight
changes = true;
}
}
}
}
return changes;
}
addScreen(screenID, width, height, pixelRatio, containerHeight, containerWidth, scale, serverWidth, serverHeight) {
if (!this._isPrimaryDisplay) { if (!this._isPrimaryDisplay) {
throw new Error("Cannot add a screen to a secondary display."); throw new Error("Cannot add a screen to a secondary display.");
} }
else if (containerHeight === 0 || containerWidth === 0 || pixelRatio === 0) {
Log.Warn("Invalid screen configuration.");
}
let screenIdx = -1; let screenIdx = -1;
//Does the screen already exist? //Does the screen already exist?
@ -329,12 +368,19 @@ export default class Display {
if (screenIdx > 0) { if (screenIdx > 0) {
//existing screen, update //existing screen, update
const screen = this._screens[screenIdx]; const screen = this._screens[screenIdx];
screen.width = width; if (screen.serverHeight !== serverHeight || screen.serverWidth !== serverWidth || screen.width !== width || screen.height !== height || screen.containerHeight !== containerHeight || screen.containerWidth !== containerWidth || screen.scale !== screen.scale || screen.pixelRatio !== screen.pixelRatio) {
screen.height = height; screen.width = width;
screen.containerHeight = containerHeight; screen.height = height;
screen.containerWidth = containerWidth; screen.containerHeight = containerHeight;
screen.pixelRatio = pixelRatio; screen.containerWidth = containerWidth;
screen.pixelRatio = pixelRatio;
screen.scale = scale;
screen.serverWidth = serverWidth;
screen.serverHeight = serverHeight;
screen.x2 = screen.x + screen.serverWidth;
screen.y2 = screen.y + screen.serverHeight;
return true;
}
} else { } else {
//New Screen, add to far right until user repositions it //New Screen, add to far right until user repositions it
let x = 0; let x = 0;
@ -347,15 +393,19 @@ export default class Display {
screenIndex: this.screens.length, screenIndex: this.screens.length,
width: width, //client width: width, //client
height: height, //client height: height, //client
serverWidth: 0, //calculated serverWidth: serverWidth,
serverHeight: 0, //calculated serverHeight: serverHeight,
serverReportedWidth: 0,
serverReportedHeight: 0,
x: x, x: x,
y: 0, y: 0,
pixelRatio: pixelRatio, pixelRatio: pixelRatio,
containerHeight: containerHeight, containerHeight: containerHeight,
containerWidth: containerWidth, containerWidth: containerWidth,
channel: null, channel: null,
scale: 0 scale: scale,
x2: x + serverWidth,
y2: serverHeight
} }
new_screen.channel = new BroadcastChannel(`screen_${screenID}_channel`); new_screen.channel = new BroadcastChannel(`screen_${screenID}_channel`);
@ -363,7 +413,11 @@ export default class Display {
this._screens.push(new_screen); this._screens.push(new_screen);
new_screen.channel.postMessage({ eventType: "registered", screenIndex: new_screen.screenIndex }); new_screen.channel.postMessage({ eventType: "registered", screenIndex: new_screen.screenIndex });
return new_screen.screenIndex;
} }
return false;
} }
removeScreen(screenID) { removeScreen(screenID) {
@ -790,7 +844,6 @@ export default class Display {
} }
autoscale(containerWidth, containerHeight, scaleRatio=0) { autoscale(containerWidth, containerHeight, scaleRatio=0) {
if (containerWidth === 0 || containerHeight === 0) { if (containerWidth === 0 || containerHeight === 0) {
scaleRatio = 0; scaleRatio = 0;
@ -814,7 +867,9 @@ export default class Display {
_writeCtxBuffer() { _writeCtxBuffer() {
//TODO: KASM-5450 Damage tracking with transparent rect overlay support //TODO: KASM-5450 Damage tracking with transparent rect overlay support
this._targetCtx.drawImage(this._backbuffer, 0, 0); if (this._backbuffer.width > 0) {
this._targetCtx.drawImage(this._backbuffer, 0, 0);
}
} }
_handleSecondaryDisplayMessage(event) { _handleSecondaryDisplayMessage(event) {
@ -836,7 +891,9 @@ export default class Display {
let imageBmpPromise = createImageBitmap(rect.arr); let imageBmpPromise = createImageBitmap(rect.arr);
imageBmpPromise.then( function(img) { imageBmpPromise.then( function(img) {
this._transparentOverlayImg = img; this._transparentOverlayImg = img;
this.enableCanvasBuffer = true; if (!this.enableCanvasBuffer) {
this._enableCanvasBuffer = true;
}
}.bind(this) ); }.bind(this) );
this._transparentOverlayRect = rect; this._transparentOverlayRect = rect;
break; break;

View File

@ -124,7 +124,6 @@ export default class RFB extends EventTargetMixin {
this._enabledContinuousUpdates = false; this._enabledContinuousUpdates = false;
this._supportsSetDesktopSize = false; this._supportsSetDesktopSize = false;
this._connectionID = window.location.href.split('?')[0].match(/^(.+)(\/)/)[0]; this._connectionID = window.location.href.split('?')[0].match(/^(.+)(\/)/)[0];
this._screenIndex = 0;
this._screenFlags = 0; this._screenFlags = 0;
this._qemuExtKeyEventSupported = false; this._qemuExtKeyEventSupported = false;
@ -419,20 +418,13 @@ export default class RFB extends EventTargetMixin {
get clipViewport() { return this._clipViewport; } get clipViewport() { return this._clipViewport; }
set clipViewport(viewport) { set clipViewport(viewport) {
this._clipViewport = viewport; this._clipViewport = viewport;
this._updateClip();
} }
get scaleViewport() { return this._scaleViewport; } get scaleViewport() { return this._scaleViewport; }
set scaleViewport(scale) { set scaleViewport(scale) {
this._scaleViewport = scale; if (this._scaleViewport !== scale) {
// Scaling trumps clipping, so we may need to adjust this._scaleViewport = scale;
// clipping when enabling or disabling scaling this._pendingApplyResolutionChange = true;
if (scale && this._clipViewport) {
this._updateClip();
}
this._updateScale();
if (!scale && this._clipViewport) {
this._updateClip();
} }
} }
@ -440,8 +432,8 @@ export default class RFB extends EventTargetMixin {
set resizeSession(resize) { set resizeSession(resize) {
this._resizeSession = resize; this._resizeSession = resize;
if (resize) { if (resize) {
this._requestRemoteResize();
this.scaleViewport = true; this.scaleViewport = true;
this._pendingApplyResolutionChange = true;
} }
} }
@ -671,9 +663,20 @@ export default class RFB extends EventTargetMixin {
} }
get forcedResolutionX() { return this._forcedResolutionX; } get forcedResolutionX() { return this._forcedResolutionX; }
set forcedResolutionX(value) {this._forcedResolutionX = value;} set forcedResolutionX(value) {
if (value !== this._forcedResolutionX) {
this._forcedResolutionX = value;
this._pendingApplyResolutionChange = true;
}
}
get forcedResolutionY() { return this._forcedResolutionY; } get forcedResolutionY() { return this._forcedResolutionY; }
set forcedResolutionY(value) {this._forcedResolutionY = value;} set forcedResolutionY(value) {
if (value !== this._forcedResolutionY) {
this._forcedResolutionY = value;
this._pendingApplyResolutionChange = true;
}
}
get qualityLevel() { get qualityLevel() {
return this._qualityLevel; return this._qualityLevel;
@ -732,16 +735,20 @@ export default class RFB extends EventTargetMixin {
set enableHiDpi(value) { set enableHiDpi(value) {
if (value !== this._hiDpi) { if (value !== this._hiDpi) {
this._hiDpi = value; this._hiDpi = value;
this._requestRemoteResize(); this._pendingApplyResolutionChange = true;
if (this._display.screens.length > 1) { this._display.applyServerResolution(0, 0, 0);
//force secondary displays to re-register and thus apply new hdpi setting
this._proxyRFBMessage('forceResize', [ value ]);
}
} }
} }
// ===== PUBLIC METHODS ===== // ===== PUBLIC METHODS =====
refreshSecondaryDisplays() {
//send secondary displays new settings
if (this._display.screens.length > 1) {
this._proxyRFBMessage('applySettings', [ this._hiDpi, this._clipViewport, this._scaleViewport, this._resizeSession, this._videoQuality, this._forcedResolutionX, this._forcedResolutionY ]);
}
}
attachSecondaryDisplay(details) { attachSecondaryDisplay(details) {
this._updateConnectionState('connecting'); this._updateConnectionState('connecting');
const screen = this._registerSecondaryDisplay(false, details); const screen = this._registerSecondaryDisplay(false, details);
@ -783,10 +790,23 @@ export default class RFB extends EventTargetMixin {
throw new Error("Screen plan contained fewer screens then there are registered.") throw new Error("Screen plan contained fewer screens then there are registered.")
} }
this._display.applyScreenPlan(screenPlan); //apply screen plan on primary display
const size = this._screenSize(); let changes = this._display.applyScreenPlan(screenPlan);
RFB.messages.setDesktopSize(this._sock, size, this._screenFlags);
this._updateContinuousUpdates(); if (changes) {
//send updates to secondary screens
for (let i = 0; i < screenPlan.screens.length; i++) {
for (let z = 1; z < fullPlan.screens.length; z++) {
if (screenPlan.screens[i].screenID == fullPlan.screens[z].screenID) {
this._proxyRFBMessage('applyScreenPlan', [ fullPlan.screens[z].screenID, fullPlan.screens[z].screenIndex, screenPlan.screens[i].width, screenPlan.screens[i].height, screenPlan.screens[i].x, screenPlan.screens[i].y ]);
}
}
}
this._pendingApplyResolutionChange = true;
}
return changes;
} }
} }
@ -818,16 +838,33 @@ export default class RFB extends EventTargetMixin {
This function must be called after changing any properties that effect rendering quality This function must be called after changing any properties that effect rendering quality
*/ */
updateConnectionSettings() { updateConnectionSettings() {
if (this._rfbConnectionState === 'connected') { if (this._rfbConnectionState === 'connected' && this._isPrimaryDisplay) {
if (this._pendingApplyVideoRes) { if (this._pendingApplyVideoRes) {
if (this._isPrimaryDisplay){ RFB.messages.setMaxVideoResolution(this._sock, this._maxVideoResolutionX, this._maxVideoResolutionY);
RFB.messages.setMaxVideoResolution(this._sock, this._maxVideoResolutionX, this._maxVideoResolutionY);
}
} }
if (this._pendingApplyResolutionChange) { if (this._pendingApplyResolutionChange) {
this._requestRemoteResize(); // Scaling trumps clipping, so we may need to adjust
// clipping when enabling or disabling scaling
if (this._scaleViewport && this._clipViewport) {
this._updateClip();
}
this._updateScale();
if (!this._scaleViewport && this._clipViewport) {
this._updateClip();
}
if (this._display.screens.length > 1) {
this.refreshSecondaryDisplays();
}
if (this._resizeSession || (this._forcedResolutionX && this._forcedResolutionY)) {
this._screenSize();
this.dispatchEvent(new CustomEvent("screenregistered", {}));
clearTimeout(this._resizeTimeout);
this._resizeTimeout = setTimeout(this._requestRemoteResize.bind(this), 500);
}
} }
if (this._pendingApplyEncodingChanges) { if (this._pendingApplyEncodingChanges) {
@ -837,6 +874,20 @@ export default class RFB extends EventTargetMixin {
this._pendingApplyVideoRes = false; this._pendingApplyVideoRes = false;
this._pendingApplyEncodingChanges = false; this._pendingApplyEncodingChanges = false;
this._pendingApplyResolutionChange = false; this._pendingApplyResolutionChange = false;
} else if (!this._isPrimaryDisplay) {
if (this._pendingApplyResolutionChange) {
if (this._scaleViewport && this._clipViewport) {
this._updateClip();
}
this._updateScale();
if (!this._scaleViewport && this._clipViewport) {
this._updateClip();
}
}
if (this._resizeSession || (this._forcedResolutionX && this._forcedResolutionY)) {
this._requestRemoteResize();
}
} }
} }
@ -851,6 +902,7 @@ export default class RFB extends EventTargetMixin {
} else { } else {
this._updateConnectionState('disconnecting'); this._updateConnectionState('disconnecting');
this._unregisterSecondaryDisplay(); this._unregisterSecondaryDisplay();
this._rfbConnectionState = "";
} }
} }
@ -1475,10 +1527,13 @@ export default class RFB extends EventTargetMixin {
// If the window resized then our screen element might have // If the window resized then our screen element might have
// as well. Update the viewport dimensions. // as well. Update the viewport dimensions.
window.requestAnimationFrame(() => { window.requestAnimationFrame(() => {
this._screenSize();
this._updateClip(); this._updateClip();
this._updateScale(); this._updateScale();
}); });
this.dispatchEvent(new CustomEvent("screenregistered", { }));
if (this._resizeSession) { if (this._resizeSession) {
// Request changing the resolution of the remote display to // Request changing the resolution of the remote display to
// the size of the local browser viewport. // the size of the local browser viewport.
@ -1531,10 +1586,18 @@ export default class RFB extends EventTargetMixin {
this._resizeTimeout = null; this._resizeTimeout = null;
if (this._isPrimaryDisplay) { if (this._isPrimaryDisplay) {
if (!this._resizeSession || this._viewOnly || if (
!this._supportsSetDesktopSize) { (this._viewOnly || !this._supportsSetDesktopSize) ||
(!this._resizeSession && !this._forcedResolutionX && !this._forcedResolutionY)
) {
return; return;
} }
//zero out the server reported resolution
for (let i=0; i < this._display.screens.length; i++) {
this._display.applyServerResolution(0, 0, this._display.screens[i].screenIndex);
}
const size = this._screenSize(); const size = this._screenSize();
RFB.messages.setDesktopSize(this._sock, size, this._screenFlags); RFB.messages.setDesktopSize(this._sock, size, this._screenFlags);
@ -1550,17 +1613,14 @@ export default class RFB extends EventTargetMixin {
top: window.screenTop top: window.screenTop
} }
} }
this._registerSecondaryDisplay(false, details);
} this._registerSecondaryDisplay(this._display.screens[0], details);
if (this._display.screens.length > 1) {
this.dispatchEvent(new CustomEvent("screenregistered", {}));
} }
} }
// Gets the the size of the available screen // Gets the the size of the available screen
_screenSize (limited) { _screenSize (limited) {
return this._display.getScreenSize(this.videoQuality, this.forcedResolutionX, this.forcedResolutionY, this._hiDpi, limited); return this._display.getScreenSize(this.videoQuality, this.forcedResolutionX, this.forcedResolutionY, this._hiDpi, limited, !this._resizeSession);
} }
_fixScrollbars() { _fixScrollbars() {
@ -1740,22 +1800,22 @@ export default class RFB extends EventTargetMixin {
...event.data.details, ...event.data.details,
screenID: event.data.screenID screenID: event.data.screenID
} }
this._display.addScreen(event.data.screenID, event.data.width, event.data.height, event.data.pixelRatio, event.data.containerHeight, event.data.containerWidth); let screenIndex = this._display.addScreen(event.data.screenID, event.data.width, event.data.height, event.data.pixelRatio, event.data.containerHeight, event.data.containerWidth, event.data.scale, event.data.serverWidth, event.data.serverHeight);
size = this._screenSize(); this._proxyRFBMessage('screenRegistrationConfirmed', [ this._display.screens[screenIndex].screenID, screenIndex ]);
RFB.messages.setDesktopSize(this._sock, size, this._screenFlags); clearTimeout(this._resizeTimeout);
this._sendEncodings(); this._resizeTimeout = setTimeout(this._requestRemoteResize.bind(this), 500);
this._updateContinuousUpdates();
this.dispatchEvent(new CustomEvent("screenregistered", { detail: details })); this.dispatchEvent(new CustomEvent("screenregistered", { detail: details }));
Log.Info(`Secondary monitor (${event.data.screenID}) has been registered.`); Log.Info(`Secondary monitor (${event.data.screenID}) has been registered.`);
break; break;
case 'reattach': case 'reattach':
this._display.addScreen(event.data.screenID, event.data.width, event.data.height, event.data.pixelRatio, event.data.containerHeight, event.data.containerWidth); let changes = this._display.addScreen(event.data.screenID, event.data.width, event.data.height, event.data.pixelRatio, event.data.containerHeight, event.data.containerWidth, event.data.scale, event.data.serverWidth, event.data.serverHeight);
size = this._screenSize();
RFB.messages.setDesktopSize(this._sock, size, this._screenFlags); if (changes) {
this._sendEncodings(); clearTimeout(this._resizeTimeout);
this._updateContinuousUpdates(); this._resizeTimeout = setTimeout(this._requestRemoteResize.bind(this), 500);
this.dispatchEvent(new CustomEvent("screenregistered", {})); this.dispatchEvent(new CustomEvent("screenregistered", {}));
Log.Info(`Secondary monitor (${event.data.screenID}) has been reattached.`); Log.Info(`Secondary monitor (${event.data.screenID}) has been reattached.`);
}
break; break;
case 'unregister': case 'unregister':
if (this._display.removeScreen(event.data.screenID)) { if (this._display.removeScreen(event.data.screenID)) {
@ -1799,9 +1859,14 @@ export default class RFB extends EventTargetMixin {
coords = this._display.getServerRelativeCoordinates(event.data.screenIndex, event.data.args[0], event.data.args[1]); coords = this._display.getServerRelativeCoordinates(event.data.screenIndex, event.data.args[0], event.data.args[1]);
this._mouseLastScreenIndex = event.data.screenIndex; this._mouseLastScreenIndex = event.data.screenIndex;
this._mousePos = { 'x': coords[0], 'y': coords[1] }; this._mousePos = { 'x': coords[0], 'y': coords[1] };
this._mouseButtonMask &= event.data.args[2]; this._mouseButtonMask &= ~event.data.args[2];
RFB.messages.pointerEvent(this._sock, this._mousePos.x, this._mousePos.y, this._mouseButtonMask); RFB.messages.pointerEvent(this._sock, this._mousePos.x, this._mousePos.y, this._mouseButtonMask);
break; break;
case 'scroll':
coords = this._display.getServerRelativeCoordinates(event.data.screenIndex, event.data.args[0], event.data.args[1]);
this._mouseLastScreenIndex = event.data.screenIndex;
this._mousePos = { 'x': coords[0], 'y': coords[1] };
RFB.messages.pointerEvent(this._sock, this._mousePos.x, this._mousePos.y, 0, event.data.args[2], event.data.args[3]);
case 'keyEvent': case 'keyEvent':
RFB.messages.keyEvent(this._sock, ...event.data.args); RFB.messages.keyEvent(this._sock, ...event.data.args);
break; break;
@ -1831,10 +1896,38 @@ export default class RFB extends EventTargetMixin {
this.disconnect(); this.disconnect();
window.close(); window.close();
break; break;
case 'forceResize': case 'applySettings':
this._hiDpi = event.data.args[0]; if (!this._isPrimaryDisplay) {
this._updateScale(); this.enableHiDpi = event.data.args[0];
this._requestRemoteResize(); this.clipViewport = event.data.args[1];
this.scaleViewport = event.data.args[2];
this.resizeSession = event.data.args[3];
this.videoQuality = event.data.args[4];
//TODO: add support for forced static resolution for multiple monitors
//this._forcedResolutionX = event.data.args[5];
//this._forcedResolutionY = event.data.args[6];
//TODO, do we need to do this twice
this.scaleViewport = event.data.args[3];
this.updateConnectionSettings();
}
break;
case 'applyScreenPlan':
if (event.data.args[0] == this._display.screenID) {
this._display.screens[0].screenIndex = event.data.args[1];
this._display.screens[0].width = event.data.args[2];
this._display.screens[0].height = event.data.args[3];
this._display.screens[0].x = event.data.args[4];
this._display.screens[0].y = event.data.args[5];
this.updateConnectionSettings();
}
break;
case 'screenRegistrationConfirmed':
if (event.data.args[0] == this._display.screenID) {
this._display.screens[0].screenIndex = event.data.args[1];
}
break; break;
} }
} }
@ -1854,23 +1947,25 @@ export default class RFB extends EventTargetMixin {
_registerSecondaryDisplay(currentScreen = false, details = null) { _registerSecondaryDisplay(currentScreen = false, details = null) {
if (!this._isPrimaryDisplay) { if (!this._isPrimaryDisplay) {
//let screen = this._screenSize().screens[0]; const registerType = (currentScreen) ? 'reattach' : 'register'
//
let size = this._screenSize(); let size = this._screenSize();
this._display.resize(size.screens[0].serverWidth, size.screens[0].serverHeight); this._display.resize(size.screens[0].serverWidth, size.screens[0].serverHeight);
this._display.autoscale(size.screens[0].serverWidth, size.screens[0].serverHeight, size.screens[0].scale); this._display.autoscale(size.screens[0].serverWidth, size.screens[0].serverHeight, size.screens[0].scale);
screen = this._screenSize().screens[0];
const registertype = (currentScreen) ? 'reattach' : 'register' let screen = size.screens[0];
let message = { let message = {
eventType: registertype, eventType: registerType,
screenID: screen.screenID, screenID: screen.screenID,
width: screen.width, width: screen.width,
height: screen.height, height: screen.height,
x: currentScreen.x || 0, x: currentScreen.x || 0,
y: currentScreen.y || 0, y: currentScreen.y || 0,
pixelRatio: screen.pixelRatio, pixelRatio: screen.pixelRatio,
scale: screen.scale,
serverWidth: screen.serverWidth,
serverHeight: screen.serverHeight,
containerWidth: screen.containerWidth, containerWidth: screen.containerWidth,
containerHeight: screen.containerHeight, containerHeight: screen.containerHeight,
channel: null, channel: null,
@ -1941,7 +2036,13 @@ export default class RFB extends EventTargetMixin {
//Ensure the window was not moved to a different screen with a different pixel ratio //Ensure the window was not moved to a different screen with a different pixel ratio
if (this._display.screens[0].pixelRatio !== window.devicePixelRatio) { if (this._display.screens[0].pixelRatio !== window.devicePixelRatio) {
Log.Debug("Window moved to another screen with different pixel ratio, sending resize request."); Log.Debug("Window moved to another screen with different pixel ratio, sending resize request.");
this._requestRemoteResize(); if (this._isPrimaryDisplay && this._display.screens.length > 1) {
//this.refreshSecondaryDisplays();
this.dispatchEvent(new CustomEvent("screenregistered", {}));
} else {
this._requestRemoteResize();
}
} }
} else { } else {
Log.Debug("Mouse left Window"); Log.Debug("Mouse left Window");
@ -2187,6 +2288,7 @@ export default class RFB extends EventTargetMixin {
_sendMouse(x, y, mask) { _sendMouse(x, y, mask) {
if (this._rfbConnectionState !== 'connected') { return; } if (this._rfbConnectionState !== 'connected') { return; }
if (this._viewOnly) { return; } // View only, skip mouse events if (this._viewOnly) { return; } // View only, skip mouse events
if (!this._isPrimaryDisplay) { return; }
if (this._pointerLock && this._pointerRelativeEnabled) { if (this._pointerLock && this._pointerRelativeEnabled) {
@ -2194,22 +2296,13 @@ export default class RFB extends EventTargetMixin {
var rel_16_x = toSignedRelative16bit(x - this._pointerLockPos.x); var rel_16_x = toSignedRelative16bit(x - this._pointerLockPos.x);
var rel_16_y = toSignedRelative16bit(y - this._pointerLockPos.y); var rel_16_y = toSignedRelative16bit(y - this._pointerLockPos.y);
if (this._isPrimaryDisplay){ RFB.messages.pointerEvent(this._sock, rel_16_x, rel_16_y, mask);
RFB.messages.pointerEvent(this._sock, rel_16_x, rel_16_y, mask);
} else {
this._proxyRFBMessage('pointerEvent', [ rel_16_x, rel_16_y, mask ]);
}
// reset the cursor position to center // reset the cursor position to center
this._mousePos = { x: this._pointerLockPos.x , y: this._pointerLockPos.y }; this._mousePos = { x: this._pointerLockPos.x , y: this._pointerLockPos.y };
this._cursor.move(this._pointerLockPos.x, this._pointerLockPos.y); this._cursor.move(this._pointerLockPos.x, this._pointerLockPos.y);
} else { } else {
if (this._isPrimaryDisplay) { RFB.messages.pointerEvent(this._sock, this._display.absX(x), this._display.absY(y), mask);
RFB.messages.pointerEvent(this._sock, this._display.absX(x), this._display.absY(y), mask);
} else {
this._proxyRFBMessage('pointerEvent', [ this._display.absX(x), this._display.absY(y), mask ]);
}
} }
} }
@ -2218,12 +2311,11 @@ export default class RFB extends EventTargetMixin {
if (this._rfbConnectionState !== 'connected') { return; } if (this._rfbConnectionState !== 'connected') { return; }
if (this._viewOnly) { return; } // View only, skip mouse events if (this._viewOnly) { return; } // View only, skip mouse events
if (this._isPrimaryDisplay){ if (this._isPrimaryDisplay) {
RFB.messages.pointerEvent(this._sock, this._display.absX(x), this._display.absY(y), 0, dX, dY); RFB.messages.pointerEvent(this._sock, this._display.absX(x), this._display.absY(y), 0, dX, dY);
} else { } else {
this._proxyRFBMessage('pointerEvent', [ this._display.absX(x), this._display.absY(y), 0, dX, dY ]); this._proxyRFBMessage('scroll', [ x, y, dX, dY ]);
} }
} }
_handleWheel(ev) { _handleWheel(ev) {
@ -4000,16 +4092,20 @@ export default class RFB extends EventTargetMixin {
for (let i = 0; i < numberOfScreens; i += 1) { for (let i = 0; i < numberOfScreens; i += 1) {
// Save the id and flags of the first screen // Save the id and flags of the first screen
if (i === 0) { let sI = this._sock.rQshift32(); // id
this._screenIndex = this._sock.rQshiftBytes(4); // id let x = this._sock.rQshift16(); // width
this._sock.rQskipBytes(2); // x-position let y = this._sock.rQshift16(); // height
this._sock.rQskipBytes(2); // y-position let w = this._sock.rQshift16(); // width
this._sock.rQskipBytes(2); // width let h = this._sock.rQshift16(); // height
this._sock.rQskipBytes(2); // height if (i == 0) {
this._screenIndex = 0;
this._screenFlags = this._sock.rQshiftBytes(4); // flags this._screenFlags = this._sock.rQshiftBytes(4); // flags
} else { } else {
this._sock.rQskipBytes(16); this._sock.rQskipBytes(4);
} }
this._display.applyServerResolution(w, h, i);
Log.Debug(`Server reported screen ${sI} with resolution ${w}x${h} at ${x}x${y}`);
} }
/* /*

View File

@ -426,6 +426,14 @@
<label for="noVNC_setting_max_video_resolution_y">Video Mode Height:</label> <label for="noVNC_setting_max_video_resolution_y">Video Mode Height:</label>
<input id="noVNC_setting_max_video_resolution_y" type="number" min="100" max="2160" value="540"> <input id="noVNC_setting_max_video_resolution_y" type="number" min="100" max="2160" value="540">
</li> </li>
<li class="noVNC_hidden">
<label for="noVNC_setting_forced_resolution_x">Static Width:</label>
<input id="noVNC_setting_forced_resolution_x" type="number" min="100" max="3840" value="960">
</li>
<li class="noVNC_hidden">
<label for="noVNC_setting_forced_resolution_y">Static Height:</label>
<input id="noVNC_setting_forced_resolution_y" type="number" min="100" max="2160" value="540">
</li>
</ul> </ul>
</div> </div>
</li> </li>