diff --git a/app/ui.js b/app/ui.js index c5b1f890d..061d48336 100644 --- a/app/ui.js +++ b/app/ui.js @@ -50,6 +50,7 @@ import { FPS } from './constants.js'; import {encodings} from "../core/encodings.js"; +import { normalizeFrameRate } from "../core/util/frame-rate.js"; import CodecDetector, {CODEC_VARIANT_NAMES, preferredCodecs} from "../core/codecs"; import { perfLogger } from '../core/util/performance-logger.js'; @@ -1451,6 +1452,7 @@ const UI = { if (val === null) { val = WebUtil.readSetting(name, defVal); } + val = UI.sanitizeSetting(name, val); WebUtil.setSetting(name, val); UI.updateSetting(name); return val; @@ -1458,6 +1460,7 @@ const UI = { // Set the new value, update and disable form control setting forceSetting(name, val, disable=true) { + val = UI.sanitizeSetting(name, val); WebUtil.setSetting(name, val); UI.updateSetting(name); if (disable) { @@ -1497,6 +1500,18 @@ const UI = { } }, + // Ensure settings stay within supported bounds + sanitizeSetting(name, value) { + switch (name) { + case 'framerate': + case 'framerate_image_mode': + case 'framerate_streaming_mode': + return String(normalizeFrameRate(value, FPS.MIN)); + default: + return value; + } + }, + // Save control setting to cookie saveSetting(name) { const ctrl = document.getElementById('noVNC_setting_' + name); @@ -1509,6 +1524,13 @@ const UI = { } else { val = ctrl.value; } + const sanitized = UI.sanitizeSetting(name, val); + if (sanitized !== val) { + if (ctrl && typeof ctrl.value !== 'undefined') { + ctrl.value = sanitized; + } + val = sanitized; + } WebUtil.writeSetting(name, val); Log.Debug("Setting saved '" + name + "=" + val + "'"); return val; @@ -1528,8 +1550,12 @@ const UI = { val = true; } } - - return val; + const sanitized = UI.sanitizeSetting(name, val); + const currentStr = (val === null || typeof val === 'undefined') ? null : String(val); + if (sanitized !== val && sanitized !== currentStr) { + WebUtil.writeSetting(name, sanitized); + } + return sanitized; }, getSettingElement(name) { diff --git a/core/encodings.js b/core/encodings.js index 4e6c97914..9888b87b2 100644 --- a/core/encodings.js +++ b/core/encodings.js @@ -30,8 +30,8 @@ export const encodings = { pseudoEncodingCompressLevel9: -247, pseudoEncodingCompressLevel0: -256, - pseudoEncodingFrameRateLevel10: -2048, - pseudoEncodingFrameRateLevel60: -1998, + pseudoEncodingFrameRateLevel10: -4096, + pseudoEncodingFrameRateLevel120: -3986, pseudoEncodingMaxVideoResolution: -1997, pseudoEncodingVideoScalingLevel0: -1996, pseudoEncodingVideoScalingLevel9: -1987, diff --git a/core/rfb.js b/core/rfb.js index e088a9ec0..f1d78007e 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -29,6 +29,12 @@ import DES from "./des.js"; import KeyTable from "./input/keysym.js"; import XtScancode from "./input/xtscancodes.js"; import { encodings } from "./encodings.js"; +import { + FRAME_RATE_MAX, + FRAME_RATE_MIN, + frameRateToPseudoEncoding, + isValidFrameRate, +} from "./util/frame-rate.js"; import { messages } from "./messages.js"; import { MouseButtonMapper, xvncButtonToMask } from "./mousebuttonmapper.js"; @@ -640,8 +646,8 @@ export default class RFB extends EventTargetMixin { get frameRate() { return this._frameRate; } set frameRate(value) { - if (!Number.isInteger(value) || value < 1 || value > 120) { - Log.Error("frame rate must be an integer between 1 and 120"); + if (!isValidFrameRate(value)) { + Log.Error(`frame rate must be an integer between ${FRAME_RATE_MIN} and ${FRAME_RATE_MAX}`); return; } @@ -3463,7 +3469,7 @@ export default class RFB extends EventTargetMixin { encs.push(encodings.pseudoEncodingVideoTimeLevel0 + this.videoTime); encs.push(encodings.pseudoEncodingVideoOutTimeLevel1 + this.videoOutTime - 1); encs.push(encodings.pseudoEncodingVideoScalingLevel0 + this.videoScaling); - encs.push(encodings.pseudoEncodingFrameRateLevel10 + this.frameRate - 10); + encs.push(frameRateToPseudoEncoding(this.frameRate)); encs.push(encodings.pseudoEncodingMaxVideoResolution); // Order is important: first options, then streaming mode diff --git a/core/util/frame-rate.js b/core/util/frame-rate.js new file mode 100644 index 000000000..16c0361c1 --- /dev/null +++ b/core/util/frame-rate.js @@ -0,0 +1,26 @@ +import { encodings } from "../encodings.js"; + +export const FRAME_RATE_MIN = 10; +export const FRAME_RATE_MAX = 120; + +export function isValidFrameRate(value) { + return Number.isInteger(value) && + value >= FRAME_RATE_MIN && value <= FRAME_RATE_MAX; +} + +export function normalizeFrameRate(value, fallback) { + const fallbackValue = isValidFrameRate(fallback) ? fallback : FRAME_RATE_MIN; + const numericValue = (value === null || value === undefined || value === "") ? + fallbackValue : Number(value); + const finiteValue = Number.isFinite(numericValue) ? numericValue : fallbackValue; + + return Math.round(Math.max(FRAME_RATE_MIN, Math.min(FRAME_RATE_MAX, finiteValue))); +} + +export function frameRateToPseudoEncoding(value) { + if (!isValidFrameRate(value)) { + throw new RangeError(`Frame rate must be an integer between ${FRAME_RATE_MIN} and ${FRAME_RATE_MAX}`); + } + + return encodings.pseudoEncodingFrameRateLevel10 + value - FRAME_RATE_MIN; +} diff --git a/index.html b/index.html index 4f30332e6..0e16ee859 100644 --- a/index.html +++ b/index.html @@ -428,7 +428,7 @@

  • - +
  • @@ -509,7 +509,7 @@

  • - +