From a30f609de48227df08f633bc0443e16678b20d32 Mon Sep 17 00:00:00 2001 From: Samuel Mannehed Date: Wed, 2 Nov 2022 10:23:36 +0100 Subject: [PATCH 01/39] Don't crash if we can't use localStorage Our settings are not a fatal requirement, we can fall back on the default values if they can't be accessed. A scenario where we've seen this happen is when cookies are disabled in the browser. It seems localStorage is disabled along with cookies in these settings. So, lets log a message about the failure and otherwise silently continue in this case. Fixes issue #1577. --- app/webutil.js | 76 +++++++++++++++++++++++++++++++++++++++---- tests/test.webutil.js | 15 +++++++++ 2 files changed, 85 insertions(+), 6 deletions(-) diff --git a/app/webutil.js b/app/webutil.js index b94f035d..6011442c 100644 --- a/app/webutil.js +++ b/app/webutil.js @@ -6,16 +6,16 @@ * See README.md for usage and integration instructions. */ -import { initLogging as mainInitLogging } from '../core/util/logging.js'; +import * as Log from '../core/util/logging.js'; // init log level reading the logging HTTP param export function initLogging(level) { "use strict"; if (typeof level !== "undefined") { - mainInitLogging(level); + Log.initLogging(level); } else { const param = document.location.href.match(/logging=([A-Za-z0-9._-]*)/); - mainInitLogging(param || undefined); + Log.initLogging(param || undefined); } } @@ -146,7 +146,7 @@ export function writeSetting(name, value) { if (window.chrome && window.chrome.storage) { window.chrome.storage.sync.set(settings); } else { - localStorage.setItem(name, value); + localStorageSet(name, value); } } @@ -156,7 +156,7 @@ export function readSetting(name, defaultValue) { if ((name in settings) || (window.chrome && window.chrome.storage)) { value = settings[name]; } else { - value = localStorage.getItem(name); + value = localStorageGet(name); settings[name] = value; } if (typeof value === "undefined") { @@ -181,6 +181,70 @@ export function eraseSetting(name) { if (window.chrome && window.chrome.storage) { window.chrome.storage.sync.remove(name); } else { - localStorage.removeItem(name); + localStorageRemove(name); + } +} + +let loggedMsgs = []; +function logOnce(msg, level = "warn") { + if (!loggedMsgs.includes(msg)) { + switch (level) { + case "error": + Log.Error(msg); + break; + case "warn": + Log.Warn(msg); + break; + case "debug": + Log.Debug(msg); + break; + default: + Log.Info(msg); + } + loggedMsgs.push(msg); + } +} + +let cookiesMsg = "Couldn't access noVNC settings, are cookies disabled?"; + +function localStorageGet(name) { + let r; + try { + r = localStorage.getItem(name); + } catch (e) { + if (e instanceof DOMException) { + logOnce(cookiesMsg); + logOnce("'localStorage.getItem(" + name + ")' failed: " + e, + "debug"); + } else { + throw e; + } + } + return r; +} +function localStorageSet(name, value) { + try { + localStorage.setItem(name, value); + } catch (e) { + if (e instanceof DOMException) { + logOnce(cookiesMsg); + logOnce("'localStorage.setItem(" + name + "," + value + + ")' failed: " + e, "debug"); + } else { + throw e; + } + } +} +function localStorageRemove(name) { + try { + localStorage.removeItem(name); + } catch (e) { + if (e instanceof DOMException) { + logOnce(cookiesMsg); + logOnce("'localStorage.removeItem(" + name + ")' failed: " + e, + "debug"); + } else { + throw e; + } } } diff --git a/tests/test.webutil.js b/tests/test.webutil.js index 6f460a3f..df8227ae 100644 --- a/tests/test.webutil.js +++ b/tests/test.webutil.js @@ -92,6 +92,11 @@ describe('WebUtil', function () { expect(window.localStorage.setItem).to.have.been.calledWithExactly('test', 'value'); expect(WebUtil.readSetting('test')).to.equal('value'); }); + + it('should not crash when local storage save fails', function () { + localStorage.setItem.throws(new DOMException()); + expect(WebUtil.writeSetting('test', 'value')).to.not.throw; + }); }); describe('setSetting', function () { @@ -137,6 +142,11 @@ describe('WebUtil', function () { WebUtil.writeSetting('test', 'something else'); expect(WebUtil.readSetting('test')).to.equal('something else'); }); + + it('should not crash when local storage read fails', function () { + localStorage.getItem.throws(new DOMException()); + expect(WebUtil.readSetting('test')).to.not.throw; + }); }); // this doesn't appear to be used anywhere @@ -145,6 +155,11 @@ describe('WebUtil', function () { WebUtil.eraseSetting('test'); expect(window.localStorage.removeItem).to.have.been.calledWithExactly('test'); }); + + it('should not crash when local storage remove fails', function () { + localStorage.removeItem.throws(new DOMException()); + expect(WebUtil.eraseSetting('test')).to.not.throw; + }); }); }); From 01bb36d43171c340cb10028e938d2c3afadb2049 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Tue, 29 Aug 2023 17:28:54 +0200 Subject: [PATCH 02/39] Use proper argument to deflateInit() This was an accidental copy error from inflator.js. The second argument to deflateInit() is the compression level, not the window bits. We have not strong opinions on an appropriate level, so stick to the default. --- core/deflator.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/core/deflator.js b/core/deflator.js index fe2a8f70..22f6770b 100644 --- a/core/deflator.js +++ b/core/deflator.js @@ -7,7 +7,7 @@ */ import { deflateInit, deflate } from "../vendor/pako/lib/zlib/deflate.js"; -import { Z_FULL_FLUSH } from "../vendor/pako/lib/zlib/deflate.js"; +import { Z_FULL_FLUSH, Z_DEFAULT_COMPRESSION } from "../vendor/pako/lib/zlib/deflate.js"; import ZStream from "../vendor/pako/lib/zlib/zstream.js"; export default class Deflator { @@ -15,9 +15,8 @@ export default class Deflator { this.strm = new ZStream(); this.chunkSize = 1024 * 10 * 10; this.outputBuffer = new Uint8Array(this.chunkSize); - this.windowBits = 5; - deflateInit(this.strm, this.windowBits); + deflateInit(this.strm, Z_DEFAULT_COMPRESSION); } deflate(inData) { From b40a45a11b98c2a2dac24f9b6151722f1e3115be Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Tue, 29 Aug 2023 17:30:00 +0200 Subject: [PATCH 03/39] Remove unused argument to inflateInit() There is just one argument to inflateInit(). It is inflateInit2() that takes two arguments. Since this argument was never used, let's just remove it and keep the existing behaviour. --- core/inflator.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/inflator.js b/core/inflator.js index 4b337607..f851f2a7 100644 --- a/core/inflator.js +++ b/core/inflator.js @@ -14,9 +14,8 @@ export default class Inflate { this.strm = new ZStream(); this.chunkSize = 1024 * 10 * 10; this.strm.output = new Uint8Array(this.chunkSize); - this.windowBits = 5; - inflateInit(this.strm, this.windowBits); + inflateInit(this.strm); } setInput(data) { From e81602d705982d29f7b46ce47c3af108865b03e7 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Tue, 29 Aug 2023 17:38:44 +0200 Subject: [PATCH 04/39] Fix zlib level change in clipboard tests The compression level got changed in 01bb36d4, but the tests weren't updated to follow this change. --- tests/test.rfb.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test.rfb.js b/tests/test.rfb.js index bf12a460..fd156340 100644 --- a/tests/test.rfb.js +++ b/tests/test.rfb.js @@ -3,7 +3,7 @@ const expect = chai.expect; import RFB from '../core/rfb.js'; import Websock from '../core/websock.js'; import ZStream from "../vendor/pako/lib/zlib/zstream.js"; -import { deflateInit, deflate } from "../vendor/pako/lib/zlib/deflate.js"; +import { deflateInit, deflate, Z_DEFAULT_COMPRESSION } from "../vendor/pako/lib/zlib/deflate.js"; import { encodings } from '../core/encodings.js'; import { toUnsigned32bit } from '../core/util/int.js'; import { encodeUTF8 } from '../core/util/strings.js'; @@ -54,7 +54,7 @@ function deflateWithSize(data) { let strm = new ZStream(); let chunkSize = 1024 * 10 * 10; strm.output = new Uint8Array(chunkSize); - deflateInit(strm, 5); + deflateInit(strm, Z_DEFAULT_COMPRESSION); /* eslint-disable camelcase */ strm.input = unCompData; From 72f6810797f7bc70b94dea62de2c8256ccb944d9 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Thu, 7 Sep 2023 14:38:04 +0200 Subject: [PATCH 05/39] Correctly handle "none" auth on old servers There is no security result for the "none" authentication until RFB 3.8. This got broken by mistake in 5671072. --- core/rfb.js | 6 +++++- tests/test.rfb.js | 19 +++++++++++++++++-- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/core/rfb.js b/core/rfb.js index fb9df0b9..477b30f5 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -1925,7 +1925,11 @@ export default class RFB extends EventTargetMixin { _negotiateAuthentication() { switch (this._rfbAuthScheme) { case securityTypeNone: - this._rfbInitState = 'SecurityResult'; + if (this._rfbVersion >= 3.8) { + this._rfbInitState = 'SecurityResult'; + } else { + this._rfbInitState = 'ClientInitialisation'; + } return true; case securityTypeXVP: diff --git a/tests/test.rfb.js b/tests/test.rfb.js index fd156340..d1313713 100644 --- a/tests/test.rfb.js +++ b/tests/test.rfb.js @@ -1257,7 +1257,15 @@ describe('Remote Frame Buffer Protocol Client', function () { client._sock._websocket._receiveData(new Uint8Array([0, 0, 0, 1])); expect(client._rfbInitState).to.equal('ServerInitialisation'); }); - }); + + it('should transition straight to ServerInitialisation on "no auth" for versions < 3.8', function () { + sendVer('003.007\n', client); + client._sock._websocket._getSentData(); + + sendSecurity(1, client); + expect(client._rfbInitState).to.equal('ServerInitialisation'); + }); + }); describe('Authentication', function () { beforeEach(function () { @@ -2231,10 +2239,17 @@ describe('Remote Frame Buffer Protocol Client', function () { describe('Legacy SecurityResult', function () { beforeEach(function () { + client.addEventListener("credentialsrequired", () => { + client.sendCredentials({ password: 'passwd' }); + }); sendVer('003.007\n', client); client._sock._websocket._getSentData(); - sendSecurity(1, client); + sendSecurity(2, client); + const challenge = []; + for (let i = 0; i < 16; i++) { challenge[i] = i; } + client._sock._websocket._receiveData(new Uint8Array(challenge)); client._sock._websocket._getSentData(); + clock.tick(); }); it('should not include reason in securityfailure event', function () { From 370f21b11723196b67f0acebe85d45ca7fc7a34e Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Thu, 7 Sep 2023 14:59:36 +0200 Subject: [PATCH 06/39] Correctly handle legacy security rejections The code comment of this code was entirely incorrect, but the commit message for 5671072 when it was added was correct. I.e. there is a result, but not a reason. Adjust the unit tests to make sure this doesn't regress again. --- core/rfb.js | 7 ------- tests/test.rfb.js | 29 +++++++++++++++++++++-------- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/core/rfb.js b/core/rfb.js index 477b30f5..6bce48fd 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -1966,13 +1966,6 @@ export default class RFB extends EventTargetMixin { } _handleSecurityResult() { - // There is no security choice, and hence no security result - // until RFB 3.7 - if (this._rfbVersion < 3.7) { - this._rfbInitState = 'ClientInitialisation'; - return true; - } - if (this._sock.rQwait('VNC auth response ', 4)) { return false; } const status = this._sock.rQshift32(); diff --git a/tests/test.rfb.js b/tests/test.rfb.js index d1313713..fa7f1402 100644 --- a/tests/test.rfb.js +++ b/tests/test.rfb.js @@ -2238,23 +2238,36 @@ describe('Remote Frame Buffer Protocol Client', function () { }); describe('Legacy SecurityResult', function () { - beforeEach(function () { + it('should not include reason in securityfailure event for versions < 3.7', function () { client.addEventListener("credentialsrequired", () => { client.sendCredentials({ password: 'passwd' }); }); + const spy = sinon.spy(); + client.addEventListener("securityfailure", spy); + sendVer('003.006\n', client); + client._sock._websocket._receiveData(new Uint8Array([0, 0, 0, 2])); + const challenge = []; + for (let i = 0; i < 16; i++) { challenge[i] = i; } + client._sock._websocket._receiveData(new Uint8Array(challenge)); + + client._sock._websocket._receiveData(new Uint8Array([0, 0, 0, 2])); + expect(spy).to.have.been.calledOnce; + expect(spy.args[0][0].detail.status).to.equal(2); + expect('reason' in spy.args[0][0].detail).to.be.false; + }); + + it('should not include reason in securityfailure event for versions < 3.8', function () { + client.addEventListener("credentialsrequired", () => { + client.sendCredentials({ password: 'passwd' }); + }); + const spy = sinon.spy(); + client.addEventListener("securityfailure", spy); sendVer('003.007\n', client); - client._sock._websocket._getSentData(); sendSecurity(2, client); const challenge = []; for (let i = 0; i < 16; i++) { challenge[i] = i; } client._sock._websocket._receiveData(new Uint8Array(challenge)); - client._sock._websocket._getSentData(); - clock.tick(); - }); - it('should not include reason in securityfailure event', function () { - const spy = sinon.spy(); - client.addEventListener("securityfailure", spy); client._sock._websocket._receiveData(new Uint8Array([0, 0, 0, 2])); expect(spy).to.have.been.calledOnce; expect(spy.args[0][0].detail.status).to.equal(2); From bf12c24f4c88d2ba3ecd9c7a27d923fbf05f0632 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Thu, 7 Sep 2023 15:35:20 +0200 Subject: [PATCH 07/39] Fix bad indentation --- tests/test.rfb.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test.rfb.js b/tests/test.rfb.js index fa7f1402..c0e85c5b 100644 --- a/tests/test.rfb.js +++ b/tests/test.rfb.js @@ -1265,7 +1265,7 @@ describe('Remote Frame Buffer Protocol Client', function () { sendSecurity(1, client); expect(client._rfbInitState).to.equal('ServerInitialisation'); }); - }); + }); describe('Authentication', function () { beforeEach(function () { From a0b7c0dac5359e4002e7f1d946e60e2eb9b4a54e Mon Sep 17 00:00:00 2001 From: Otto van Houten Date: Wed, 26 Jul 2023 14:38:31 +0200 Subject: [PATCH 08/39] Add QEMU Led Pseudo encoding support Previously, num-lock and caps-lock syncing was performed on a best effort basis by qemu. Now, the syncing is performed by no-vnc instead. This allows the led state syncing to work in cases where it previously couldn't, since no-vnc has with this extension knowledge of both the remote and local capslock and numlock status, which QEMU doesn't have. --- core/encodings.js | 1 + core/input/keyboard.js | 34 ++++++---- core/rfb.js | 51 ++++++++++++++- tests/test.keyboard.js | 48 ++++++++++++++ tests/test.rfb.js | 143 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 263 insertions(+), 14 deletions(-) diff --git a/core/encodings.js b/core/encodings.js index 2041b6e0..1a79989d 100644 --- a/core/encodings.js +++ b/core/encodings.js @@ -22,6 +22,7 @@ export const encodings = { pseudoEncodingLastRect: -224, pseudoEncodingCursor: -239, pseudoEncodingQEMUExtendedKeyEvent: -258, + pseudoEncodingQEMULedEvent: -261, pseudoEncodingDesktopName: -307, pseudoEncodingExtendedDesktopSize: -308, pseudoEncodingXvp: -309, diff --git a/core/input/keyboard.js b/core/input/keyboard.js index ddb5ce09..9068e9e9 100644 --- a/core/input/keyboard.js +++ b/core/input/keyboard.js @@ -36,7 +36,7 @@ export default class Keyboard { // ===== PRIVATE METHODS ===== - _sendKeyEvent(keysym, code, down) { + _sendKeyEvent(keysym, code, down, numlock = null, capslock = null) { if (down) { this._keyDownList[code] = keysym; } else { @@ -48,8 +48,8 @@ export default class Keyboard { } Log.Debug("onkeyevent " + (down ? "down" : "up") + - ", keysym: " + keysym, ", code: " + code); - this.onkeyevent(keysym, code, down); + ", keysym: " + keysym, ", code: " + code, + ", numlock: " + numlock + ", capslock: " + capslock); + this.onkeyevent(keysym, code, down, numlock, capslock); } _getKeyCode(e) { @@ -86,6 +86,14 @@ export default class Keyboard { _handleKeyDown(e) { const code = this._getKeyCode(e); let keysym = KeyboardUtil.getKeysym(e); + let numlock = e.getModifierState('NumLock'); + let capslock = e.getModifierState('CapsLock'); + + // getModifierState for NumLock is not supported on mac and ios and always returns false. + // Set to null to indicate unknown/unsupported instead. + if (browser.isMac() || browser.isIOS()) { + numlock = null; + } // Windows doesn't have a proper AltGr, but handles it using // fake Ctrl+Alt. However the remote end might not be Windows, @@ -107,7 +115,7 @@ export default class Keyboard { // key to "AltGraph". keysym = KeyTable.XK_ISO_Level3_Shift; } else { - this._sendKeyEvent(KeyTable.XK_Control_L, "ControlLeft", true); + this._sendKeyEvent(KeyTable.XK_Control_L, "ControlLeft", true, numlock, capslock); } } @@ -118,8 +126,8 @@ export default class Keyboard { // If it's a virtual keyboard then it should be // sufficient to just send press and release right // after each other - this._sendKeyEvent(keysym, code, true); - this._sendKeyEvent(keysym, code, false); + this._sendKeyEvent(keysym, code, true, numlock, capslock); + this._sendKeyEvent(keysym, code, false, numlock, capslock); } stopEvent(e); @@ -157,8 +165,8 @@ export default class Keyboard { // while meta is held down if ((browser.isMac() || browser.isIOS()) && (e.metaKey && code !== 'MetaLeft' && code !== 'MetaRight')) { - this._sendKeyEvent(keysym, code, true); - this._sendKeyEvent(keysym, code, false); + this._sendKeyEvent(keysym, code, true, numlock, capslock); + this._sendKeyEvent(keysym, code, false, numlock, capslock); stopEvent(e); return; } @@ -168,8 +176,8 @@ export default class Keyboard { // which toggles on each press, but not on release. So pretend // it was a quick press and release of the button. if ((browser.isMac() || browser.isIOS()) && (code === 'CapsLock')) { - this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', true); - this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', false); + this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', true, numlock, capslock); + this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', false, numlock, capslock); stopEvent(e); return; } @@ -182,8 +190,8 @@ export default class Keyboard { KeyTable.XK_Hiragana, KeyTable.XK_Romaji ]; if (browser.isWindows() && jpBadKeys.includes(keysym)) { - this._sendKeyEvent(keysym, code, true); - this._sendKeyEvent(keysym, code, false); + this._sendKeyEvent(keysym, code, true, numlock, capslock); + this._sendKeyEvent(keysym, code, false, numlock, capslock); stopEvent(e); return; } @@ -199,7 +207,7 @@ export default class Keyboard { return; } - this._sendKeyEvent(keysym, code, true); + this._sendKeyEvent(keysym, code, true, numlock, capslock); } _handleKeyUp(e) { diff --git a/core/rfb.js b/core/rfb.js index fb9df0b9..cb972600 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -260,6 +260,8 @@ export default class RFB extends EventTargetMixin { this._keyboard = new Keyboard(this._canvas); this._keyboard.onkeyevent = this._handleKeyEvent.bind(this); + this._remoteCapsLock = null; // Null indicates unknown or irrelevant + this._remoteNumLock = null; this._gestures = new GestureHandler(); @@ -993,7 +995,35 @@ export default class RFB extends EventTargetMixin { } } - _handleKeyEvent(keysym, code, down) { + _handleKeyEvent(keysym, code, down, numlock, capslock) { + // If remote state of capslock is known, and it doesn't match the local led state of + // the keyboard, we send a capslock keypress first to bring it into sync. + // If we just pressed CapsLock, or we toggled it remotely due to it being out of sync + // we clear the remote state so that we don't send duplicate or spurious fixes, + // since it may take some time to receive the new remote CapsLock state. + if (code == 'CapsLock' && down) { + this._remoteCapsLock = null; + } + if (this._remoteCapsLock !== null && capslock !== null && this._remoteCapsLock !== capslock && down) { + Log.Debug("Fixing remote caps lock"); + + this.sendKey(KeyTable.XK_Caps_Lock, 'CapsLock', true); + this.sendKey(KeyTable.XK_Caps_Lock, 'CapsLock', false); + // We clear the remote capsLock state when we do this to prevent issues with doing this twice + // before we receive an update of the the remote state. + this._remoteCapsLock = null; + } + + // Logic for numlock is exactly the same. + if (code == 'NumLock' && down) { + this._remoteNumLock = null; + } + if (this._remoteNumLock !== null && numlock !== null && this._remoteNumLock !== numlock && down) { + Log.Debug("Fixing remote num lock"); + this.sendKey(KeyTable.XK_Num_Lock, 'NumLock', true); + this.sendKey(KeyTable.XK_Num_Lock, 'NumLock', false); + this._remoteNumLock = null; + } this.sendKey(keysym, code, down); } @@ -2104,6 +2134,7 @@ export default class RFB extends EventTargetMixin { encs.push(encodings.pseudoEncodingDesktopSize); encs.push(encodings.pseudoEncodingLastRect); encs.push(encodings.pseudoEncodingQEMUExtendedKeyEvent); + encs.push(encodings.pseudoEncodingQEMULedEvent); encs.push(encodings.pseudoEncodingExtendedDesktopSize); encs.push(encodings.pseudoEncodingXvp); encs.push(encodings.pseudoEncodingFence); @@ -2539,6 +2570,9 @@ export default class RFB extends EventTargetMixin { case encodings.pseudoEncodingExtendedDesktopSize: return this._handleExtendedDesktopSize(); + case encodings.pseudoEncodingQEMULedEvent: + return this._handleLedEvent(); + default: return this._handleDataRect(); } @@ -2716,6 +2750,21 @@ export default class RFB extends EventTargetMixin { return true; } + _handleLedEvent() { + if (this._sock.rQwait("LED Status", 1)) { + return false; + } + + let data = this._sock.rQshift8(); + // ScrollLock state can be retrieved with data & 1. This is currently not needed. + let numLock = data & 2 ? true : false; + let capsLock = data & 4 ? true : false; + this._remoteCapsLock = capsLock; + this._remoteNumLock = numLock; + + return true; + } + _handleExtendedDesktopSize() { if (this._sock.rQwait("ExtendedDesktopSize", 4)) { return false; diff --git a/tests/test.keyboard.js b/tests/test.keyboard.js index 0d8cac60..efc84c30 100644 --- a/tests/test.keyboard.js +++ b/tests/test.keyboard.js @@ -14,6 +14,10 @@ describe('Key Event Handling', function () { } e.stopPropagation = sinon.spy(); e.preventDefault = sinon.spy(); + e.getModifierState = function (key) { + return e[key]; + }; + return e; } @@ -310,6 +314,50 @@ describe('Key Event Handling', function () { }); }); + describe('Modifier status info', function () { + let origNavigator; + beforeEach(function () { + // window.navigator is a protected read-only property in many + // environments, so we need to redefine it whilst running these + // tests. + origNavigator = Object.getOwnPropertyDescriptor(window, "navigator"); + + Object.defineProperty(window, "navigator", {value: {}}); + }); + + afterEach(function () { + Object.defineProperty(window, "navigator", origNavigator); + }); + + it('should provide caps lock state', function () { + const kbd = new Keyboard(document); + kbd.onkeyevent = sinon.spy(); + kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'A', NumLock: false, CapsLock: true})); + + expect(kbd.onkeyevent).to.have.been.calledOnce; + expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0x41, "KeyA", true, false, true); + }); + + it('should provide num lock state', function () { + const kbd = new Keyboard(document); + kbd.onkeyevent = sinon.spy(); + kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'A', NumLock: true, CapsLock: false})); + + expect(kbd.onkeyevent).to.have.been.calledOnce; + expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0x41, "KeyA", true, true, false); + }); + + it('should have no num lock state on mac', function () { + window.navigator.platform = "Mac"; + const kbd = new Keyboard(document); + kbd.onkeyevent = sinon.spy(); + kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'A', NumLock: false, CapsLock: true})); + + expect(kbd.onkeyevent).to.have.been.calledOnce; + expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0x41, "KeyA", true, null, true); + }); + }); + describe('Japanese IM keys on Windows', function () { let origNavigator; beforeEach(function () { diff --git a/tests/test.rfb.js b/tests/test.rfb.js index bf12a460..ec3b66a3 100644 --- a/tests/test.rfb.js +++ b/tests/test.rfb.js @@ -2979,6 +2979,149 @@ describe('Remote Frame Buffer Protocol Client', function () { expect(spy.args[0][0].detail.name).to.equal('som€ nam€'); }); }); + + describe('Caps Lock and Num Lock remote fixup', function () { + function sendLedStateUpdate(state) { + let data = []; + push8(data, state); + sendFbuMsg([{ x: 0, y: 0, width: 0, height: 0, encoding: -261 }], [data], client); + } + + let client; + beforeEach(function () { + client = makeRFB(); + sinon.stub(client, 'sendKey'); + }); + + it('should toggle caps lock if remote caps lock is on and local is off', function () { + sendLedStateUpdate(0b100); + client._handleKeyEvent(0x61, 'KeyA', true, null, false); + + expect(client.sendKey).to.have.been.calledThrice; + expect(client.sendKey.firstCall).to.have.been.calledWith(0xFFE5, "CapsLock", true); + expect(client.sendKey.secondCall).to.have.been.calledWith(0xFFE5, "CapsLock", false); + expect(client.sendKey.thirdCall).to.have.been.calledWith(0x61, "KeyA", true); + }); + + it('should toggle caps lock if remote caps lock is off and local is on', function () { + sendLedStateUpdate(0b011); + client._handleKeyEvent(0x41, 'KeyA', true, null, true); + + expect(client.sendKey).to.have.been.calledThrice; + expect(client.sendKey.firstCall).to.have.been.calledWith(0xFFE5, "CapsLock", true); + expect(client.sendKey.secondCall).to.have.been.calledWith(0xFFE5, "CapsLock", false); + expect(client.sendKey.thirdCall).to.have.been.calledWith(0x41, "KeyA", true); + }); + + it('should not toggle caps lock if remote caps lock is on and local is on', function () { + sendLedStateUpdate(0b100); + client._handleKeyEvent(0x41, 'KeyA', true, null, true); + + expect(client.sendKey).to.have.been.calledOnce; + expect(client.sendKey.firstCall).to.have.been.calledWith(0x41, "KeyA", true); + }); + + it('should not toggle caps lock if remote caps lock is off and local is off', function () { + sendLedStateUpdate(0b011); + client._handleKeyEvent(0x61, 'KeyA', true, null, false); + + expect(client.sendKey).to.have.been.calledOnce; + expect(client.sendKey.firstCall).to.have.been.calledWith(0x61, "KeyA", true); + }); + + it('should not toggle caps lock if the key is caps lock', function () { + sendLedStateUpdate(0b011); + client._handleKeyEvent(0xFFE5, 'CapsLock', true, null, true); + + expect(client.sendKey).to.have.been.calledOnce; + expect(client.sendKey.firstCall).to.have.been.calledWith(0xFFE5, "CapsLock", true); + }); + + it('should toggle caps lock only once', function () { + sendLedStateUpdate(0b100); + client._handleKeyEvent(0x61, 'KeyA', true, null, false); + client._handleKeyEvent(0x61, 'KeyA', true, null, false); + + expect(client.sendKey).to.have.callCount(4); + expect(client.sendKey.firstCall).to.have.been.calledWith(0xFFE5, "CapsLock", true); + expect(client.sendKey.secondCall).to.have.been.calledWith(0xFFE5, "CapsLock", false); + expect(client.sendKey.thirdCall).to.have.been.calledWith(0x61, "KeyA", true); + expect(client.sendKey.lastCall).to.have.been.calledWith(0x61, "KeyA", true); + }); + + it('should retain remote caps lock state on capslock key up', function () { + sendLedStateUpdate(0b100); + client._handleKeyEvent(0xFFE5, 'CapsLock', false, null, true); + + expect(client.sendKey).to.have.been.calledOnce; + expect(client.sendKey.firstCall).to.have.been.calledWith(0xFFE5, "CapsLock", false); + expect(client._remoteCapsLock).to.equal(true); + }); + + it('should toggle num lock if remote num lock is on and local is off', function () { + sendLedStateUpdate(0b010); + client._handleKeyEvent(0xFF9C, 'NumPad1', true, false, null); + + expect(client.sendKey).to.have.been.calledThrice; + expect(client.sendKey.firstCall).to.have.been.calledWith(0xFF7F, "NumLock", true); + expect(client.sendKey.secondCall).to.have.been.calledWith(0xFF7F, "NumLock", false); + expect(client.sendKey.thirdCall).to.have.been.calledWith(0xFF9C, "NumPad1", true); + }); + + it('should toggle num lock if remote num lock is off and local is on', function () { + sendLedStateUpdate(0b101); + client._handleKeyEvent(0xFFB1, 'NumPad1', true, true, null); + + expect(client.sendKey).to.have.been.calledThrice; + expect(client.sendKey.firstCall).to.have.been.calledWith(0xFF7F, "NumLock", true); + expect(client.sendKey.secondCall).to.have.been.calledWith(0xFF7F, "NumLock", false); + expect(client.sendKey.thirdCall).to.have.been.calledWith(0xFFB1, "NumPad1", true); + }); + + it('should not toggle num lock if remote num lock is on and local is on', function () { + sendLedStateUpdate(0b010); + client._handleKeyEvent(0xFFB1, 'NumPad1', true, true, null); + + expect(client.sendKey).to.have.been.calledOnce; + expect(client.sendKey.firstCall).to.have.been.calledWith(0xFFB1, "NumPad1", true); + }); + + it('should not toggle num lock if remote num lock is off and local is off', function () { + sendLedStateUpdate(0b101); + client._handleKeyEvent(0xFF9C, 'NumPad1', true, false, null); + + expect(client.sendKey).to.have.been.calledOnce; + expect(client.sendKey.firstCall).to.have.been.calledWith(0xFF9C, "NumPad1", true); + }); + + it('should not toggle num lock if the key is num lock', function () { + sendLedStateUpdate(0b101); + client._handleKeyEvent(0xFF7F, 'NumLock', true, true, null); + + expect(client.sendKey).to.have.been.calledOnce; + expect(client.sendKey.firstCall).to.have.been.calledWith(0xFF7F, "NumLock", true); + }); + + it('should not toggle num lock if local state is unknown', function () { + sendLedStateUpdate(0b010); + client._handleKeyEvent(0xFFB1, 'NumPad1', true, null, null); + + expect(client.sendKey).to.have.been.calledOnce; + expect(client.sendKey.firstCall).to.have.been.calledWith(0xFFB1, "NumPad1", true); + }); + + it('should toggle num lock only once', function () { + sendLedStateUpdate(0b010); + client._handleKeyEvent(0xFF9C, 'NumPad1', true, false, null); + client._handleKeyEvent(0xFF9C, 'NumPad1', true, false, null); + + expect(client.sendKey).to.have.callCount(4); + expect(client.sendKey.firstCall).to.have.been.calledWith(0xFF7F, "NumLock", true); + expect(client.sendKey.secondCall).to.have.been.calledWith(0xFF7F, "NumLock", false); + expect(client.sendKey.thirdCall).to.have.been.calledWith(0xFF9C, "NumPad1", true); + expect(client.sendKey.lastCall).to.have.been.calledWith(0xFF9C, "NumPad1", true); + }); + }); }); describe('XVP Message Handling', function () { From f1174023c1b15f65991bbfca41cc8182c466b7e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20F=20Bj=C3=B6rklund?= Date: Wed, 11 Oct 2023 12:28:39 +0200 Subject: [PATCH 09/39] Add the possibility to listen on a specific host For instance, for listening only on "localhost" That is, bind on 127.0.0.1 instead of 0.0.0.0 --- docs/novnc_proxy.1 | 4 ++-- utils/novnc_proxy | 45 ++++++++++++++++++++++++++++++--------------- 2 files changed, 32 insertions(+), 17 deletions(-) diff --git a/docs/novnc_proxy.1 b/docs/novnc_proxy.1 index 11a003b3..78f06355 100644 --- a/docs/novnc_proxy.1 +++ b/docs/novnc_proxy.1 @@ -3,12 +3,12 @@ .SH NAME novnc_proxy - noVNC proxy server .SH SYNOPSIS -.B novnc_proxy [--listen PORT] [--vnc VNC_HOST:PORT] [--cert CERT] [--ssl-only] +.B novnc_proxy [--listen [HOST:]PORT] [--vnc VNC_HOST:PORT] [--cert CERT] [--ssl-only] Starts the WebSockets proxy and a mini-webserver and provides a cut-and-paste URL to go to. - --listen PORT Port for proxy/webserver to listen on + --listen [HOST:]PORT Port for proxy/webserver to listen on Default: 6080 --vnc VNC_HOST:PORT VNC server host:port proxy target Default: localhost:5900 diff --git a/utils/novnc_proxy b/utils/novnc_proxy index ea3ea706..d5b17dd0 100755 --- a/utils/novnc_proxy +++ b/utils/novnc_proxy @@ -8,12 +8,12 @@ usage() { echo "$*" echo fi - echo "Usage: ${NAME} [--listen PORT] [--vnc VNC_HOST:PORT] [--cert CERT] [--ssl-only]" + echo "Usage: ${NAME} [--listen [HOST:]PORT] [--vnc VNC_HOST:PORT] [--cert CERT] [--ssl-only]" echo echo "Starts the WebSockets proxy and a mini-webserver and " echo "provides a cut-and-paste URL to go to." echo - echo " --listen PORT Port for proxy/webserver to listen on" + echo " --listen [HOST:]PORT Port for proxy/webserver to listen on" echo " Default: 6080" echo " --vnc VNC_HOST:PORT VNC server host:port proxy target" echo " Default: localhost:5900" @@ -47,7 +47,9 @@ usage() { NAME="$(basename $0)" REAL_NAME="$(readlink -f $0)" HERE="$(cd "$(dirname "$REAL_NAME")" && pwd)" +HOST="" PORT="6080" +LISTEN="$PORT" VNC_DEST="localhost:5900" CERT="" KEY="" @@ -86,7 +88,7 @@ cleanup() { while [ "$*" ]; do param=$1; shift; OPTARG=$1 case $param in - --listen) PORT="${OPTARG}"; shift ;; + --listen) LISTEN="${OPTARG}"; shift ;; --vnc) VNC_DEST="${OPTARG}"; shift ;; --cert) CERT="${OPTARG}"; shift ;; --key) KEY="${OPTARG}"; shift ;; @@ -107,14 +109,23 @@ while [ "$*" ]; do esac done +if [ "$LISTEN" != "$PORT" ]; then + HOST=${LISTEN%:*} + PORT=${LISTEN##*:} + # if no host was given, restore + [ "$HOST" = "$PORT" ] && HOST="" +fi + # Sanity checks -if bash -c "exec 7<>/dev/tcp/localhost/${PORT}" &> /dev/null; then - exec 7<&- - exec 7>&- - die "Port ${PORT} in use. Try --listen PORT" -else - exec 7<&- - exec 7>&- +if [ -z "${HOST}" ]; then + if bash -c "exec 7<>/dev/tcp/localhost/${PORT}" &> /dev/null; then + exec 7<&- + exec 7>&- + die "Port ${PORT} in use. Try --listen PORT" + else + exec 7<&- + exec 7>&- + fi fi trap "cleanup" TERM QUIT INT EXIT @@ -191,9 +202,9 @@ else fi fi -echo "Starting webserver and WebSockets proxy on port ${PORT}" -#${HERE}/websockify --web ${WEB} ${CERT:+--cert ${CERT}} ${PORT} ${VNC_DEST} & -${WEBSOCKIFY} ${SYSLOG_ARG} ${SSLONLY} ${FILEONLY_ARG} --web ${WEB} ${CERT:+--cert ${CERT}} ${KEY:+--key ${KEY}} ${PORT} ${VNC_DEST} ${HEARTBEAT_ARG} ${IDLETIMEOUT_ARG} ${RECORD_ARG} ${TIMEOUT_ARG} ${WEBAUTH_ARG} ${AUTHPLUGIN_ARG} ${AUTHSOURCE_ARG} & +echo "Starting webserver and WebSockets proxy on${HOST:+ host ${HOST}} port ${PORT}" +#${HERE}/websockify --web ${WEB} ${CERT:+--cert ${CERT}} ${LISTEN} ${VNC_DEST} & +${WEBSOCKIFY} ${SYSLOG_ARG} ${SSLONLY} ${FILEONLY_ARG} --web ${WEB} ${CERT:+--cert ${CERT}} ${KEY:+--key ${KEY}} ${LISTEN} ${VNC_DEST} ${HEARTBEAT_ARG} ${IDLETIMEOUT_ARG} ${RECORD_ARG} ${TIMEOUT_ARG} ${WEBAUTH_ARG} ${AUTHPLUGIN_ARG} ${AUTHSOURCE_ARG} & proxy_pid="$!" sleep 1 if [ -z "$proxy_pid" ] || ! ps -eo pid= | grep -w "$proxy_pid" > /dev/null; then @@ -202,11 +213,15 @@ if [ -z "$proxy_pid" ] || ! ps -eo pid= | grep -w "$proxy_pid" > /dev/null; then exit 1 fi +if [ -z "$HOST" ]; then + HOST=$(hostname) +fi + echo -e "\n\nNavigate to this URL:\n" if [ "x$SSLONLY" == "x" ]; then - echo -e " http://$(hostname):${PORT}/vnc.html?host=$(hostname)&port=${PORT}\n" + echo -e " http://${HOST}:${PORT}/vnc.html?host=${HOST}&port=${PORT}\n" else - echo -e " https://$(hostname):${PORT}/vnc.html?host=$(hostname)&port=${PORT}\n" + echo -e " https://${HOST}:${PORT}/vnc.html?host=${HOST}&port=${PORT}\n" fi echo -e "Press Ctrl-C to exit\n\n" From a792b7f39e8d2c429655bcff2e54a35b3cf9f049 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20F=20Bj=C3=B6rklund?= Date: Sat, 14 Oct 2023 09:51:29 +0200 Subject: [PATCH 10/39] Document default port applies to all interfaces --- docs/novnc_proxy.1 | 2 +- utils/novnc_proxy | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/novnc_proxy.1 b/docs/novnc_proxy.1 index 78f06355..259e1b41 100644 --- a/docs/novnc_proxy.1 +++ b/docs/novnc_proxy.1 @@ -9,7 +9,7 @@ Starts the WebSockets proxy and a mini-webserver and provides a cut-and-paste URL to go to. --listen [HOST:]PORT Port for proxy/webserver to listen on - Default: 6080 + Default: 6080 (on all interfaces) --vnc VNC_HOST:PORT VNC server host:port proxy target Default: localhost:5900 --cert CERT Path to combined cert/key file, or just diff --git a/utils/novnc_proxy b/utils/novnc_proxy index d5b17dd0..9d2ae002 100755 --- a/utils/novnc_proxy +++ b/utils/novnc_proxy @@ -14,7 +14,7 @@ usage() { echo "provides a cut-and-paste URL to go to." echo echo " --listen [HOST:]PORT Port for proxy/webserver to listen on" - echo " Default: 6080" + echo " Default: 6080 (on all interfaces)" echo " --vnc VNC_HOST:PORT VNC server host:port proxy target" echo " Default: localhost:5900" echo " --cert CERT Path to combined cert/key file, or just" From 5ebc297164a3c8a194200e141332f3a0a2bc7cc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20F=20Bj=C3=B6rklund?= Date: Sat, 14 Oct 2023 09:52:13 +0200 Subject: [PATCH 11/39] Remove comment about websockify command arguments --- utils/novnc_proxy | 1 - 1 file changed, 1 deletion(-) diff --git a/utils/novnc_proxy b/utils/novnc_proxy index 9d2ae002..f805db23 100755 --- a/utils/novnc_proxy +++ b/utils/novnc_proxy @@ -203,7 +203,6 @@ else fi echo "Starting webserver and WebSockets proxy on${HOST:+ host ${HOST}} port ${PORT}" -#${HERE}/websockify --web ${WEB} ${CERT:+--cert ${CERT}} ${LISTEN} ${VNC_DEST} & ${WEBSOCKIFY} ${SYSLOG_ARG} ${SSLONLY} ${FILEONLY_ARG} --web ${WEB} ${CERT:+--cert ${CERT}} ${KEY:+--key ${KEY}} ${LISTEN} ${VNC_DEST} ${HEARTBEAT_ARG} ${IDLETIMEOUT_ARG} ${RECORD_ARG} ${TIMEOUT_ARG} ${WEBAUTH_ARG} ${AUTHPLUGIN_ARG} ${AUTHSOURCE_ARG} & proxy_pid="$!" sleep 1 From 9ac632deee0410be07187dcbb3d40fbf00ede0c3 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Tue, 5 Dec 2023 11:30:30 +0100 Subject: [PATCH 12/39] Handle immediate connection errors The browser might throw an exception right away if there is something it doesn't like with our connect attempt. E.g. using a non-TLS WebSocket from a TLS web page. --- app/ui.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/app/ui.js b/app/ui.js index 85695ca2..00628c72 100644 --- a/app/ui.js +++ b/app/ui.js @@ -1041,10 +1041,18 @@ const UI = { } url += '/' + path; - UI.rfb = new RFB(document.getElementById('noVNC_container'), url, - { shared: UI.getSetting('shared'), - repeaterID: UI.getSetting('repeaterID'), - credentials: { password: password } }); + try { + UI.rfb = new RFB(document.getElementById('noVNC_container'), url, + { shared: UI.getSetting('shared'), + repeaterID: UI.getSetting('repeaterID'), + credentials: { password: password } }); + } catch (exc) { + Log.Error("Failed to connect to server: " + exc); + UI.updateVisualState('disconnected'); + UI.showStatus(_("Failed to connect to server: ") + exc, 'error'); + return; + } + UI.rfb.addEventListener("connect", UI.connectFinished); UI.rfb.addEventListener("disconnect", UI.disconnectFinished); UI.rfb.addEventListener("serververification", UI.serverVerify); From 829725b30e3d3486991e34db0c86406e556ebf98 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Tue, 5 Dec 2023 11:33:15 +0100 Subject: [PATCH 13/39] Handle relative paths in novnc_proxy websockify changes the working directory before it starts looking for files, so we must give it relative paths for things to work reliably. --- utils/novnc_proxy | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/utils/novnc_proxy b/utils/novnc_proxy index f805db23..4b2e3032 100755 --- a/utils/novnc_proxy +++ b/utils/novnc_proxy @@ -56,7 +56,7 @@ KEY="" WEB="" proxy_pid="" SSLONLY="" -RECORD_ARG="" +RECORD="" SYSLOG_ARG="" HEARTBEAT_ARG="" IDLETIMEOUT_ARG="" @@ -95,7 +95,7 @@ while [ "$*" ]; do --web) WEB="${OPTARG}"; shift ;; --ssl-only) SSLONLY="--ssl-only" ;; --file-only) FILEONLY_ARG="--file-only" ;; - --record) RECORD_ARG="--record ${OPTARG}"; shift ;; + --record) RECORD="${OPTARG}"; shift ;; --syslog) SYSLOG_ARG="--syslog ${OPTARG}"; shift ;; --heartbeat) HEARTBEAT_ARG="--heartbeat ${OPTARG}"; shift ;; --idle-timeout) IDLETIMEOUT_ARG="--idle-timeout ${OPTARG}"; shift ;; @@ -202,8 +202,14 @@ else fi fi +# Make all file paths absolute as websockify changes working directory +WEB=`realpath "${WEB}"` +[ -n "${CERT}" ] && CERT=`realpath "${CERT}"` +[ -n "${KEY}" ] && KEY=`realpath "${KEY}"` +[ -n "${RECORD}" ] && RECORD=`realpath "${RECORD}"` + echo "Starting webserver and WebSockets proxy on${HOST:+ host ${HOST}} port ${PORT}" -${WEBSOCKIFY} ${SYSLOG_ARG} ${SSLONLY} ${FILEONLY_ARG} --web ${WEB} ${CERT:+--cert ${CERT}} ${KEY:+--key ${KEY}} ${LISTEN} ${VNC_DEST} ${HEARTBEAT_ARG} ${IDLETIMEOUT_ARG} ${RECORD_ARG} ${TIMEOUT_ARG} ${WEBAUTH_ARG} ${AUTHPLUGIN_ARG} ${AUTHSOURCE_ARG} & +${WEBSOCKIFY} ${SYSLOG_ARG} ${SSLONLY} ${FILEONLY_ARG} --web ${WEB} ${CERT:+--cert ${CERT}} ${KEY:+--key ${KEY}} ${LISTEN} ${VNC_DEST} ${HEARTBEAT_ARG} ${IDLETIMEOUT_ARG} ${RECORD:+--record ${RECORD}} ${TIMEOUT_ARG} ${WEBAUTH_ARG} ${AUTHPLUGIN_ARG} ${AUTHSOURCE_ARG} & proxy_pid="$!" sleep 1 if [ -z "$proxy_pid" ] || ! ps -eo pid= | grep -w "$proxy_pid" > /dev/null; then From 796e924e477fca35c10e8d344fe38351a55565b6 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Wed, 10 Jan 2024 07:56:13 +0100 Subject: [PATCH 14/39] Remove unused npm dependencies These should have been removed as part of 890cff9. --- package.json | 6 ------ 1 file changed, 6 deletions(-) diff --git a/package.json b/package.json index 5847887f..740f0d21 100644 --- a/package.json +++ b/package.json @@ -39,17 +39,11 @@ "homepage": "https://github.com/novnc/noVNC", "devDependencies": { "@babel/core": "latest", - "@babel/plugin-syntax-dynamic-import": "latest", - "@babel/plugin-transform-modules-commonjs": "latest", "@babel/preset-env": "latest", - "@babel/cli": "latest", "babel-plugin-import-redirect": "latest", "browserify": "latest", - "babelify": "latest", - "core-js": "latest", "chai": "latest", "commander": "latest", - "es-module-loader": "latest", "eslint": "latest", "fs-extra": "latest", "jsdom": "latest", From d3aaf4d5b3f33b80bb6fe21ae71a63c8a331da9b Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Wed, 10 Jan 2024 14:44:44 +0100 Subject: [PATCH 15/39] Upgrade base snap to Ubuntu 22.04 Ubuntu 18.04 base snap is no longer supported, so switch to the currently newest one. --- snap/snapcraft.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 02094820..ccf61893 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,5 +1,5 @@ name: novnc -base: core18 # the base snap is the execution environment for this snap +base: core22 # the base snap is the execution environment for this snap version: git summary: Open Source VNC client using HTML5 (WebSockets, Canvas) description: | From b35cf6dd1253142267f68f052986d0560f7a495c Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Wed, 17 Jan 2024 16:19:16 +0100 Subject: [PATCH 16/39] Don't include ES6 module versions in npm package The npm package is supposed to be for CommonJS usage, so only package that to avoid confusion. This has become an issue now that nodejs supports ES6 modules, where users are accidentally trying to import the wrong files and get errors. --- package.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/package.json b/package.json index 740f0d21..482b86eb 100644 --- a/package.json +++ b/package.json @@ -14,9 +14,7 @@ "VERSION", "docs/API.md", "docs/LIBRARY.md", - "docs/LICENSE*", - "core", - "vendor/pako" + "docs/LICENSE*" ], "scripts": { "lint": "eslint app core po/po2js po/xgettext-html tests utils", From fca48df85d394b2c1e9d26ed6e9d10d33db9f4f4 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Fri, 19 Jan 2024 15:58:48 +0100 Subject: [PATCH 17/39] Increase test timeout for Chrome on Windows There is some bug in Chrome 119+ on some systems, where it takes forever for the first readback from a canvas, timing out the first test that does this. Work around the issue by increasing the timeout on that platform until Chrome manages to resolve the issue. --- karma.conf.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/karma.conf.js b/karma.conf.js index 1ea17475..faa8beea 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -81,5 +81,12 @@ module.exports = (config) => { singleRun: true, }; + if (process.env.TEST_BROWSER_NAME === 'ChromeHeadless') { + let os = require('os'); + if (os.platform() === 'win32') { + my_conf.client.mocha['timeout'] = 5000; + } + } + config.set(my_conf); }; From ab2fd4169348e968ddf60b3334dcbe3b377e7cbc Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Tue, 23 Jan 2024 12:51:35 +0100 Subject: [PATCH 18/39] Handle broken Oculus browser keyboard events It sets KeyboardEvent.key to "Unidentified" for all non-character keys, which means we must ignore it and use the legacy handling to figure out the key pressed. --- core/input/util.js | 2 +- tests/test.helper.js | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/core/input/util.js b/core/input/util.js index 58f84e55..36b69817 100644 --- a/core/input/util.js +++ b/core/input/util.js @@ -67,7 +67,7 @@ export function getKeycode(evt) { // Get 'KeyboardEvent.key', handling legacy browsers export function getKey(evt) { // Are we getting a proper key value? - if (evt.key !== undefined) { + if ((evt.key !== undefined) && (evt.key !== 'Unidentified')) { // Mozilla isn't fully in sync with the spec yet switch (evt.key) { case 'OS': return 'Meta'; diff --git a/tests/test.helper.js b/tests/test.helper.js index ff83c539..9995973f 100644 --- a/tests/test.helper.js +++ b/tests/test.helper.js @@ -108,6 +108,8 @@ describe('Helpers', function () { }); it('should use charCode if no key', function () { expect(KeyboardUtil.getKey({charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43})).to.be.equal('Š'); + // Broken Oculus browser + expect(KeyboardUtil.getKey({charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43, key: 'Unidentified'})).to.be.equal('Š'); }); it('should return Unidentified when it cannot map the key', function () { expect(KeyboardUtil.getKey({keycode: 0x42})).to.be.equal('Unidentified'); From 48c8e41877a98dbf6c65e2a8086160c82b01e7dd Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Tue, 23 Jan 2024 12:54:18 +0100 Subject: [PATCH 19/39] Fix key event debug output Fix for a0b7c0dac5359e4002e7f1d946e60e2eb9b4a54e. --- core/input/keyboard.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/input/keyboard.js b/core/input/keyboard.js index 9068e9e9..68da2312 100644 --- a/core/input/keyboard.js +++ b/core/input/keyboard.js @@ -48,7 +48,8 @@ export default class Keyboard { } Log.Debug("onkeyevent " + (down ? "down" : "up") + - ", keysym: " + keysym, ", code: " + code, + ", numlock: " + numlock + ", capslock: " + capslock); + ", keysym: " + keysym, ", code: " + code + + ", numlock: " + numlock + ", capslock: " + capslock); this.onkeyevent(keysym, code, down, numlock, capslock); } From bd32922ff8f6209bdb20dc0b75c034d1b304dc7a Mon Sep 17 00:00:00 2001 From: Simon Bungartz Date: Wed, 31 Jan 2024 16:16:01 +0000 Subject: [PATCH 20/39] Avoid exception when cursor was removed from DOM already --- core/util/cursor.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/util/cursor.js b/core/util/cursor.js index 3000cf0e..20e75f1b 100644 --- a/core/util/cursor.js +++ b/core/util/cursor.js @@ -69,7 +69,9 @@ export default class Cursor { this._target.removeEventListener('mousemove', this._eventHandlers.mousemove, options); this._target.removeEventListener('mouseup', this._eventHandlers.mouseup, options); - document.body.removeChild(this._canvas); + if (document.contains(this._canvas)) { + document.body.removeChild(this._canvas); + } } this._target = null; From e75938bebcf88b678f24ed35b78be9eaf982d213 Mon Sep 17 00:00:00 2001 From: Samuel Mannehed Date: Fri, 2 Feb 2024 16:51:21 +0100 Subject: [PATCH 21/39] Make non-HTTPS message more harsh As browsers are placing more and more new functionality as secure-context only, we need to prepare users for more problems. I find it likely that we will disable non-HTTPS connections in the future. --- app/ui.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/ui.js b/app/ui.js index 00628c72..f27dfe28 100644 --- a/app/ui.js +++ b/app/ui.js @@ -66,7 +66,7 @@ const UI = { // insecure context if (!window.isSecureContext) { // FIXME: This gets hidden when connecting - UI.showStatus(_("HTTPS is required for full functionality"), 'error'); + UI.showStatus(_("Running without HTTPS is not recommended, crashes or other issues are likely."), 'error'); } // Try to fetch version number From 60643fe6955f107faecdd0d6af8a213d65fdc68b Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Mon, 5 Feb 2024 16:34:47 +0100 Subject: [PATCH 22/39] Update github actions to latest versions Primarily to avoid the versions that are now deprecated, but also update actions/upload-artifact to keep us up to date. --- .github/workflows/deploy.yml | 10 +++++----- .github/workflows/lint.yml | 8 ++++---- .github/workflows/test.yml | 4 ++-- .github/workflows/translate.yml | 4 ++-- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 84e634d2..a11d3d0a 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -10,18 +10,18 @@ jobs: npm: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - run: | GITREV=$(git rev-parse --short HEAD) echo $GITREV sed -i "s/^\(.*\"version\".*\)\"\([^\"]\+\)\"\(.*\)\$/\1\"\2-g$GITREV\"\3/" package.json if: github.event_name != 'release' - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: # Needs to be explicitly specified for auth to work registry-url: 'https://registry.npmjs.org' - run: npm install - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: npm path: lib @@ -49,7 +49,7 @@ jobs: snap: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - run: | GITREV=$(git rev-parse --short HEAD) echo $GITREV @@ -61,7 +61,7 @@ jobs: sed -i "s/^version:.*/version: '$VERSION'/" snap/snapcraft.yaml - uses: snapcore/action-build@v1 id: snapcraft - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: snap path: ${{ steps.snapcraft.outputs.snap }} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 7cd5b215..540bb990 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -6,14 +6,14 @@ jobs: eslint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 - run: npm update - run: npm run lint html: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 - run: npm update - run: git ls-tree --name-only -r HEAD | grep -E "[.](html|css)$" | xargs ./utils/validate diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1bc1728a..b72195b5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -20,8 +20,8 @@ jobs: fail-fast: false runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 - run: npm update - run: npm run test env: diff --git a/.github/workflows/translate.yml b/.github/workflows/translate.yml index ea6e6bb3..a4da9cbf 100644 --- a/.github/workflows/translate.yml +++ b/.github/workflows/translate.yml @@ -6,8 +6,8 @@ jobs: translate: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 - run: npm update - run: sudo apt-get install gettext - run: make -C po update-pot From cd927723bc98bd56f23119b8bcc011139a382702 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Mon, 5 Feb 2024 16:43:29 +0100 Subject: [PATCH 23/39] Fix import of "commander" The default import was deprecated ages ago, and in v12 it has now finally been changed in a breaking way. Change the code to import things the proper way. --- utils/convert.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/convert.js b/utils/convert.js index aeba49d9..617f4ed6 100755 --- a/utils/convert.js +++ b/utils/convert.js @@ -1,7 +1,7 @@ #!/usr/bin/env node const path = require('path'); -const program = require('commander'); +const { program } = require('commander'); const fs = require('fs'); const fse = require('fs-extra'); const babel = require('@babel/core'); From 9a1b1f0d06567739251314a3b67ac5c432ecad47 Mon Sep 17 00:00:00 2001 From: Kostiantyn Syrykh Date: Mon, 25 Mar 2024 17:35:28 +0200 Subject: [PATCH 24/39] Clipboard: handle multiple CR+LF --- core/rfb.js | 2 +- tests/test.rfb.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/rfb.js b/core/rfb.js index c71d6b88..f2deb0e7 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -2356,7 +2356,7 @@ export default class RFB extends EventTargetMixin { textData = textData.slice(0, -1); } - textData = textData.replace("\r\n", "\n"); + textData = textData.replaceAll("\r\n", "\n"); this.dispatchEvent(new CustomEvent( "clipboard", diff --git a/tests/test.rfb.js b/tests/test.rfb.js index c1ee8b16..62b80ca3 100644 --- a/tests/test.rfb.js +++ b/tests/test.rfb.js @@ -3263,11 +3263,11 @@ describe('Remote Frame Buffer Protocol Client', function () { }); it('should update clipboard with correct escape characters from a Provide message ', function () { - let expectedData = "Oh\nmy!"; + let expectedData = "Oh\nmy\n!"; let data = [3, 0, 0, 0]; const flags = [0x10, 0x00, 0x00, 0x01]; - let text = encodeUTF8("Oh\r\nmy!\0"); + let text = encodeUTF8("Oh\r\nmy\r\n!\0"); let deflatedText = deflateWithSize(text); From 92c8a91964313e51008c6c9bd73234d508d2584b Mon Sep 17 00:00:00 2001 From: Bubble Date: Wed, 24 Apr 2024 22:54:24 +0800 Subject: [PATCH 25/39] Update zh_CN.po (#1851) Update Chinese translation --- po/zh_CN.po | 136 ++++++++++++++++++++++++++-------------------------- 1 file changed, 68 insertions(+), 68 deletions(-) diff --git a/po/zh_CN.po b/po/zh_CN.po index ede9d441..caae2850 100644 --- a/po/zh_CN.po +++ b/po/zh_CN.po @@ -15,58 +15,42 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: ../app/ui.js:395 +#: ../app/ui.js:430 msgid "Connecting..." msgstr "连接中..." -#: ../app/ui.js:402 +#: ../app/ui.js:438 +msgid "Connected (encrypted) to " +msgstr "已连接(已加密)到" + +#: ../app/ui.js:440 +msgid "Connected (unencrypted) to " +msgstr "已连接(未加密)到" + +#: ../app/ui.js:446 msgid "Disconnecting..." msgstr "正在断开连接..." -#: ../app/ui.js:408 -msgid "Reconnecting..." -msgstr "重新连接中..." - -#: ../app/ui.js:413 -msgid "Internal error" -msgstr "内部错误" - -#: ../app/ui.js:1015 -msgid "Must set host" -msgstr "请提供主机名" - -#: ../app/ui.js:1097 -msgid "Connected (encrypted) to " -msgstr "已连接到(加密)" - -#: ../app/ui.js:1099 -msgid "Connected (unencrypted) to " -msgstr "已连接到(未加密)" - -#: ../app/ui.js:1120 -msgid "Something went wrong, connection is closed" -msgstr "发生错误,连接已关闭" - -#: ../app/ui.js:1123 -msgid "Failed to connect to server" -msgstr "无法连接到服务器" - -#: ../app/ui.js:1133 +#: ../app/ui.js:450 msgid "Disconnected" msgstr "已断开连接" -#: ../app/ui.js:1146 -msgid "New connection has been rejected with reason: " -msgstr "连接被拒绝,原因:" +#: ../app/ui.js:1052 ../core/rfb.js:248 +msgid "Must set host" +msgstr "必须设置主机" -#: ../app/ui.js:1149 -msgid "New connection has been rejected" -msgstr "连接被拒绝" +#: ../app/ui.js:1101 +msgid "Reconnecting..." +msgstr "重新连接中..." -#: ../app/ui.js:1170 +#: ../app/ui.js:1140 msgid "Password is required" msgstr "请提供密码" +#: ../core/rfb.js:548 +msgid "Disconnect timeout" +msgstr "超时断开" + #: ../vnc.html:89 msgid "noVNC encountered an error:" msgstr "noVNC 遇到一个错误:" @@ -77,31 +61,31 @@ msgstr "显示/隐藏控制栏" #: ../vnc.html:106 msgid "Move/Drag Viewport" -msgstr "拖放显示范围" +msgstr "移动/拖动窗口" #: ../vnc.html:106 msgid "viewport drag" -msgstr "显示范围拖放" +msgstr "窗口拖动" #: ../vnc.html:112 ../vnc.html:115 ../vnc.html:118 ../vnc.html:121 msgid "Active Mouse Button" -msgstr "启动鼠标按鍵" +msgstr "启动鼠标按键" #: ../vnc.html:112 msgid "No mousebutton" -msgstr "禁用鼠标按鍵" +msgstr "禁用鼠标按键" #: ../vnc.html:115 msgid "Left mousebutton" -msgstr "鼠标左鍵" +msgstr "鼠标左键" #: ../vnc.html:118 msgid "Middle mousebutton" -msgstr "鼠标中鍵" +msgstr "鼠标中键" #: ../vnc.html:121 msgid "Right mousebutton" -msgstr "鼠标右鍵" +msgstr "鼠标右键" #: ../vnc.html:124 msgid "Keyboard" @@ -127,6 +111,10 @@ msgstr "Ctrl" msgid "Toggle Ctrl" msgstr "切换 Ctrl" +#: ../vnc.html:136 +msgid "Edit clipboard content in the textarea below." +msgstr "在下面的文本区域中编辑剪贴板内容。" + #: ../vnc.html:139 msgid "Alt" msgstr "Alt" @@ -153,19 +141,19 @@ msgstr "发送 Escape 键" #: ../vnc.html:148 msgid "Ctrl+Alt+Del" -msgstr "Ctrl-Alt-Del" +msgstr "Ctrl+Alt+Del" #: ../vnc.html:148 msgid "Send Ctrl-Alt-Del" -msgstr "发送 Ctrl-Alt-Del 键" +msgstr "发送 Ctrl+Alt+Del 键" #: ../vnc.html:156 msgid "Shutdown/Reboot" -msgstr "关机/重新启动" +msgstr "关机/重启" #: ../vnc.html:156 msgid "Shutdown/Reboot..." -msgstr "关机/重新启动..." +msgstr "关机/重启..." #: ../vnc.html:162 msgid "Power" @@ -177,7 +165,7 @@ msgstr "关机" #: ../vnc.html:165 msgid "Reboot" -msgstr "重新启动" +msgstr "重启" #: ../vnc.html:166 msgid "Reset" @@ -199,6 +187,10 @@ msgstr "全屏" msgid "Settings" msgstr "设置" +#: ../vnc.html:200 +msgid "Encrypt" +msgstr "加密" + #: ../vnc.html:202 msgid "Shared Mode" msgstr "分享模式" @@ -224,61 +216,69 @@ msgid "Local Scaling" msgstr "本地缩放" #: ../vnc.html:216 +msgid "Local Downscaling" +msgstr "降低本地尺寸" + +#: ../vnc.html:217 msgid "Remote Resizing" msgstr "远程调整大小" -#: ../vnc.html:221 +#: ../vnc.html:222 msgid "Advanced" msgstr "高级" -#: ../vnc.html:224 +#: ../vnc.html:225 +msgid "Local Cursor" +msgstr "本地光标" + +#: ../vnc.html:229 msgid "Repeater ID:" msgstr "中继站 ID" -#: ../vnc.html:228 +#: ../vnc.html:233 msgid "WebSocket" msgstr "WebSocket" -#: ../vnc.html:231 -msgid "Encrypt" -msgstr "加密" - -#: ../vnc.html:234 +#: ../vnc.html:239 msgid "Host:" msgstr "主机:" -#: ../vnc.html:238 +#: ../vnc.html:243 msgid "Port:" msgstr "端口:" -#: ../vnc.html:242 +#: ../vnc.html:247 msgid "Path:" msgstr "路径:" -#: ../vnc.html:249 +#: ../vnc.html:254 msgid "Automatic Reconnect" msgstr "自动重新连接" -#: ../vnc.html:252 +#: ../vnc.html:257 msgid "Reconnect Delay (ms):" msgstr "重新连接间隔 (ms):" -#: ../vnc.html:258 +#: ../vnc.html:263 msgid "Logging:" msgstr "日志级别:" -#: ../vnc.html:270 +#: ../vnc.html:275 msgid "Disconnect" -msgstr "中断连接" +msgstr "断开连接" -#: ../vnc.html:289 +#: ../vnc.html:294 msgid "Connect" msgstr "连接" -#: ../vnc.html:299 +#: ../vnc.html:304 msgid "Password:" msgstr "密码:" -#: ../vnc.html:313 +#: ../vnc.html:318 msgid "Cancel" msgstr "取消" + +#: ../vnc.html:334 +msgid "Canvas not supported." +msgstr "不支持 Canvas。" \ No newline at end of file From 9d293f1ababd710d4ebc225300ef075b78d637c5 Mon Sep 17 00:00:00 2001 From: Giannis Kosmas Date: Wed, 24 Apr 2024 19:53:49 +0300 Subject: [PATCH 26/39] Updated el.po --- po/el.po | 262 +++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 169 insertions(+), 93 deletions(-) diff --git a/po/el.po b/po/el.po index 5213ae54..de690fe9 100644 --- a/po/el.po +++ b/po/el.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: noVNC 0.6.1\n" "Report-Msgid-Bugs-To: novnc@googlegroups.com\n" -"POT-Creation-Date: 2017-11-17 21:40+0200\n" +"POT-Creation-Date: 2022-12-27 15:24+0100\n" "PO-Revision-Date: 2017-10-11 16:16+0200\n" "Last-Translator: Giannis Kosmas \n" "Language-Team: none\n" @@ -17,273 +17,349 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: ../app/ui.js:404 +#: ../app/ui.js:69 +msgid "HTTPS is required for full functionality" +msgstr "Το HTTPS είναι απαιτούμενο για πλήρη λειτουργικότητα" + +#: ../app/ui.js:410 msgid "Connecting..." msgstr "Συνδέεται..." -#: ../app/ui.js:411 +#: ../app/ui.js:417 msgid "Disconnecting..." msgstr "Aποσυνδέεται..." -#: ../app/ui.js:417 +#: ../app/ui.js:423 msgid "Reconnecting..." msgstr "Επανασυνδέεται..." -#: ../app/ui.js:422 +#: ../app/ui.js:428 msgid "Internal error" msgstr "Εσωτερικό σφάλμα" -#: ../app/ui.js:1019 +#: ../app/ui.js:1026 msgid "Must set host" msgstr "Πρέπει να οριστεί ο διακομιστής" -#: ../app/ui.js:1099 +#: ../app/ui.js:1110 msgid "Connected (encrypted) to " msgstr "Συνδέθηκε (κρυπτογραφημένα) με το " -#: ../app/ui.js:1101 +#: ../app/ui.js:1112 msgid "Connected (unencrypted) to " msgstr "Συνδέθηκε (μη κρυπτογραφημένα) με το " -#: ../app/ui.js:1119 +#: ../app/ui.js:1135 msgid "Something went wrong, connection is closed" msgstr "Κάτι πήγε στραβά, η σύνδεση διακόπηκε" -#: ../app/ui.js:1129 +#: ../app/ui.js:1138 +msgid "Failed to connect to server" +msgstr "Αποτυχία στη σύνδεση με το διακομιστή" + +#: ../app/ui.js:1150 msgid "Disconnected" msgstr "Αποσυνδέθηκε" -#: ../app/ui.js:1142 +#: ../app/ui.js:1165 msgid "New connection has been rejected with reason: " msgstr "Η νέα σύνδεση απορρίφθηκε διότι: " -#: ../app/ui.js:1145 +#: ../app/ui.js:1168 msgid "New connection has been rejected" msgstr "Η νέα σύνδεση απορρίφθηκε " -#: ../app/ui.js:1166 -msgid "Password is required" -msgstr "Απαιτείται ο κωδικός πρόσβασης" +#: ../app/ui.js:1234 +msgid "Credentials are required" +msgstr "Απαιτούνται διαπιστευτήρια" -#: ../vnc.html:89 +#: ../vnc.html:57 msgid "noVNC encountered an error:" msgstr "το noVNC αντιμετώπισε ένα σφάλμα:" -#: ../vnc.html:99 +#: ../vnc.html:67 msgid "Hide/Show the control bar" msgstr "Απόκρυψη/Εμφάνιση γραμμής ελέγχου" -#: ../vnc.html:106 +#: ../vnc.html:76 +msgid "Drag" +msgstr "Σύρσιμο" + +#: ../vnc.html:76 msgid "Move/Drag Viewport" msgstr "Μετακίνηση/Σύρσιμο Θεατού πεδίου" -#: ../vnc.html:106 -msgid "viewport drag" -msgstr "σύρσιμο θεατού πεδίου" - -#: ../vnc.html:112 ../vnc.html:115 ../vnc.html:118 ../vnc.html:121 -msgid "Active Mouse Button" -msgstr "Ενεργό Πλήκτρο Ποντικιού" - -#: ../vnc.html:112 -msgid "No mousebutton" -msgstr "Χωρίς Πλήκτρο Ποντικιού" - -#: ../vnc.html:115 -msgid "Left mousebutton" -msgstr "Αριστερό Πλήκτρο Ποντικιού" - -#: ../vnc.html:118 -msgid "Middle mousebutton" -msgstr "Μεσαίο Πλήκτρο Ποντικιού" - -#: ../vnc.html:121 -msgid "Right mousebutton" -msgstr "Δεξί Πλήκτρο Ποντικιού" - -#: ../vnc.html:124 +#: ../vnc.html:82 msgid "Keyboard" msgstr "Πληκτρολόγιο" -#: ../vnc.html:124 +#: ../vnc.html:82 msgid "Show Keyboard" msgstr "Εμφάνιση Πληκτρολογίου" -#: ../vnc.html:131 +#: ../vnc.html:87 msgid "Extra keys" msgstr "Επιπλέον πλήκτρα" -#: ../vnc.html:131 +#: ../vnc.html:87 msgid "Show Extra Keys" msgstr "Εμφάνιση Επιπλέον Πλήκτρων" -#: ../vnc.html:136 +#: ../vnc.html:92 msgid "Ctrl" msgstr "Ctrl" -#: ../vnc.html:136 +#: ../vnc.html:92 msgid "Toggle Ctrl" msgstr "Εναλλαγή Ctrl" -#: ../vnc.html:139 +#: ../vnc.html:95 msgid "Alt" msgstr "Alt" -#: ../vnc.html:139 +#: ../vnc.html:95 msgid "Toggle Alt" msgstr "Εναλλαγή Alt" -#: ../vnc.html:142 +#: ../vnc.html:98 +msgid "Toggle Windows" +msgstr "Εναλλαγή Παράθυρων" + +#: ../vnc.html:98 +msgid "Windows" +msgstr "Παράθυρα" + +#: ../vnc.html:101 msgid "Send Tab" msgstr "Αποστολή Tab" -#: ../vnc.html:142 +#: ../vnc.html:101 msgid "Tab" msgstr "Tab" -#: ../vnc.html:145 +#: ../vnc.html:104 msgid "Esc" msgstr "Esc" -#: ../vnc.html:145 +#: ../vnc.html:104 msgid "Send Escape" msgstr "Αποστολή Escape" -#: ../vnc.html:148 +#: ../vnc.html:107 msgid "Ctrl+Alt+Del" msgstr "Ctrl+Alt+Del" -#: ../vnc.html:148 +#: ../vnc.html:107 msgid "Send Ctrl-Alt-Del" msgstr "Αποστολή Ctrl-Alt-Del" -#: ../vnc.html:156 +#: ../vnc.html:114 msgid "Shutdown/Reboot" msgstr "Κλείσιμο/Επανεκκίνηση" -#: ../vnc.html:156 +#: ../vnc.html:114 msgid "Shutdown/Reboot..." msgstr "Κλείσιμο/Επανεκκίνηση..." -#: ../vnc.html:162 +#: ../vnc.html:120 msgid "Power" msgstr "Απενεργοποίηση" -#: ../vnc.html:164 +#: ../vnc.html:122 msgid "Shutdown" msgstr "Κλείσιμο" -#: ../vnc.html:165 +#: ../vnc.html:123 msgid "Reboot" msgstr "Επανεκκίνηση" -#: ../vnc.html:166 +#: ../vnc.html:124 msgid "Reset" msgstr "Επαναφορά" -#: ../vnc.html:171 ../vnc.html:177 +#: ../vnc.html:129 ../vnc.html:135 msgid "Clipboard" msgstr "Πρόχειρο" -#: ../vnc.html:181 -msgid "Clear" -msgstr "Καθάρισμα" +#: ../vnc.html:137 +msgid "Edit clipboard content in the textarea below." +msgstr "Επεξεργαστείτε το περιεχόμενο του πρόχειρου στην περιοχή κειμένου παρακάτω." -#: ../vnc.html:187 -msgid "Fullscreen" +#: ../vnc.html:145 +#, fuzzy +msgid "Full Screen" msgstr "Πλήρης Οθόνη" -#: ../vnc.html:192 ../vnc.html:199 +#: ../vnc.html:150 ../vnc.html:156 msgid "Settings" msgstr "Ρυθμίσεις" -#: ../vnc.html:202 +#: ../vnc.html:160 msgid "Shared Mode" msgstr "Κοινόχρηστη Λειτουργία" -#: ../vnc.html:205 +#: ../vnc.html:163 msgid "View Only" msgstr "Μόνο Θέαση" -#: ../vnc.html:209 +#: ../vnc.html:167 msgid "Clip to Window" msgstr "Αποκοπή στο όριο του Παράθυρου" -#: ../vnc.html:212 +#: ../vnc.html:170 msgid "Scaling Mode:" msgstr "Λειτουργία Κλιμάκωσης:" -#: ../vnc.html:214 +#: ../vnc.html:172 msgid "None" msgstr "Καμία" -#: ../vnc.html:215 +#: ../vnc.html:173 msgid "Local Scaling" msgstr "Τοπική Κλιμάκωση" -#: ../vnc.html:216 +#: ../vnc.html:174 msgid "Remote Resizing" msgstr "Απομακρυσμένη Αλλαγή μεγέθους" -#: ../vnc.html:221 +#: ../vnc.html:179 msgid "Advanced" msgstr "Για προχωρημένους" -#: ../vnc.html:224 +#: ../vnc.html:182 +msgid "Quality:" +msgstr "Ποιότητα:" + +#: ../vnc.html:186 +msgid "Compression level:" +msgstr "Επίπεδο συμπίεσης:" + +#: ../vnc.html:191 msgid "Repeater ID:" msgstr "Repeater ID:" -#: ../vnc.html:228 +#: ../vnc.html:195 msgid "WebSocket" msgstr "WebSocket" -#: ../vnc.html:231 +#: ../vnc.html:198 msgid "Encrypt" msgstr "Κρυπτογράφηση" -#: ../vnc.html:234 +#: ../vnc.html:201 msgid "Host:" msgstr "Όνομα διακομιστή:" -#: ../vnc.html:238 +#: ../vnc.html:205 msgid "Port:" msgstr "Πόρτα διακομιστή:" -#: ../vnc.html:242 +#: ../vnc.html:209 msgid "Path:" msgstr "Διαδρομή:" -#: ../vnc.html:249 +#: ../vnc.html:216 msgid "Automatic Reconnect" msgstr "Αυτόματη επανασύνδεση" -#: ../vnc.html:252 +#: ../vnc.html:219 msgid "Reconnect Delay (ms):" msgstr "Καθυστέρηση επανασύνδεσης (ms):" -#: ../vnc.html:258 +#: ../vnc.html:224 +msgid "Show Dot when No Cursor" +msgstr "Εμφάνιση Τελείας όταν δεν υπάρχει Δρομέας" + +#: ../vnc.html:229 msgid "Logging:" msgstr "Καταγραφή:" -#: ../vnc.html:270 +#: ../vnc.html:238 +msgid "Version:" +msgstr "Έκδοση:" + +#: ../vnc.html:246 msgid "Disconnect" msgstr "Αποσύνδεση" -#: ../vnc.html:289 +#: ../vnc.html:269 msgid "Connect" msgstr "Σύνδεση" -#: ../vnc.html:299 +#: ../vnc.html:278 +msgid "Server identity" +msgstr "Ταυτότητα Διακομιστή" + +#: ../vnc.html:281 +msgid "The server has provided the following identifying information:" +msgstr "Ο διακομιστής παρείχε την ακόλουθη πληροφορία ταυτοποίησης:" + +#: ../vnc.html:285 +msgid "Fingerprint:" +msgstr "Δακτυλικό αποτύπωμα:" + +#: ../vnc.html:288 +msgid "" +"Please verify that the information is correct and press \"Approve\". " +"Otherwise press \"Reject\"." +msgstr "" +"Παρακαλώ επαληθεύσετε ότι η πληροφορία είναι σωστή και πιέστε \"Αποδοχή\". " +"Αλλιώς πιέστε \"Απόρριψη\"." + +#: ../vnc.html:293 +msgid "Approve" +msgstr "Αποδοχή" + +#: ../vnc.html:294 +msgid "Reject" +msgstr "Απόρριψη" + +#: ../vnc.html:302 +msgid "Credentials" +msgstr "Διαπιστευτήρια" + +#: ../vnc.html:306 +msgid "Username:" +msgstr "Κωδικός Χρήστη:" + +#: ../vnc.html:310 msgid "Password:" msgstr "Κωδικός Πρόσβασης:" -#: ../vnc.html:313 +#: ../vnc.html:314 +msgid "Send Credentials" +msgstr "Αποστολή Διαπιστευτηρίων" + +#: ../vnc.html:323 msgid "Cancel" msgstr "Ακύρωση" -#: ../vnc.html:329 -msgid "Canvas not supported." -msgstr "Δεν υποστηρίζεται το στοιχείο Canvas" +#~ msgid "Password is required" +#~ msgstr "Απαιτείται ο κωδικός πρόσβασης" + +#~ msgid "viewport drag" +#~ msgstr "σύρσιμο θεατού πεδίου" + +#~ msgid "Active Mouse Button" +#~ msgstr "Ενεργό Πλήκτρο Ποντικιού" + +#~ msgid "No mousebutton" +#~ msgstr "Χωρίς Πλήκτρο Ποντικιού" + +#~ msgid "Left mousebutton" +#~ msgstr "Αριστερό Πλήκτρο Ποντικιού" + +#~ msgid "Middle mousebutton" +#~ msgstr "Μεσαίο Πλήκτρο Ποντικιού" + +#~ msgid "Right mousebutton" +#~ msgstr "Δεξί Πλήκτρο Ποντικιού" + +#~ msgid "Clear" +#~ msgstr "Καθάρισμα" + +#~ msgid "Canvas not supported." +#~ msgstr "Δεν υποστηρίζεται το στοιχείο Canvas" #~ msgid "Disconnect timeout" #~ msgstr "Παρέλευση χρονικού ορίου αποσύνδεσης" From 8d1b665808ce3da8307c5597d821f31eb925cb99 Mon Sep 17 00:00:00 2001 From: Samuel Mannehed Date: Mon, 22 Apr 2024 08:49:30 +0200 Subject: [PATCH 27/39] Migrate deprecated eslint config to to new format The .eslintrc and .eslintignore formats are deprecated. The new format uses a single eslint.config.js (or .mjs) file at the top. --- .eslintignore | 1 - .eslintrc | 54 -------------------------- eslint.config.mjs | 98 +++++++++++++++++++++++++++++++++++++++++++++++ package.json | 1 + po/.eslintrc | 5 --- tests/.eslintrc | 15 -------- utils/.eslintrc | 8 ---- 7 files changed, 99 insertions(+), 83 deletions(-) delete mode 100644 .eslintignore delete mode 100644 .eslintrc create mode 100644 eslint.config.mjs delete mode 100644 po/.eslintrc delete mode 100644 tests/.eslintrc delete mode 100644 utils/.eslintrc diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index d3816280..00000000 --- a/.eslintignore +++ /dev/null @@ -1 +0,0 @@ -**/xtscancodes.js diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index 10e15cec..00000000 --- a/.eslintrc +++ /dev/null @@ -1,54 +0,0 @@ -{ - "env": { - "browser": true, - "es2020": true - }, - "parserOptions": { - "sourceType": "module", - "ecmaVersion": 2020 - }, - "extends": "eslint:recommended", - "rules": { - // Unsafe or confusing stuff that we forbid - - "no-unused-vars": ["error", { "vars": "all", "args": "none", "ignoreRestSiblings": true }], - "no-constant-condition": ["error", { "checkLoops": false }], - "no-var": "error", - "no-useless-constructor": "error", - "object-shorthand": ["error", "methods", { "avoidQuotes": true }], - "prefer-arrow-callback": "error", - "arrow-body-style": ["error", "as-needed", { "requireReturnForObjectLiteral": false } ], - "arrow-parens": ["error", "as-needed", { "requireForBlockBody": true }], - "arrow-spacing": ["error"], - "no-confusing-arrow": ["error", { "allowParens": true }], - - // Enforced coding style - - "brace-style": ["error", "1tbs", { "allowSingleLine": true }], - "indent": ["error", 4, { "SwitchCase": 1, - "VariableDeclarator": "first", - "FunctionDeclaration": { "parameters": "first" }, - "FunctionExpression": { "parameters": "first" }, - "CallExpression": { "arguments": "first" }, - "ArrayExpression": "first", - "ObjectExpression": "first", - "ImportDeclaration": "first", - "ignoreComments": true }], - "comma-spacing": ["error"], - "comma-style": ["error"], - "curly": ["error", "multi-line"], - "func-call-spacing": ["error"], - "func-names": ["error"], - "func-style": ["error", "declaration", { "allowArrowFunctions": true }], - "key-spacing": ["error"], - "keyword-spacing": ["error"], - "no-trailing-spaces": ["error"], - "semi": ["error"], - "space-before-blocks": ["error"], - "space-before-function-paren": ["error", { "anonymous": "always", - "named": "never", - "asyncArrow": "always" }], - "switch-colon-spacing": ["error"], - "camelcase": ["error", { allow: ["^XK_", "^XF86XK_"] }], - } -} diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 00000000..e3bfcd78 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,98 @@ +import globals from "globals"; +import js from "@eslint/js"; + +export default [ + js.configs.recommended, + { + languageOptions: { + ecmaVersion: 2020, + sourceType: "module", + globals: { + ...globals.browser, + ...globals.es2020, + } + }, + ignores: ["**/xtscancodes.js"], + rules: { + // Unsafe or confusing stuff that we forbid + + "no-unused-vars": ["error", { "vars": "all", + "args": "none", + "ignoreRestSiblings": true, + "caughtErrors": "none" }], + "no-constant-condition": ["error", { "checkLoops": false }], + "no-var": "error", + "no-useless-constructor": "error", + "object-shorthand": ["error", "methods", { "avoidQuotes": true }], + "prefer-arrow-callback": "error", + "arrow-body-style": ["error", "as-needed", { "requireReturnForObjectLiteral": false } ], + "arrow-parens": ["error", "as-needed", { "requireForBlockBody": true }], + "arrow-spacing": ["error"], + "no-confusing-arrow": ["error", { "allowParens": true }], + + // Enforced coding style + + "brace-style": ["error", "1tbs", { "allowSingleLine": true }], + "indent": ["error", 4, { "SwitchCase": 1, + "VariableDeclarator": "first", + "FunctionDeclaration": { "parameters": "first" }, + "FunctionExpression": { "parameters": "first" }, + "CallExpression": { "arguments": "first" }, + "ArrayExpression": "first", + "ObjectExpression": "first", + "ImportDeclaration": "first", + "ignoreComments": true }], + "comma-spacing": ["error"], + "comma-style": ["error"], + "curly": ["error", "multi-line"], + "func-call-spacing": ["error"], + "func-names": ["error"], + "func-style": ["error", "declaration", { "allowArrowFunctions": true }], + "key-spacing": ["error"], + "keyword-spacing": ["error"], + "no-trailing-spaces": ["error"], + "semi": ["error"], + "space-before-blocks": ["error"], + "space-before-function-paren": ["error", { "anonymous": "always", + "named": "never", + "asyncArrow": "always" }], + "switch-colon-spacing": ["error"], + "camelcase": ["error", { "allow": ["^XK_", "^XF86XK_"] }], + } + }, + { + files: ["po/po2js", "po/xgettext-html"], + languageOptions: { + globals: { + ...globals.node, + } + }, + }, + { + files: ["tests/*"], + languageOptions: { + globals: { + ...globals.node, + ...globals.mocha, + sinon: false, + chai: false, + } + }, + rules: { + "prefer-arrow-callback": 0, + // Too many anonymous callbacks + "func-names": "off", + }, + }, + { + files: ["utils/*"], + languageOptions: { + globals: { + ...globals.node, + } + }, + rules: { + "no-console": 0, + }, + }, +]; diff --git a/package.json b/package.json index 482b86eb..9f22165b 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "commander": "latest", "eslint": "latest", "fs-extra": "latest", + "globals": "latest", "jsdom": "latest", "karma": "latest", "karma-mocha": "latest", diff --git a/po/.eslintrc b/po/.eslintrc deleted file mode 100644 index a0157e2a..00000000 --- a/po/.eslintrc +++ /dev/null @@ -1,5 +0,0 @@ -{ - "env": { - "node": true, - }, -} diff --git a/tests/.eslintrc b/tests/.eslintrc deleted file mode 100644 index 545fa2ed..00000000 --- a/tests/.eslintrc +++ /dev/null @@ -1,15 +0,0 @@ -{ - "env": { - "node": true, - "mocha": true - }, - "globals": { - "chai": false, - "sinon": false - }, - "rules": { - "prefer-arrow-callback": 0, - // Too many anonymous callbacks - "func-names": "off", - } -} diff --git a/utils/.eslintrc b/utils/.eslintrc deleted file mode 100644 index b7dc129f..00000000 --- a/utils/.eslintrc +++ /dev/null @@ -1,8 +0,0 @@ -{ - "env": { - "node": true - }, - "rules": { - "no-console": 0 - } -} \ No newline at end of file From 10ee10ce56d5b7aaf642457bfb99b56a13d56ad6 Mon Sep 17 00:00:00 2001 From: Samuel Mannehed Date: Tue, 30 Apr 2024 15:25:03 +0200 Subject: [PATCH 28/39] Cleanup "no-console" eslint rules Removes unexpected exceptions and clarifies where we want to avoid console calls. --- eslint.config.mjs | 4 ++++ tests/test.browser.js | 1 - tests/test.deflator.js | 1 - tests/test.inflator.js | 1 - tests/test.int.js | 1 - 5 files changed, 4 insertions(+), 4 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index e3bfcd78..c88e7b75 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -58,6 +58,7 @@ export default [ "asyncArrow": "always" }], "switch-colon-spacing": ["error"], "camelcase": ["error", { "allow": ["^XK_", "^XF86XK_"] }], + "no-console": ["error"], } }, { @@ -67,6 +68,9 @@ export default [ ...globals.node, } }, + rules: { + "no-console": 0, + }, }, { files: ["tests/*"], diff --git a/tests/test.browser.js b/tests/test.browser.js index 3b2299f6..1beeb48d 100644 --- a/tests/test.browser.js +++ b/tests/test.browser.js @@ -1,4 +1,3 @@ -/* eslint-disable no-console */ const expect = chai.expect; import { isMac, isWindows, isIOS, isAndroid, isChromeOS, diff --git a/tests/test.deflator.js b/tests/test.deflator.js index 12e8a46b..a7e972ec 100644 --- a/tests/test.deflator.js +++ b/tests/test.deflator.js @@ -1,4 +1,3 @@ -/* eslint-disable no-console */ const expect = chai.expect; import { inflateInit, inflate } from "../vendor/pako/lib/zlib/inflate.js"; diff --git a/tests/test.inflator.js b/tests/test.inflator.js index 533bcd86..304e7a0f 100644 --- a/tests/test.inflator.js +++ b/tests/test.inflator.js @@ -1,4 +1,3 @@ -/* eslint-disable no-console */ const expect = chai.expect; import { deflateInit, deflate, Z_FULL_FLUSH } from "../vendor/pako/lib/zlib/deflate.js"; diff --git a/tests/test.int.js b/tests/test.int.js index 954fd279..084d68ab 100644 --- a/tests/test.int.js +++ b/tests/test.int.js @@ -1,4 +1,3 @@ -/* eslint-disable no-console */ const expect = chai.expect; import { toUnsigned32bit, toSigned32bit } from '../core/util/int.js'; From c187b2e5e0e44b4157f5e6220b263940f600bbca Mon Sep 17 00:00:00 2001 From: Jiang XueQian Date: Thu, 2 May 2024 20:41:38 +0800 Subject: [PATCH 29/39] Implement gradient filter of tight decoder, fixing issue #1767 This commit is a basic implementation of the gradient filter required by qemu `lossy` option. --- core/decoders/tight.js | 68 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) diff --git a/core/decoders/tight.js b/core/decoders/tight.js index 45622080..8bc977a7 100644 --- a/core/decoders/tight.js +++ b/core/decoders/tight.js @@ -285,7 +285,73 @@ export default class TightDecoder { } _gradientFilter(streamId, x, y, width, height, sock, display, depth) { - throw new Error("Gradient filter not implemented"); + // assume the TPIXEL is 3 bytes long + const uncompressedSize = width * height * 3; + let data; + + if (uncompressedSize === 0) { + return true; + } + + if (uncompressedSize < 12) { + if (sock.rQwait("TIGHT", uncompressedSize)) { + return false; + } + + data = sock.rQshiftBytes(uncompressedSize); + } else { + data = this._readData(sock); + if (data === null) { + return false; + } + + this._zlibs[streamId].setInput(data); + data = this._zlibs[streamId].inflate(uncompressedSize); + this._zlibs[streamId].setInput(null); + } + + let rgbx = new Uint8Array(4 * width * height); + + let rgbxIndex = 0, dataIndex = 0; + let left = new Uint8Array(3); + for (let x = 0; x < width; x++) { + for (let c = 0; c < 3; c++) { + const prediction = left[c]; + const value = data[dataIndex++] + prediction; + rgbx[rgbxIndex++] = value; + left[c] = value; + } + rgbx[rgbxIndex++] = 255; + } + + let upperIndex = 0; + let upper = new Uint8Array(3), + upperleft = new Uint8Array(3); + for (let y = 1; y < height; y++) { + left.fill(0); + upperleft.fill(0); + for (let x = 0; x < width; x++) { + for (let c = 0; c < 3; c++) { + upper[c] = rgbx[upperIndex++]; + let prediction = left[c] + upper[c] - upperleft[c]; + if (prediction < 0) { + prediction = 0; + } else if (prediction > 255) { + prediction = 255; + } + const value = data[dataIndex++] + prediction; + rgbx[rgbxIndex++] = value; + upperleft[c] = upper[c]; + left[c] = value; + } + rgbx[rgbxIndex++] = 255; + upperIndex++; + } + } + + display.blitImage(x, y, width, height, rgbx, 0, false); + + return true; } _readData(sock) { From d80e3bfa2f372e2b210f46b8c46796a9754abf27 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Thu, 16 May 2024 16:53:49 +0200 Subject: [PATCH 30/39] Add unit tests for Tight gradient filter --- tests/test.tight.js | 74 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 70 insertions(+), 4 deletions(-) diff --git a/tests/test.tight.js b/tests/test.tight.js index b3457a88..141d7b6e 100644 --- a/tests/test.tight.js +++ b/tests/test.tight.js @@ -228,12 +228,59 @@ describe('Tight Decoder', function () { expect(display).to.have.displayed(targetData); }); - it.skip('should handle uncompressed gradient rects', function () { - // Not implemented yet + it('should handle uncompressed gradient rects', function () { + let done; + let blueData = [ 0x40, 0x02, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00 ]; + let greenData = [ 0x40, 0x02, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00 ]; + + done = testDecodeRect(decoder, 0, 0, 2, 1, blueData, display, 24); + expect(done).to.be.true; + done = testDecodeRect(decoder, 0, 1, 2, 1, blueData, display, 24); + expect(done).to.be.true; + done = testDecodeRect(decoder, 2, 0, 2, 1, greenData, display, 24); + expect(done).to.be.true; + done = testDecodeRect(decoder, 2, 1, 2, 1, greenData, display, 24); + expect(done).to.be.true; + done = testDecodeRect(decoder, 0, 2, 2, 1, greenData, display, 24); + expect(done).to.be.true; + done = testDecodeRect(decoder, 0, 3, 2, 1, greenData, display, 24); + expect(done).to.be.true; + done = testDecodeRect(decoder, 2, 2, 2, 1, blueData, display, 24); + expect(done).to.be.true; + done = testDecodeRect(decoder, 2, 3, 2, 1, blueData, display, 24); + expect(done).to.be.true; + + let targetData = new Uint8Array([ + 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, + 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, + 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, + 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255 + ]); + + expect(display).to.have.displayed(targetData); }); - it.skip('should handle compressed gradient rects', function () { - // Not implemented yet + it('should handle compressed gradient rects', function () { + let data = [ + // Control byte + 0x40, 0x02, + // Pixels (compressed) + 0x18, + 0x78, 0x9c, 0x62, 0x60, 0xf8, 0xcf, 0x00, 0x04, + 0xff, 0x19, 0x19, 0xd0, 0x00, 0x44, 0x84, 0xf1, + 0x3f, 0x9a, 0x30, 0x00, 0x00, 0x00, 0xff, 0xff ]; + + let done = testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24); + + let targetData = new Uint8Array([ + 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, + 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, + 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, + 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255 + ]); + + expect(done).to.be.true; + expect(display).to.have.displayed(targetData); }); it('should handle empty copy rects', function () { @@ -275,6 +322,25 @@ describe('Tight Decoder', function () { expect(display).to.have.displayed(targetData); }); + it('should handle empty gradient rects', function () { + display.fillRect(0, 0, 4, 4, [ 0x00, 0x00, 0xff ]); + display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]); + display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]); + + let done = testDecodeRect(decoder, 1, 2, 0, 0, + [ 0x40, 0x02 ], display, 24); + + let targetData = new Uint8Array([ + 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, + 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, + 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, + 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255 + ]); + + expect(done).to.be.true; + expect(display).to.have.displayed(targetData); + }); + it('should handle empty fill rects', function () { display.fillRect(0, 0, 4, 4, [ 0x00, 0x00, 0xff ]); display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]); From fc11b9d2b095b0dd0db106ca0f80a47d6e262f6c Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Mon, 3 Jun 2024 14:09:00 +0200 Subject: [PATCH 31/39] Remove Twitter links These are not updated anymore as they are not under the control of the current team. --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index a771cb43..b95d15e6 100644 --- a/README.md +++ b/README.md @@ -32,8 +32,6 @@ for a more complete list with additional info and links. ### News/help/contact The project website is found at [novnc.com](http://novnc.com). -Notable commits, announcements and news are posted to -[@noVNC](http://www.twitter.com/noVNC). If you are a noVNC developer/integrator/user (or want to be) please join the [noVNC discussion group](https://groups.google.com/forum/?fromgroups#!forum/novnc). @@ -59,7 +57,6 @@ profits such as: [Electronic Frontier Foundation](https://www.eff.org/), [Against Malaria Foundation](http://www.againstmalaria.com/), [Nothing But Nets](http://www.nothingbutnets.net/), etc. -Please tweet [@noVNC](http://www.twitter.com/noVNC) if you do. ### Features From f28e9daec387c681a70b10b1328f1a365ef51363 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Mon, 3 Jun 2024 14:10:47 +0200 Subject: [PATCH 32/39] Update translation template file --- po/noVNC.pot | 155 ++++++++++++++++++++++++++------------------------- 1 file changed, 80 insertions(+), 75 deletions(-) diff --git a/po/noVNC.pot b/po/noVNC.pot index 3641e3bc..0f85a82e 100644 --- a/po/noVNC.pot +++ b/po/noVNC.pot @@ -6,9 +6,9 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: noVNC 1.4.0\n" +"Project-Id-Version: noVNC 1.5.0\n" "Report-Msgid-Bugs-To: novnc@googlegroups.com\n" -"POT-Creation-Date: 2022-12-27 15:24+0100\n" +"POT-Creation-Date: 2024-06-03 14:10+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -18,7 +18,8 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" #: ../app/ui.js:69 -msgid "HTTPS is required for full functionality" +msgid "" +"Running without HTTPS is not recommended, crashes or other issues are likely." msgstr "" #: ../app/ui.js:410 @@ -41,292 +42,296 @@ msgstr "" msgid "Must set host" msgstr "" -#: ../app/ui.js:1110 +#: ../app/ui.js:1052 +msgid "Failed to connect to server: " +msgstr "" + +#: ../app/ui.js:1118 msgid "Connected (encrypted) to " msgstr "" -#: ../app/ui.js:1112 +#: ../app/ui.js:1120 msgid "Connected (unencrypted) to " msgstr "" -#: ../app/ui.js:1135 +#: ../app/ui.js:1143 msgid "Something went wrong, connection is closed" msgstr "" -#: ../app/ui.js:1138 +#: ../app/ui.js:1146 msgid "Failed to connect to server" msgstr "" -#: ../app/ui.js:1150 +#: ../app/ui.js:1158 msgid "Disconnected" msgstr "" -#: ../app/ui.js:1165 +#: ../app/ui.js:1173 msgid "New connection has been rejected with reason: " msgstr "" -#: ../app/ui.js:1168 +#: ../app/ui.js:1176 msgid "New connection has been rejected" msgstr "" -#: ../app/ui.js:1234 +#: ../app/ui.js:1242 msgid "Credentials are required" msgstr "" -#: ../vnc.html:57 +#: ../vnc.html:55 msgid "noVNC encountered an error:" msgstr "" -#: ../vnc.html:67 +#: ../vnc.html:65 msgid "Hide/Show the control bar" msgstr "" -#: ../vnc.html:76 +#: ../vnc.html:74 msgid "Drag" msgstr "" -#: ../vnc.html:76 +#: ../vnc.html:74 msgid "Move/Drag Viewport" msgstr "" -#: ../vnc.html:82 +#: ../vnc.html:80 msgid "Keyboard" msgstr "" -#: ../vnc.html:82 +#: ../vnc.html:80 msgid "Show Keyboard" msgstr "" -#: ../vnc.html:87 +#: ../vnc.html:85 msgid "Extra keys" msgstr "" -#: ../vnc.html:87 +#: ../vnc.html:85 msgid "Show Extra Keys" msgstr "" -#: ../vnc.html:92 +#: ../vnc.html:90 msgid "Ctrl" msgstr "" -#: ../vnc.html:92 +#: ../vnc.html:90 msgid "Toggle Ctrl" msgstr "" -#: ../vnc.html:95 +#: ../vnc.html:93 msgid "Alt" msgstr "" -#: ../vnc.html:95 +#: ../vnc.html:93 msgid "Toggle Alt" msgstr "" -#: ../vnc.html:98 +#: ../vnc.html:96 msgid "Toggle Windows" msgstr "" -#: ../vnc.html:98 +#: ../vnc.html:96 msgid "Windows" msgstr "" -#: ../vnc.html:101 +#: ../vnc.html:99 msgid "Send Tab" msgstr "" -#: ../vnc.html:101 +#: ../vnc.html:99 msgid "Tab" msgstr "" -#: ../vnc.html:104 +#: ../vnc.html:102 msgid "Esc" msgstr "" -#: ../vnc.html:104 +#: ../vnc.html:102 msgid "Send Escape" msgstr "" -#: ../vnc.html:107 +#: ../vnc.html:105 msgid "Ctrl+Alt+Del" msgstr "" -#: ../vnc.html:107 +#: ../vnc.html:105 msgid "Send Ctrl-Alt-Del" msgstr "" -#: ../vnc.html:114 +#: ../vnc.html:112 msgid "Shutdown/Reboot" msgstr "" -#: ../vnc.html:114 +#: ../vnc.html:112 msgid "Shutdown/Reboot..." msgstr "" -#: ../vnc.html:120 +#: ../vnc.html:118 msgid "Power" msgstr "" -#: ../vnc.html:122 +#: ../vnc.html:120 msgid "Shutdown" msgstr "" -#: ../vnc.html:123 +#: ../vnc.html:121 msgid "Reboot" msgstr "" -#: ../vnc.html:124 +#: ../vnc.html:122 msgid "Reset" msgstr "" -#: ../vnc.html:129 ../vnc.html:135 +#: ../vnc.html:127 ../vnc.html:133 msgid "Clipboard" msgstr "" -#: ../vnc.html:137 +#: ../vnc.html:135 msgid "Edit clipboard content in the textarea below." msgstr "" -#: ../vnc.html:145 +#: ../vnc.html:143 msgid "Full Screen" msgstr "" -#: ../vnc.html:150 ../vnc.html:156 +#: ../vnc.html:148 ../vnc.html:154 msgid "Settings" msgstr "" -#: ../vnc.html:160 +#: ../vnc.html:158 msgid "Shared Mode" msgstr "" -#: ../vnc.html:163 +#: ../vnc.html:161 msgid "View Only" msgstr "" -#: ../vnc.html:167 +#: ../vnc.html:165 msgid "Clip to Window" msgstr "" -#: ../vnc.html:170 +#: ../vnc.html:168 msgid "Scaling Mode:" msgstr "" -#: ../vnc.html:172 +#: ../vnc.html:170 msgid "None" msgstr "" -#: ../vnc.html:173 +#: ../vnc.html:171 msgid "Local Scaling" msgstr "" -#: ../vnc.html:174 +#: ../vnc.html:172 msgid "Remote Resizing" msgstr "" -#: ../vnc.html:179 +#: ../vnc.html:177 msgid "Advanced" msgstr "" -#: ../vnc.html:182 +#: ../vnc.html:180 msgid "Quality:" msgstr "" -#: ../vnc.html:186 +#: ../vnc.html:184 msgid "Compression level:" msgstr "" -#: ../vnc.html:191 +#: ../vnc.html:189 msgid "Repeater ID:" msgstr "" -#: ../vnc.html:195 +#: ../vnc.html:193 msgid "WebSocket" msgstr "" -#: ../vnc.html:198 +#: ../vnc.html:196 msgid "Encrypt" msgstr "" -#: ../vnc.html:201 +#: ../vnc.html:199 msgid "Host:" msgstr "" -#: ../vnc.html:205 +#: ../vnc.html:203 msgid "Port:" msgstr "" -#: ../vnc.html:209 +#: ../vnc.html:207 msgid "Path:" msgstr "" -#: ../vnc.html:216 +#: ../vnc.html:214 msgid "Automatic Reconnect" msgstr "" -#: ../vnc.html:219 +#: ../vnc.html:217 msgid "Reconnect Delay (ms):" msgstr "" -#: ../vnc.html:224 +#: ../vnc.html:222 msgid "Show Dot when No Cursor" msgstr "" -#: ../vnc.html:229 +#: ../vnc.html:227 msgid "Logging:" msgstr "" -#: ../vnc.html:238 +#: ../vnc.html:236 msgid "Version:" msgstr "" -#: ../vnc.html:246 +#: ../vnc.html:244 msgid "Disconnect" msgstr "" -#: ../vnc.html:269 +#: ../vnc.html:267 msgid "Connect" msgstr "" -#: ../vnc.html:278 +#: ../vnc.html:276 msgid "Server identity" msgstr "" -#: ../vnc.html:281 +#: ../vnc.html:279 msgid "The server has provided the following identifying information:" msgstr "" -#: ../vnc.html:285 +#: ../vnc.html:283 msgid "Fingerprint:" msgstr "" -#: ../vnc.html:288 +#: ../vnc.html:286 msgid "" "Please verify that the information is correct and press \"Approve\". " "Otherwise press \"Reject\"." msgstr "" -#: ../vnc.html:293 +#: ../vnc.html:291 msgid "Approve" msgstr "" -#: ../vnc.html:294 +#: ../vnc.html:292 msgid "Reject" msgstr "" -#: ../vnc.html:302 +#: ../vnc.html:300 msgid "Credentials" msgstr "" -#: ../vnc.html:306 +#: ../vnc.html:304 msgid "Username:" msgstr "" -#: ../vnc.html:310 +#: ../vnc.html:308 msgid "Password:" msgstr "" -#: ../vnc.html:314 +#: ../vnc.html:312 msgid "Send Credentials" msgstr "" -#: ../vnc.html:323 +#: ../vnc.html:321 msgid "Cancel" msgstr "" From 68e09ee8b3f06dac37a08725b9ccc2a64b2fda4f Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Mon, 3 Jun 2024 14:45:11 +0200 Subject: [PATCH 33/39] Upgrade to websockify 0.12.0 in snap package --- snap/snapcraft.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index ccf61893..82d52de4 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -42,7 +42,7 @@ parts: - jq websockify: - source: https://github.com/novnc/websockify/archive/v0.11.0.tar.gz + source: https://github.com/novnc/websockify/archive/v0.12.0.tar.gz plugin: python stage-packages: - python3-numpy From aead0b2f891732687b970225350419ae62f1942c Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Mon, 3 Jun 2024 14:14:24 +0200 Subject: [PATCH 34/39] noVNC 1.5.0 beta --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9f22165b..af6cfc74 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@novnc/novnc", - "version": "1.4.0", + "version": "1.5.0-beta", "description": "An HTML5 VNC client", "browser": "lib/rfb", "directories": { From fb1817c99fdf71180e8d05bdb6f9595be2b5541b Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Thu, 13 Jun 2024 08:38:00 +0200 Subject: [PATCH 35/39] Remove Chrome timeout workaround This is a revert of fca48df85d394b2c1e9d26ed6e9d10d33db9f4f4. The issue seems to be fixed in the current version of Chrome, so let's keep things simple again. --- karma.conf.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/karma.conf.js b/karma.conf.js index faa8beea..1ea17475 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -81,12 +81,5 @@ module.exports = (config) => { singleRun: true, }; - if (process.env.TEST_BROWSER_NAME === 'ChromeHeadless') { - let os = require('os'); - if (os.platform() === 'win32') { - my_conf.client.mocha['timeout'] = 5000; - } - } - config.set(my_conf); }; From 1a62eb7d3e769e21bf9894e7808ee8143c07080f Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Tue, 18 Jun 2024 14:01:40 +0200 Subject: [PATCH 36/39] Don't include missing translation in .js It just adds size and confusion. Instead, omit any lines where no translation is available. --- app/locale/fr.json | 8 -------- app/locale/it.json | 4 ---- po/po2js | 12 +++++++----- 3 files changed, 7 insertions(+), 17 deletions(-) diff --git a/app/locale/fr.json b/app/locale/fr.json index 22531f73..e25f34ec 100644 --- a/app/locale/fr.json +++ b/app/locale/fr.json @@ -1,5 +1,4 @@ { - "HTTPS is required for full functionality": "", "Connecting...": "En cours de connexion...", "Disconnecting...": "Déconnexion en cours...", "Reconnecting...": "Reconnexion en cours...", @@ -40,7 +39,6 @@ "Reboot": "Redémarrer", "Reset": "Réinitialiser", "Clipboard": "Presse-papiers", - "Edit clipboard content in the textarea below.": "", "Settings": "Paramètres", "Shared Mode": "Mode partagé", "View Only": "Afficher uniquement", @@ -65,12 +63,6 @@ "Version:": "Version :", "Disconnect": "Déconnecter", "Connect": "Connecter", - "Server identity": "", - "The server has provided the following identifying information:": "", - "Fingerprint:": "", - "Please verify that the information is correct and press \"Approve\". Otherwise press \"Reject\".": "", - "Approve": "", - "Reject": "", "Username:": "Nom d'utilisateur :", "Password:": "Mot de passe :", "Send Credentials": "Envoyer les identifiants", diff --git a/app/locale/it.json b/app/locale/it.json index 6fd25702..18a7f744 100644 --- a/app/locale/it.json +++ b/app/locale/it.json @@ -14,8 +14,6 @@ "Credentials are required": "Le credenziali sono obbligatorie", "noVNC encountered an error:": "noVNC ha riscontrato un errore:", "Hide/Show the control bar": "Nascondi/Mostra la barra di controllo", - "Drag": "", - "Move/Drag Viewport": "", "Keyboard": "Tastiera", "Show Keyboard": "Mostra tastiera", "Extra keys": "Tasti Aggiuntivi", @@ -44,7 +42,6 @@ "Settings": "Impostazioni", "Shared Mode": "Modalità condivisa", "View Only": "Sola Visualizzazione", - "Clip to Window": "", "Scaling Mode:": "Modalità di ridimensionamento:", "None": "Nessuna", "Local Scaling": "Ridimensionamento Locale", @@ -61,7 +58,6 @@ "Automatic Reconnect": "Riconnessione Automatica", "Reconnect Delay (ms):": "Ritardo Riconnessione (ms):", "Show Dot when No Cursor": "Mostra Punto quando Nessun Cursore", - "Logging:": "", "Version:": "Versione:", "Disconnect": "Disconnetti", "Connect": "Connetti", diff --git a/po/po2js b/po/po2js index fc6e8810..e293deda 100755 --- a/po/po2js +++ b/po/po2js @@ -32,11 +32,13 @@ if (opt.argv.length != 2) { const data = po2json.parseFileSync(opt.argv[0]); -const bodyPart = Object.keys(data).filter(msgid => msgid !== "").map((msgid) => { - if (msgid === "") return; - const msgstr = data[msgid][1]; - return " " + JSON.stringify(msgid) + ": " + JSON.stringify(msgstr); -}).join(",\n"); +const bodyPart = Object.keys(data) + .filter(msgid => msgid !== "") + .filter(msgid => data[msgid][1] !== "") + .map((msgid) => { + const msgstr = data[msgid][1]; + return " " + JSON.stringify(msgid) + ": " + JSON.stringify(msgstr); + }).join(",\n"); const output = "{\n" + bodyPart + "\n}"; From 7f364a173d1875ba029b6f7be1bd41f89d8ca00c Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Tue, 18 Jun 2024 14:02:34 +0200 Subject: [PATCH 37/39] Update Swedish translation --- app/locale/sv.json | 3 ++- po/sv.po | 42 ++++++++++++++++++++++++++---------------- 2 files changed, 28 insertions(+), 17 deletions(-) diff --git a/app/locale/sv.json b/app/locale/sv.json index 077ef42c..80a400bf 100644 --- a/app/locale/sv.json +++ b/app/locale/sv.json @@ -1,10 +1,11 @@ { - "HTTPS is required for full functionality": "HTTPS krävs för full funktionalitet", + "Running without HTTPS is not recommended, crashes or other issues are likely.": "Det är ej rekommenderat att köra utan HTTPS, krascher och andra problem är troliga.", "Connecting...": "Ansluter...", "Disconnecting...": "Kopplar ner...", "Reconnecting...": "Återansluter...", "Internal error": "Internt fel", "Must set host": "Du måste specifiera en värd", + "Failed to connect to server: ": "Misslyckades att ansluta till servern: ", "Connected (encrypted) to ": "Ansluten (krypterat) till ", "Connected (unencrypted) to ": "Ansluten (okrypterat) till ", "Something went wrong, connection is closed": "Något gick fel, anslutningen avslutades", diff --git a/po/sv.po b/po/sv.po index 972e4000..85c4e305 100644 --- a/po/sv.po +++ b/po/sv.po @@ -8,20 +8,23 @@ msgid "" msgstr "" "Project-Id-Version: noVNC 1.3.0\n" "Report-Msgid-Bugs-To: novnc@googlegroups.com\n" -"POT-Creation-Date: 2023-01-20 12:54+0100\n" -"PO-Revision-Date: 2023-01-20 12:58+0100\n" -"Last-Translator: Samuel Mannehed \n" +"POT-Creation-Date: 2024-06-03 14:10+0200\n" +"PO-Revision-Date: 2024-06-18 13:52+0200\n" +"Last-Translator: Pierre Ossman \n" "Language-Team: none\n" "Language: sv\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Poedit 3.2.2\n" +"X-Generator: Poedit 3.4.4\n" #: ../app/ui.js:69 -msgid "HTTPS is required for full functionality" -msgstr "HTTPS krävs för full funktionalitet" +msgid "" +"Running without HTTPS is not recommended, crashes or other issues are likely." +msgstr "" +"Det är ej rekommenderat att köra utan HTTPS, krascher och andra problem är " +"troliga." #: ../app/ui.js:410 msgid "Connecting..." @@ -43,35 +46,39 @@ msgstr "Internt fel" msgid "Must set host" msgstr "Du måste specifiera en värd" -#: ../app/ui.js:1110 +#: ../app/ui.js:1052 +msgid "Failed to connect to server: " +msgstr "Misslyckades att ansluta till servern: " + +#: ../app/ui.js:1118 msgid "Connected (encrypted) to " msgstr "Ansluten (krypterat) till " -#: ../app/ui.js:1112 +#: ../app/ui.js:1120 msgid "Connected (unencrypted) to " msgstr "Ansluten (okrypterat) till " -#: ../app/ui.js:1135 +#: ../app/ui.js:1143 msgid "Something went wrong, connection is closed" msgstr "Något gick fel, anslutningen avslutades" -#: ../app/ui.js:1138 +#: ../app/ui.js:1146 msgid "Failed to connect to server" msgstr "Misslyckades att ansluta till servern" -#: ../app/ui.js:1150 +#: ../app/ui.js:1158 msgid "Disconnected" msgstr "Frånkopplad" -#: ../app/ui.js:1165 +#: ../app/ui.js:1173 msgid "New connection has been rejected with reason: " msgstr "Ny anslutning har blivit nekad med följande skäl: " -#: ../app/ui.js:1168 +#: ../app/ui.js:1176 msgid "New connection has been rejected" msgstr "Ny anslutning har blivit nekad" -#: ../app/ui.js:1234 +#: ../app/ui.js:1242 msgid "Credentials are required" msgstr "Användaruppgifter krävs" @@ -304,8 +311,8 @@ msgid "" "Please verify that the information is correct and press \"Approve\". " "Otherwise press \"Reject\"." msgstr "" -"Kontrollera att informationen är korrekt och tryck sedan " -"\"Godkänn\". Tryck annars \"Neka\"." +"Kontrollera att informationen är korrekt och tryck sedan \"Godkänn\". Tryck " +"annars \"Neka\"." #: ../vnc.html:291 msgid "Approve" @@ -335,5 +342,8 @@ msgstr "Skicka Användaruppgifter" msgid "Cancel" msgstr "Avbryt" +#~ msgid "HTTPS is required for full functionality" +#~ msgstr "HTTPS krävs för full funktionalitet" + #~ msgid "Clear" #~ msgstr "Rensa" From aaadec4f1343e73a3bc9766305544ab896c04ced Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Tue, 18 Jun 2024 14:03:30 +0200 Subject: [PATCH 38/39] Update json files for new translations --- app/locale/el.json | 32 +++++++++++++++++++---------- app/locale/fr.json | 2 ++ app/locale/ja.json | 22 +++++++++++++------- app/locale/zh_CN.json | 48 +++++++++++++++++++++---------------------- 4 files changed, 62 insertions(+), 42 deletions(-) diff --git a/app/locale/el.json b/app/locale/el.json index f801251c..4df3e03c 100644 --- a/app/locale/el.json +++ b/app/locale/el.json @@ -1,4 +1,5 @@ { + "HTTPS is required for full functionality": "Το HTTPS είναι απαιτούμενο για πλήρη λειτουργικότητα", "Connecting...": "Συνδέεται...", "Disconnecting...": "Aποσυνδέεται...", "Reconnecting...": "Επανασυνδέεται...", @@ -7,19 +8,15 @@ "Connected (encrypted) to ": "Συνδέθηκε (κρυπτογραφημένα) με το ", "Connected (unencrypted) to ": "Συνδέθηκε (μη κρυπτογραφημένα) με το ", "Something went wrong, connection is closed": "Κάτι πήγε στραβά, η σύνδεση διακόπηκε", + "Failed to connect to server": "Αποτυχία στη σύνδεση με το διακομιστή", "Disconnected": "Αποσυνδέθηκε", "New connection has been rejected with reason: ": "Η νέα σύνδεση απορρίφθηκε διότι: ", "New connection has been rejected": "Η νέα σύνδεση απορρίφθηκε ", - "Password is required": "Απαιτείται ο κωδικός πρόσβασης", + "Credentials are required": "Απαιτούνται διαπιστευτήρια", "noVNC encountered an error:": "το noVNC αντιμετώπισε ένα σφάλμα:", "Hide/Show the control bar": "Απόκρυψη/Εμφάνιση γραμμής ελέγχου", + "Drag": "Σύρσιμο", "Move/Drag Viewport": "Μετακίνηση/Σύρσιμο Θεατού πεδίου", - "viewport drag": "σύρσιμο θεατού πεδίου", - "Active Mouse Button": "Ενεργό Πλήκτρο Ποντικιού", - "No mousebutton": "Χωρίς Πλήκτρο Ποντικιού", - "Left mousebutton": "Αριστερό Πλήκτρο Ποντικιού", - "Middle mousebutton": "Μεσαίο Πλήκτρο Ποντικιού", - "Right mousebutton": "Δεξί Πλήκτρο Ποντικιού", "Keyboard": "Πληκτρολόγιο", "Show Keyboard": "Εμφάνιση Πληκτρολογίου", "Extra keys": "Επιπλέον πλήκτρα", @@ -28,6 +25,8 @@ "Toggle Ctrl": "Εναλλαγή Ctrl", "Alt": "Alt", "Toggle Alt": "Εναλλαγή Alt", + "Toggle Windows": "Εναλλαγή Παράθυρων", + "Windows": "Παράθυρα", "Send Tab": "Αποστολή Tab", "Tab": "Tab", "Esc": "Esc", @@ -41,8 +40,7 @@ "Reboot": "Επανεκκίνηση", "Reset": "Επαναφορά", "Clipboard": "Πρόχειρο", - "Clear": "Καθάρισμα", - "Fullscreen": "Πλήρης Οθόνη", + "Edit clipboard content in the textarea below.": "Επεξεργαστείτε το περιεχόμενο του πρόχειρου στην περιοχή κειμένου παρακάτω.", "Settings": "Ρυθμίσεις", "Shared Mode": "Κοινόχρηστη Λειτουργία", "View Only": "Μόνο Θέαση", @@ -52,6 +50,8 @@ "Local Scaling": "Τοπική Κλιμάκωση", "Remote Resizing": "Απομακρυσμένη Αλλαγή μεγέθους", "Advanced": "Για προχωρημένους", + "Quality:": "Ποιότητα:", + "Compression level:": "Επίπεδο συμπίεσης:", "Repeater ID:": "Repeater ID:", "WebSocket": "WebSocket", "Encrypt": "Κρυπτογράφηση", @@ -60,10 +60,20 @@ "Path:": "Διαδρομή:", "Automatic Reconnect": "Αυτόματη επανασύνδεση", "Reconnect Delay (ms):": "Καθυστέρηση επανασύνδεσης (ms):", + "Show Dot when No Cursor": "Εμφάνιση Τελείας όταν δεν υπάρχει Δρομέας", "Logging:": "Καταγραφή:", + "Version:": "Έκδοση:", "Disconnect": "Αποσύνδεση", "Connect": "Σύνδεση", + "Server identity": "Ταυτότητα Διακομιστή", + "The server has provided the following identifying information:": "Ο διακομιστής παρείχε την ακόλουθη πληροφορία ταυτοποίησης:", + "Fingerprint:": "Δακτυλικό αποτύπωμα:", + "Please verify that the information is correct and press \"Approve\". Otherwise press \"Reject\".": "Παρακαλώ επαληθεύσετε ότι η πληροφορία είναι σωστή και πιέστε \"Αποδοχή\". Αλλιώς πιέστε \"Απόρριψη\".", + "Approve": "Αποδοχή", + "Reject": "Απόρριψη", + "Credentials": "Διαπιστευτήρια", + "Username:": "Κωδικός Χρήστη:", "Password:": "Κωδικός Πρόσβασης:", - "Cancel": "Ακύρωση", - "Canvas not supported.": "Δεν υποστηρίζεται το στοιχείο Canvas" + "Send Credentials": "Αποστολή Διαπιστευτηρίων", + "Cancel": "Ακύρωση" } \ No newline at end of file diff --git a/app/locale/fr.json b/app/locale/fr.json index e25f34ec..c0eeec7d 100644 --- a/app/locale/fr.json +++ b/app/locale/fr.json @@ -39,6 +39,8 @@ "Reboot": "Redémarrer", "Reset": "Réinitialiser", "Clipboard": "Presse-papiers", + "Clear": "Effacer", + "Fullscreen": "Plein écran", "Settings": "Paramètres", "Shared Mode": "Mode partagé", "View Only": "Afficher uniquement", diff --git a/app/locale/ja.json b/app/locale/ja.json index 43fc5bf3..70fd7a5d 100644 --- a/app/locale/ja.json +++ b/app/locale/ja.json @@ -1,4 +1,5 @@ { + "HTTPS is required for full functionality": "すべての機能を使用するにはHTTPS接続が必要です", "Connecting...": "接続しています...", "Disconnecting...": "切断しています...", "Reconnecting...": "再接続しています...", @@ -21,10 +22,10 @@ "Extra keys": "追加キー", "Show Extra Keys": "追加キーを表示", "Ctrl": "Ctrl", - "Toggle Ctrl": "Ctrl キーを切り替え", + "Toggle Ctrl": "Ctrl キーをトグル", "Alt": "Alt", - "Toggle Alt": "Alt キーを切り替え", - "Toggle Windows": "Windows キーを切り替え", + "Toggle Alt": "Alt キーをトグル", + "Toggle Windows": "Windows キーをトグル", "Windows": "Windows", "Send Tab": "Tab キーを送信", "Tab": "Tab", @@ -39,11 +40,11 @@ "Reboot": "再起動", "Reset": "リセット", "Clipboard": "クリップボード", - "Clear": "クリア", - "Fullscreen": "全画面表示", + "Edit clipboard content in the textarea below.": "以下の入力欄からクリップボードの内容を編集できます。", + "Full Screen": "全画面表示", "Settings": "設定", "Shared Mode": "共有モード", - "View Only": "表示のみ", + "View Only": "表示専用", "Clip to Window": "ウィンドウにクリップ", "Scaling Mode:": "スケーリングモード:", "None": "なし", @@ -60,11 +61,18 @@ "Path:": "パス:", "Automatic Reconnect": "自動再接続", "Reconnect Delay (ms):": "再接続する遅延 (ミリ秒):", - "Show Dot when No Cursor": "カーソルがないときにドットを表示", + "Show Dot when No Cursor": "カーソルがないときにドットを表示する", "Logging:": "ロギング:", "Version:": "バージョン:", "Disconnect": "切断", "Connect": "接続", + "Server identity": "サーバーの識別情報", + "The server has provided the following identifying information:": "サーバーは以下の識別情報を提供しています:", + "Fingerprint:": "フィンガープリント:", + "Please verify that the information is correct and press \"Approve\". Otherwise press \"Reject\".": "この情報が正しい場合は「承認」を、そうでない場合は「拒否」を押してください。", + "Approve": "承認", + "Reject": "拒否", + "Credentials": "資格情報", "Username:": "ユーザー名:", "Password:": "パスワード:", "Send Credentials": "資格情報を送信", diff --git a/app/locale/zh_CN.json b/app/locale/zh_CN.json index f0aea9af..3679eadd 100644 --- a/app/locale/zh_CN.json +++ b/app/locale/zh_CN.json @@ -1,69 +1,69 @@ { "Connecting...": "连接中...", + "Connected (encrypted) to ": "已连接(已加密)到", + "Connected (unencrypted) to ": "已连接(未加密)到", "Disconnecting...": "正在断开连接...", - "Reconnecting...": "重新连接中...", - "Internal error": "内部错误", - "Must set host": "请提供主机名", - "Connected (encrypted) to ": "已连接到(加密)", - "Connected (unencrypted) to ": "已连接到(未加密)", - "Something went wrong, connection is closed": "发生错误,连接已关闭", - "Failed to connect to server": "无法连接到服务器", "Disconnected": "已断开连接", - "New connection has been rejected with reason: ": "连接被拒绝,原因:", - "New connection has been rejected": "连接被拒绝", + "Must set host": "必须设置主机", + "Reconnecting...": "重新连接中...", "Password is required": "请提供密码", + "Disconnect timeout": "超时断开", "noVNC encountered an error:": "noVNC 遇到一个错误:", "Hide/Show the control bar": "显示/隐藏控制栏", - "Move/Drag Viewport": "拖放显示范围", - "viewport drag": "显示范围拖放", - "Active Mouse Button": "启动鼠标按鍵", - "No mousebutton": "禁用鼠标按鍵", - "Left mousebutton": "鼠标左鍵", - "Middle mousebutton": "鼠标中鍵", - "Right mousebutton": "鼠标右鍵", + "Move/Drag Viewport": "移动/拖动窗口", + "viewport drag": "窗口拖动", + "Active Mouse Button": "启动鼠标按键", + "No mousebutton": "禁用鼠标按键", + "Left mousebutton": "鼠标左键", + "Middle mousebutton": "鼠标中键", + "Right mousebutton": "鼠标右键", "Keyboard": "键盘", "Show Keyboard": "显示键盘", "Extra keys": "额外按键", "Show Extra Keys": "显示额外按键", "Ctrl": "Ctrl", "Toggle Ctrl": "切换 Ctrl", + "Edit clipboard content in the textarea below.": "在下面的文本区域中编辑剪贴板内容。", "Alt": "Alt", "Toggle Alt": "切换 Alt", "Send Tab": "发送 Tab 键", "Tab": "Tab", "Esc": "Esc", "Send Escape": "发送 Escape 键", - "Ctrl+Alt+Del": "Ctrl-Alt-Del", - "Send Ctrl-Alt-Del": "发送 Ctrl-Alt-Del 键", - "Shutdown/Reboot": "关机/重新启动", - "Shutdown/Reboot...": "关机/重新启动...", + "Ctrl+Alt+Del": "Ctrl+Alt+Del", + "Send Ctrl-Alt-Del": "发送 Ctrl+Alt+Del 键", + "Shutdown/Reboot": "关机/重启", + "Shutdown/Reboot...": "关机/重启...", "Power": "电源", "Shutdown": "关机", - "Reboot": "重新启动", + "Reboot": "重启", "Reset": "重置", "Clipboard": "剪贴板", "Clear": "清除", "Fullscreen": "全屏", "Settings": "设置", + "Encrypt": "加密", "Shared Mode": "分享模式", "View Only": "仅查看", "Clip to Window": "限制/裁切窗口大小", "Scaling Mode:": "缩放模式:", "None": "无", "Local Scaling": "本地缩放", + "Local Downscaling": "降低本地尺寸", "Remote Resizing": "远程调整大小", "Advanced": "高级", + "Local Cursor": "本地光标", "Repeater ID:": "中继站 ID", "WebSocket": "WebSocket", - "Encrypt": "加密", "Host:": "主机:", "Port:": "端口:", "Path:": "路径:", "Automatic Reconnect": "自动重新连接", "Reconnect Delay (ms):": "重新连接间隔 (ms):", "Logging:": "日志级别:", - "Disconnect": "中断连接", + "Disconnect": "断开连接", "Connect": "连接", "Password:": "密码:", - "Cancel": "取消" + "Cancel": "取消", + "Canvas not supported.": "不支持 Canvas。" } \ No newline at end of file From 7fcf9dcfe0cc5b14e3841a4429dc091a6ffca861 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Tue, 18 Jun 2024 14:05:35 +0200 Subject: [PATCH 39/39] noVNC 1.5.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index af6cfc74..9fa8c312 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@novnc/novnc", - "version": "1.5.0-beta", + "version": "1.5.0", "description": "An HTML5 VNC client", "browser": "lib/rfb", "directories": {