Merge e8bde27810 into 2ea0e7d4eb
This commit is contained in:
commit
f5d7e64c74
31
app/ui.js
31
app/ui.js
|
|
@ -180,6 +180,7 @@ const UI = {
|
||||||
UI.initSetting('autoconnect', false);
|
UI.initSetting('autoconnect', false);
|
||||||
UI.initSetting('view_clip', false);
|
UI.initSetting('view_clip', false);
|
||||||
UI.initSetting('resize', 'off');
|
UI.initSetting('resize', 'off');
|
||||||
|
UI.initSetting('crop_rect');
|
||||||
UI.initSetting('quality', 6);
|
UI.initSetting('quality', 6);
|
||||||
UI.initSetting('compression', 2);
|
UI.initSetting('compression', 2);
|
||||||
UI.initSetting('shared', true);
|
UI.initSetting('shared', true);
|
||||||
|
|
@ -362,6 +363,8 @@ const UI = {
|
||||||
UI.addSettingChangeHandler('resize');
|
UI.addSettingChangeHandler('resize');
|
||||||
UI.addSettingChangeHandler('resize', UI.applyResizeMode);
|
UI.addSettingChangeHandler('resize', UI.applyResizeMode);
|
||||||
UI.addSettingChangeHandler('resize', UI.updateViewClip);
|
UI.addSettingChangeHandler('resize', UI.updateViewClip);
|
||||||
|
UI.addSettingChangeHandler('crop_rect');
|
||||||
|
UI.addSettingChangeHandler('crop_rect', UI.updateCropRect);
|
||||||
UI.addSettingChangeHandler('quality');
|
UI.addSettingChangeHandler('quality');
|
||||||
UI.addSettingChangeHandler('quality', UI.updateQuality);
|
UI.addSettingChangeHandler('quality', UI.updateQuality);
|
||||||
UI.addSettingChangeHandler('compression');
|
UI.addSettingChangeHandler('compression');
|
||||||
|
|
@ -468,6 +471,17 @@ const UI = {
|
||||||
.classList.remove('noVNC_open');
|
.classList.remove('noVNC_open');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
showConnectedStatus(e) {
|
||||||
|
let msg;
|
||||||
|
if (UI.getSetting('encrypt')) {
|
||||||
|
msg = _("Connected (encrypted) to ") + UI.desktopName;
|
||||||
|
} else {
|
||||||
|
msg = _("Connected (unencrypted) to ") + UI.desktopName;
|
||||||
|
}
|
||||||
|
msg += ' [' + UI.rfb.cropRect + ']';
|
||||||
|
UI.showStatus(msg);
|
||||||
|
},
|
||||||
|
|
||||||
showStatus(text, statusType, time) {
|
showStatus(text, statusType, time) {
|
||||||
const statusElem = document.getElementById('noVNC_status');
|
const statusElem = document.getElementById('noVNC_status');
|
||||||
|
|
||||||
|
|
@ -1103,9 +1117,11 @@ const UI = {
|
||||||
UI.rfb.addEventListener("clipboard", UI.clipboardReceive);
|
UI.rfb.addEventListener("clipboard", UI.clipboardReceive);
|
||||||
UI.rfb.addEventListener("bell", UI.bell);
|
UI.rfb.addEventListener("bell", UI.bell);
|
||||||
UI.rfb.addEventListener("desktopname", UI.updateDesktopName);
|
UI.rfb.addEventListener("desktopname", UI.updateDesktopName);
|
||||||
|
UI.rfb.addEventListener("croprectchanged", UI.showConnectedStatus);
|
||||||
UI.rfb.clipViewport = UI.getSetting('view_clip');
|
UI.rfb.clipViewport = UI.getSetting('view_clip');
|
||||||
UI.rfb.scaleViewport = UI.getSetting('resize') === 'scale';
|
UI.rfb.scaleViewport = UI.getSetting('resize') === 'scale';
|
||||||
UI.rfb.resizeSession = UI.getSetting('resize') === 'remote';
|
UI.rfb.resizeSession = UI.getSetting('resize') === 'remote';
|
||||||
|
UI.rfb.cropRect = UI.getSetting('crop_rect');
|
||||||
UI.rfb.qualityLevel = parseInt(UI.getSetting('quality'));
|
UI.rfb.qualityLevel = parseInt(UI.getSetting('quality'));
|
||||||
UI.rfb.compressionLevel = parseInt(UI.getSetting('compression'));
|
UI.rfb.compressionLevel = parseInt(UI.getSetting('compression'));
|
||||||
UI.rfb.showDotCursor = UI.getSetting('show_dot');
|
UI.rfb.showDotCursor = UI.getSetting('show_dot');
|
||||||
|
|
@ -1153,14 +1169,7 @@ const UI = {
|
||||||
connectFinished(e) {
|
connectFinished(e) {
|
||||||
UI.connected = true;
|
UI.connected = true;
|
||||||
UI.inhibitReconnect = false;
|
UI.inhibitReconnect = false;
|
||||||
|
UI.showConnectedStatus();
|
||||||
let msg;
|
|
||||||
if (UI.getSetting('encrypt')) {
|
|
||||||
msg = _("Connected (encrypted) to ") + UI.desktopName;
|
|
||||||
} else {
|
|
||||||
msg = _("Connected (unencrypted) to ") + UI.desktopName;
|
|
||||||
}
|
|
||||||
UI.showStatus(msg);
|
|
||||||
UI.updateVisualState('connected');
|
UI.updateVisualState('connected');
|
||||||
|
|
||||||
UI.updateBeforeUnload();
|
UI.updateBeforeUnload();
|
||||||
|
|
@ -1470,6 +1479,12 @@ const UI = {
|
||||||
viewDragButton.disabled = !UI.rfb.clippingViewport;
|
viewDragButton.disabled = !UI.rfb.clippingViewport;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
updateCropRect() {
|
||||||
|
if (!UI.connected) return;
|
||||||
|
|
||||||
|
UI.rfb.cropRect = UI.getSetting('crop_rect');
|
||||||
|
},
|
||||||
|
|
||||||
/* ------^-------
|
/* ------^-------
|
||||||
* /VIEWDRAG
|
* /VIEWDRAG
|
||||||
* ==============
|
* ==============
|
||||||
|
|
|
||||||
118
core/rfb.js
118
core/rfb.js
|
|
@ -88,6 +88,18 @@ const extendedClipboardActionPeek = 1 << 26;
|
||||||
const extendedClipboardActionNotify = 1 << 27;
|
const extendedClipboardActionNotify = 1 << 27;
|
||||||
const extendedClipboardActionProvide = 1 << 28;
|
const extendedClipboardActionProvide = 1 << 28;
|
||||||
|
|
||||||
|
// [GEOMETRY SPECIFICATIONS](https://www.x.org/releases/X11R7.7/doc/man/man7/X.7.xhtml#heading7)
|
||||||
|
// [X0VNCSERVER −Geometry](https://tigervnc.org/doc/x0vncserver.html#:~:text=is%2060.-,%E2%88%92Geometry%20geometry)
|
||||||
|
const geometryRegExp = new RegExp([
|
||||||
|
'^',
|
||||||
|
'(?<width>0|[1-9][0-9]*)', 'x', '(?<height>0|[1-9][0-9]*)',
|
||||||
|
'(?:',
|
||||||
|
'(?:', '[+](?<left>0|[1-9][0-9]*)', '|', '[-](?<right>0|[1-9][0-9]*)', ')',
|
||||||
|
'(?:', '[+](?<top>0|[1-9][0-9]*)', '|', '[-](?<bottom>0|[1-9][0-9]*)', ')',
|
||||||
|
')?',
|
||||||
|
'$',
|
||||||
|
].join(''));
|
||||||
|
|
||||||
export default class RFB extends EventTargetMixin {
|
export default class RFB extends EventTargetMixin {
|
||||||
constructor(target, urlOrChannel, options) {
|
constructor(target, urlOrChannel, options) {
|
||||||
if (!target) {
|
if (!target) {
|
||||||
|
|
@ -137,6 +149,19 @@ export default class RFB extends EventTargetMixin {
|
||||||
|
|
||||||
this._fbWidth = 0;
|
this._fbWidth = 0;
|
||||||
this._fbHeight = 0;
|
this._fbHeight = 0;
|
||||||
|
this._cropRect = {
|
||||||
|
geometry: undefined,
|
||||||
|
width: 0,
|
||||||
|
height: 0,
|
||||||
|
left: 0,
|
||||||
|
right: undefined,
|
||||||
|
top: 0,
|
||||||
|
bottom: undefined,
|
||||||
|
// we keep a redundant copy of fbWidth and fbHeight here
|
||||||
|
// to avoid conflicts with existing code
|
||||||
|
fbWidth: undefined,
|
||||||
|
fbHeight: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
this._fbName = "";
|
this._fbName = "";
|
||||||
|
|
||||||
|
|
@ -363,6 +388,45 @@ export default class RFB extends EventTargetMixin {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get cropRect() {
|
||||||
|
const { width, height, left, right, top, bottom, fbWidth, fbHeight } = this._cropRect;
|
||||||
|
return `${width}x${height}${
|
||||||
|
right ?? false ? `+${left}` : `-${right}`
|
||||||
|
}${
|
||||||
|
bottom ?? false ? `+${top}` : `-${bottom}`
|
||||||
|
}${
|
||||||
|
width === fbWidth && height === fbHeight ? '' : ` (${fbWidth}x${fbHeight})`
|
||||||
|
}`;
|
||||||
|
}
|
||||||
|
set cropRect(geometry) {
|
||||||
|
const rect = Object.assign( this._cropRect, { geometry } );
|
||||||
|
const { fbWidth, fbHeight } = rect;
|
||||||
|
if (geometry && (geometry = geometry.match(geometryRegExp)?.groups)) {
|
||||||
|
Object.assign(rect, Object.fromEntries(
|
||||||
|
Object.entries(geometry).map(([k, v]) => ([k, v === undefined ? undefined : +v]))
|
||||||
|
));
|
||||||
|
} else { // empty or invalid geometry
|
||||||
|
Object.assign(rect, {
|
||||||
|
width: 0,
|
||||||
|
height: 0,
|
||||||
|
left: 0,
|
||||||
|
right: undefined,
|
||||||
|
top: 0,
|
||||||
|
bottom: undefined,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const { width, height, left, top } = this._updateCropRect(fbWidth, fbHeight);
|
||||||
|
if (width && height) {
|
||||||
|
this._resize(width, height);
|
||||||
|
if (this._rfbConnectionState === 'connected') {
|
||||||
|
RFB.messages.fbUpdateRequest(this._sock, false, left, top, width, height);
|
||||||
|
this.dispatchEvent(new CustomEvent('croprectchanged', {
|
||||||
|
detail: this.cropRect,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
get resizeSession() { return this._resizeSession; }
|
get resizeSession() { return this._resizeSession; }
|
||||||
set resizeSession(resize) {
|
set resizeSession(resize) {
|
||||||
this._resizeSession = resize;
|
this._resizeSession = resize;
|
||||||
|
|
@ -790,6 +854,36 @@ export default class RFB extends EventTargetMixin {
|
||||||
this._fixScrollbars();
|
this._fixScrollbars();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_updateCropRect(fbWidth, fbHeight) {
|
||||||
|
const rect = this._cropRect;
|
||||||
|
const { fbWidth: prevFbWidth, fbHeight: prevFbHeight, geometry } = rect;
|
||||||
|
let { width, height, left, right, top, bottom } = Object.assign(rect, { fbWidth, fbHeight });
|
||||||
|
function compute(width, left, right, maxWidth) {
|
||||||
|
if (width === 0 || width > maxWidth) { width = maxWidth; }
|
||||||
|
if (right === undefined) {
|
||||||
|
left ??= 0;
|
||||||
|
if (left + width > maxWidth) {
|
||||||
|
left = maxWidth - width;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (right + width > maxWidth) {
|
||||||
|
right = 0;
|
||||||
|
left = 0;
|
||||||
|
} else {
|
||||||
|
left = maxWidth - (right + width);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [ width, left, right ];
|
||||||
|
}
|
||||||
|
[ width, left, right ] = compute(width, left, right, fbWidth);
|
||||||
|
[ height, top, bottom ] = compute(height, top, bottom, fbHeight);
|
||||||
|
Object.assign(rect, { width, height, left, right, top, bottom });
|
||||||
|
if (prevFbWidth !== fbWidth || prevFbHeight !== fbHeight) {
|
||||||
|
this.cropRect = geometry;
|
||||||
|
}
|
||||||
|
return rect;
|
||||||
|
}
|
||||||
|
|
||||||
// Requests a change of remote desktop size. This message is an extension
|
// Requests a change of remote desktop size. This message is an extension
|
||||||
// and may only be sent if we have received an ExtendedDesktopSize message
|
// and may only be sent if we have received an ExtendedDesktopSize message
|
||||||
_requestRemoteResize() {
|
_requestRemoteResize() {
|
||||||
|
|
@ -2148,8 +2242,9 @@ export default class RFB extends EventTargetMixin {
|
||||||
if (this._sock.rQwait("server initialization", 24)) { return false; }
|
if (this._sock.rQwait("server initialization", 24)) { return false; }
|
||||||
|
|
||||||
/* Screen size */
|
/* Screen size */
|
||||||
const width = this._sock.rQshift16();
|
const fbWidth = this._sock.rQshift16();
|
||||||
const height = this._sock.rQshift16();
|
const fbHeight = this._sock.rQshift16();
|
||||||
|
const { width, height, left, top } = this._updateCropRect(fbWidth, fbHeight);
|
||||||
|
|
||||||
/* PIXEL_FORMAT */
|
/* PIXEL_FORMAT */
|
||||||
const bpp = this._sock.rQshift8();
|
const bpp = this._sock.rQshift8();
|
||||||
|
|
@ -2229,7 +2324,7 @@ export default class RFB extends EventTargetMixin {
|
||||||
|
|
||||||
RFB.messages.pixelFormat(this._sock, this._fbDepth, true);
|
RFB.messages.pixelFormat(this._sock, this._fbDepth, true);
|
||||||
this._sendEncodings();
|
this._sendEncodings();
|
||||||
RFB.messages.fbUpdateRequest(this._sock, false, 0, 0, this._fbWidth, this._fbHeight);
|
RFB.messages.fbUpdateRequest(this._sock, false, left, top, width, height);
|
||||||
|
|
||||||
this._updateConnectionState('connected');
|
this._updateConnectionState('connected');
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -2584,8 +2679,8 @@ export default class RFB extends EventTargetMixin {
|
||||||
case 0: // FramebufferUpdate
|
case 0: // FramebufferUpdate
|
||||||
ret = this._framebufferUpdate();
|
ret = this._framebufferUpdate();
|
||||||
if (ret && !this._enabledContinuousUpdates) {
|
if (ret && !this._enabledContinuousUpdates) {
|
||||||
RFB.messages.fbUpdateRequest(this._sock, true, 0, 0,
|
const { left, top, width, height } = this._cropRect;
|
||||||
this._fbWidth, this._fbHeight);
|
RFB.messages.fbUpdateRequest(this._sock, true, left, top, width, height);
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
|
|
@ -2656,8 +2751,9 @@ export default class RFB extends EventTargetMixin {
|
||||||
if (this._sock.rQwait("rect header", 12)) { return false; }
|
if (this._sock.rQwait("rect header", 12)) { return false; }
|
||||||
/* New FramebufferUpdate */
|
/* New FramebufferUpdate */
|
||||||
|
|
||||||
this._FBU.x = this._sock.rQshift16();
|
const { left: offsetX, top: offsetY } = this._cropRect;
|
||||||
this._FBU.y = this._sock.rQshift16();
|
this._FBU.x = this._sock.rQshift16() - offsetX;
|
||||||
|
this._FBU.y = this._sock.rQshift16() - offsetY;
|
||||||
this._FBU.width = this._sock.rQshift16();
|
this._FBU.width = this._sock.rQshift16();
|
||||||
this._FBU.height = this._sock.rQshift16();
|
this._FBU.height = this._sock.rQshift16();
|
||||||
this._FBU.encoding = this._sock.rQshift32();
|
this._FBU.encoding = this._sock.rQshift32();
|
||||||
|
|
@ -2698,7 +2794,7 @@ export default class RFB extends EventTargetMixin {
|
||||||
return this._handleDesktopName();
|
return this._handleDesktopName();
|
||||||
|
|
||||||
case encodings.pseudoEncodingDesktopSize:
|
case encodings.pseudoEncodingDesktopSize:
|
||||||
this._resize(this._FBU.width, this._FBU.height);
|
this._updateCropRect(this._FBU.width, this._FBU.height);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case encodings.pseudoEncodingExtendedDesktopSize:
|
case encodings.pseudoEncodingExtendedDesktopSize:
|
||||||
|
|
@ -3009,9 +3105,9 @@ export default class RFB extends EventTargetMixin {
|
||||||
|
|
||||||
_updateContinuousUpdates() {
|
_updateContinuousUpdates() {
|
||||||
if (!this._enabledContinuousUpdates) { return; }
|
if (!this._enabledContinuousUpdates) { return; }
|
||||||
|
// TODO: test required
|
||||||
RFB.messages.enableContinuousUpdates(this._sock, true, 0, 0,
|
const { left, top, width, height } = this._cropRect;
|
||||||
this._fbWidth, this._fbHeight);
|
RFB.messages.enableContinuousUpdates(this._sock, true, left, top, width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle resize-messages from the server
|
// Handle resize-messages from the server
|
||||||
|
|
|
||||||
|
|
@ -85,6 +85,13 @@ Currently, the following options are available:
|
||||||
* `resize` - How to resize the remote session if it is not the same size as
|
* `resize` - How to resize the remote session if it is not the same size as
|
||||||
the browser window. Can be one of `off`, `scale` and `remote`.
|
the browser window. Can be one of `off`, `scale` and `remote`.
|
||||||
|
|
||||||
|
* `crop_rect` - This option specifies the remote framebuffer area that will be
|
||||||
|
shown in the noVNC client. The format is widthxheight+xoffset+yoffset, where
|
||||||
|
‘+’ signs can be replaced with ‘−’ signs to specify offsets from the right
|
||||||
|
and/or from the bottom of the screen. Offsets are optional, +0+0 is assumed
|
||||||
|
by default (top left corner). If the argument is empty, full screen is shown
|
||||||
|
to VNC clients (this is the default). See -Geometry parameter of TigerVNC.
|
||||||
|
|
||||||
* `quality` - The session JPEG quality level. Can be `0` to `9`.
|
* `quality` - The session JPEG quality level. Can be `0` to `9`.
|
||||||
|
|
||||||
* `compression` - The session compression level. Can be `0` to `9`.
|
* `compression` - The session compression level. Can be `0` to `9`.
|
||||||
|
|
|
||||||
4
vnc.html
4
vnc.html
|
|
@ -235,6 +235,10 @@
|
||||||
<option value="remote">Remote resizing</option>
|
<option value="remote">Remote resizing</option>
|
||||||
</select>
|
</select>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<label for="noVNC_setting_crop_rect">Crop:</label>
|
||||||
|
<input id="noVNC_setting_crop_rect">
|
||||||
|
</li>
|
||||||
<li><hr></li>
|
<li><hr></li>
|
||||||
<li>
|
<li>
|
||||||
<div class="noVNC_expander">Advanced</div>
|
<div class="noVNC_expander">Advanced</div>
|
||||||
|
|
|
||||||
|
|
@ -165,6 +165,7 @@
|
||||||
// Set parameters that can be changed on an active connection
|
// Set parameters that can be changed on an active connection
|
||||||
rfb.viewOnly = readQueryVariable('view_only', false);
|
rfb.viewOnly = readQueryVariable('view_only', false);
|
||||||
rfb.scaleViewport = readQueryVariable('scale', false);
|
rfb.scaleViewport = readQueryVariable('scale', false);
|
||||||
|
rfb.cropRect = readQueryVariable('crop_rect', '');
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue