Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 23 additions & 8 deletions app/ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ const UI = {
UI.initSetting('autoconnect', false);
UI.initSetting('view_clip', false);
UI.initSetting('resize', 'off');
UI.initSetting('crop_rect');
UI.initSetting('quality', 6);
UI.initSetting('compression', 2);
UI.initSetting('shared', true);
Expand Down Expand Up @@ -360,6 +361,8 @@ const UI = {
UI.addSettingChangeHandler('resize');
UI.addSettingChangeHandler('resize', UI.applyResizeMode);
UI.addSettingChangeHandler('resize', UI.updateViewClip);
UI.addSettingChangeHandler('crop_rect');
UI.addSettingChangeHandler('crop_rect', UI.updateCropRect);
UI.addSettingChangeHandler('quality');
UI.addSettingChangeHandler('quality', UI.updateQuality);
UI.addSettingChangeHandler('compression');
Expand Down Expand Up @@ -464,6 +467,17 @@ const UI = {
.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) {
const statusElem = document.getElementById('noVNC_status');

Expand Down Expand Up @@ -1095,9 +1109,11 @@ const UI = {
UI.rfb.addEventListener("clipboard", UI.clipboardReceive);
UI.rfb.addEventListener("bell", UI.bell);
UI.rfb.addEventListener("desktopname", UI.updateDesktopName);
UI.rfb.addEventListener("croprectchanged", UI.showConnectedStatus);
UI.rfb.clipViewport = UI.getSetting('view_clip');
UI.rfb.scaleViewport = UI.getSetting('resize') === 'scale';
UI.rfb.resizeSession = UI.getSetting('resize') === 'remote';
UI.rfb.cropRect = UI.getSetting('crop_rect');
UI.rfb.qualityLevel = parseInt(UI.getSetting('quality'));
UI.rfb.compressionLevel = parseInt(UI.getSetting('compression'));
UI.rfb.showDotCursor = UI.getSetting('show_dot');
Expand Down Expand Up @@ -1144,14 +1160,7 @@ const UI = {
connectFinished(e) {
UI.connected = true;
UI.inhibitReconnect = false;

let msg;
if (UI.getSetting('encrypt')) {
msg = _("Connected (encrypted) to ") + UI.desktopName;
} else {
msg = _("Connected (unencrypted) to ") + UI.desktopName;
}
UI.showStatus(msg);
UI.showConnectedStatus();
UI.updateVisualState('connected');

// Do this last because it can only be used on rendered elements
Expand Down Expand Up @@ -1438,6 +1447,12 @@ const UI = {
viewDragButton.disabled = !UI.rfb.clippingViewport;
},

updateCropRect() {
if (!UI.connected) return;

UI.rfb.cropRect = UI.getSetting('crop_rect');
},

/* ------^-------
* /VIEWDRAG
* ==============
Expand Down
118 changes: 107 additions & 11 deletions core/rfb.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,18 @@ const extendedClipboardActionPeek = 1 << 26;
const extendedClipboardActionNotify = 1 << 27;
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 {
constructor(target, urlOrChannel, options) {
if (!target) {
Expand Down Expand Up @@ -136,6 +148,19 @@ export default class RFB extends EventTargetMixin {

this._fbWidth = 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 = "";

Expand Down Expand Up @@ -356,6 +381,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; }
set resizeSession(resize) {
this._resizeSession = resize;
Expand Down Expand Up @@ -783,6 +847,36 @@ export default class RFB extends EventTargetMixin {
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
// and may only be sent if we have received an ExtendedDesktopSize message
_requestRemoteResize() {
Expand Down Expand Up @@ -2141,8 +2235,9 @@ export default class RFB extends EventTargetMixin {
if (this._sock.rQwait("server initialization", 24)) { return false; }

/* Screen size */
const width = this._sock.rQshift16();
const height = this._sock.rQshift16();
const fbWidth = this._sock.rQshift16();
const fbHeight = this._sock.rQshift16();
const { width, height, left, top } = this._updateCropRect(fbWidth, fbHeight);

/* PIXEL_FORMAT */
const bpp = this._sock.rQshift8();
Expand Down Expand Up @@ -2219,7 +2314,7 @@ export default class RFB extends EventTargetMixin {

RFB.messages.pixelFormat(this._sock, this._fbDepth, true);
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');
return true;
Expand Down Expand Up @@ -2569,8 +2664,8 @@ export default class RFB extends EventTargetMixin {
case 0: // FramebufferUpdate
ret = this._framebufferUpdate();
if (ret && !this._enabledContinuousUpdates) {
RFB.messages.fbUpdateRequest(this._sock, true, 0, 0,
this._fbWidth, this._fbHeight);
const { left, top, width, height } = this._cropRect;
RFB.messages.fbUpdateRequest(this._sock, true, left, top, width, height);
}
return ret;

Expand Down Expand Up @@ -2641,8 +2736,9 @@ export default class RFB extends EventTargetMixin {
if (this._sock.rQwait("rect header", 12)) { return false; }
/* New FramebufferUpdate */

this._FBU.x = this._sock.rQshift16();
this._FBU.y = this._sock.rQshift16();
const { left: offsetX, top: offsetY } = this._cropRect;
this._FBU.x = this._sock.rQshift16() - offsetX;
this._FBU.y = this._sock.rQshift16() - offsetY;
this._FBU.width = this._sock.rQshift16();
this._FBU.height = this._sock.rQshift16();
this._FBU.encoding = this._sock.rQshift32();
Expand Down Expand Up @@ -2683,7 +2779,7 @@ export default class RFB extends EventTargetMixin {
return this._handleDesktopName();

case encodings.pseudoEncodingDesktopSize:
this._resize(this._FBU.width, this._FBU.height);
this._updateCropRect(this._FBU.width, this._FBU.height);
return true;

case encodings.pseudoEncodingExtendedDesktopSize:
Expand Down Expand Up @@ -2994,9 +3090,9 @@ export default class RFB extends EventTargetMixin {

_updateContinuousUpdates() {
if (!this._enabledContinuousUpdates) { return; }

RFB.messages.enableContinuousUpdates(this._sock, true, 0, 0,
this._fbWidth, this._fbHeight);
// TODO: test required
const { left, top, width, height } = this._cropRect;
RFB.messages.enableContinuousUpdates(this._sock, true, left, top, width, height);
}

// Handle resize-messages from the server
Expand Down
7 changes: 7 additions & 0 deletions docs/EMBEDDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
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`.

* `compression` - The session compression level. Can be `0` to `9`.
Expand Down
4 changes: 4 additions & 0 deletions vnc.html
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,10 @@ <h1 class="noVNC_logo" translate="no"><span>no</span><br>VNC</h1>
<option value="remote">Remote resizing</option>
</select>
</li>
<li>
<label for="noVNC_setting_crop_rect">Crop:</label>
<input id="noVNC_setting_crop_rect">
</li>
<li><hr></li>
<li>
<div class="noVNC_expander">Advanced</div>
Expand Down
1 change: 1 addition & 0 deletions vnc_lite.html
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@
// Set parameters that can be changed on an active connection
rfb.viewOnly = readQueryVariable('view_only', false);
rfb.scaleViewport = readQueryVariable('scale', false);
rfb.cropRect = readQueryVariable('crop_rect', '');
</script>
</head>

Expand Down