From 52392ec150fd79d023718c424bdf6cbc0b758fd9 Mon Sep 17 00:00:00 2001 From: NNN1590 Date: Sat, 14 Dec 2024 15:59:01 +0900 Subject: [PATCH 01/70] Update Japanese translation --- app/locale/ja.json | 7 +- po/ja.po | 199 ++++++++++++++++++++------------------------- 2 files changed, 93 insertions(+), 113 deletions(-) diff --git a/app/locale/ja.json b/app/locale/ja.json index 078adcc8..4fc9b8a9 100644 --- a/app/locale/ja.json +++ b/app/locale/ja.json @@ -1,13 +1,14 @@ { - "HTTPS is required for full functionality": "すべての機能を使用するにはHTTPS接続が必要です", + "Running without HTTPS is not recommended, crashes or other issues are likely.": "HTTPS接続なしで実行することは推奨されません。クラッシュしたりその他の問題が発生したりする可能性があります。", "Connecting...": "接続しています...", "Disconnecting...": "切断しています...", "Reconnecting...": "再接続しています...", "Internal error": "内部エラー", "Must set host": "ホストを設定する必要があります", + "Failed to connect to server: ": "サーバーへの接続に失敗しました: ", "Connected (encrypted) to ": "接続しました (暗号化済み): ", "Connected (unencrypted) to ": "接続しました (暗号化されていません): ", - "Something went wrong, connection is closed": "何らかの問題で、接続が閉じられました", + "Something went wrong, connection is closed": "問題が発生したため、接続が閉じられました", "Failed to connect to server": "サーバーへの接続に失敗しました", "Disconnected": "切断しました", "New connection has been rejected with reason: ": "新規接続は次の理由で拒否されました: ", @@ -48,7 +49,7 @@ "Clip to window": "ウィンドウにクリップ", "Scaling mode:": "スケーリングモード:", "None": "なし", - "Local scaling": "ローカルスケーリング", + "Local scaling": "ローカルでスケーリング", "Remote resizing": "リモートでリサイズ", "Advanced": "高度", "Quality:": "品質:", diff --git a/po/ja.po b/po/ja.po index 0f95bc6f..af0a1ba1 100644 --- a/po/ja.po +++ b/po/ja.po @@ -1,15 +1,15 @@ # Japanese translations for noVNC package # noVNC パッケージに対する日訳 -# Copyright (C) 2019 The noVNC authors +# Copyright (C) 2019-2024 The noVNC authors # This file is distributed under the same license as the noVNC package. -# nnn1590 , 2019-2020. +# nnn1590 , 2019-2024. # msgid "" msgstr "" -"Project-Id-Version: noVNC 1.1.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" -"PO-Revision-Date: 2023-03-21 12:42+0900\n" +"POT-Creation-Date: 2024-06-03 14:10+0200\n" +"PO-Revision-Date: 2024-12-14 15:22+0900\n" "Last-Translator: nnn1590 \n" "Language-Team: Japanese\n" "Language: ja\n" @@ -20,8 +20,11 @@ msgstr "" "X-Generator: Poedit 2.3\n" #: ../app/ui.js:69 -msgid "HTTPS is required for full functionality" -msgstr "すべての機能を使用するにはHTTPS接続が必要です" +msgid "" +"Running without HTTPS is not recommended, crashes or other issues are likely." +msgstr "" +"HTTPS接続なしで実行することは推奨されません。クラッシュしたりその他の問題が発" +"生したりする可能性があります。" #: ../app/ui.js:410 msgid "Connecting..." @@ -43,321 +46,297 @@ 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 "何らかの問題で、接続が閉じられました" +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 "noVNC でエラーが発生しました:" -#: ../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 "Ctrl" -#: ../vnc.html:92 +#: ../vnc.html:90 msgid "Toggle Ctrl" msgstr "Ctrl キーをトグル" -#: ../vnc.html:95 +#: ../vnc.html:93 msgid "Alt" msgstr "Alt" -#: ../vnc.html:95 +#: ../vnc.html:93 msgid "Toggle Alt" msgstr "Alt キーをトグル" -#: ../vnc.html:98 +#: ../vnc.html:96 msgid "Toggle Windows" msgstr "Windows キーをトグル" -#: ../vnc.html:98 +#: ../vnc.html:96 msgid "Windows" msgstr "Windows" -#: ../vnc.html:101 +#: ../vnc.html:99 msgid "Send Tab" msgstr "Tab キーを送信" -#: ../vnc.html:101 +#: ../vnc.html:99 msgid "Tab" msgstr "Tab" -#: ../vnc.html:104 +#: ../vnc.html:102 msgid "Esc" msgstr "Esc" -#: ../vnc.html:104 +#: ../vnc.html:102 msgid "Send Escape" msgstr "Escape キーを送信" -#: ../vnc.html:107 +#: ../vnc.html:105 msgid "Ctrl+Alt+Del" msgstr "Ctrl+Alt+Del" -#: ../vnc.html:107 +#: ../vnc.html:105 msgid "Send Ctrl-Alt-Del" msgstr "Ctrl-Alt-Del を送信" -#: ../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 "ローカルスケーリング" +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 "リピーター ID:" -#: ../vnc.html:195 +#: ../vnc.html:193 msgid "WebSocket" msgstr "WebSocket" -#: ../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 "キャンセル" - -#~ msgid "Clear" -#~ msgstr "クリア" - -#~ 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 "Send Password" -#~ msgstr "パスワードを送信" From 3e2e04bea132de6c5485fce41cc947aa86f33d5f Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Tue, 17 Dec 2024 16:43:44 +0100 Subject: [PATCH 02/70] Replace node-getopt with commander for args node-getopt isn't maintained and nodejs has started complaining about deprecated features in it. --- package.json | 1 - po/po2js | 18 +++++++----------- po/xgettext-html | 16 ++++++++-------- 3 files changed, 15 insertions(+), 20 deletions(-) diff --git a/package.json b/package.json index e28850a8..e45fde83 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,6 @@ "karma-safari-launcher": "latest", "karma-script-launcher": "latest", "mocha": "latest", - "node-getopt": "latest", "po2json": "latest", "sinon": "latest", "sinon-chai": "latest" diff --git a/po/po2js b/po/po2js index 23b8bb24..8d353a1d 100755 --- a/po/po2js +++ b/po/po2js @@ -17,20 +17,16 @@ * along with this program. If not, see . */ -const getopt = require('node-getopt'); +const { program } = require('commander'); const fs = require('fs'); const po2json = require("po2json"); -const opt = getopt.create([ - ['h', 'help', 'display this help'], -]).bindHelp().parseSystem(); +program + .argument('') + .argument('') + .parse(process.argv); -if (opt.argv.length != 2) { - console.error("Incorrect number of arguments given"); - process.exit(1); -} - -const data = po2json.parseFileSync(opt.argv[0]); +const data = po2json.parseFileSync(program.args[0]); const bodyPart = Object.keys(data) .filter(msgid => msgid !== "") @@ -42,4 +38,4 @@ const bodyPart = Object.keys(data) const output = "{\n" + bodyPart + "\n}"; -fs.writeFileSync(opt.argv[1], output); +fs.writeFileSync(program.args[1], output); diff --git a/po/xgettext-html b/po/xgettext-html index 72d49235..f5ba57cc 100755 --- a/po/xgettext-html +++ b/po/xgettext-html @@ -5,14 +5,14 @@ * Licensed under MPL 2.0 (see LICENSE.txt) */ -const getopt = require('node-getopt'); +const { program } = require('commander'); const jsdom = require("jsdom"); const fs = require("fs"); -const opt = getopt.create([ - ['o', 'output=FILE', 'write output to specified file'], - ['h', 'help', 'display this help'], -]).bindHelp().parseSystem(); +program + .argument('') + .requiredOption('-o, --output ', 'write output to specified file') + .parse(process.argv); const strings = {}; @@ -87,8 +87,8 @@ function process(elem, locator, enabled) { } } -for (let i = 0; i < opt.argv.length; i++) { - const fn = opt.argv[i]; +for (let i = 0; i < program.args.length; i++) { + const fn = program.args[i]; const file = fs.readFileSync(fn, "utf8"); const dom = new jsdom.JSDOM(file, { includeNodeLocations: true }); const body = dom.window.document.body; @@ -116,4 +116,4 @@ for (let str in strings) { output += "\n"; } -fs.writeFileSync(opt.options.output, output); +fs.writeFileSync(program.opts().output, output); From 673cb349fd9ec31dd928590e3ac4efe452a30b60 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Tue, 17 Dec 2024 17:13:30 +0100 Subject: [PATCH 03/70] Replace po2json with pofile The former doesn't seem to be properly maintained and nodejs gives deprecation warnings. --- package.json | 2 +- po/po2js | 17 ++++++++--------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index e45fde83..907cf630 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "karma-safari-launcher": "latest", "karma-script-launcher": "latest", "mocha": "latest", - "po2json": "latest", + "pofile": "latest", "sinon": "latest", "sinon-chai": "latest" }, diff --git a/po/po2js b/po/po2js index 8d353a1d..e74b1d4f 100755 --- a/po/po2js +++ b/po/po2js @@ -19,22 +19,21 @@ const { program } = require('commander'); const fs = require('fs'); -const po2json = require("po2json"); +const pofile = require("pofile"); program .argument('') .argument('') .parse(process.argv); -const data = po2json.parseFileSync(program.args[0]); +let data = fs.readFileSync(program.args[0], "utf8"); +let po = pofile.parse(data); -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 bodyPart = po.items + .filter(item => item.msgid !== "") + .filter(item => item.msgstr[0] !== "") + .map(item => " " + JSON.stringify(item.msgid) + ": " + JSON.stringify(item.msgstr[0])) + .join(",\n"); const output = "{\n" + bodyPart + "\n}"; From e6e03a226f8574a9b2743cac1d757360be7eb1b4 Mon Sep 17 00:00:00 2001 From: Samuel Mannehed Date: Fri, 27 Dec 2024 12:47:38 +0100 Subject: [PATCH 04/70] Fix resizes back to initial remote session size Since the expected client size wasn't updated when the browser window resized, noVNC didn't resize the canvas properly when going back to the exact same dimensions. Fixes issue #1903 --- core/rfb.js | 1 + tests/test.rfb.js | 65 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/core/rfb.js b/core/rfb.js index 9559e487..e1482c3f 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -730,6 +730,7 @@ export default class RFB extends EventTargetMixin { window.requestAnimationFrame(() => { this._updateClip(); this._updateScale(); + this._saveExpectedClientSize(); }); if (this._resizeSession) { diff --git a/tests/test.rfb.js b/tests/test.rfb.js index 19894ba1..7e406321 100644 --- a/tests/test.rfb.js +++ b/tests/test.rfb.js @@ -730,6 +730,28 @@ describe('Remote Frame Buffer protocol client', function () { container.style.width = '70px'; container.style.height = '80px'; client.scaleViewport = true; + + const incoming = [ 0x00, // msg-type=FBU + 0x00, // padding + 0x00, 0x01, // number of rects = 1 + 0x00, 0x00, // reason = server initialized + 0x00, 0x00, // status = no error + 0x00, 0x04, // new width = 4 + 0x00, 0x04, // new height = 4 + 0xff, 0xff, + 0xfe, 0xcc, // enc = (-308) ExtendedDesktopSize + 0x01, // number of screens = 1 + 0x00, 0x00, + 0x00, // padding + 0x78, 0x90, + 0xab, 0xcd, // screen id = 0 + 0x00, 0x00, // screen x = 0 + 0x00, 0x00, // screen y = 0 + 0x00, 0x04, // screen width = 4 + 0x00, 0x04, // screen height = 4 + 0x12, 0x34, + 0x56, 0x78]; // screen flags + client._sock._websocket._receiveData(new Uint8Array(incoming)); }); it('should update display scale factor when changing the property', function () { @@ -774,6 +796,28 @@ describe('Remote Frame Buffer protocol client', function () { expect(client._display.autoscale).to.have.been.calledWith(40, 50); }); + it('should update the scaling resized back to initial size', function () { + sinon.spy(client._display, "autoscale"); + + container.style.width = '40px'; + container.style.height = '50px'; + fakeResizeObserver.fire(); + clock.tick(1000); + + expect(client._display.autoscale).to.have.been.calledOnce; + expect(client._display.autoscale).to.have.been.calledWith(40, 50); + client._display.autoscale.resetHistory(); + + container.style.width = '70px'; + container.style.height = '80px'; + fakeResizeObserver.fire(); + clock.tick(1000); + + expect(client._display.autoscale).to.have.been.calledOnce; + expect(client._display.autoscale).to.have.been.calledWith(70, 80); + client._display.autoscale.resetHistory(); + }); + it('should update the scaling when the remote session resizes', function () { // Simple ExtendedDesktopSize FBU message const incoming = [ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, @@ -941,6 +985,27 @@ describe('Remote Frame Buffer protocol client', function () { expect(RFB.messages.setDesktopSize).to.not.have.been.called; }); + it('should request a resize when resized back to initial size', function () { + container.style.width = '40px'; + container.style.height = '50px'; + fakeResizeObserver.fire(); + clock.tick(1000); + + expect(RFB.messages.setDesktopSize).to.have.been.calledOnce; + expect(RFB.messages.setDesktopSize).to.have.been.calledWith( + sinon.match.object, 40, 50, 0x7890abcd, 0x12345678); + RFB.messages.setDesktopSize.resetHistory(); + + container.style.width = '70px'; + container.style.height = '80px'; + fakeResizeObserver.fire(); + clock.tick(1000); + + expect(RFB.messages.setDesktopSize).to.have.been.calledOnce; + expect(RFB.messages.setDesktopSize).to.have.been.calledWith( + sinon.match.object, 70, 80, 0x7890abcd, 0x12345678); + }); + it('should not resize until the container size is stable', function () { container.style.width = '20px'; container.style.height = '30px'; From 3193f808b5366392de94c1a9b144fc6845daeabb Mon Sep 17 00:00:00 2001 From: Samuel Mannehed Date: Fri, 27 Dec 2024 14:48:20 +0100 Subject: [PATCH 05/70] Comment different resize functions in rfb.js --- core/rfb.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/rfb.js b/core/rfb.js index e1482c3f..87fac3c2 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -720,6 +720,7 @@ export default class RFB extends EventTargetMixin { currentHeight == this._expectedClientHeight; } + // Handle browser window resizes _handleResize() { // Don't change anything if the client size is already as expected if (this._clientHasExpectedSize()) { @@ -2872,6 +2873,7 @@ export default class RFB extends EventTargetMixin { this._fbWidth, this._fbHeight); } + // Handle resize-messages from the server _resize(width, height) { this._fbWidth = width; this._fbHeight = height; From 4bbed1dc12e6cd8b7a7fff23b5b7b3e9f7c8eecc Mon Sep 17 00:00:00 2001 From: Samuel Mannehed Date: Sat, 11 Jan 2025 17:51:02 +0100 Subject: [PATCH 06/70] Standardize on 4 space-indentation in CSS files This is what we use in every other file in noVNC. It also much more common for a CSS file in general. By standardizing on 4 spaces we can avoid indentation mistakes. --- app/styles/base.css | 888 +++++++++++++++++++++---------------------- app/styles/input.css | 262 ++++++------- 2 files changed, 575 insertions(+), 575 deletions(-) diff --git a/app/styles/base.css b/app/styles/base.css index 395c18e6..06e3d5ac 100644 --- a/app/styles/base.css +++ b/app/styles/base.css @@ -30,30 +30,30 @@ */ :root { - font-family: sans-serif; + font-family: sans-serif; } body { - margin:0; - padding:0; - /*Background image with light grey curve.*/ - background-color:#494949; - background-repeat:no-repeat; - background-position:right bottom; - height:100%; - touch-action: none; + margin:0; + padding:0; + /*Background image with light grey curve.*/ + background-color:#494949; + background-repeat:no-repeat; + background-position:right bottom; + height:100%; + touch-action: none; } html { - height:100%; + height:100%; } .noVNC_only_touch.noVNC_hidden { - display: none; + display: none; } .noVNC_disabled { - color: rgb(128, 128, 128); + color: rgb(128, 128, 128); } /* ---------------------------------------- @@ -62,33 +62,33 @@ html { */ .noVNC_spinner { - position: relative; + position: relative; } .noVNC_spinner, .noVNC_spinner::before, .noVNC_spinner::after { - width: 10px; - height: 10px; - border-radius: 2px; - box-shadow: -60px 10px 0 rgba(255, 255, 255, 0); - animation: noVNC_spinner 1.0s linear infinite; + width: 10px; + height: 10px; + border-radius: 2px; + box-shadow: -60px 10px 0 rgba(255, 255, 255, 0); + animation: noVNC_spinner 1.0s linear infinite; } .noVNC_spinner::before { - content: ""; - position: absolute; - left: 0px; - top: 0px; - animation-delay: -0.1s; + content: ""; + position: absolute; + left: 0px; + top: 0px; + animation-delay: -0.1s; } .noVNC_spinner::after { - content: ""; - position: absolute; - top: 0px; - left: 0px; - animation-delay: 0.1s; + content: ""; + position: absolute; + top: 0px; + left: 0px; + animation-delay: 0.1s; } @keyframes noVNC_spinner { - 0% { box-shadow: -60px 10px 0 rgba(255, 255, 255, 0); width: 20px; } - 25% { box-shadow: 20px 10px 0 rgba(255, 255, 255, 1); width: 10px; } - 50% { box-shadow: 60px 10px 0 rgba(255, 255, 255, 0); width: 10px; } + 0% { box-shadow: -60px 10px 0 rgba(255, 255, 255, 0); width: 20px; } + 25% { box-shadow: 20px 10px 0 rgba(255, 255, 255, 1); width: 10px; } + 50% { box-shadow: 60px 10px 0 rgba(255, 255, 255, 0); width: 10px; } } /* ---------------------------------------- @@ -97,39 +97,39 @@ html { */ .noVNC_center { - /* - * This is a workaround because webkit misrenders transforms and - * uses non-integer coordinates, resulting in blurry content. - * Ideally we'd use "top: 50%; transform: translateY(-50%);" on - * the objects instead. - */ - display: flex; - align-items: center; - justify-content: center; - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - pointer-events: none; + /* + * This is a workaround because webkit misrenders transforms and + * uses non-integer coordinates, resulting in blurry content. + * Ideally we'd use "top: 50%; transform: translateY(-50%);" on + * the objects instead. + */ + display: flex; + align-items: center; + justify-content: center; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + pointer-events: none; } .noVNC_center > * { - pointer-events: auto; + pointer-events: auto; } .noVNC_vcenter { - display: flex !important; - flex-direction: column; - justify-content: center; - position: fixed; - top: 0; - left: 0; - height: 100%; - margin: 0 !important; - padding: 0 !important; - pointer-events: none; + display: flex !important; + flex-direction: column; + justify-content: center; + position: fixed; + top: 0; + left: 0; + height: 100%; + margin: 0 !important; + padding: 0 !important; + pointer-events: none; } .noVNC_vcenter > * { - pointer-events: auto; + pointer-events: auto; } /* ---------------------------------------- @@ -138,7 +138,7 @@ html { */ .noVNC_connect_layer { - z-index: 60; + z-index: 60; } /* ---------------------------------------- @@ -147,69 +147,69 @@ html { */ #noVNC_fallback_error { - z-index: 1000; - visibility: hidden; - /* Put a dark background in front of everything but the error, - and don't let mouse events pass through */ - background: rgba(0, 0, 0, 0.8); - pointer-events: all; + z-index: 1000; + visibility: hidden; + /* Put a dark background in front of everything but the error, + and don't let mouse events pass through */ + background: rgba(0, 0, 0, 0.8); + pointer-events: all; } #noVNC_fallback_error.noVNC_open { - visibility: visible; + visibility: visible; } #noVNC_fallback_error > div { - max-width: calc(100vw - 30px - 30px); - max-height: calc(100vh - 30px - 30px); - overflow: auto; + max-width: calc(100vw - 30px - 30px); + max-height: calc(100vh - 30px - 30px); + overflow: auto; - padding: 15px; + padding: 15px; - transition: 0.5s ease-in-out; + transition: 0.5s ease-in-out; - transform: translateY(-50px); - opacity: 0; + transform: translateY(-50px); + opacity: 0; - text-align: center; - font-weight: bold; - color: #fff; + text-align: center; + font-weight: bold; + color: #fff; - border-radius: 10px; - box-shadow: 6px 6px 0px rgba(0, 0, 0, 0.5); - background: rgba(200,55,55,0.8); + border-radius: 10px; + box-shadow: 6px 6px 0px rgba(0, 0, 0, 0.5); + background: rgba(200,55,55,0.8); } #noVNC_fallback_error.noVNC_open > div { - transform: translateY(0); - opacity: 1; + transform: translateY(0); + opacity: 1; } #noVNC_fallback_errormsg { - font-weight: normal; + font-weight: normal; } #noVNC_fallback_errormsg .noVNC_message { - display: inline-block; - text-align: left; - font-family: monospace; - white-space: pre-wrap; + display: inline-block; + text-align: left; + font-family: monospace; + white-space: pre-wrap; } #noVNC_fallback_error .noVNC_location { - font-style: italic; - font-size: 0.8em; - color: rgba(255, 255, 255, 0.8); + font-style: italic; + font-size: 0.8em; + color: rgba(255, 255, 255, 0.8); } #noVNC_fallback_error .noVNC_stack { - padding: 10px; - margin: 10px; - font-size: 0.8em; - text-align: left; - font-family: monospace; - white-space: pre; - border: 1px solid rgba(0, 0, 0, 0.5); - background: rgba(0, 0, 0, 0.2); - overflow: auto; + padding: 10px; + margin: 10px; + font-size: 0.8em; + text-align: left; + font-family: monospace; + white-space: pre; + border: 1px solid rgba(0, 0, 0, 0.5); + background: rgba(0, 0, 0, 0.2); + overflow: auto; } /* ---------------------------------------- @@ -218,325 +218,325 @@ html { */ #noVNC_control_bar_anchor { - /* The anchor is needed to get z-stacking to work */ - position: fixed; - z-index: 10; + /* The anchor is needed to get z-stacking to work */ + position: fixed; + z-index: 10; - transition: 0.5s ease-in-out; + transition: 0.5s ease-in-out; - /* Edge misrenders animations wihthout this */ - transform: translateX(0); + /* Edge misrenders animations wihthout this */ + transform: translateX(0); } :root.noVNC_connected #noVNC_control_bar_anchor.noVNC_idle { - opacity: 0.8; + opacity: 0.8; } #noVNC_control_bar_anchor.noVNC_right { - left: auto; - right: 0; + left: auto; + right: 0; } #noVNC_control_bar { - position: relative; - left: -100%; + position: relative; + left: -100%; - transition: 0.5s ease-in-out; + transition: 0.5s ease-in-out; - background-color: rgb(110, 132, 163); - border-radius: 0 10px 10px 0; + background-color: rgb(110, 132, 163); + border-radius: 0 10px 10px 0; - user-select: none; - -webkit-user-select: none; - -webkit-touch-callout: none; /* Disable iOS image long-press popup */ + user-select: none; + -webkit-user-select: none; + -webkit-touch-callout: none; /* Disable iOS image long-press popup */ } #noVNC_control_bar.noVNC_open { - box-shadow: 6px 6px 0px rgba(0, 0, 0, 0.5); - left: 0; + box-shadow: 6px 6px 0px rgba(0, 0, 0, 0.5); + left: 0; } #noVNC_control_bar::before { - /* This extra element is to get a proper shadow */ - content: ""; - position: absolute; - z-index: -1; - height: 100%; - width: 30px; - left: -30px; - transition: box-shadow 0.5s ease-in-out; + /* This extra element is to get a proper shadow */ + content: ""; + position: absolute; + z-index: -1; + height: 100%; + width: 30px; + left: -30px; + transition: box-shadow 0.5s ease-in-out; } #noVNC_control_bar.noVNC_open::before { - box-shadow: 6px 6px 0px rgba(0, 0, 0, 0.5); + box-shadow: 6px 6px 0px rgba(0, 0, 0, 0.5); } .noVNC_right #noVNC_control_bar { - left: 100%; - border-radius: 10px 0 0 10px; + left: 100%; + border-radius: 10px 0 0 10px; } .noVNC_right #noVNC_control_bar.noVNC_open { - left: 0; + left: 0; } .noVNC_right #noVNC_control_bar::before { - visibility: hidden; + visibility: hidden; } #noVNC_control_bar_handle { - position: absolute; - left: -15px; - top: 0; - transform: translateY(35px); - width: calc(100% + 30px); - height: 50px; - z-index: -1; - cursor: pointer; - border-radius: 5px; - background-color: rgb(83, 99, 122); - background-image: url("../images/handle_bg.svg"); - background-repeat: no-repeat; - background-position: right; - box-shadow: 3px 3px 0px rgba(0, 0, 0, 0.5); + position: absolute; + left: -15px; + top: 0; + transform: translateY(35px); + width: calc(100% + 30px); + height: 50px; + z-index: -1; + cursor: pointer; + border-radius: 5px; + background-color: rgb(83, 99, 122); + background-image: url("../images/handle_bg.svg"); + background-repeat: no-repeat; + background-position: right; + box-shadow: 3px 3px 0px rgba(0, 0, 0, 0.5); } #noVNC_control_bar_handle:after { - content: ""; - transition: transform 0.5s ease-in-out; - background: url("../images/handle.svg"); - position: absolute; - top: 22px; /* (50px-6px)/2 */ - right: 5px; - width: 5px; - height: 6px; + content: ""; + transition: transform 0.5s ease-in-out; + background: url("../images/handle.svg"); + position: absolute; + top: 22px; /* (50px-6px)/2 */ + right: 5px; + width: 5px; + height: 6px; } #noVNC_control_bar.noVNC_open #noVNC_control_bar_handle:after { - transform: translateX(1px) rotate(180deg); + transform: translateX(1px) rotate(180deg); } :root:not(.noVNC_connected) #noVNC_control_bar_handle { - display: none; + display: none; } .noVNC_right #noVNC_control_bar_handle { - background-position: left; + background-position: left; } .noVNC_right #noVNC_control_bar_handle:after { - left: 5px; - right: 0; - transform: translateX(1px) rotate(180deg); + left: 5px; + right: 0; + transform: translateX(1px) rotate(180deg); } .noVNC_right #noVNC_control_bar.noVNC_open #noVNC_control_bar_handle:after { - transform: none; + transform: none; } /* Larger touch area for the handle, used when a touch screen is available */ #noVNC_control_bar_handle div { - position: absolute; - right: -35px; - top: 0; - width: 50px; - height: 100%; - display: none; + position: absolute; + right: -35px; + top: 0; + width: 50px; + height: 100%; + display: none; } @media (any-pointer: coarse) { - #noVNC_control_bar_handle div { - display: initial; - } + #noVNC_control_bar_handle div { + display: initial; + } } .noVNC_right #noVNC_control_bar_handle div { - left: -35px; - right: auto; + left: -35px; + right: auto; } #noVNC_control_bar > .noVNC_scroll { - max-height: 100vh; /* Chrome is buggy with 100% */ - overflow-x: hidden; - overflow-y: auto; - padding: 0 10px; + max-height: 100vh; /* Chrome is buggy with 100% */ + overflow-x: hidden; + overflow-y: auto; + padding: 0 10px; } #noVNC_control_bar > .noVNC_scroll > * { - display: block; - margin: 10px auto; + display: block; + margin: 10px auto; } /* Control bar hint */ #noVNC_hint_anchor { - position: fixed; - right: -50px; - left: auto; + position: fixed; + right: -50px; + left: auto; } #noVNC_control_bar_anchor.noVNC_right + #noVNC_hint_anchor { - left: -50px; - right: auto; + left: -50px; + right: auto; } #noVNC_control_bar_hint { - position: relative; - transform: scale(0); - width: 100px; - height: 50%; - max-height: 600px; + position: relative; + transform: scale(0); + width: 100px; + height: 50%; + max-height: 600px; - visibility: hidden; - opacity: 0; - transition: 0.2s ease-in-out; - background: transparent; - box-shadow: 0 0 10px black, inset 0 0 10px 10px rgba(110, 132, 163, 0.8); - border-radius: 10px; - transition-delay: 0s; + visibility: hidden; + opacity: 0; + transition: 0.2s ease-in-out; + background: transparent; + box-shadow: 0 0 10px black, inset 0 0 10px 10px rgba(110, 132, 163, 0.8); + border-radius: 10px; + transition-delay: 0s; } #noVNC_control_bar_hint.noVNC_active { - visibility: visible; - opacity: 1; - transition-delay: 0.2s; - transform: scale(1); + visibility: visible; + opacity: 1; + transition-delay: 0.2s; + transform: scale(1); } #noVNC_control_bar_hint.noVNC_notransition { - transition: none !important; + transition: none !important; } /* Control bar buttons */ #noVNC_control_bar .noVNC_button { - padding: 4px 4px; - vertical-align: middle; - border:1px solid rgba(255, 255, 255, 0.2); - border-radius: 6px; - background-color: transparent; - background-image: unset; /* we don't want the gradiant from input.css */ + padding: 4px 4px; + vertical-align: middle; + border:1px solid rgba(255, 255, 255, 0.2); + border-radius: 6px; + background-color: transparent; + background-image: unset; /* we don't want the gradiant from input.css */ } #noVNC_control_bar .noVNC_button.noVNC_selected { - border-color: rgba(0, 0, 0, 0.8); - background-color: rgba(0, 0, 0, 0.5); + border-color: rgba(0, 0, 0, 0.8); + background-color: rgba(0, 0, 0, 0.5); } #noVNC_control_bar .noVNC_button.noVNC_selected:not(:disabled):hover { - border-color: rgba(0, 0, 0, 0.4); - background-color: rgba(0, 0, 0, 0.2); + border-color: rgba(0, 0, 0, 0.4); + background-color: rgba(0, 0, 0, 0.2); } #noVNC_control_bar .noVNC_button:not(:disabled):hover { - background-color: rgba(255, 255, 255, 0.2); + background-color: rgba(255, 255, 255, 0.2); } #noVNC_control_bar .noVNC_button:not(:disabled):active { - padding-top: 5px; - padding-bottom: 3px; + padding-top: 5px; + padding-bottom: 3px; } #noVNC_control_bar .noVNC_button.noVNC_hidden { - display: none !important; + display: none !important; } /* Android browsers don't properly update hover state if touch events are * intercepted, like they are when clicking on the remote screen. */ @media (any-pointer: coarse) { - #noVNC_control_bar .noVNC_button:not(:disabled):hover { - background-color: transparent; - } - #noVNC_control_bar .noVNC_button.noVNC_selected:not(:disabled):hover { - border-color: rgba(0, 0, 0, 0.8); - background-color: rgba(0, 0, 0, 0.5); - } + #noVNC_control_bar .noVNC_button:not(:disabled):hover { + background-color: transparent; + } + #noVNC_control_bar .noVNC_button.noVNC_selected:not(:disabled):hover { + border-color: rgba(0, 0, 0, 0.8); + background-color: rgba(0, 0, 0, 0.5); + } } /* Panels */ .noVNC_panel { - transform: translateX(25px); + transform: translateX(25px); - transition: 0.5s ease-in-out; + transition: 0.5s ease-in-out; - box-sizing: border-box; /* so max-width don't have to care about padding */ - max-width: calc(100vw - 75px - 25px); /* minus left and right margins */ - max-height: 100vh; /* Chrome is buggy with 100% */ - overflow-x: hidden; - overflow-y: auto; + box-sizing: border-box; /* so max-width don't have to care about padding */ + max-width: calc(100vw - 75px - 25px); /* minus left and right margins */ + max-height: 100vh; /* Chrome is buggy with 100% */ + overflow-x: hidden; + overflow-y: auto; - visibility: hidden; - opacity: 0; + visibility: hidden; + opacity: 0; - padding: 15px; + padding: 15px; - background: #fff; - border-radius: 10px; - color: #000; - border: 2px solid #E0E0E0; - box-shadow: 6px 6px 0px rgba(0, 0, 0, 0.5); + background: #fff; + border-radius: 10px; + color: #000; + border: 2px solid #E0E0E0; + box-shadow: 6px 6px 0px rgba(0, 0, 0, 0.5); } .noVNC_panel.noVNC_open { - visibility: visible; - opacity: 1; - transform: translateX(75px); + visibility: visible; + opacity: 1; + transform: translateX(75px); } .noVNC_right .noVNC_vcenter { - left: auto; - right: 0; + left: auto; + right: 0; } .noVNC_right .noVNC_panel { - transform: translateX(-25px); + transform: translateX(-25px); } .noVNC_right .noVNC_panel.noVNC_open { - transform: translateX(-75px); + transform: translateX(-75px); } .noVNC_panel > * { - display: block; - margin: 10px auto; + display: block; + margin: 10px auto; } .noVNC_panel > *:first-child { - margin-top: 0 !important; + margin-top: 0 !important; } .noVNC_panel > *:last-child { - margin-bottom: 0 !important; + margin-bottom: 0 !important; } .noVNC_panel hr { - border: none; - border-top: 1px solid rgb(192, 192, 192); + border: none; + border-top: 1px solid rgb(192, 192, 192); } .noVNC_panel label { - display: block; - white-space: nowrap; - margin: 5px; + display: block; + white-space: nowrap; + margin: 5px; } .noVNC_panel li { - margin: 5px; + margin: 5px; } .noVNC_panel .noVNC_heading { - background-color: rgb(110, 132, 163); - border-radius: 5px; - padding: 5px; - /* Compensate for padding in image */ - padding-right: 8px; - color: white; - font-size: 20px; - white-space: nowrap; + background-color: rgb(110, 132, 163); + border-radius: 5px; + padding: 5px; + /* Compensate for padding in image */ + padding-right: 8px; + color: white; + font-size: 20px; + white-space: nowrap; } .noVNC_panel .noVNC_heading img { - vertical-align: bottom; + vertical-align: bottom; } .noVNC_submit { - float: right; + float: right; } /* Expanders */ .noVNC_expander { - cursor: pointer; + cursor: pointer; } .noVNC_expander::before { - content: url("../images/expander.svg"); - display: inline-block; - margin-right: 5px; - transition: 0.2s ease-in-out; + content: url("../images/expander.svg"); + display: inline-block; + margin-right: 5px; + transition: 0.2s ease-in-out; } .noVNC_expander.noVNC_open::before { - transform: rotateZ(90deg); + transform: rotateZ(90deg); } .noVNC_expander ~ * { - margin: 5px; - margin-left: 10px; - padding: 5px; - background: rgba(0, 0, 0, 0.05); - border-radius: 5px; + margin: 5px; + margin-left: 10px; + padding: 5px; + background: rgba(0, 0, 0, 0.05); + border-radius: 5px; } .noVNC_expander:not(.noVNC_open) ~ * { - display: none; + display: none; } /* Control bar content */ #noVNC_control_bar .noVNC_logo { - font-size: 13px; + font-size: 13px; } .noVNC_logo + hr { @@ -546,92 +546,92 @@ html { } :root:not(.noVNC_connected) #noVNC_view_drag_button { - display: none; + display: none; } /* noVNC Touch Device only buttons */ :root:not(.noVNC_connected) #noVNC_mobile_buttons { - display: none; + display: none; } @media not all and (any-pointer: coarse) { - /* FIXME: The button for the virtual keyboard is the only button in this - group of "mobile buttons". It is bad to assume that no touch - devices have physical keyboards available. Hopefully we can get - a media query for this: - https://github.com/w3c/csswg-drafts/issues/3871 */ - :root.noVNC_connected #noVNC_mobile_buttons { - display: none; - } + /* FIXME: The button for the virtual keyboard is the only button in this + group of "mobile buttons". It is bad to assume that no touch + devices have physical keyboards available. Hopefully we can get + a media query for this: + https://github.com/w3c/csswg-drafts/issues/3871 */ + :root.noVNC_connected #noVNC_mobile_buttons { + display: none; + } } /* Extra manual keys */ :root:not(.noVNC_connected) #noVNC_toggle_extra_keys_button { - display: none; + display: none; } #noVNC_modifiers { - background-color: rgb(92, 92, 92); - border: none; - padding: 10px; + background-color: rgb(92, 92, 92); + border: none; + padding: 10px; } /* Shutdown/Reboot */ :root:not(.noVNC_connected) #noVNC_power_button { - display: none; + display: none; } #noVNC_power { } #noVNC_power_buttons { - display: none; + display: none; } #noVNC_power input[type=button] { - width: 100%; + width: 100%; } /* Clipboard */ :root:not(.noVNC_connected) #noVNC_clipboard_button { - display: none; + display: none; } #noVNC_clipboard_text { - width: 360px; - min-width: 150px; - height: 160px; - min-height: 70px; + width: 360px; + min-width: 150px; + height: 160px; + min-height: 70px; - box-sizing: border-box; - max-width: 100%; - /* minus approximate height of title, height of subtitle, and margin */ - max-height: calc(100vh - 10em - 25px); + box-sizing: border-box; + max-width: 100%; + /* minus approximate height of title, height of subtitle, and margin */ + max-height: calc(100vh - 10em - 25px); } /* Settings */ #noVNC_settings { } #noVNC_settings ul { - list-style: none; - padding: 0px; + list-style: none; + padding: 0px; } #noVNC_setting_port { - width: 80px; + width: 80px; } #noVNC_setting_path { - width: 100px; + width: 100px; } /* Version */ .noVNC_version_wrapper { - font-size: small; + font-size: small; } .noVNC_version { - margin-left: 1rem; + margin-left: 1rem; } /* Connection controls */ :root:not(.noVNC_connected) #noVNC_disconnect_button { - display: none; + display: none; } /* ---------------------------------------- @@ -640,64 +640,64 @@ html { */ #noVNC_status { - position: fixed; - top: 0; - left: 0; - width: 100%; - z-index: 100; - transform: translateY(-100%); + position: fixed; + top: 0; + left: 0; + width: 100%; + z-index: 100; + transform: translateY(-100%); - cursor: pointer; + cursor: pointer; - transition: 0.5s ease-in-out; + transition: 0.5s ease-in-out; - visibility: hidden; - opacity: 0; + visibility: hidden; + opacity: 0; - padding: 5px; + padding: 5px; - display: flex; - flex-direction: row; - justify-content: center; - align-content: center; + display: flex; + flex-direction: row; + justify-content: center; + align-content: center; - line-height: 1.6; - word-wrap: break-word; - color: #fff; + line-height: 1.6; + word-wrap: break-word; + color: #fff; - border-bottom: 1px solid rgba(0, 0, 0, 0.9); + border-bottom: 1px solid rgba(0, 0, 0, 0.9); } #noVNC_status.noVNC_open { - transform: translateY(0); - visibility: visible; - opacity: 1; + transform: translateY(0); + visibility: visible; + opacity: 1; } #noVNC_status::before { - content: ""; - display: inline-block; - width: 25px; - height: 25px; - margin-right: 5px; + content: ""; + display: inline-block; + width: 25px; + height: 25px; + margin-right: 5px; } #noVNC_status.noVNC_status_normal { - background: rgba(128,128,128,0.9); + background: rgba(128,128,128,0.9); } #noVNC_status.noVNC_status_normal::before { - content: url("../images/info.svg") " "; + content: url("../images/info.svg") " "; } #noVNC_status.noVNC_status_error { - background: rgba(200,55,55,0.9); + background: rgba(200,55,55,0.9); } #noVNC_status.noVNC_status_error::before { - content: url("../images/error.svg") " "; + content: url("../images/error.svg") " "; } #noVNC_status.noVNC_status_warn { - background: rgba(180,180,30,0.9); + background: rgba(180,180,30,0.9); } #noVNC_status.noVNC_status_warn::before { - content: url("../images/warning.svg") " "; + content: url("../images/warning.svg") " "; } /* ---------------------------------------- @@ -706,67 +706,67 @@ html { */ #noVNC_connect_dlg { - transition: 0.5s ease-in-out; + transition: 0.5s ease-in-out; - transform: scale(0, 0); - visibility: hidden; - opacity: 0; + transform: scale(0, 0); + visibility: hidden; + opacity: 0; } #noVNC_connect_dlg.noVNC_open { - transform: scale(1, 1); - visibility: visible; - opacity: 1; + transform: scale(1, 1); + visibility: visible; + opacity: 1; } #noVNC_connect_dlg .noVNC_logo { - transition: 0.5s ease-in-out; - padding: 10px; - margin-bottom: 10px; + transition: 0.5s ease-in-out; + padding: 10px; + margin-bottom: 10px; - font-size: 80px; - text-align: center; + font-size: 80px; + text-align: center; - border-radius: 5px; + border-radius: 5px; } @media (max-width: 440px) { - #noVNC_connect_dlg { - max-width: calc(100vw - 100px); - } - #noVNC_connect_dlg .noVNC_logo { - font-size: calc(25vw - 30px); - } + #noVNC_connect_dlg { + max-width: calc(100vw - 100px); + } + #noVNC_connect_dlg .noVNC_logo { + font-size: calc(25vw - 30px); + } } #noVNC_connect_dlg div { - padding: 12px; + padding: 12px; - background-color: rgb(110, 132, 163); - border-radius: 12px; - text-align: center; - font-size: 20px; + background-color: rgb(110, 132, 163); + border-radius: 12px; + text-align: center; + font-size: 20px; - box-shadow: 6px 6px 0px rgba(0, 0, 0, 0.5); + box-shadow: 6px 6px 0px rgba(0, 0, 0, 0.5); } #noVNC_connect_button { - width: 100%; - padding: 5px 30px; + width: 100%; + padding: 5px 30px; - cursor: pointer; + cursor: pointer; - border-color: rgb(83, 99, 122); - border-radius: 5px; + border-color: rgb(83, 99, 122); + border-radius: 5px; - background: linear-gradient(to top, rgb(110, 132, 163), rgb(99, 119, 147)); - color: white; + background: linear-gradient(to top, rgb(110, 132, 163), rgb(99, 119, 147)); + color: white; - /* This avoids it jumping around when :active */ - vertical-align: middle; + /* This avoids it jumping around when :active */ + vertical-align: middle; } #noVNC_connect_button:hover { - background: linear-gradient(to top, rgb(110, 132, 163), rgb(105, 125, 155)); + background: linear-gradient(to top, rgb(110, 132, 163), rgb(105, 125, 155)); } #noVNC_connect_button img { - vertical-align: bottom; - height: 1.3em; + vertical-align: bottom; + height: 1.3em; } /* ---------------------------------------- @@ -775,15 +775,15 @@ html { */ #noVNC_verify_server_dlg { - position: relative; + position: relative; - transform: translateY(-50px); + transform: translateY(-50px); } #noVNC_verify_server_dlg.noVNC_open { - transform: translateY(0); + transform: translateY(0); } #noVNC_fingerprint_block { - margin: 10px; + margin: 10px; } /* ---------------------------------------- @@ -792,16 +792,16 @@ html { */ #noVNC_credentials_dlg { - position: relative; + position: relative; - transform: translateY(-50px); + transform: translateY(-50px); } #noVNC_credentials_dlg.noVNC_open { - transform: translateY(0); + transform: translateY(0); } #noVNC_username_block.noVNC_hidden, #noVNC_password_block.noVNC_hidden { - display: none; + display: none; } @@ -812,90 +812,90 @@ html { /* Transition screen */ #noVNC_transition { - transition: 0.5s ease-in-out; + transition: 0.5s ease-in-out; - display: flex; - opacity: 0; - visibility: hidden; + display: flex; + opacity: 0; + visibility: hidden; - position: fixed; - top: 0; - left: 0; - bottom: 0; - right: 0; + position: fixed; + top: 0; + left: 0; + bottom: 0; + right: 0; - color: white; - background: rgba(0, 0, 0, 0.5); - z-index: 50; + color: white; + background: rgba(0, 0, 0, 0.5); + z-index: 50; - /*display: flex;*/ - align-items: center; - justify-content: center; - flex-direction: column; + /*display: flex;*/ + align-items: center; + justify-content: center; + flex-direction: column; } :root.noVNC_loading #noVNC_transition, :root.noVNC_connecting #noVNC_transition, :root.noVNC_disconnecting #noVNC_transition, :root.noVNC_reconnecting #noVNC_transition { - opacity: 1; - visibility: visible; + opacity: 1; + visibility: visible; } :root:not(.noVNC_reconnecting) #noVNC_cancel_reconnect_button { - display: none; + display: none; } #noVNC_transition_text { - font-size: 1.5em; + font-size: 1.5em; } /* Main container */ #noVNC_container { - width: 100%; - height: 100%; - background-color: #313131; - border-bottom-right-radius: 800px 600px; - /*border-top-left-radius: 800px 600px;*/ + width: 100%; + height: 100%; + background-color: #313131; + border-bottom-right-radius: 800px 600px; + /*border-top-left-radius: 800px 600px;*/ - /* If selection isn't disabled, long-pressing stuff in the sidebar - can accidentally select the container or the canvas. This can - happen when attempting to move the handle. */ - user-select: none; - -webkit-user-select: none; + /* If selection isn't disabled, long-pressing stuff in the sidebar + can accidentally select the container or the canvas. This can + happen when attempting to move the handle. */ + user-select: none; + -webkit-user-select: none; } #noVNC_keyboardinput { - width: 1px; - height: 1px; - background-color: #fff; - color: #fff; - border: 0; - position: absolute; - left: -40px; - z-index: -1; - ime-mode: disabled; + width: 1px; + height: 1px; + background-color: #fff; + color: #fff; + border: 0; + position: absolute; + left: -40px; + z-index: -1; + ime-mode: disabled; } /*Default noVNC logo.*/ /* From: http://fonts.googleapis.com/css?family=Orbitron:700 */ @font-face { - font-family: 'Orbitron'; - font-style: normal; - font-weight: 700; - src: local('?'), url('Orbitron700.woff') format('woff'), - url('Orbitron700.ttf') format('truetype'); + font-family: 'Orbitron'; + font-style: normal; + font-weight: 700; + src: local('?'), url('Orbitron700.woff') format('woff'), + url('Orbitron700.ttf') format('truetype'); } .noVNC_logo { - color:yellow; - font-family: 'Orbitron', 'OrbitronTTF', sans-serif; - line-height: 0.9; - text-shadow: 0.1em 0.1em 0 black; + color:yellow; + font-family: 'Orbitron', 'OrbitronTTF', sans-serif; + line-height: 0.9; + text-shadow: 0.1em 0.1em 0 black; } .noVNC_logo span{ - color:green; + color:green; } #noVNC_bell { - display: none; + display: none; } /* ---------------------------------------- @@ -904,19 +904,19 @@ html { */ @media screen and (max-width: 640px){ - #noVNC_logo { - font-size: 150px; - } + #noVNC_logo { + font-size: 150px; + } } @media screen and (min-width: 321px) and (max-width: 480px) { - #noVNC_logo { - font-size: 110px; - } + #noVNC_logo { + font-size: 110px; + } } @media screen and (max-width: 320px) { - #noVNC_logo { - font-size: 90px; - } + #noVNC_logo { + font-size: 90px; + } } diff --git a/app/styles/input.css b/app/styles/input.css index 911cf20c..a0cba473 100644 --- a/app/styles/input.css +++ b/app/styles/input.css @@ -9,19 +9,19 @@ * Common for all inputs */ input, input::file-selector-button, button, select, textarea { - /* Respect standard font settings */ - font: inherit; + /* Respect standard font settings */ + font: inherit; - /* Disable default rendering */ - appearance: none; - background: none; + /* Disable default rendering */ + appearance: none; + background: none; - padding: 5px; - border: 1px solid rgb(192, 192, 192); - border-radius: 5px; - color: black; - --bg-gradient: linear-gradient(to top, rgb(255, 255, 255) 80%, rgb(240, 240, 240)); - background-image: var(--bg-gradient); + padding: 5px; + border: 1px solid rgb(192, 192, 192); + border-radius: 5px; + color: black; + --bg-gradient: linear-gradient(to top, rgb(255, 255, 255) 80%, rgb(240, 240, 240)); + background-image: var(--bg-gradient); } /* @@ -35,151 +35,151 @@ input[type=submit], input::file-selector-button, button, select { - border-bottom-width: 2px; + border-bottom-width: 2px; - /* This avoids it jumping around when :active */ - vertical-align: middle; - margin-top: 0; + /* This avoids it jumping around when :active */ + vertical-align: middle; + margin-top: 0; - padding-left: 20px; - padding-right: 20px; + padding-left: 20px; + padding-right: 20px; - /* Disable Chrome's touch tap highlight */ - -webkit-tap-highlight-color: transparent; + /* Disable Chrome's touch tap highlight */ + -webkit-tap-highlight-color: transparent; } /* * Select dropdowns */ select { - --select-arrow: url('data:image/svg+xml;utf8, \ - \ - \ - '); - background-image: var(--select-arrow), var(--bg-gradient); - background-position: calc(100% - 7px), left top; - background-repeat: no-repeat; - padding-right: calc(2*7px + 8px); - padding-left: 7px; + --select-arrow: url('data:image/svg+xml;utf8, \ + \ + \ + '); + background-image: var(--select-arrow), var(--bg-gradient); + background-position: calc(100% - 7px), left top; + background-repeat: no-repeat; + padding-right: calc(2*7px + 8px); + padding-left: 7px; } /* FIXME: :active isn't set when the is opened in Firefox: https://bugzilla.mozilla.org/show_bug.cgi?id=1805406 */ From 72cac2ef6a6f5f4536fcb2ec2f430a78bd0a9194 Mon Sep 17 00:00:00 2001 From: Samuel Mannehed Date: Sat, 11 Jan 2025 23:20:33 +0100 Subject: [PATCH 08/70] Add margin between label and input in noVNC_panel To make stuff feel less cramped, lets add some margin here. As of comitting this, it only affects the logging-level select dropdown in the settings, but this is a general rule of thumb. It doesn't apply to checkboxes or radios since they have a margin by default, and their label to the left. --- app/styles/base.css | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/styles/base.css b/app/styles/base.css index 06e3d5ac..f2f49869 100644 --- a/app/styles/base.css +++ b/app/styles/base.css @@ -491,6 +491,13 @@ html { margin: 5px; } +.noVNC_panel label > button, +.noVNC_panel label > select, +.noVNC_panel label > textarea, +.noVNC_panel label > input:not([type=checkbox]):not([type=radio]) { + margin-left: 6px; +} + .noVNC_panel .noVNC_heading { background-color: rgb(110, 132, 163); border-radius: 5px; From 7603ced54e9e73fb969b705da50716699fbee608 Mon Sep 17 00:00:00 2001 From: Samuel Mannehed Date: Sun, 12 Jan 2025 00:09:25 +0100 Subject: [PATCH 09/70] Create CSS variables for common noVNC-colors --- app/styles/base.css | 26 +++++++++++++------------- app/styles/constants.css | 21 +++++++++++++++++++++ app/styles/input.css | 14 +++++++------- vnc.html | 1 + 4 files changed, 42 insertions(+), 20 deletions(-) create mode 100644 app/styles/constants.css diff --git a/app/styles/base.css b/app/styles/base.css index f2f49869..ecef3771 100644 --- a/app/styles/base.css +++ b/app/styles/base.css @@ -53,7 +53,7 @@ html { } .noVNC_disabled { - color: rgb(128, 128, 128); + color: var(--novnc-grey); } /* ---------------------------------------- @@ -241,7 +241,7 @@ html { transition: 0.5s ease-in-out; - background-color: rgb(110, 132, 163); + background-color: var(--novnc-blue); border-radius: 0 10px 10px 0; user-select: none; @@ -286,7 +286,7 @@ html { z-index: -1; cursor: pointer; border-radius: 5px; - background-color: rgb(83, 99, 122); + background-color: var(--novnc-darkblue); background-image: url("../images/handle_bg.svg"); background-repeat: no-repeat; background-position: right; @@ -371,7 +371,7 @@ html { opacity: 0; transition: 0.2s ease-in-out; background: transparent; - box-shadow: 0 0 10px black, inset 0 0 10px 10px rgba(110, 132, 163, 0.8); + box-shadow: 0 0 10px black, inset 0 0 10px 10px var(--novnc-darkblue); border-radius: 10px; transition-delay: 0s; } @@ -478,7 +478,7 @@ html { .noVNC_panel hr { border: none; - border-top: 1px solid rgb(192, 192, 192); + border-top: 1px solid var(--novnc-lightgrey); } .noVNC_panel label { @@ -499,7 +499,7 @@ html { } .noVNC_panel .noVNC_heading { - background-color: rgb(110, 132, 163); + background-color: var(--novnc-blue); border-radius: 5px; padding: 5px; /* Compensate for padding in image */ @@ -577,7 +577,7 @@ html { } #noVNC_modifiers { - background-color: rgb(92, 92, 92); + background-color: var(--novnc-darkgrey); border: none; padding: 10px; } @@ -745,7 +745,7 @@ html { #noVNC_connect_dlg div { padding: 12px; - background-color: rgb(110, 132, 163); + background-color: var(--novnc-blue); border-radius: 12px; text-align: center; font-size: 20px; @@ -758,17 +758,17 @@ html { cursor: pointer; - border-color: rgb(83, 99, 122); + border-color: var(--novnc-darkblue); border-radius: 5px; - background: linear-gradient(to top, rgb(110, 132, 163), rgb(99, 119, 147)); + background: linear-gradient(to top, var(--novnc-blue), rgb(99, 119, 147)); color: white; /* This avoids it jumping around when :active */ vertical-align: middle; } #noVNC_connect_button:hover { - background: linear-gradient(to top, rgb(110, 132, 163), rgb(105, 125, 155)); + background: linear-gradient(to top, var(--novnc-blue), rgb(105, 125, 155)); } #noVNC_connect_button img { @@ -892,13 +892,13 @@ html { } .noVNC_logo { - color:yellow; + color: var(--novnc-yellow); font-family: 'Orbitron', 'OrbitronTTF', sans-serif; line-height: 0.9; text-shadow: 0.1em 0.1em 0 black; } .noVNC_logo span{ - color:green; + color: var(--novnc-green); } #noVNC_bell { diff --git a/app/styles/constants.css b/app/styles/constants.css new file mode 100644 index 00000000..fb1f5509 --- /dev/null +++ b/app/styles/constants.css @@ -0,0 +1,21 @@ +/* + * noVNC general CSS constant variables + * Copyright (C) 2025 The noVNC authors + * noVNC is licensed under the MPL 2.0 (see LICENSE.txt) + * This file is licensed under the 2-Clause BSD license (see LICENSE.txt). + */ + +/* ---------- COLORS ----------- */ + +:root { + --novnc-grey: rgb(128, 128, 128); + --novnc-lightgrey: rgb(192, 192, 192); + --novnc-darkgrey: rgb(92, 92, 92); + + --novnc-blue: rgb(110, 132, 163); + --novnc-lightblue: rgb(74, 144, 217); + --novnc-darkblue: rgb(83, 99, 122); + + --novnc-green: rgb(0, 128, 0); + --novnc-yellow: rgb(255, 255, 0); +} diff --git a/app/styles/input.css b/app/styles/input.css index fc36e71e..a2b289bf 100644 --- a/app/styles/input.css +++ b/app/styles/input.css @@ -21,7 +21,7 @@ input, input::file-selector-button, button, select, textarea { background: none; padding: 0.5em var(--input-xpadding); - border: 1px solid rgb(192, 192, 192); + border: 1px solid var(--novnc-lightgrey); border-radius: 5px; color: black; --bg-gradient: linear-gradient(to top, rgb(255, 255, 255) 80%, rgb(240, 240, 240)); @@ -101,8 +101,8 @@ input[type=checkbox] { transition: 0.2s background-color linear; } input[type=checkbox]:checked { - background-color: rgb(110, 132, 163); - border-color: rgb(110, 132, 163); + background-color: var(--novnc-blue); + border-color: var(--novnc-blue); } input[type=checkbox]:checked::after { content: ""; @@ -127,7 +127,7 @@ input[type=radio] { transition: 0.2s border linear; } input[type=radio]:checked { - border: 6px solid rgb(110, 132, 163); + border: 6px solid var(--novnc-blue); } /* @@ -143,12 +143,12 @@ input[type=range] { /* -webkit-slider.. & -moz-range.. cant be in selector lists: https://bugs.chromium.org/p/chromium/issues/detail?id=1154623 */ input[type=range]::-webkit-slider-runnable-track { - background-color: rgb(110, 132, 163); + background-color: var(--novnc-blue); height: 6px; border-radius: 3px; } input[type=range]::-moz-range-track { - background-color: rgb(110, 132, 163); + background-color: var(--novnc-blue); height: 6px; border-radius: 3px; } @@ -239,7 +239,7 @@ input:focus-visible::file-selector-button, button:focus-visible, select:focus-visible, textarea:focus-visible { - outline: 2px solid rgb(74, 144, 217); + outline: 2px solid var(--novnc-lightblue); outline-offset: 1px; } input[type=file]:focus-visible { diff --git a/vnc.html b/vnc.html index c2cc4e55..ed82b603 100644 --- a/vnc.html +++ b/vnc.html @@ -37,6 +37,7 @@ + From 7a4d1a82749060ba5f3167e387e1c39af5c8c9ff Mon Sep 17 00:00:00 2001 From: Adam Halim Date: Thu, 9 Jan 2025 14:51:44 +0100 Subject: [PATCH 10/70] Move mouse event help functions to broader scope These functions can be used elsewhere in the tests. We want to use these in the dragging tests in the future instead of directly calling private methods. --- tests/test.rfb.js | 163 +++++++++++++++++++++++----------------------- 1 file changed, 82 insertions(+), 81 deletions(-) diff --git a/tests/test.rfb.js b/tests/test.rfb.js index 7e406321..c8d2693f 100644 --- a/tests/test.rfb.js +++ b/tests/test.rfb.js @@ -158,6 +158,50 @@ describe('Remote Frame Buffer protocol client', function () { return rfb; } + function elementToClient(x, y, client) { + let res = { x: 0, y: 0 }; + + let bounds = client._canvas.getBoundingClientRect(); + + /* + * If the canvas is on a fractional position we will calculate + * a fractional mouse position. But that gets truncated when we + * send the event, AND the same thing happens in RFB when it + * generates the PointerEvent message. To compensate for that + * fact we round the value upwards here. + */ + res.x = Math.ceil(bounds.left + x); + res.y = Math.ceil(bounds.top + y); + + return res; + } + + function sendMouseMoveEvent(x, y, client) { + let pos = elementToClient(x, y, client); + let ev; + + ev = new MouseEvent('mousemove', + { 'screenX': pos.x + window.screenX, + 'screenY': pos.y + window.screenY, + 'clientX': pos.x, + 'clientY': pos.y }); + client._canvas.dispatchEvent(ev); + } + + function sendMouseButtonEvent(x, y, down, button, client) { + let pos = elementToClient(x, y, client); + let ev; + + ev = new MouseEvent(down ? 'mousedown' : 'mouseup', + { 'screenX': pos.x + window.screenX, + 'screenY': pos.y + window.screenY, + 'clientX': pos.x, + 'clientY': pos.y, + 'button': button, + 'buttons': 1 << button }); + client._canvas.dispatchEvent(ev); + } + describe('Connecting/Disconnecting', function () { describe('#RFB (constructor)', function () { let open, attach; @@ -3584,107 +3628,64 @@ describe('Remote Frame Buffer protocol client', function () { qemuKeyEvent.restore(); }); - function elementToClient(x, y) { - let res = { x: 0, y: 0 }; - - let bounds = client._canvas.getBoundingClientRect(); - - /* - * If the canvas is on a fractional position we will calculate - * a fractional mouse position. But that gets truncated when we - * send the event, AND the same thing happens in RFB when it - * generates the PointerEvent message. To compensate for that - * fact we round the value upwards here. - */ - res.x = Math.ceil(bounds.left + x); - res.y = Math.ceil(bounds.top + y); - - return res; - } - describe('Mouse events', function () { - function sendMouseMoveEvent(x, y) { - let pos = elementToClient(x, y); - let ev; - - ev = new MouseEvent('mousemove', - { 'screenX': pos.x + window.screenX, - 'screenY': pos.y + window.screenY, - 'clientX': pos.x, - 'clientY': pos.y }); - client._canvas.dispatchEvent(ev); - } - - function sendMouseButtonEvent(x, y, down, button) { - let pos = elementToClient(x, y); - let ev; - - ev = new MouseEvent(down ? 'mousedown' : 'mouseup', - { 'screenX': pos.x + window.screenX, - 'screenY': pos.y + window.screenY, - 'clientX': pos.x, - 'clientY': pos.y, - 'button': button, - 'buttons': 1 << button }); - client._canvas.dispatchEvent(ev); - } it('should not send button messages in view-only mode', function () { client._viewOnly = true; - sendMouseButtonEvent(10, 10, true, 0); + sendMouseButtonEvent(10, 10, true, 0, client); clock.tick(50); expect(pointerEvent).to.not.have.been.called; }); it('should not send movement messages in view-only mode', function () { client._viewOnly = true; - sendMouseMoveEvent(10, 10); + sendMouseMoveEvent(10, 10, client); clock.tick(50); expect(pointerEvent).to.not.have.been.called; }); it('should handle left mouse button', function () { - sendMouseButtonEvent(10, 10, true, 0); + sendMouseButtonEvent(10, 10, true, 0, client); expect(pointerEvent).to.have.been.calledOnceWith(client._sock, 10, 10, 0x1); pointerEvent.resetHistory(); - sendMouseButtonEvent(10, 10, false, 0); + sendMouseButtonEvent(10, 10, false, 0, client); expect(pointerEvent).to.have.been.calledOnceWith(client._sock, 10, 10, 0x0); }); it('should handle middle mouse button', function () { - sendMouseButtonEvent(10, 10, true, 1); + sendMouseButtonEvent(10, 10, true, 1, client); expect(pointerEvent).to.have.been.calledOnceWith(client._sock, 10, 10, 0x2); pointerEvent.resetHistory(); - sendMouseButtonEvent(10, 10, false, 1); + sendMouseButtonEvent(10, 10, false, 1, client); expect(pointerEvent).to.have.been.calledOnceWith(client._sock, 10, 10, 0x0); }); it('should handle right mouse button', function () { - sendMouseButtonEvent(10, 10, true, 2); + sendMouseButtonEvent(10, 10, true, 2, client); expect(pointerEvent).to.have.been.calledOnceWith(client._sock, 10, 10, 0x4); pointerEvent.resetHistory(); - sendMouseButtonEvent(10, 10, false, 2); + sendMouseButtonEvent(10, 10, false, 2, client); expect(pointerEvent).to.have.been.calledOnceWith(client._sock, 10, 10, 0x0); }); it('should handle multiple mouse buttons', function () { - sendMouseButtonEvent(10, 10, true, 0); - sendMouseButtonEvent(10, 10, true, 2); + sendMouseButtonEvent(10, 10, true, 0, client); + sendMouseButtonEvent(10, 10, true, 2, client); expect(pointerEvent).to.have.been.calledTwice; expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock, @@ -3694,8 +3695,8 @@ describe('Remote Frame Buffer protocol client', function () { pointerEvent.resetHistory(); - sendMouseButtonEvent(10, 10, false, 0); - sendMouseButtonEvent(10, 10, false, 2); + sendMouseButtonEvent(10, 10, false, 0, client); + sendMouseButtonEvent(10, 10, false, 2, client); expect(pointerEvent).to.have.been.calledTwice; expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock, @@ -3705,14 +3706,14 @@ describe('Remote Frame Buffer protocol client', function () { }); it('should handle mouse movement', function () { - sendMouseMoveEvent(50, 70); + sendMouseMoveEvent(50, 70, client); expect(pointerEvent).to.have.been.calledOnceWith(client._sock, 50, 70, 0x0); }); it('should handle click and drag', function () { - sendMouseButtonEvent(10, 10, true, 0); - sendMouseMoveEvent(50, 70); + sendMouseButtonEvent(10, 10, true, 0, client); + sendMouseMoveEvent(50, 70, client); expect(pointerEvent).to.have.been.calledTwice; expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock, @@ -3722,7 +3723,7 @@ describe('Remote Frame Buffer protocol client', function () { pointerEvent.resetHistory(); - sendMouseButtonEvent(50, 70, false, 0); + sendMouseButtonEvent(50, 70, false, 0, client); expect(pointerEvent).to.have.been.calledOnceWith(client._sock, 50, 70, 0x0); @@ -3730,15 +3731,15 @@ describe('Remote Frame Buffer protocol client', function () { describe('Event aggregation', function () { it('should send a single pointer event on mouse movement', function () { - sendMouseMoveEvent(50, 70); + sendMouseMoveEvent(50, 70, client); clock.tick(100); expect(pointerEvent).to.have.been.calledOnceWith(client._sock, 50, 70, 0x0); }); it('should delay one move if two events are too close', function () { - sendMouseMoveEvent(18, 30); - sendMouseMoveEvent(20, 50); + sendMouseMoveEvent(18, 30, client); + sendMouseMoveEvent(20, 50, client); expect(pointerEvent).to.have.been.calledOnceWith(client._sock, 18, 30, 0x0); @@ -3751,9 +3752,9 @@ describe('Remote Frame Buffer protocol client', function () { }); it('should only send first and last move of many close events', function () { - sendMouseMoveEvent(18, 30); - sendMouseMoveEvent(20, 50); - sendMouseMoveEvent(21, 55); + sendMouseMoveEvent(18, 30, client); + sendMouseMoveEvent(20, 50, client); + sendMouseMoveEvent(21, 55, client); expect(pointerEvent).to.have.been.calledOnceWith(client._sock, 18, 30, 0x0); @@ -3767,46 +3768,46 @@ describe('Remote Frame Buffer protocol client', function () { // We selected the 17ms since that is ~60 FPS it('should send move events every 17 ms', function () { - sendMouseMoveEvent(1, 10); // instant send + sendMouseMoveEvent(1, 10, client); // instant send clock.tick(10); expect(pointerEvent).to.have.been.calledOnceWith(client._sock, 1, 10, 0x0); pointerEvent.resetHistory(); - sendMouseMoveEvent(2, 20); // delayed + sendMouseMoveEvent(2, 20, client); // delayed clock.tick(10); // timeout send expect(pointerEvent).to.have.been.calledOnceWith(client._sock, 2, 20, 0x0); pointerEvent.resetHistory(); - sendMouseMoveEvent(3, 30); // delayed + sendMouseMoveEvent(3, 30, client); // delayed clock.tick(10); - sendMouseMoveEvent(4, 40); // delayed + sendMouseMoveEvent(4, 40, client); // delayed clock.tick(10); // timeout send expect(pointerEvent).to.have.been.calledOnceWith(client._sock, 4, 40, 0x0); pointerEvent.resetHistory(); - sendMouseMoveEvent(5, 50); // delayed + sendMouseMoveEvent(5, 50, client); // delayed expect(pointerEvent).to.not.have.been.called; }); it('should send waiting move events before a button press', function () { - sendMouseMoveEvent(13, 9); + sendMouseMoveEvent(13, 9, client); expect(pointerEvent).to.have.been.calledOnceWith(client._sock, 13, 9, 0x0); pointerEvent.resetHistory(); - sendMouseMoveEvent(20, 70); + sendMouseMoveEvent(20, 70, client); expect(pointerEvent).to.not.have.been.called; - sendMouseButtonEvent(20, 70, true, 0); + sendMouseButtonEvent(20, 70, true, 0, client); expect(pointerEvent).to.have.been.calledTwice; expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock, @@ -3816,7 +3817,7 @@ describe('Remote Frame Buffer protocol client', function () { }); it('should send move events with enough time apart normally', function () { - sendMouseMoveEvent(58, 60); + sendMouseMoveEvent(58, 60, client); expect(pointerEvent).to.have.been.calledOnceWith(client._sock, 58, 60, 0x0); @@ -3824,7 +3825,7 @@ describe('Remote Frame Buffer protocol client', function () { clock.tick(20); - sendMouseMoveEvent(25, 60); + sendMouseMoveEvent(25, 60, client); expect(pointerEvent).to.have.been.calledOnceWith(client._sock, 25, 60, 0x0); @@ -3832,13 +3833,13 @@ describe('Remote Frame Buffer protocol client', function () { }); it('should not send waiting move events if disconnected', function () { - sendMouseMoveEvent(88, 99); + sendMouseMoveEvent(88, 99, client); expect(pointerEvent).to.have.been.calledOnceWith(client._sock, 88, 99, 0x0); pointerEvent.resetHistory(); - sendMouseMoveEvent(66, 77); + sendMouseMoveEvent(66, 77, client); client.disconnect(); clock.tick(20); @@ -3857,7 +3858,7 @@ describe('Remote Frame Buffer protocol client', function () { describe('Wheel events', function () { function sendWheelEvent(x, y, dx, dy, mode=0) { - let pos = elementToClient(x, y); + let pos = elementToClient(x, y, client); let ev; ev = new WheelEvent('wheel', @@ -3990,7 +3991,7 @@ describe('Remote Frame Buffer protocol client', function () { describe('Gesture event handlers', function () { function gestureStart(gestureType, x, y, magnitudeX = 0, magnitudeY = 0) { - let pos = elementToClient(x, y); + let pos = elementToClient(x, y, client); let detail = {type: gestureType, clientX: pos.x, clientY: pos.y}; detail.magnitudeX = magnitudeX; @@ -4002,7 +4003,7 @@ describe('Remote Frame Buffer protocol client', function () { function gestureMove(gestureType, x, y, magnitudeX = 0, magnitudeY = 0) { - let pos = elementToClient(x, y); + let pos = elementToClient(x, y, client); let detail = {type: gestureType, clientX: pos.x, clientY: pos.y}; detail.magnitudeX = magnitudeX; @@ -4013,7 +4014,7 @@ describe('Remote Frame Buffer protocol client', function () { } function gestureEnd(gestureType, x, y) { - let pos = elementToClient(x, y); + let pos = elementToClient(x, y, client); let detail = {type: gestureType, clientX: pos.x, clientY: pos.y}; let ev = new CustomEvent('gestureend', { detail: detail }); client._canvas.dispatchEvent(ev); From de9d6888db04a6b43bc8a5d39f26c0dd51f71247 Mon Sep 17 00:00:00 2001 From: Adam Halim Date: Fri, 10 Jan 2025 14:22:14 +0100 Subject: [PATCH 11/70] Add unit test for wheel + buttons pressed --- tests/test.rfb.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/test.rfb.js b/tests/test.rfb.js index c8d2693f..66050bb0 100644 --- a/tests/test.rfb.js +++ b/tests/test.rfb.js @@ -3964,6 +3964,21 @@ describe('Remote Frame Buffer protocol client', function () { expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock, 10, 10, 0); }); + + it('should handle wheel event with buttons pressed', function () { + sendMouseButtonEvent(10, 10, true, 0, client); + sendWheelEvent(10, 10, 0, 50); + + expect(pointerEvent).to.have.been.called.calledThrice; + + expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock, + 10, 10, 0x1); + expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock, + 10, 10, 0x11); + expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock, + 10, 10, 0x1); + }); + }); describe('Keyboard events', function () { From db22ec6ee6ecc88e8d45f60a9022facd64784143 Mon Sep 17 00:00:00 2001 From: Adam Halim Date: Thu, 9 Jan 2025 14:59:19 +0100 Subject: [PATCH 12/70] Split button click with dragging test --- 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 66050bb0..861b808a 100644 --- a/tests/test.rfb.js +++ b/tests/test.rfb.js @@ -686,9 +686,9 @@ describe('Remote Frame Buffer protocol client', function () { client._handleMouseButton(13, 9, 0x001); client._handleMouseButton(13, 9, 0x000); expect(RFB.messages.pointerEvent).to.have.been.calledTwice; + }); - RFB.messages.pointerEvent.resetHistory(); - + it('should send button messages when release with small movement', function () { // Small movement client._handleMouseButton(13, 9, 0x001); client._handleMouseMove(15, 14); From c3934e0938ebe10e2749b8a51d70aa208dc73346 Mon Sep 17 00:00:00 2001 From: Adam Halim Date: Tue, 14 Jan 2025 12:14:34 +0100 Subject: [PATCH 13/70] Move mouse move flushing to separate function --- core/rfb.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/core/rfb.js b/core/rfb.js index 87fac3c2..3f946142 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -1104,11 +1104,7 @@ export default class RFB extends EventTargetMixin { } // Flush waiting move event first - if (this._mouseMoveTimer !== null) { - clearTimeout(this._mouseMoveTimer); - this._mouseMoveTimer = null; - this._sendMouse(x, y, this._mouseButtonMask); - } + this._flushMouseMoveTimer(x, y); if (down) { this._mouseButtonMask |= bmask; @@ -1380,6 +1376,14 @@ export default class RFB extends EventTargetMixin { } } + _flushMouseMoveTimer(x, y) { + if (this._mouseMoveTimer !== null) { + clearTimeout(this._mouseMoveTimer); + this._mouseMoveTimer = null; + this._sendMouse(x, y, this._mouseButtonMask); + } + } + // Message handlers _negotiateProtocolVersion() { From dce8ab395b3efdc79ee5db12edfe390528bda6e5 Mon Sep 17 00:00:00 2001 From: Adam Halim Date: Thu, 9 Jan 2025 15:58:35 +0100 Subject: [PATCH 14/70] Dispatch mouse events in dragging unit tests This makes our tests reflect the real world better, as we now send real mouse events instead of calling private methods directly. --- tests/test.rfb.js | 47 +++++++++++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/tests/test.rfb.js b/tests/test.rfb.js index 861b808a..061aff30 100644 --- a/tests/test.rfb.js +++ b/tests/test.rfb.js @@ -668,7 +668,10 @@ describe('Remote Frame Buffer protocol client', function () { describe('Dragging', function () { beforeEach(function () { + client = makeRFB(); client.dragViewport = true; + client._display.resize(100, 100); + sinon.spy(RFB.messages, "pointerEvent"); }); @@ -677,35 +680,39 @@ describe('Remote Frame Buffer protocol client', function () { }); it('should not send button messages when initiating viewport dragging', function () { - client._handleMouseButton(13, 9, 0x001); + sendMouseButtonEvent(13, 9, true, 0, client); expect(RFB.messages.pointerEvent).to.not.have.been.called; }); it('should send button messages when release without movement', function () { // Just up and down - client._handleMouseButton(13, 9, 0x001); - client._handleMouseButton(13, 9, 0x000); + sendMouseButtonEvent(13, 9, true, 0, client); + sendMouseButtonEvent(13, 9, false, 0, client); + expect(RFB.messages.pointerEvent).to.have.been.calledTwice; }); it('should send button messages when release with small movement', function () { // Small movement - client._handleMouseButton(13, 9, 0x001); - client._handleMouseMove(15, 14); - client._handleMouseButton(15, 14, 0x000); + sendMouseButtonEvent(13, 9, true, 0, client); + sendMouseMoveEvent(15, 14, client); + sendMouseButtonEvent(15, 14, false, 0, client); + expect(RFB.messages.pointerEvent).to.have.been.calledTwice; }); it('should not send button messages when in view only', function () { client._viewOnly = true; - client._handleMouseButton(13, 9, 0x001); - client._handleMouseButton(13, 9, 0x000); + + sendMouseButtonEvent(13, 9, true, 0, client); + sendMouseButtonEvent(13, 9, false, 0, client); + expect(RFB.messages.pointerEvent).to.not.have.been.called; }); it('should send button message directly when drag is disabled', function () { client.dragViewport = false; - client._handleMouseButton(13, 9, 0x001); + sendMouseButtonEvent(13, 9, true, 0, client); expect(RFB.messages.pointerEvent).to.have.been.calledOnce; }); @@ -713,16 +720,16 @@ describe('Remote Frame Buffer protocol client', function () { sinon.spy(client._display, "viewportChangePos"); // Too small movement + sendMouseButtonEvent(13, 9, true, 0, client); + sendMouseMoveEvent(18, 9, client); - client._handleMouseButton(13, 9, 0x001); - client._handleMouseMove(18, 9); expect(RFB.messages.pointerEvent).to.not.have.been.called; expect(client._display.viewportChangePos).to.not.have.been.called; // Sufficient movement - client._handleMouseMove(43, 9); + sendMouseMoveEvent(43, 9, client); expect(RFB.messages.pointerEvent).to.not.have.been.called; expect(client._display.viewportChangePos).to.have.been.calledOnce; @@ -732,7 +739,7 @@ describe('Remote Frame Buffer protocol client', function () { // Now a small movement should move right away - client._handleMouseMove(43, 14); + sendMouseMoveEvent(43, 14, client); expect(RFB.messages.pointerEvent).to.not.have.been.called; expect(client._display.viewportChangePos).to.have.been.calledOnce; @@ -742,9 +749,9 @@ describe('Remote Frame Buffer protocol client', function () { it('should not send button messages when dragging ends', function () { // First the movement - client._handleMouseButton(13, 9, 0x001); - client._handleMouseMove(43, 9); - client._handleMouseButton(43, 9, 0x000); + sendMouseButtonEvent(13, 9, true, 0, client); + sendMouseMoveEvent(43, 9, client); + sendMouseButtonEvent(43, 9, false, 0, client); expect(RFB.messages.pointerEvent).to.not.have.been.called; }); @@ -752,15 +759,15 @@ describe('Remote Frame Buffer protocol client', function () { it('should terminate viewport dragging on a button up event', function () { // First the dragging movement - client._handleMouseButton(13, 9, 0x001); - client._handleMouseMove(43, 9); - client._handleMouseButton(43, 9, 0x000); + sendMouseButtonEvent(13, 9, true, 0, client); + sendMouseMoveEvent(43, 9, client); + sendMouseButtonEvent(43, 9, false, 0, client); // Another movement now should not move the viewport sinon.spy(client._display, "viewportChangePos"); - client._handleMouseMove(43, 59); + sendMouseMoveEvent(43, 59, client); expect(client._display.viewportChangePos).to.not.have.been.called; }); From 31d6a77af63feb211bcf5fc103b28eb755173fc4 Mon Sep 17 00:00:00 2001 From: Adam Halim Date: Fri, 10 Jan 2025 12:21:52 +0100 Subject: [PATCH 15/70] Check for correct button events in dragging tests Previously, these unit tests did not check which events were sent to the server, only how many events were sent. This commit adds checks to see that the expected button events are sent. --- tests/test.rfb.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/test.rfb.js b/tests/test.rfb.js index 061aff30..7bd25e4e 100644 --- a/tests/test.rfb.js +++ b/tests/test.rfb.js @@ -690,6 +690,10 @@ describe('Remote Frame Buffer protocol client', function () { sendMouseButtonEvent(13, 9, false, 0, client); expect(RFB.messages.pointerEvent).to.have.been.calledTwice; + expect(RFB.messages.pointerEvent.firstCall).to.have.been.calledWith(client._sock, + 13, 9, 0x1); + expect(RFB.messages.pointerEvent.secondCall).to.have.been.calledWith(client._sock, + 13, 9, 0x0); }); it('should send button messages when release with small movement', function () { @@ -699,6 +703,10 @@ describe('Remote Frame Buffer protocol client', function () { sendMouseButtonEvent(15, 14, false, 0, client); expect(RFB.messages.pointerEvent).to.have.been.calledTwice; + expect(RFB.messages.pointerEvent.firstCall).to.have.been.calledWith(client._sock, + 15, 14, 0x1); + expect(RFB.messages.pointerEvent.secondCall).to.have.been.calledWith(client._sock, + 15, 14, 0x0); }); it('should not send button messages when in view only', function () { @@ -714,6 +722,8 @@ describe('Remote Frame Buffer protocol client', function () { client.dragViewport = false; sendMouseButtonEvent(13, 9, true, 0, client); expect(RFB.messages.pointerEvent).to.have.been.calledOnce; + expect(RFB.messages.pointerEvent.firstCall).to.have.been.calledWith(client._sock, + 13, 9, 0x1); }); it('should be initiate viewport dragging on sufficient movement', function () { From ea057d079329c9e22a3e940cfdb13eef132c9ad2 Mon Sep 17 00:00:00 2001 From: Adam Halim Date: Fri, 10 Jan 2025 13:14:49 +0100 Subject: [PATCH 16/70] Move gesture event help functions to broader scope This is needed if we want to test gestures with dragging. --- tests/test.rfb.js | 184 +++++++++++++++++++++++----------------------- 1 file changed, 92 insertions(+), 92 deletions(-) diff --git a/tests/test.rfb.js b/tests/test.rfb.js index 7bd25e4e..bdf86e60 100644 --- a/tests/test.rfb.js +++ b/tests/test.rfb.js @@ -202,6 +202,37 @@ describe('Remote Frame Buffer protocol client', function () { client._canvas.dispatchEvent(ev); } + function gestureStart(gestureType, x, y, client, + magnitudeX = 0, magnitudeY = 0) { + let pos = elementToClient(x, y, client); + let detail = { type: gestureType, clientX: pos.x, clientY: pos.y }; + + detail.magnitudeX = magnitudeX; + detail.magnitudeY = magnitudeY; + + let ev = new CustomEvent('gesturestart', { detail: detail }); + client._canvas.dispatchEvent(ev); + } + + function gestureMove(gestureType, x, y, client, + magnitudeX = 0, magnitudeY = 0) { + let pos = elementToClient(x, y, client); + let detail = { type: gestureType, clientX: pos.x, clientY: pos.y }; + + detail.magnitudeX = magnitudeX; + detail.magnitudeY = magnitudeY; + + let ev = new CustomEvent('gesturemove', { detail: detail }, client); + client._canvas.dispatchEvent(ev); + } + + function gestureEnd(gestureType, x, y, client) { + let pos = elementToClient(x, y, client); + let detail = { type: gestureType, clientX: pos.x, clientY: pos.y }; + let ev = new CustomEvent('gestureend', { detail: detail }); + client._canvas.dispatchEvent(ev); + } + describe('Connecting/Disconnecting', function () { describe('#RFB (constructor)', function () { let open, attach; @@ -4021,43 +4052,12 @@ describe('Remote Frame Buffer protocol client', function () { }); describe('Gesture event handlers', function () { - function gestureStart(gestureType, x, y, - magnitudeX = 0, magnitudeY = 0) { - let pos = elementToClient(x, y, client); - let detail = {type: gestureType, clientX: pos.x, clientY: pos.y}; - - detail.magnitudeX = magnitudeX; - detail.magnitudeY = magnitudeY; - - let ev = new CustomEvent('gesturestart', { detail: detail }); - client._canvas.dispatchEvent(ev); - } - - function gestureMove(gestureType, x, y, - magnitudeX = 0, magnitudeY = 0) { - let pos = elementToClient(x, y, client); - let detail = {type: gestureType, clientX: pos.x, clientY: pos.y}; - - detail.magnitudeX = magnitudeX; - detail.magnitudeY = magnitudeY; - - let ev = new CustomEvent('gesturemove', { detail: detail }); - client._canvas.dispatchEvent(ev); - } - - function gestureEnd(gestureType, x, y) { - let pos = elementToClient(x, y, client); - let detail = {type: gestureType, clientX: pos.x, clientY: pos.y}; - let ev = new CustomEvent('gestureend', { detail: detail }); - client._canvas.dispatchEvent(ev); - } - describe('Gesture onetap', function () { it('should handle onetap events', function () { let bmask = 0x1; - gestureStart('onetap', 20, 40); - gestureEnd('onetap', 20, 40); + gestureStart('onetap', 20, 40, client); + gestureEnd('onetap', 20, 40, client); expect(pointerEvent).to.have.been.calledThrice; expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock, @@ -4071,8 +4071,8 @@ describe('Remote Frame Buffer protocol client', function () { it('should keep same position for multiple onetap events', function () { let bmask = 0x1; - gestureStart('onetap', 20, 40); - gestureEnd('onetap', 20, 40); + gestureStart('onetap', 20, 40, client); + gestureEnd('onetap', 20, 40, client); expect(pointerEvent).to.have.been.calledThrice; expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock, @@ -4084,8 +4084,8 @@ describe('Remote Frame Buffer protocol client', function () { pointerEvent.resetHistory(); - gestureStart('onetap', 20, 50); - gestureEnd('onetap', 20, 50); + gestureStart('onetap', 20, 50, client); + gestureEnd('onetap', 20, 50, client); expect(pointerEvent).to.have.been.calledThrice; expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock, @@ -4097,8 +4097,8 @@ describe('Remote Frame Buffer protocol client', function () { pointerEvent.resetHistory(); - gestureStart('onetap', 30, 50); - gestureEnd('onetap', 30, 50); + gestureStart('onetap', 30, 50, client); + gestureEnd('onetap', 30, 50, client); expect(pointerEvent).to.have.been.calledThrice; expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock, @@ -4112,8 +4112,8 @@ describe('Remote Frame Buffer protocol client', function () { it('should not keep same position for onetap events when too far apart', function () { let bmask = 0x1; - gestureStart('onetap', 20, 40); - gestureEnd('onetap', 20, 40); + gestureStart('onetap', 20, 40, client); + gestureEnd('onetap', 20, 40, client); expect(pointerEvent).to.have.been.calledThrice; expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock, @@ -4125,8 +4125,8 @@ describe('Remote Frame Buffer protocol client', function () { pointerEvent.resetHistory(); - gestureStart('onetap', 80, 95); - gestureEnd('onetap', 80, 95); + gestureStart('onetap', 80, 95, client); + gestureEnd('onetap', 80, 95, client); expect(pointerEvent).to.have.been.calledThrice; expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock, @@ -4140,8 +4140,8 @@ describe('Remote Frame Buffer protocol client', function () { it('should not keep same position for onetap events when enough time inbetween', function () { let bmask = 0x1; - gestureStart('onetap', 10, 20); - gestureEnd('onetap', 10, 20); + gestureStart('onetap', 10, 20, client); + gestureEnd('onetap', 10, 20, client); expect(pointerEvent).to.have.been.calledThrice; expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock, @@ -4154,8 +4154,8 @@ describe('Remote Frame Buffer protocol client', function () { pointerEvent.resetHistory(); this.clock.tick(1500); - gestureStart('onetap', 15, 20); - gestureEnd('onetap', 15, 20); + gestureStart('onetap', 15, 20, client); + gestureEnd('onetap', 15, 20, client); expect(pointerEvent).to.have.been.calledThrice; expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock, @@ -4173,7 +4173,7 @@ describe('Remote Frame Buffer protocol client', function () { it('should handle gesture twotap events', function () { let bmask = 0x4; - gestureStart("twotap", 20, 40); + gestureStart("twotap", 20, 40, client); expect(pointerEvent).to.have.been.calledThrice; expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock, @@ -4190,8 +4190,8 @@ describe('Remote Frame Buffer protocol client', function () { for (let offset = 0;offset < 30;offset += 10) { pointerEvent.resetHistory(); - gestureStart('twotap', 20, 40 + offset); - gestureEnd('twotap', 20, 40 + offset); + gestureStart('twotap', 20, 40 + offset, client); + gestureEnd('twotap', 20, 40 + offset, client); expect(pointerEvent).to.have.been.calledThrice; expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock, @@ -4208,7 +4208,7 @@ describe('Remote Frame Buffer protocol client', function () { it('should handle gesture start for threetap events', function () { let bmask = 0x2; - gestureStart("threetap", 20, 40); + gestureStart("threetap", 20, 40, client); expect(pointerEvent).to.have.been.calledThrice; expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock, @@ -4225,8 +4225,8 @@ describe('Remote Frame Buffer protocol client', function () { for (let offset = 0;offset < 30;offset += 10) { pointerEvent.resetHistory(); - gestureStart('threetap', 20, 40 + offset); - gestureEnd('threetap', 20, 40 + offset); + gestureStart('threetap', 20, 40 + offset, client); + gestureEnd('threetap', 20, 40 + offset, client); expect(pointerEvent).to.have.been.calledThrice; expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock, @@ -4243,7 +4243,7 @@ describe('Remote Frame Buffer protocol client', function () { it('should handle gesture drag events', function () { let bmask = 0x1; - gestureStart('drag', 20, 40); + gestureStart('drag', 20, 40, client); expect(pointerEvent).to.have.been.calledTwice; expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock, @@ -4253,7 +4253,7 @@ describe('Remote Frame Buffer protocol client', function () { pointerEvent.resetHistory(); - gestureMove('drag', 30, 50); + gestureMove('drag', 30, 50, client); clock.tick(50); expect(pointerEvent).to.have.been.calledOnce; @@ -4262,7 +4262,7 @@ describe('Remote Frame Buffer protocol client', function () { pointerEvent.resetHistory(); - gestureEnd('drag', 30, 50); + gestureEnd('drag', 30, 50, client); expect(pointerEvent).to.have.been.calledTwice; expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock, @@ -4276,7 +4276,7 @@ describe('Remote Frame Buffer protocol client', function () { it('should handle long press events', function () { let bmask = 0x4; - gestureStart('longpress', 20, 40); + gestureStart('longpress', 20, 40, client); expect(pointerEvent).to.have.been.calledTwice; expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock, @@ -4285,7 +4285,7 @@ describe('Remote Frame Buffer protocol client', function () { 20, 40, bmask); pointerEvent.resetHistory(); - gestureMove('longpress', 40, 60); + gestureMove('longpress', 40, 60, client); clock.tick(50); expect(pointerEvent).to.have.been.calledOnceWith(client._sock, @@ -4293,7 +4293,7 @@ describe('Remote Frame Buffer protocol client', function () { pointerEvent.resetHistory(); - gestureEnd('longpress', 40, 60); + gestureEnd('longpress', 40, 60, client); expect(pointerEvent).to.have.been.calledTwice; expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock, @@ -4307,14 +4307,14 @@ describe('Remote Frame Buffer protocol client', function () { it('should handle gesture twodrag up events', function () { let bmask = 0x10; // Button mask for scroll down - gestureStart('twodrag', 20, 40, 0, 0); + gestureStart('twodrag', 20, 40, client, 0, 0); expect(pointerEvent).to.have.been.calledOnceWith(client._sock, 20, 40, 0x0); pointerEvent.resetHistory(); - gestureMove('twodrag', 20, 40, 0, -60); + gestureMove('twodrag', 20, 40, client, 0, -60); expect(pointerEvent).to.have.been.calledThrice; expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock, @@ -4328,14 +4328,14 @@ describe('Remote Frame Buffer protocol client', function () { it('should handle gesture twodrag down events', function () { let bmask = 0x8; // Button mask for scroll up - gestureStart('twodrag', 20, 40, 0, 0); + gestureStart('twodrag', 20, 40, client, 0, 0); expect(pointerEvent).to.have.been.calledOnceWith(client._sock, 20, 40, 0x0); pointerEvent.resetHistory(); - gestureMove('twodrag', 20, 40, 0, 60); + gestureMove('twodrag', 20, 40, client, 0, 60); expect(pointerEvent).to.have.been.calledThrice; expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock, @@ -4349,14 +4349,14 @@ describe('Remote Frame Buffer protocol client', function () { it('should handle gesture twodrag right events', function () { let bmask = 0x20; // Button mask for scroll right - gestureStart('twodrag', 20, 40, 0, 0); + gestureStart('twodrag', 20, 40, client, 0, 0); expect(pointerEvent).to.have.been.calledOnceWith(client._sock, 20, 40, 0x0); pointerEvent.resetHistory(); - gestureMove('twodrag', 20, 40, 60, 0); + gestureMove('twodrag', 20, 40, client, 60, 0); expect(pointerEvent).to.have.been.calledThrice; expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock, @@ -4370,14 +4370,14 @@ describe('Remote Frame Buffer protocol client', function () { it('should handle gesture twodrag left events', function () { let bmask = 0x40; // Button mask for scroll left - gestureStart('twodrag', 20, 40, 0, 0); + gestureStart('twodrag', 20, 40, client, 0, 0); expect(pointerEvent).to.have.been.calledOnceWith(client._sock, 20, 40, 0x0); pointerEvent.resetHistory(); - gestureMove('twodrag', 20, 40, -60, 0); + gestureMove('twodrag', 20, 40, client, -60, 0); expect(pointerEvent).to.have.been.calledThrice; expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock, @@ -4392,14 +4392,14 @@ describe('Remote Frame Buffer protocol client', function () { let scrlUp = 0x8; // Button mask for scroll up let scrlRight = 0x20; // Button mask for scroll right - gestureStart('twodrag', 20, 40, 0, 0); + gestureStart('twodrag', 20, 40, client, 0, 0); expect(pointerEvent).to.have.been.calledOnceWith(client._sock, 20, 40, 0x0); pointerEvent.resetHistory(); - gestureMove('twodrag', 20, 40, 60, 60); + gestureMove('twodrag', 20, 40, client, 60, 60); expect(pointerEvent).to.have.been.callCount(5); expect(pointerEvent.getCall(0)).to.have.been.calledWith(client._sock, @@ -4417,14 +4417,14 @@ describe('Remote Frame Buffer protocol client', function () { it('should handle multiple small gesture twodrag events', function () { let bmask = 0x8; // Button mask for scroll up - gestureStart('twodrag', 20, 40, 0, 0); + gestureStart('twodrag', 20, 40, client, 0, 0); expect(pointerEvent).to.have.been.calledOnceWith(client._sock, 20, 40, 0x0); pointerEvent.resetHistory(); - gestureMove('twodrag', 20, 40, 0, 10); + gestureMove('twodrag', 20, 40, client, 0, 10); clock.tick(50); expect(pointerEvent).to.have.been.calledOnceWith(client._sock, @@ -4432,7 +4432,7 @@ describe('Remote Frame Buffer protocol client', function () { pointerEvent.resetHistory(); - gestureMove('twodrag', 20, 40, 0, 20); + gestureMove('twodrag', 20, 40, client, 0, 20); clock.tick(50); expect(pointerEvent).to.have.been.calledOnceWith(client._sock, @@ -4440,7 +4440,7 @@ describe('Remote Frame Buffer protocol client', function () { pointerEvent.resetHistory(); - gestureMove('twodrag', 20, 40, 0, 60); + gestureMove('twodrag', 20, 40, client, 0, 60); expect(pointerEvent).to.have.been.calledThrice; expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock, @@ -4454,14 +4454,14 @@ describe('Remote Frame Buffer protocol client', function () { it('should handle large gesture twodrag events', function () { let bmask = 0x8; // Button mask for scroll up - gestureStart('twodrag', 30, 50, 0, 0); + gestureStart('twodrag', 30, 50, client, 0, 0); expect(pointerEvent). to.have.been.calledOnceWith(client._sock, 30, 50, 0x0); pointerEvent.resetHistory(); - gestureMove('twodrag', 30, 50, 0, 200); + gestureMove('twodrag', 30, 50, client, 0, 200); expect(pointerEvent).to.have.callCount(7); expect(pointerEvent.getCall(0)).to.have.been.calledWith(client._sock, @@ -4486,7 +4486,7 @@ describe('Remote Frame Buffer protocol client', function () { let keysym = KeyTable.XK_Control_L; let bmask = 0x10; // Button mask for scroll down - gestureStart('pinch', 20, 40, 90, 90); + gestureStart('pinch', 20, 40, client, 90, 90); expect(pointerEvent).to.have.been.calledOnceWith(client._sock, 20, 40, 0x0); @@ -4494,7 +4494,7 @@ describe('Remote Frame Buffer protocol client', function () { pointerEvent.resetHistory(); - gestureMove('pinch', 20, 40, 30, 30); + gestureMove('pinch', 20, 40, client, 30, 30); expect(pointerEvent).to.have.been.calledThrice; expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock, @@ -4516,7 +4516,7 @@ describe('Remote Frame Buffer protocol client', function () { pointerEvent.resetHistory(); keyEvent.resetHistory(); - gestureEnd('pinch', 20, 40); + gestureEnd('pinch', 20, 40, client); expect(pointerEvent).to.not.have.been.called; expect(keyEvent).to.not.have.been.called; @@ -4526,7 +4526,7 @@ describe('Remote Frame Buffer protocol client', function () { let keysym = KeyTable.XK_Control_L; let bmask = 0x8; // Button mask for scroll up - gestureStart('pinch', 10, 20, 10, 20); + gestureStart('pinch', 10, 20, client, 10, 20); expect(pointerEvent).to.have.been.calledOnceWith(client._sock, 10, 20, 0x0); @@ -4534,7 +4534,7 @@ describe('Remote Frame Buffer protocol client', function () { pointerEvent.resetHistory(); - gestureMove('pinch', 10, 20, 70, 80); + gestureMove('pinch', 10, 20, client, 70, 80); expect(pointerEvent).to.have.been.calledThrice; expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock, @@ -4556,7 +4556,7 @@ describe('Remote Frame Buffer protocol client', function () { pointerEvent.resetHistory(); keyEvent.resetHistory(); - gestureEnd('pinch', 10, 20); + gestureEnd('pinch', 10, 20, client); expect(pointerEvent).to.not.have.been.called; expect(keyEvent).to.not.have.been.called; @@ -4566,7 +4566,7 @@ describe('Remote Frame Buffer protocol client', function () { let keysym = KeyTable.XK_Control_L; let bmask = 0x10; // Button mask for scroll down - gestureStart('pinch', 20, 40, 150, 150); + gestureStart('pinch', 20, 40, client, 150, 150); expect(pointerEvent).to.have.been.calledOnceWith(client._sock, 20, 40, 0x0); @@ -4574,7 +4574,7 @@ describe('Remote Frame Buffer protocol client', function () { pointerEvent.resetHistory(); - gestureMove('pinch', 20, 40, 30, 30); + gestureMove('pinch', 20, 40, client, 30, 30); expect(pointerEvent).to.have.been.callCount(5); expect(pointerEvent.getCall(0)).to.have.been.calledWith(client._sock, @@ -4600,7 +4600,7 @@ describe('Remote Frame Buffer protocol client', function () { pointerEvent.resetHistory(); keyEvent.resetHistory(); - gestureEnd('pinch', 20, 40); + gestureEnd('pinch', 20, 40, client); expect(pointerEvent).to.not.have.been.called; expect(keyEvent).to.not.have.been.called; @@ -4610,7 +4610,7 @@ describe('Remote Frame Buffer protocol client', function () { let keysym = KeyTable.XK_Control_L; let bmask = 0x8; // Button mask for scroll down - gestureStart('pinch', 20, 40, 0, 10); + gestureStart('pinch', 20, 40, client, 0, 10); expect(pointerEvent).to.have.been.calledOnceWith(client._sock, 20, 40, 0x0); @@ -4618,7 +4618,7 @@ describe('Remote Frame Buffer protocol client', function () { pointerEvent.resetHistory(); - gestureMove('pinch', 20, 40, 0, 30); + gestureMove('pinch', 20, 40, client, 0, 30); clock.tick(50); expect(pointerEvent).to.have.been.calledWith(client._sock, @@ -4626,7 +4626,7 @@ describe('Remote Frame Buffer protocol client', function () { pointerEvent.resetHistory(); - gestureMove('pinch', 20, 40, 0, 60); + gestureMove('pinch', 20, 40, client, 0, 60); clock.tick(50); expect(pointerEvent).to.have.been.calledWith(client._sock, @@ -4635,7 +4635,7 @@ describe('Remote Frame Buffer protocol client', function () { pointerEvent.resetHistory(); keyEvent.resetHistory(); - gestureMove('pinch', 20, 40, 0, 90); + gestureMove('pinch', 20, 40, client, 0, 90); expect(pointerEvent).to.have.been.calledThrice; expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock, @@ -4657,7 +4657,7 @@ describe('Remote Frame Buffer protocol client', function () { pointerEvent.resetHistory(); keyEvent.resetHistory(); - gestureEnd('pinch', 20, 40); + gestureEnd('pinch', 20, 40, client); expect(keyEvent).to.not.have.been.called; }); @@ -4669,7 +4669,7 @@ describe('Remote Frame Buffer protocol client', function () { client._qemuExtKeyEventSupported = true; - gestureStart('pinch', 20, 40, 90, 90); + gestureStart('pinch', 20, 40, client, 90, 90); expect(pointerEvent).to.have.been.calledOnceWith(client._sock, 20, 40, 0x0); @@ -4677,7 +4677,7 @@ describe('Remote Frame Buffer protocol client', function () { pointerEvent.resetHistory(); - gestureMove('pinch', 20, 40, 30, 30); + gestureMove('pinch', 20, 40, client, 30, 30); expect(pointerEvent).to.have.been.calledThrice; expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock, @@ -4703,7 +4703,7 @@ describe('Remote Frame Buffer protocol client', function () { pointerEvent.resetHistory(); qemuKeyEvent.resetHistory(); - gestureEnd('pinch', 20, 40); + gestureEnd('pinch', 20, 40, client); expect(pointerEvent).to.not.have.been.called; expect(qemuKeyEvent).to.not.have.been.called; From f9eb476f6df2dc1bf05411d2efbc4ec49658939f Mon Sep 17 00:00:00 2001 From: Adam Halim Date: Tue, 14 Jan 2025 09:39:03 +0100 Subject: [PATCH 17/70] Add tests for dragging with gestures There were no test for viewport dragging using gesture previously, so let's add some. Note that there currently are some viewport dragging behaviours that we don't want to have, so some tests have commented out what our desired behaviour should be. --- tests/test.rfb.js | 93 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/tests/test.rfb.js b/tests/test.rfb.js index bdf86e60..b643e169 100644 --- a/tests/test.rfb.js +++ b/tests/test.rfb.js @@ -727,6 +727,20 @@ describe('Remote Frame Buffer protocol client', function () { 13, 9, 0x0); }); + it('should send button messages when tapping', function () { + // Just up and down + gestureStart('onetap', 13, 9, client); + gestureEnd('onetap', 13, 9, client); + + expect(RFB.messages.pointerEvent).to.have.been.calledThrice; + expect(RFB.messages.pointerEvent.firstCall).to.have.been.calledWith(client._sock, + 13, 9, 0x0); + expect(RFB.messages.pointerEvent.secondCall).to.have.been.calledWith(client._sock, + 13, 9, 0x1); + expect(RFB.messages.pointerEvent.thirdCall).to.have.been.calledWith(client._sock, + 13, 9, 0x0); + }); + it('should send button messages when release with small movement', function () { // Small movement sendMouseButtonEvent(13, 9, true, 0, client); @@ -787,6 +801,85 @@ describe('Remote Frame Buffer protocol client', function () { expect(client._display.viewportChangePos).to.have.been.calledWith(0, -5); }); + it('should initiate viewport dragging on sufficient drag gesture movement', function () { + sinon.spy(client._display, "viewportChangePos"); + + // Sufficient movement + gestureStart('drag', 13, 9, client); + gestureMove('drag', 43, 9, client); + + // FIXME: We don't want to send a pointer event here + // expect(RFB.messages.pointerEvent).to.not.have.been.called; + expect(client._display.viewportChangePos).to.have.been.calledOnce; + expect(client._display.viewportChangePos).to.have.been.calledWith(-30, 0); + + client._display.viewportChangePos.resetHistory(); + RFB.messages.pointerEvent.resetHistory(); + + // Now a small movement should move right away + + gestureMove('drag', 43, 14, client); + gestureEnd('drag', 43, 14, client); + + expect(RFB.messages.pointerEvent).to.not.have.been.called; + + // FIXME: We only want to move the viewport once + // expect(client._display.viewportChangePos).to.have.been.calledOnce; + expect(client._display.viewportChangePos).to.have.been.calledWith(0, -5); + }); + + it('should initiate viewport dragging on sufficient longpress gesture movement', function () { + sinon.spy(client._display, "viewportChangePos"); + + // A small movement below the threshold should not move. + gestureStart('longpress', 13, 9, client); + gestureMove('longpress', 14, 9, client); + + // FIXME: We don't want to send a pointer event here + // expect(RFB.messages.pointerEvent).to.not.have.been.called; + expect(client._display.viewportChangePos).to.not.have.been.called; + + client._display.viewportChangePos.resetHistory(); + RFB.messages.pointerEvent.resetHistory(); + + gestureMove('longpress', 43, 9, client); + gestureEnd('longpress', 43, 9, client); + + expect(RFB.messages.pointerEvent).to.not.have.been.called; + // FIXME: We only want to move the viewport once + // expect(client._display.viewportChangePos).to.have.been.calledOnce; + expect(client._display.viewportChangePos).to.have.been.calledWith(-30, 0); + }); + + it('should send button messages on small longpress gesture movement', function () { + sinon.spy(client._display, "viewportChangePos"); + + // A small movement below the threshold should not move. + gestureStart('longpress', 13, 9, client); + gestureMove('longpress', 14, 10, client); + + // FIXME: We don't want to send a pointer event here + // expect(RFB.messages.pointerEvent).to.not.have.been.called; + expect(client._display.viewportChangePos).to.not.have.been.called; + + client._display.viewportChangePos.resetHistory(); + RFB.messages.pointerEvent.resetHistory(); + + gestureEnd('longpress', 14, 9, client); + + // FIXME: We want the pointer event to come after the + // 'gestureEnd' call instead. + // expect(RFB.messages.pointerEvent).to.have.been.calledThrice; + // expect(RFB.messages.pointerEvent.firstCall).to.have.been.calledWith(client._sock, + // 14, 9, 0x0); + expect(RFB.messages.pointerEvent.firstCall).to.have.been.calledWith(client._sock, + 14, 9, 0x4); + expect(RFB.messages.pointerEvent.secondCall).to.have.been.calledWith(client._sock, + 14, 9, 0x0); + + expect(client._display.viewportChangePos).to.not.have.been.called; + }); + it('should not send button messages when dragging ends', function () { // First the movement From b9230cf23eb0f57dec14b5d7f7fb6e753638b0f5 Mon Sep 17 00:00:00 2001 From: Adam Halim Date: Fri, 29 Nov 2024 14:47:19 +0100 Subject: [PATCH 18/70] Use MouseEvent.buttons for button state tracking Instead of keeping track of button states ourselves by looking at MouseEvent.button, we can use the MouseEvent.buttons which already contains the state of all buttons. --- core/rfb.js | 246 ++++++++++++++++++++++++++++++---------------- tests/test.rfb.js | 143 ++++++++++++++------------- 2 files changed, 234 insertions(+), 155 deletions(-) diff --git a/core/rfb.js b/core/rfb.js index 3f946142..4b105cb5 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -1033,6 +1033,35 @@ export default class RFB extends EventTargetMixin { this.sendKey(keysym, code, down); } + static _convertButtonMask(buttons) { + /* The bits in MouseEvent.buttons property correspond + * to the following mouse buttons: + * 0: Left + * 1: Right + * 2: Middle + * 3: Back + * 4: Forward + * + * These bits needs to be converted to what they are defined as + * in the RFB protocol. + */ + + const buttonMaskMap = { + 0: 1 << 0, // Left + 1: 1 << 2, // Right + 2: 1 << 1, // Middle + 3: 1 << 7, // Back + }; + + let bmask = 0; + for (let i = 0; i < 4; i++) { + if (buttons & (1 << i)) { + bmask |= buttonMaskMap[i]; + } + } + return bmask; + } + _handleMouse(ev) { /* * We don't check connection status or viewOnly here as the @@ -1062,76 +1091,73 @@ export default class RFB extends EventTargetMixin { let pos = clientToElement(ev.clientX, ev.clientY, this._canvas); + let bmask = RFB._convertButtonMask(ev.buttons); + + let down = ev.type == 'mousedown'; switch (ev.type) { case 'mousedown': - setCapture(this._canvas); - this._handleMouseButton(pos.x, pos.y, - true, 1 << ev.button); - break; case 'mouseup': - this._handleMouseButton(pos.x, pos.y, - false, 1 << ev.button); + if (this.dragViewport) { + if (down && !this._viewportDragging) { + this._viewportDragging = true; + this._viewportDragPos = {'x': pos.x, 'y': pos.y}; + this._viewportHasMoved = false; + + // Skip sending mouse events, instead save the current + // mouse mask so we can send it later. + this._mouseButtonMask = bmask; + break; + } else { + this._viewportDragging = false; + + // If we actually performed a drag then we are done + // here and should not send any mouse events + if (this._viewportHasMoved) { + this._mouseButtonMask = bmask; + break; + } + // Otherwise we treat this as a mouse click event. + // Send the previously saved button mask, followed + // by the current button mask at the end of this + // function. + this._sendMouse(pos.x, pos.y, this._mouseButtonMask); + } + } + if (down) { + setCapture(this._canvas); + } + this._handleMouseButton(pos.x, pos.y, bmask); break; case 'mousemove': + if (this._viewportDragging) { + const deltaX = this._viewportDragPos.x - pos.x; + const deltaY = this._viewportDragPos.y - pos.y; + + if (this._viewportHasMoved || (Math.abs(deltaX) > dragThreshold || + Math.abs(deltaY) > dragThreshold)) { + this._viewportHasMoved = true; + + this._viewportDragPos = {'x': pos.x, 'y': pos.y}; + this._display.viewportChangePos(deltaX, deltaY); + } + + // Skip sending mouse events + break; + } this._handleMouseMove(pos.x, pos.y); break; } } - _handleMouseButton(x, y, down, bmask) { - if (this.dragViewport) { - if (down && !this._viewportDragging) { - this._viewportDragging = true; - this._viewportDragPos = {'x': x, 'y': y}; - this._viewportHasMoved = false; - - // Skip sending mouse events - return; - } else { - this._viewportDragging = false; - - // If we actually performed a drag then we are done - // here and should not send any mouse events - if (this._viewportHasMoved) { - return; - } - - // Otherwise we treat this as a mouse click event. - // Send the button down event here, as the button up - // event is sent at the end of this function. - this._sendMouse(x, y, bmask); - } - } - + _handleMouseButton(x, y, bmask) { // Flush waiting move event first this._flushMouseMoveTimer(x, y); - if (down) { - this._mouseButtonMask |= bmask; - } else { - this._mouseButtonMask &= ~bmask; - } - + this._mouseButtonMask = bmask; this._sendMouse(x, y, this._mouseButtonMask); } _handleMouseMove(x, y) { - if (this._viewportDragging) { - const deltaX = this._viewportDragPos.x - x; - const deltaY = this._viewportDragPos.y - y; - - if (this._viewportHasMoved || (Math.abs(deltaX) > dragThreshold || - Math.abs(deltaY) > dragThreshold)) { - this._viewportHasMoved = true; - - this._viewportDragPos = {'x': x, 'y': y}; - this._display.viewportChangePos(deltaX, deltaY); - } - - // Skip sending mouse events - return; - } - this._mousePos = { 'x': x, 'y': y }; // Limit many mouse move events to one every MOUSE_MOVE_DELAY ms @@ -1175,6 +1201,7 @@ export default class RFB extends EventTargetMixin { let pos = clientToElement(ev.clientX, ev.clientY, this._canvas); + let bmask = RFB._convertButtonMask(ev.buttons); let dX = ev.deltaX; let dY = ev.deltaY; @@ -1194,26 +1221,27 @@ export default class RFB extends EventTargetMixin { this._accumulatedWheelDeltaX += dX; this._accumulatedWheelDeltaY += dY; + // Generate a mouse wheel step event when the accumulated delta // for one of the axes is large enough. if (Math.abs(this._accumulatedWheelDeltaX) >= WHEEL_STEP) { if (this._accumulatedWheelDeltaX < 0) { - this._handleMouseButton(pos.x, pos.y, true, 1 << 5); - this._handleMouseButton(pos.x, pos.y, false, 1 << 5); + this._handleMouseButton(pos.x, pos.y, bmask | 1 << 5); + this._handleMouseButton(pos.x, pos.y, bmask); } else if (this._accumulatedWheelDeltaX > 0) { - this._handleMouseButton(pos.x, pos.y, true, 1 << 6); - this._handleMouseButton(pos.x, pos.y, false, 1 << 6); + this._handleMouseButton(pos.x, pos.y, bmask | 1 << 6); + this._handleMouseButton(pos.x, pos.y, bmask); } this._accumulatedWheelDeltaX = 0; } if (Math.abs(this._accumulatedWheelDeltaY) >= WHEEL_STEP) { if (this._accumulatedWheelDeltaY < 0) { - this._handleMouseButton(pos.x, pos.y, true, 1 << 3); - this._handleMouseButton(pos.x, pos.y, false, 1 << 3); + this._handleMouseButton(pos.x, pos.y, bmask | 1 << 3); + this._handleMouseButton(pos.x, pos.y, bmask); } else if (this._accumulatedWheelDeltaY > 0) { - this._handleMouseButton(pos.x, pos.y, true, 1 << 4); - this._handleMouseButton(pos.x, pos.y, false, 1 << 4); + this._handleMouseButton(pos.x, pos.y, bmask | 1 << 4); + this._handleMouseButton(pos.x, pos.y, bmask); } this._accumulatedWheelDeltaY = 0; @@ -1252,8 +1280,8 @@ export default class RFB extends EventTargetMixin { this._gestureLastTapTime = Date.now(); this._fakeMouseMove(this._gestureFirstDoubleTapEv, pos.x, pos.y); - this._handleMouseButton(pos.x, pos.y, true, bmask); - this._handleMouseButton(pos.x, pos.y, false, bmask); + this._handleMouseButton(pos.x, pos.y, bmask); + this._handleMouseButton(pos.x, pos.y, 0x0); } _handleGesture(ev) { @@ -1274,14 +1302,31 @@ export default class RFB extends EventTargetMixin { this._handleTapEvent(ev, 0x2); break; case 'drag': - this._fakeMouseMove(ev, pos.x, pos.y); - this._handleMouseButton(pos.x, pos.y, true, 0x1); + if (this.dragViewport) { + this._viewportHasMoved = false; + this._viewportDragging = true; + this._viewportDragPos = {'x': pos.x, 'y': pos.y}; + + this._fakeMouseMove(ev, pos.x, pos.y); + } else { + this._fakeMouseMove(ev, pos.x, pos.y); + this._handleMouseButton(pos.x, pos.y, 0x1); + } break; case 'longpress': - this._fakeMouseMove(ev, pos.x, pos.y); - this._handleMouseButton(pos.x, pos.y, true, 0x4); - break; + if (this.dragViewport) { + // If dragViewport is true, we need to wait to see + // if we have dragged outside the threshold before + // sending any events to the server. + this._viewportHasMoved = false; + this._viewportDragPos = {'x': pos.x, 'y': pos.y}; + this._fakeMouseMove(ev, pos.x, pos.y); + } else { + this._fakeMouseMove(ev, pos.x, pos.y); + this._handleMouseButton(pos.x, pos.y, 0x4); + } + break; case 'twodrag': this._gestureLastMagnitudeX = ev.detail.magnitudeX; this._gestureLastMagnitudeY = ev.detail.magnitudeY; @@ -1303,6 +1348,19 @@ export default class RFB extends EventTargetMixin { break; case 'drag': case 'longpress': + if (this.dragViewport) { + this._viewportDragging = true; + const deltaX = this._viewportDragPos.x - pos.x; + const deltaY = this._viewportDragPos.y - pos.y; + + if (this._viewportHasMoved || (Math.abs(deltaX) > dragThreshold || + Math.abs(deltaY) > dragThreshold)) { + this._viewportHasMoved = true; + + this._viewportDragPos = {'x': pos.x, 'y': pos.y}; + this._display.viewportChangePos(deltaX, deltaY); + } + } this._fakeMouseMove(ev, pos.x, pos.y); break; case 'twodrag': @@ -1311,23 +1369,23 @@ export default class RFB extends EventTargetMixin { // every update. this._fakeMouseMove(ev, pos.x, pos.y); while ((ev.detail.magnitudeY - this._gestureLastMagnitudeY) > GESTURE_SCRLSENS) { - this._handleMouseButton(pos.x, pos.y, true, 0x8); - this._handleMouseButton(pos.x, pos.y, false, 0x8); + this._handleMouseButton(pos.x, pos.y, 0x8); + this._handleMouseButton(pos.x, pos.y, 0x0); this._gestureLastMagnitudeY += GESTURE_SCRLSENS; } while ((ev.detail.magnitudeY - this._gestureLastMagnitudeY) < -GESTURE_SCRLSENS) { - this._handleMouseButton(pos.x, pos.y, true, 0x10); - this._handleMouseButton(pos.x, pos.y, false, 0x10); + this._handleMouseButton(pos.x, pos.y, 0x10); + this._handleMouseButton(pos.x, pos.y, 0x0); this._gestureLastMagnitudeY -= GESTURE_SCRLSENS; } while ((ev.detail.magnitudeX - this._gestureLastMagnitudeX) > GESTURE_SCRLSENS) { - this._handleMouseButton(pos.x, pos.y, true, 0x20); - this._handleMouseButton(pos.x, pos.y, false, 0x20); + this._handleMouseButton(pos.x, pos.y, 0x20); + this._handleMouseButton(pos.x, pos.y, 0x0); this._gestureLastMagnitudeX += GESTURE_SCRLSENS; } while ((ev.detail.magnitudeX - this._gestureLastMagnitudeX) < -GESTURE_SCRLSENS) { - this._handleMouseButton(pos.x, pos.y, true, 0x40); - this._handleMouseButton(pos.x, pos.y, false, 0x40); + this._handleMouseButton(pos.x, pos.y, 0x40); + this._handleMouseButton(pos.x, pos.y, 0x0); this._gestureLastMagnitudeX -= GESTURE_SCRLSENS; } break; @@ -1340,13 +1398,13 @@ export default class RFB extends EventTargetMixin { if (Math.abs(magnitude - this._gestureLastMagnitudeX) > GESTURE_ZOOMSENS) { this._handleKeyEvent(KeyTable.XK_Control_L, "ControlLeft", true); while ((magnitude - this._gestureLastMagnitudeX) > GESTURE_ZOOMSENS) { - this._handleMouseButton(pos.x, pos.y, true, 0x8); - this._handleMouseButton(pos.x, pos.y, false, 0x8); + this._handleMouseButton(pos.x, pos.y, 0x8); + this._handleMouseButton(pos.x, pos.y, 0x0); this._gestureLastMagnitudeX += GESTURE_ZOOMSENS; } while ((magnitude - this._gestureLastMagnitudeX) < -GESTURE_ZOOMSENS) { - this._handleMouseButton(pos.x, pos.y, true, 0x10); - this._handleMouseButton(pos.x, pos.y, false, 0x10); + this._handleMouseButton(pos.x, pos.y, 0x10); + this._handleMouseButton(pos.x, pos.y, 0x0); this._gestureLastMagnitudeX -= GESTURE_ZOOMSENS; } } @@ -1364,12 +1422,32 @@ export default class RFB extends EventTargetMixin { case 'twodrag': break; case 'drag': - this._fakeMouseMove(ev, pos.x, pos.y); - this._handleMouseButton(pos.x, pos.y, false, 0x1); + if (this.dragViewport) { + this._viewportDragging = false; + } else { + this._fakeMouseMove(ev, pos.x, pos.y); + this._handleMouseButton(pos.x, pos.y, 0x0); + } break; case 'longpress': - this._fakeMouseMove(ev, pos.x, pos.y); - this._handleMouseButton(pos.x, pos.y, false, 0x4); + if (this._viewportHasMoved) { + // We don't want to send any events if we have moved + // our viewport + break; + } + + if (this.dragViewport && !this._viewportHasMoved) { + this._fakeMouseMove(ev, pos.x, pos.y); + // If dragViewport is true, we need to wait to see + // if we have dragged outside the threshold before + // sending any events to the server. + this._handleMouseButton(pos.x, pos.y, 0x4); + this._handleMouseButton(pos.x, pos.y, 0x0); + this._viewportDragging = false; + } else { + this._fakeMouseMove(ev, pos.x, pos.y); + this._handleMouseButton(pos.x, pos.y, 0x0); + } break; } break; diff --git a/tests/test.rfb.js b/tests/test.rfb.js index b643e169..84298188 100644 --- a/tests/test.rfb.js +++ b/tests/test.rfb.js @@ -176,7 +176,7 @@ describe('Remote Frame Buffer protocol client', function () { return res; } - function sendMouseMoveEvent(x, y, client) { + function sendMouseMoveEvent(x, y, buttons, client) { let pos = elementToClient(x, y, client); let ev; @@ -184,11 +184,12 @@ describe('Remote Frame Buffer protocol client', function () { { 'screenX': pos.x + window.screenX, 'screenY': pos.y + window.screenY, 'clientX': pos.x, - 'clientY': pos.y }); + 'clientY': pos.y, + 'buttons': buttons }); client._canvas.dispatchEvent(ev); } - function sendMouseButtonEvent(x, y, down, button, client) { + function sendMouseButtonEvent(x, y, down, buttons, client) { let pos = elementToClient(x, y, client); let ev; @@ -197,8 +198,7 @@ describe('Remote Frame Buffer protocol client', function () { 'screenY': pos.y + window.screenY, 'clientX': pos.x, 'clientY': pos.y, - 'button': button, - 'buttons': 1 << button }); + 'buttons': buttons}); client._canvas.dispatchEvent(ev); } @@ -711,14 +711,14 @@ describe('Remote Frame Buffer protocol client', function () { }); it('should not send button messages when initiating viewport dragging', function () { - sendMouseButtonEvent(13, 9, true, 0, client); + sendMouseButtonEvent(13, 9, true, 0x1, client); expect(RFB.messages.pointerEvent).to.not.have.been.called; }); it('should send button messages when release without movement', function () { // Just up and down - sendMouseButtonEvent(13, 9, true, 0, client); - sendMouseButtonEvent(13, 9, false, 0, client); + sendMouseButtonEvent(13, 9, true, 0x1, client); + sendMouseButtonEvent(13, 9, false, 0x0, client); expect(RFB.messages.pointerEvent).to.have.been.calledTwice; expect(RFB.messages.pointerEvent.firstCall).to.have.been.calledWith(client._sock, @@ -743,9 +743,9 @@ describe('Remote Frame Buffer protocol client', function () { it('should send button messages when release with small movement', function () { // Small movement - sendMouseButtonEvent(13, 9, true, 0, client); - sendMouseMoveEvent(15, 14, client); - sendMouseButtonEvent(15, 14, false, 0, client); + sendMouseButtonEvent(13, 9, true, 0x1, client); + sendMouseMoveEvent(15, 14, 0x1, client); + sendMouseButtonEvent(15, 14, false, 0x0, client); expect(RFB.messages.pointerEvent).to.have.been.calledTwice; expect(RFB.messages.pointerEvent.firstCall).to.have.been.calledWith(client._sock, @@ -757,15 +757,15 @@ describe('Remote Frame Buffer protocol client', function () { it('should not send button messages when in view only', function () { client._viewOnly = true; - sendMouseButtonEvent(13, 9, true, 0, client); - sendMouseButtonEvent(13, 9, false, 0, client); + sendMouseButtonEvent(13, 9, true, 0x1, client); + sendMouseButtonEvent(13, 9, false, 0x0, client); expect(RFB.messages.pointerEvent).to.not.have.been.called; }); it('should send button message directly when drag is disabled', function () { client.dragViewport = false; - sendMouseButtonEvent(13, 9, true, 0, client); + sendMouseButtonEvent(13, 9, true, 0x1, client); expect(RFB.messages.pointerEvent).to.have.been.calledOnce; expect(RFB.messages.pointerEvent.firstCall).to.have.been.calledWith(client._sock, 13, 9, 0x1); @@ -775,8 +775,8 @@ describe('Remote Frame Buffer protocol client', function () { sinon.spy(client._display, "viewportChangePos"); // Too small movement - sendMouseButtonEvent(13, 9, true, 0, client); - sendMouseMoveEvent(18, 9, client); + sendMouseButtonEvent(13, 9, true, 0x1, client); + sendMouseMoveEvent(18, 9, 0x1, client); expect(RFB.messages.pointerEvent).to.not.have.been.called; @@ -784,7 +784,7 @@ describe('Remote Frame Buffer protocol client', function () { // Sufficient movement - sendMouseMoveEvent(43, 9, client); + sendMouseMoveEvent(43, 9, 0x1, client); expect(RFB.messages.pointerEvent).to.not.have.been.called; expect(client._display.viewportChangePos).to.have.been.calledOnce; @@ -794,7 +794,7 @@ describe('Remote Frame Buffer protocol client', function () { // Now a small movement should move right away - sendMouseMoveEvent(43, 14, client); + sendMouseMoveEvent(43, 14, 0x1, client); expect(RFB.messages.pointerEvent).to.not.have.been.called; expect(client._display.viewportChangePos).to.have.been.calledOnce; @@ -823,8 +823,7 @@ describe('Remote Frame Buffer protocol client', function () { expect(RFB.messages.pointerEvent).to.not.have.been.called; - // FIXME: We only want to move the viewport once - // expect(client._display.viewportChangePos).to.have.been.calledOnce; + expect(client._display.viewportChangePos).to.have.been.calledOnce; expect(client._display.viewportChangePos).to.have.been.calledWith(0, -5); }); @@ -867,14 +866,12 @@ describe('Remote Frame Buffer protocol client', function () { gestureEnd('longpress', 14, 9, client); - // FIXME: We want the pointer event to come after the - // 'gestureEnd' call instead. - // expect(RFB.messages.pointerEvent).to.have.been.calledThrice; - // expect(RFB.messages.pointerEvent.firstCall).to.have.been.calledWith(client._sock, - // 14, 9, 0x0); + expect(RFB.messages.pointerEvent).to.have.been.calledThrice; expect(RFB.messages.pointerEvent.firstCall).to.have.been.calledWith(client._sock, - 14, 9, 0x4); + 14, 9, 0x0); expect(RFB.messages.pointerEvent.secondCall).to.have.been.calledWith(client._sock, + 14, 9, 0x4); + expect(RFB.messages.pointerEvent.thirdCall).to.have.been.calledWith(client._sock, 14, 9, 0x0); expect(client._display.viewportChangePos).to.not.have.been.called; @@ -883,9 +880,9 @@ describe('Remote Frame Buffer protocol client', function () { it('should not send button messages when dragging ends', function () { // First the movement - sendMouseButtonEvent(13, 9, true, 0, client); - sendMouseMoveEvent(43, 9, client); - sendMouseButtonEvent(43, 9, false, 0, client); + sendMouseButtonEvent(13, 9, true, 0x1, client); + sendMouseMoveEvent(43, 9, 0x1, client); + sendMouseButtonEvent(43, 9, false, 0x0, client); expect(RFB.messages.pointerEvent).to.not.have.been.called; }); @@ -893,15 +890,15 @@ describe('Remote Frame Buffer protocol client', function () { it('should terminate viewport dragging on a button up event', function () { // First the dragging movement - sendMouseButtonEvent(13, 9, true, 0, client); - sendMouseMoveEvent(43, 9, client); - sendMouseButtonEvent(43, 9, false, 0, client); + sendMouseButtonEvent(13, 9, true, 0x1, client); + sendMouseMoveEvent(43, 9, 0x1, client); + sendMouseButtonEvent(43, 9, false, 0x0, client); // Another movement now should not move the viewport sinon.spy(client._display, "viewportChangePos"); - sendMouseMoveEvent(43, 59, client); + sendMouseMoveEvent(43, 59, 0x0, client); expect(client._display.viewportChangePos).to.not.have.been.called; }); @@ -3773,60 +3770,62 @@ describe('Remote Frame Buffer protocol client', function () { it('should not send button messages in view-only mode', function () { client._viewOnly = true; - sendMouseButtonEvent(10, 10, true, 0, client); + sendMouseButtonEvent(10, 10, true, 0x1, client); + clock.tick(50); expect(pointerEvent).to.not.have.been.called; }); it('should not send movement messages in view-only mode', function () { client._viewOnly = true; - sendMouseMoveEvent(10, 10, client); + sendMouseMoveEvent(10, 10, 0x0, client); + clock.tick(50); expect(pointerEvent).to.not.have.been.called; }); it('should handle left mouse button', function () { - sendMouseButtonEvent(10, 10, true, 0, client); + sendMouseButtonEvent(10, 10, true, 0x1, client); expect(pointerEvent).to.have.been.calledOnceWith(client._sock, 10, 10, 0x1); pointerEvent.resetHistory(); - sendMouseButtonEvent(10, 10, false, 0, client); + sendMouseButtonEvent(10, 10, false, 0x0, client); expect(pointerEvent).to.have.been.calledOnceWith(client._sock, 10, 10, 0x0); }); it('should handle middle mouse button', function () { - sendMouseButtonEvent(10, 10, true, 1, client); + sendMouseButtonEvent(10, 10, true, 0x4, client); expect(pointerEvent).to.have.been.calledOnceWith(client._sock, 10, 10, 0x2); pointerEvent.resetHistory(); - sendMouseButtonEvent(10, 10, false, 1, client); + sendMouseButtonEvent(10, 10, false, 0x0, client); expect(pointerEvent).to.have.been.calledOnceWith(client._sock, 10, 10, 0x0); }); it('should handle right mouse button', function () { - sendMouseButtonEvent(10, 10, true, 2, client); + sendMouseButtonEvent(10, 10, true, 0x2, client); expect(pointerEvent).to.have.been.calledOnceWith(client._sock, 10, 10, 0x4); pointerEvent.resetHistory(); - sendMouseButtonEvent(10, 10, false, 2, client); + sendMouseButtonEvent(10, 10, false, 0x0, client); expect(pointerEvent).to.have.been.calledOnceWith(client._sock, 10, 10, 0x0); }); it('should handle multiple mouse buttons', function () { - sendMouseButtonEvent(10, 10, true, 0, client); - sendMouseButtonEvent(10, 10, true, 2, client); + sendMouseButtonEvent(10, 10, true, 0x1, client); + sendMouseButtonEvent(10, 10, true, 0x3, client); expect(pointerEvent).to.have.been.calledTwice; expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock, @@ -3836,8 +3835,9 @@ describe('Remote Frame Buffer protocol client', function () { pointerEvent.resetHistory(); - sendMouseButtonEvent(10, 10, false, 0, client); - sendMouseButtonEvent(10, 10, false, 2, client); + + sendMouseButtonEvent(10, 10, false, 0x2, client); + sendMouseButtonEvent(10, 10, false, 0x0, client); expect(pointerEvent).to.have.been.calledTwice; expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock, @@ -3847,14 +3847,14 @@ describe('Remote Frame Buffer protocol client', function () { }); it('should handle mouse movement', function () { - sendMouseMoveEvent(50, 70, client); + sendMouseMoveEvent(50, 70, 0x0, client); expect(pointerEvent).to.have.been.calledOnceWith(client._sock, 50, 70, 0x0); }); it('should handle click and drag', function () { - sendMouseButtonEvent(10, 10, true, 0, client); - sendMouseMoveEvent(50, 70, client); + sendMouseButtonEvent(10, 10, true, 0x1, client); + sendMouseMoveEvent(50, 70, 0x1, client); expect(pointerEvent).to.have.been.calledTwice; expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock, @@ -3864,7 +3864,7 @@ describe('Remote Frame Buffer protocol client', function () { pointerEvent.resetHistory(); - sendMouseButtonEvent(50, 70, false, 0, client); + sendMouseButtonEvent(50, 70, false, 0x0, client); expect(pointerEvent).to.have.been.calledOnceWith(client._sock, 50, 70, 0x0); @@ -3872,15 +3872,15 @@ describe('Remote Frame Buffer protocol client', function () { describe('Event aggregation', function () { it('should send a single pointer event on mouse movement', function () { - sendMouseMoveEvent(50, 70, client); + sendMouseMoveEvent(50, 70, 0x0, client); clock.tick(100); expect(pointerEvent).to.have.been.calledOnceWith(client._sock, 50, 70, 0x0); }); it('should delay one move if two events are too close', function () { - sendMouseMoveEvent(18, 30, client); - sendMouseMoveEvent(20, 50, client); + sendMouseMoveEvent(18, 30, 0x0, client); + sendMouseMoveEvent(20, 50, 0x0, client); expect(pointerEvent).to.have.been.calledOnceWith(client._sock, 18, 30, 0x0); @@ -3893,9 +3893,9 @@ describe('Remote Frame Buffer protocol client', function () { }); it('should only send first and last move of many close events', function () { - sendMouseMoveEvent(18, 30, client); - sendMouseMoveEvent(20, 50, client); - sendMouseMoveEvent(21, 55, client); + sendMouseMoveEvent(18, 30, 0x0, client); + sendMouseMoveEvent(20, 50, 0x0, client); + sendMouseMoveEvent(21, 55, 0x0, client); expect(pointerEvent).to.have.been.calledOnceWith(client._sock, 18, 30, 0x0); @@ -3909,46 +3909,46 @@ describe('Remote Frame Buffer protocol client', function () { // We selected the 17ms since that is ~60 FPS it('should send move events every 17 ms', function () { - sendMouseMoveEvent(1, 10, client); // instant send + sendMouseMoveEvent(1, 10, 0x0, client); // instant send clock.tick(10); expect(pointerEvent).to.have.been.calledOnceWith(client._sock, 1, 10, 0x0); pointerEvent.resetHistory(); - sendMouseMoveEvent(2, 20, client); // delayed + sendMouseMoveEvent(2, 20, 0x0, client); // delayed clock.tick(10); // timeout send expect(pointerEvent).to.have.been.calledOnceWith(client._sock, 2, 20, 0x0); pointerEvent.resetHistory(); - sendMouseMoveEvent(3, 30, client); // delayed + sendMouseMoveEvent(3, 30, 0x0, client); // delayed clock.tick(10); - sendMouseMoveEvent(4, 40, client); // delayed + sendMouseMoveEvent(4, 40, 0x0, client); // delayed clock.tick(10); // timeout send expect(pointerEvent).to.have.been.calledOnceWith(client._sock, 4, 40, 0x0); pointerEvent.resetHistory(); - sendMouseMoveEvent(5, 50, client); // delayed + sendMouseMoveEvent(5, 50, 0x0, client); // delayed expect(pointerEvent).to.not.have.been.called; }); it('should send waiting move events before a button press', function () { - sendMouseMoveEvent(13, 9, client); + sendMouseMoveEvent(13, 9, 0x0, client); expect(pointerEvent).to.have.been.calledOnceWith(client._sock, 13, 9, 0x0); pointerEvent.resetHistory(); - sendMouseMoveEvent(20, 70, client); + sendMouseMoveEvent(20, 70, 0x0, client); expect(pointerEvent).to.not.have.been.called; - sendMouseButtonEvent(20, 70, true, 0, client); + sendMouseButtonEvent(20, 70, true, 0x1, client); expect(pointerEvent).to.have.been.calledTwice; expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock, @@ -3958,7 +3958,7 @@ describe('Remote Frame Buffer protocol client', function () { }); it('should send move events with enough time apart normally', function () { - sendMouseMoveEvent(58, 60, client); + sendMouseMoveEvent(58, 60, 0x0, client); expect(pointerEvent).to.have.been.calledOnceWith(client._sock, 58, 60, 0x0); @@ -3966,7 +3966,7 @@ describe('Remote Frame Buffer protocol client', function () { clock.tick(20); - sendMouseMoveEvent(25, 60, client); + sendMouseMoveEvent(25, 60, 0x0, client); expect(pointerEvent).to.have.been.calledOnceWith(client._sock, 25, 60, 0x0); @@ -3974,13 +3974,13 @@ describe('Remote Frame Buffer protocol client', function () { }); it('should not send waiting move events if disconnected', function () { - sendMouseMoveEvent(88, 99, client); + sendMouseMoveEvent(88, 99, 0x0, client); expect(pointerEvent).to.have.been.calledOnceWith(client._sock, 88, 99, 0x0); pointerEvent.resetHistory(); - sendMouseMoveEvent(66, 77, client); + sendMouseMoveEvent(66, 77, 0x0, client); client.disconnect(); clock.tick(20); @@ -3998,7 +3998,7 @@ describe('Remote Frame Buffer protocol client', function () { }); describe('Wheel events', function () { - function sendWheelEvent(x, y, dx, dy, mode=0) { + function sendWheelEvent(x, y, dx, dy, mode=0, buttons=0) { let pos = elementToClient(x, y, client); let ev; @@ -4009,7 +4009,8 @@ describe('Remote Frame Buffer protocol client', function () { 'clientY': pos.y, 'deltaX': dx, 'deltaY': dy, - 'deltaMode': mode }); + 'deltaMode': mode, + 'buttons': buttons }); client._canvas.dispatchEvent(ev); } @@ -4107,8 +4108,8 @@ describe('Remote Frame Buffer protocol client', function () { }); it('should handle wheel event with buttons pressed', function () { - sendMouseButtonEvent(10, 10, true, 0, client); - sendWheelEvent(10, 10, 0, 50); + sendMouseButtonEvent(10, 10, true, 0x1, client); + sendWheelEvent(10, 10, 0, 50, 0, 0x1); expect(pointerEvent).to.have.been.called.calledThrice; From d1548c12ecb08a2aa5a871d8a621563f9688c1d1 Mon Sep 17 00:00:00 2001 From: Adam Halim Date: Mon, 13 Jan 2025 15:43:47 +0100 Subject: [PATCH 19/70] Don't send mouse events when dragging viewport We don't want to send any mouse events to the server when dragging the viewport. Instead, we treat them as a client-only operation. --- core/rfb.js | 7 ++----- tests/test.rfb.js | 16 ++++++---------- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/core/rfb.js b/core/rfb.js index 4b105cb5..5e08e32f 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -1306,8 +1306,6 @@ export default class RFB extends EventTargetMixin { this._viewportHasMoved = false; this._viewportDragging = true; this._viewportDragPos = {'x': pos.x, 'y': pos.y}; - - this._fakeMouseMove(ev, pos.x, pos.y); } else { this._fakeMouseMove(ev, pos.x, pos.y); this._handleMouseButton(pos.x, pos.y, 0x1); @@ -1320,8 +1318,6 @@ export default class RFB extends EventTargetMixin { // sending any events to the server. this._viewportHasMoved = false; this._viewportDragPos = {'x': pos.x, 'y': pos.y}; - - this._fakeMouseMove(ev, pos.x, pos.y); } else { this._fakeMouseMove(ev, pos.x, pos.y); this._handleMouseButton(pos.x, pos.y, 0x4); @@ -1360,8 +1356,9 @@ export default class RFB extends EventTargetMixin { this._viewportDragPos = {'x': pos.x, 'y': pos.y}; this._display.viewportChangePos(deltaX, deltaY); } + } else { + this._fakeMouseMove(ev, pos.x, pos.y); } - this._fakeMouseMove(ev, pos.x, pos.y); break; case 'twodrag': // Always scroll in the same position. diff --git a/tests/test.rfb.js b/tests/test.rfb.js index 84298188..85c9eb66 100644 --- a/tests/test.rfb.js +++ b/tests/test.rfb.js @@ -808,8 +808,7 @@ describe('Remote Frame Buffer protocol client', function () { gestureStart('drag', 13, 9, client); gestureMove('drag', 43, 9, client); - // FIXME: We don't want to send a pointer event here - // expect(RFB.messages.pointerEvent).to.not.have.been.called; + expect(RFB.messages.pointerEvent).to.not.have.been.called; expect(client._display.viewportChangePos).to.have.been.calledOnce; expect(client._display.viewportChangePos).to.have.been.calledWith(-30, 0); @@ -834,8 +833,7 @@ describe('Remote Frame Buffer protocol client', function () { gestureStart('longpress', 13, 9, client); gestureMove('longpress', 14, 9, client); - // FIXME: We don't want to send a pointer event here - // expect(RFB.messages.pointerEvent).to.not.have.been.called; + expect(RFB.messages.pointerEvent).to.not.have.been.called; expect(client._display.viewportChangePos).to.not.have.been.called; client._display.viewportChangePos.resetHistory(); @@ -845,8 +843,7 @@ describe('Remote Frame Buffer protocol client', function () { gestureEnd('longpress', 43, 9, client); expect(RFB.messages.pointerEvent).to.not.have.been.called; - // FIXME: We only want to move the viewport once - // expect(client._display.viewportChangePos).to.have.been.calledOnce; + expect(client._display.viewportChangePos).to.have.been.calledOnce; expect(client._display.viewportChangePos).to.have.been.calledWith(-30, 0); }); @@ -857,8 +854,7 @@ describe('Remote Frame Buffer protocol client', function () { gestureStart('longpress', 13, 9, client); gestureMove('longpress', 14, 10, client); - // FIXME: We don't want to send a pointer event here - // expect(RFB.messages.pointerEvent).to.not.have.been.called; + expect(RFB.messages.pointerEvent).to.not.have.been.called; expect(client._display.viewportChangePos).to.not.have.been.called; client._display.viewportChangePos.resetHistory(); @@ -870,9 +866,9 @@ describe('Remote Frame Buffer protocol client', function () { expect(RFB.messages.pointerEvent.firstCall).to.have.been.calledWith(client._sock, 14, 9, 0x0); expect(RFB.messages.pointerEvent.secondCall).to.have.been.calledWith(client._sock, - 14, 9, 0x4); + 14, 9, 0x4); expect(RFB.messages.pointerEvent.thirdCall).to.have.been.calledWith(client._sock, - 14, 9, 0x0); + 14, 9, 0x0); expect(client._display.viewportChangePos).to.not.have.been.called; }); From 6383fa6384ed672c83f63693cdc3609851889a0a Mon Sep 17 00:00:00 2001 From: Adam Halim Date: Tue, 14 Jan 2025 12:32:51 +0100 Subject: [PATCH 20/70] Flush mouseMove when initiating viewport dragging We want to flush pending mouse moves before we initiate viewport dragging. Before this commit, there were scenarios where the _mouseButtonMask would track a released button as being down. --- core/rfb.js | 2 ++ tests/test.rfb.js | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/core/rfb.js b/core/rfb.js index 5e08e32f..89e9197d 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -1103,6 +1103,8 @@ export default class RFB extends EventTargetMixin { this._viewportDragPos = {'x': pos.x, 'y': pos.y}; this._viewportHasMoved = false; + this._flushMouseMoveTimer(pos.x, pos.y); + // Skip sending mouse events, instead save the current // mouse mask so we can send it later. this._mouseButtonMask = bmask; diff --git a/tests/test.rfb.js b/tests/test.rfb.js index 85c9eb66..62f2a649 100644 --- a/tests/test.rfb.js +++ b/tests/test.rfb.js @@ -898,6 +898,24 @@ describe('Remote Frame Buffer protocol client', function () { expect(client._display.viewportChangePos).to.not.have.been.called; }); + + it('should flush move events when initiating viewport drag', function () { + sendMouseMoveEvent(13, 9, 0x0, client); + sendMouseMoveEvent(14, 9, 0x0, client); + sendMouseButtonEvent(14, 9, true, 0x1, client); + + expect(RFB.messages.pointerEvent).to.have.been.calledTwice; + expect(RFB.messages.pointerEvent.firstCall).to.have.been.calledWith(client._sock, + 13, 9, 0x0); + expect(RFB.messages.pointerEvent.secondCall).to.have.been.calledWith(client._sock, + 14, 9, 0x0); + + RFB.messages.pointerEvent.resetHistory(); + + clock.tick(100); + + expect(RFB.messages.pointerEvent).to.not.have.been.called;; + }); }); }); From e8602f23abede3fb981ff605b2c8e511a024f3f8 Mon Sep 17 00:00:00 2001 From: Adam Halim Date: Tue, 14 Jan 2025 16:26:11 +0100 Subject: [PATCH 21/70] Move sendFbuMsg() to broader scope This is needed if we want to use this function elsewhere in our tests. --- tests/test.rfb.js | 48 +++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/tests/test.rfb.js b/tests/test.rfb.js index 62f2a649..cb2945a5 100644 --- a/tests/test.rfb.js +++ b/tests/test.rfb.js @@ -233,6 +233,30 @@ describe('Remote Frame Buffer protocol client', function () { client._canvas.dispatchEvent(ev); } + function sendFbuMsg(rectInfo, rectData, client, rectCnt) { + let data = []; + + if (!rectCnt || rectCnt > -1) { + // header + data.push(0); // msg type + data.push(0); // padding + push16(data, rectCnt || rectData.length); + } + + for (let i = 0; i < rectData.length; i++) { + if (rectInfo[i]) { + push16(data, rectInfo[i].x); + push16(data, rectInfo[i].y); + push16(data, rectInfo[i].width); + push16(data, rectInfo[i].height); + push32(data, rectInfo[i].encoding); + } + data = data.concat(rectData[i]); + } + + client._sock._websocket._receiveData(new Uint8Array(data)); + } + describe('Connecting/Disconnecting', function () { describe('#RFB (constructor)', function () { let open, attach; @@ -2757,30 +2781,6 @@ describe('Remote Frame Buffer protocol client', function () { }); describe('Framebuffer update handling', function () { - function sendFbuMsg(rectInfo, rectData, client, rectCnt) { - let data = []; - - if (!rectCnt || rectCnt > -1) { - // header - data.push(0); // msg type - data.push(0); // padding - push16(data, rectCnt || rectData.length); - } - - for (let i = 0; i < rectData.length; i++) { - if (rectInfo[i]) { - push16(data, rectInfo[i].x); - push16(data, rectInfo[i].y); - push16(data, rectInfo[i].width); - push16(data, rectInfo[i].height); - push32(data, rectInfo[i].encoding); - } - data = data.concat(rectData[i]); - } - - client._sock._websocket._receiveData(new Uint8Array(data)); - } - it('should send an update request if there is sufficient data', function () { let esock = new Websock(); let ews = new FakeWebSocket(); From e081d1415ac0f3620d5ff3f81080711ef20002de Mon Sep 17 00:00:00 2001 From: Adam Halim Date: Thu, 9 Jan 2025 09:19:21 +0100 Subject: [PATCH 22/70] Add support for forward and back mouse buttons This commit implements the extendedMouseButtons pseudo-encoding, which makes it possible to use the forward and back mouse buttons. --- core/encodings.js | 1 + core/rfb.js | 51 ++++++++++++++++++++++++++++++++++++++++++++--- tests/test.rfb.js | 48 +++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 96 insertions(+), 4 deletions(-) diff --git a/core/encodings.js b/core/encodings.js index bf25ac91..7afcb17f 100644 --- a/core/encodings.js +++ b/core/encodings.js @@ -30,6 +30,7 @@ export const encodings = { pseudoEncodingXvp: -309, pseudoEncodingFence: -312, pseudoEncodingContinuousUpdates: -313, + pseudoEncodingExtendedMouseButtons: -316, pseudoEncodingCompressLevel9: -247, pseudoEncodingCompressLevel0: -256, pseudoEncodingVMwareCursor: 0x574d5664, diff --git a/core/rfb.js b/core/rfb.js index 89e9197d..57f02581 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -152,6 +152,8 @@ export default class RFB extends EventTargetMixin { this._qemuExtKeyEventSupported = false; + this._extendedPointerEventSupported = false; + this._clipboardText = null; this._clipboardServerCapabilitiesActions = {}; this._clipboardServerCapabilitiesFormats = {}; @@ -1051,10 +1053,11 @@ export default class RFB extends EventTargetMixin { 1: 1 << 2, // Right 2: 1 << 1, // Middle 3: 1 << 7, // Back + 4: 1 << 8, // Forward }; let bmask = 0; - for (let i = 0; i < 4; i++) { + for (let i = 0; i < 5; i++) { if (buttons & (1 << i)) { bmask |= buttonMaskMap[i]; } @@ -1189,8 +1192,20 @@ export default class RFB extends EventTargetMixin { if (this._rfbConnectionState !== 'connected') { return; } if (this._viewOnly) { return; } // View only, skip mouse events - RFB.messages.pointerEvent(this._sock, this._display.absX(x), - this._display.absY(y), mask); + // Highest bit in mask is never sent to the server + if (mask & 0x8000) { + throw new Error("Illegal mouse button mask (mask: " + mask + ")"); + } + + let extendedMouseButtons = mask & 0x7f80; + + if (this._extendedPointerEventSupported && extendedMouseButtons) { + RFB.messages.extendedPointerEvent(this._sock, this._display.absX(x), + this._display.absY(y), mask); + } else { + RFB.messages.pointerEvent(this._sock, this._display.absX(x), + this._display.absY(y), mask); + } } _handleWheel(ev) { @@ -2229,6 +2244,7 @@ export default class RFB extends EventTargetMixin { encs.push(encodings.pseudoEncodingContinuousUpdates); encs.push(encodings.pseudoEncodingDesktopName); encs.push(encodings.pseudoEncodingExtendedClipboard); + encs.push(encodings.pseudoEncodingExtendedMouseButtons); if (this._fbDepth == 24) { encs.push(encodings.pseudoEncodingVMwareCursor); @@ -2658,6 +2674,10 @@ export default class RFB extends EventTargetMixin { case encodings.pseudoEncodingExtendedDesktopSize: return this._handleExtendedDesktopSize(); + case encodings.pseudoEncodingExtendedMouseButtons: + this._extendedPointerEventSupported = true; + return true; + case encodings.pseudoEncodingQEMULedEvent: return this._handleLedEvent(); @@ -3067,6 +3087,10 @@ RFB.messages = { pointerEvent(sock, x, y, mask) { sock.sQpush8(5); // msg-type + // Marker bit must be set to 0, otherwise the server might + // confuse the marker bit with the highest bit in a normal + // PointerEvent message. + mask = mask & 0x7f; sock.sQpush8(mask); sock.sQpush16(x); @@ -3075,6 +3099,27 @@ RFB.messages = { sock.flush(); }, + extendedPointerEvent(sock, x, y, mask) { + sock.sQpush8(5); // msg-type + + let higherBits = (mask >> 7) & 0xff; + + // Bits 2-7 are reserved + if (higherBits & 0xfc) { + throw new Error("Invalid mouse button mask: " + mask); + } + + let lowerBits = mask & 0x7f; + lowerBits |= 0x80; // Set marker bit to 1 + + sock.sQpush8(lowerBits); + sock.sQpush16(x); + sock.sQpush16(y); + sock.sQpush8(higherBits); + + sock.flush(); + }, + // Used to build Notify and Request data. _buildExtendedClipboardFlags(actions, formats) { let data = new Uint8Array(4); diff --git a/tests/test.rfb.js b/tests/test.rfb.js index cb2945a5..8cdd2e36 100644 --- a/tests/test.rfb.js +++ b/tests/test.rfb.js @@ -3265,6 +3265,7 @@ describe('Remote Frame Buffer protocol client', function () { expect(spy).to.have.been.calledOnce; expect(spy.args[0][0].detail.name).to.equal('som€ nam€'); }); + }); describe('Caps Lock and Num Lock remote fixup', function () { @@ -3757,6 +3758,7 @@ describe('Remote Frame Buffer protocol client', function () { describe('Asynchronous events', function () { let client; let pointerEvent; + let extendedPointerEvent; let keyEvent; let qemuKeyEvent; @@ -3770,12 +3772,14 @@ describe('Remote Frame Buffer protocol client', function () { client.focusOnClick = false; pointerEvent = sinon.spy(RFB.messages, 'pointerEvent'); + extendedPointerEvent = sinon.spy(RFB.messages, 'extendedPointerEvent'); keyEvent = sinon.spy(RFB.messages, 'keyEvent'); qemuKeyEvent = sinon.spy(RFB.messages, 'QEMUExtendedKeyEvent'); }); afterEach(function () { pointerEvent.restore(); + extendedPointerEvent.restore(); keyEvent.restore(); qemuKeyEvent.restore(); }); @@ -3884,6 +3888,23 @@ describe('Remote Frame Buffer protocol client', function () { 50, 70, 0x0); }); + it('should send extended pointer event when server supports extended pointer events', function () { + // Enable extended pointer events + sendFbuMsg([{ x: 0, y: 0, width: 0, height: 0, encoding: -316 }], [[]], client); + + sendMouseButtonEvent(50, 70, true, 0x10, client); + + expect(extendedPointerEvent).to.have.been.calledOnceWith(client._sock, + 50, 70, 0x100); + }); + + it('should send normal pointer event when server does not support extended pointer events', function () { + sendMouseButtonEvent(50, 70, true, 0x10, client); + + expect(pointerEvent).to.have.been.calledOnceWith(client._sock, + 50, 70, 0x100); + }); + describe('Event aggregation', function () { it('should send a single pointer event on mouse movement', function () { sendMouseMoveEvent(50, 70, 0x0, client); @@ -5135,11 +5156,36 @@ describe('RFB messages', function () { }); it('should send correct data for pointer events', function () { + RFB.messages.pointerEvent(sock, 12345, 54321, 0x2b); + let expected = + [ 5, 0x2b, 0x30, 0x39, 0xd4, 0x31]; + expect(sock).to.have.sent(new Uint8Array(expected)); + }); + + it('should send correct data for pointer events with marker bit set', function () { RFB.messages.pointerEvent(sock, 12345, 54321, 0xab); let expected = - [ 5, 0xab, 0x30, 0x39, 0xd4, 0x31]; + [ 5, 0x2b, 0x30, 0x39, 0xd4, 0x31]; expect(sock).to.have.sent(new Uint8Array(expected)); }); + + it('should send correct data for pointer events with extended button bits set', function () { + RFB.messages.pointerEvent(sock, 12345, 54321, 0x3ab); + let expected = + [ 5, 0x2b, 0x30, 0x39, 0xd4, 0x31]; + expect(sock).to.have.sent(new Uint8Array(expected)); + }); + + it('should send correct data for extended pointer events', function () { + RFB.messages.extendedPointerEvent(sock, 12345, 54321, 0xab); + let expected = + [ 5, 0xab, 0x30, 0x39, 0xd4, 0x31, 0x1]; + expect(sock).to.have.sent(new Uint8Array(expected)); + }); + + it('should not send invalid data for extended pointer events', function () { + expect(() => RFB.messages.extendedPointerEvent(sock, 12345, 54321, 0x3ab)).to.throw(Error); + }); }); describe('Clipboard events', function () { From 83a5e9e9dbf420b606439626642ecdbdf18721f8 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Wed, 15 Jan 2025 12:43:44 +0100 Subject: [PATCH 23/70] Also test Ctrl+AltGr, as that is what browsers use Modern browsers now send the odd sequence of Ctrl+AltGr, rather than the raw Ctrl+Alt, or the fully adjusted just AltGr. Make sure we have a test for this scenario and don't break it. --- tests/test.keyboard.js | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/tests/test.keyboard.js b/tests/test.keyboard.js index 47be623d..ccc01247 100644 --- a/tests/test.keyboard.js +++ b/tests/test.keyboard.js @@ -525,6 +525,37 @@ describe('Key event handling', function () { expect(kbd.onkeyevent).to.not.have.been.called; }); + it('should generate AltGraph for quick Ctrl+AltGraph sequence', function () { + const kbd = new Keyboard(document); + kbd.onkeyevent = sinon.spy(); + kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1, timeStamp: Date.now()})); + this.clock.tick(20); + kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'AltGraph', location: 2, timeStamp: Date.now()})); + expect(kbd.onkeyevent).to.have.been.calledOnce; + expect(kbd.onkeyevent).to.have.been.calledWith(0xfe03, 'AltRight', true); + + // Check that the timer is properly dead + kbd.onkeyevent.resetHistory(); + this.clock.tick(100); + expect(kbd.onkeyevent).to.not.have.been.called; + }); + + it('should generate Ctrl, AltGraph for slow Ctrl+AltGraph sequence', function () { + const kbd = new Keyboard(document); + kbd.onkeyevent = sinon.spy(); + kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1, timeStamp: Date.now()})); + this.clock.tick(60); + kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'AltGraph', location: 2, timeStamp: Date.now()})); + expect(kbd.onkeyevent).to.have.been.calledTwice; + expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0xffe3, "ControlLeft", true); + expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0xfe03, "AltRight", true); + + // Check that the timer is properly dead + kbd.onkeyevent.resetHistory(); + this.clock.tick(100); + expect(kbd.onkeyevent).to.not.have.been.called; + }); + it('should pass through single Alt', function () { const kbd = new Keyboard(document); kbd.onkeyevent = sinon.spy(); From 4f284c2f153ae7d2597d8d7d91724462d736362a Mon Sep 17 00:00:00 2001 From: Alexander Zeijlon Date: Fri, 17 Jan 2025 11:29:07 +0100 Subject: [PATCH 24/70] Remove bold styling tags in vnc.html We aren't emphasizing important information with bold tags anywhere else, so we shouldn't do it here either. --- vnc.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vnc.html b/vnc.html index c2cc4e55..c4033c2d 100644 --- a/vnc.html +++ b/vnc.html @@ -330,7 +330,7 @@ The server has provided the following identifying information:
- Fingerprint: + Fingerprint:
From ca270efcc36a8f6df97d44977d14bb64693a6217 Mon Sep 17 00:00:00 2001 From: Samuel Mannehed Date: Sun, 12 Jan 2025 00:36:44 +0100 Subject: [PATCH 25/70] Standardize on 6px or 12px border-radius This results in a few things becoming slighly more rounded, for example the controlbar, the settings panel and buttons/inputs. Increased rounding gives a more friendly feel. --- app/styles/base.css | 20 ++++++++++---------- app/styles/input.css | 6 +++--- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/app/styles/base.css b/app/styles/base.css index ecef3771..de2624f5 100644 --- a/app/styles/base.css +++ b/app/styles/base.css @@ -174,7 +174,7 @@ html { font-weight: bold; color: #fff; - border-radius: 10px; + border-radius: 12px; box-shadow: 6px 6px 0px rgba(0, 0, 0, 0.5); background: rgba(200,55,55,0.8); } @@ -242,7 +242,7 @@ html { transition: 0.5s ease-in-out; background-color: var(--novnc-blue); - border-radius: 0 10px 10px 0; + border-radius: 0 12px 12px 0; user-select: none; -webkit-user-select: none; @@ -267,7 +267,7 @@ html { } .noVNC_right #noVNC_control_bar { left: 100%; - border-radius: 10px 0 0 10px; + border-radius: 12px 0 0 12px; } .noVNC_right #noVNC_control_bar.noVNC_open { left: 0; @@ -285,7 +285,7 @@ html { height: 50px; z-index: -1; cursor: pointer; - border-radius: 5px; + border-radius: 6px; background-color: var(--novnc-darkblue); background-image: url("../images/handle_bg.svg"); background-repeat: no-repeat; @@ -372,7 +372,7 @@ html { transition: 0.2s ease-in-out; background: transparent; box-shadow: 0 0 10px black, inset 0 0 10px 10px var(--novnc-darkblue); - border-radius: 10px; + border-radius: 12px; transition-delay: 0s; } #noVNC_control_bar_hint.noVNC_active { @@ -444,7 +444,7 @@ html { padding: 15px; background: #fff; - border-radius: 10px; + border-radius: 12px; color: #000; border: 2px solid #E0E0E0; box-shadow: 6px 6px 0px rgba(0, 0, 0, 0.5); @@ -500,7 +500,7 @@ html { .noVNC_panel .noVNC_heading { background-color: var(--novnc-blue); - border-radius: 5px; + border-radius: 6px; padding: 5px; /* Compensate for padding in image */ padding-right: 8px; @@ -534,7 +534,7 @@ html { margin-left: 10px; padding: 5px; background: rgba(0, 0, 0, 0.05); - border-radius: 5px; + border-radius: 6px; } .noVNC_expander:not(.noVNC_open) ~ * { display: none; @@ -732,7 +732,7 @@ html { font-size: 80px; text-align: center; - border-radius: 5px; + border-radius: 6px; } @media (max-width: 440px) { #noVNC_connect_dlg { @@ -759,7 +759,7 @@ html { cursor: pointer; border-color: var(--novnc-darkblue); - border-radius: 5px; + border-radius: 6px; background: linear-gradient(to top, var(--novnc-blue), rgb(99, 119, 147)); color: white; diff --git a/app/styles/input.css b/app/styles/input.css index a2b289bf..2be7fe44 100644 --- a/app/styles/input.css +++ b/app/styles/input.css @@ -22,7 +22,7 @@ input, input::file-selector-button, button, select, textarea { padding: 0.5em var(--input-xpadding); border: 1px solid var(--novnc-lightgrey); - border-radius: 5px; + border-radius: 6px; color: black; --bg-gradient: linear-gradient(to top, rgb(255, 255, 255) 80%, rgb(240, 240, 240)); background-image: var(--bg-gradient); @@ -156,7 +156,7 @@ input[type=range]::-webkit-slider-thumb { appearance: none; width: 18px; height: 20px; - border-radius: 5px; + border-radius: 6px; background-color: white; border: 1px solid dimgray; margin-top: -7px; @@ -165,7 +165,7 @@ input[type=range]::-moz-range-thumb { appearance: none; width: 18px; height: 20px; - border-radius: 5px; + border-radius: 6px; background-color: white; border: 1px solid dimgray; margin-top: -7px; From e9b48ae40938865fa83ebd2d39b25d64f92582b2 Mon Sep 17 00:00:00 2001 From: Samuel Mannehed Date: Sun, 12 Jan 2025 01:29:06 +0100 Subject: [PATCH 26/70] Get rid of gradients on buttons and inputs Lets make things more flat and modern. --- app/styles/base.css | 4 ++-- app/styles/input.css | 29 ++++++++++++++--------------- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/app/styles/base.css b/app/styles/base.css index de2624f5..3c96933f 100644 --- a/app/styles/base.css +++ b/app/styles/base.css @@ -761,14 +761,14 @@ html { border-color: var(--novnc-darkblue); border-radius: 6px; - background: linear-gradient(to top, var(--novnc-blue), rgb(99, 119, 147)); + background-color: var(--novnc-blue); color: white; /* This avoids it jumping around when :active */ vertical-align: middle; } #noVNC_connect_button:hover { - background: linear-gradient(to top, var(--novnc-blue), rgb(105, 125, 155)); + background-color: var(--novnc-darkblue); } #noVNC_connect_button img { diff --git a/app/styles/input.css b/app/styles/input.css index 2be7fe44..1a51e54e 100644 --- a/app/styles/input.css +++ b/app/styles/input.css @@ -18,14 +18,10 @@ input, input::file-selector-button, button, select, textarea { /* Disable default rendering */ appearance: none; - background: none; padding: 0.5em var(--input-xpadding); border: 1px solid var(--novnc-lightgrey); border-radius: 6px; - color: black; - --bg-gradient: linear-gradient(to top, rgb(255, 255, 255) 80%, rgb(240, 240, 240)); - background-image: var(--bg-gradient); } /* @@ -44,6 +40,8 @@ select { /* This avoids it jumping around when :active */ vertical-align: middle; margin-top: 0; + color: black; + background-color: white; /* Disable Chrome's touch tap highlight */ -webkit-tap-highlight-color: transparent; @@ -60,7 +58,8 @@ select { stroke="rgb(31,31,31)" fill="none" \ stroke-linecap="round" stroke-linejoin="round" /> \ '); - background-image: var(--select-arrow), var(--bg-gradient); + background-color: white; + background-image: var(--select-arrow); background-position: calc(100% - var(--input-xpadding)), left top, left top; background-repeat: no-repeat; padding-right: calc(2*var(--input-xpadding) + 8px); @@ -75,7 +74,7 @@ select:active { \ - '), var(--bg-gradient); + '); } option { color: black; @@ -192,13 +191,13 @@ input[type=reset]:hover, input[type=submit]:hover, input::file-selector-button:hover, button:hover { - background-image: linear-gradient(to top, rgb(255, 255, 255), rgb(250, 250, 250)); + background-color: var(--novnc-lightgrey); } select:hover { - background-image: var(--select-arrow), - linear-gradient(to top, rgb(255, 255, 255), rgb(250, 250, 250)); - background-position: calc(100% - 7px), left top; - background-repeat: no-repeat; + background-image: + var(--select-arrow), + linear-gradient(var(--novnc-lightgrey) 100%, + transparent); } @media (any-pointer: coarse) { /* We don't want a hover style after touch input */ @@ -209,10 +208,10 @@ select:hover { input[type=submit]:hover, input::file-selector-button:hover, button:hover { - background-image: var(--bg-gradient); + background-color: white; } select:hover { - background-image: var(--select-arrow), var(--bg-gradient); + background-image: var(--select-arrow); } } @@ -264,7 +263,7 @@ input[type=submit]:disabled, input:disabled::file-selector-button, button:disabled, select:disabled { - background-image: var(--bg-gradient); + background-color: white; border-bottom-width: 2px; margin-top: 0; } @@ -272,7 +271,7 @@ input[type=file]:disabled { background-image: none; } select:disabled { - background-image: var(--select-arrow), var(--bg-gradient); + background-image: var(--select-arrow), } input[type=image]:disabled { /* See Firefox bug: From 3f29c9d9930cf5f6b67cb6692477948bb441ea2c Mon Sep 17 00:00:00 2001 From: Samuel Mannehed Date: Sun, 12 Jan 2025 01:37:19 +0100 Subject: [PATCH 27/70] Differentiate buttons from text inputs By making buttons grey with bold text, they are easy to distinguish from text inputs. --- app/styles/constants.css | 3 +++ app/styles/input.css | 34 ++++++++++++++++++++++++++-------- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/app/styles/constants.css b/app/styles/constants.css index fb1f5509..daf39616 100644 --- a/app/styles/constants.css +++ b/app/styles/constants.css @@ -12,6 +12,9 @@ --novnc-lightgrey: rgb(192, 192, 192); --novnc-darkgrey: rgb(92, 92, 92); + /* Transparent to make button colors adapt to the background */ + --novnc-buttongrey: rgba(192, 192, 192, 0.5); + --novnc-blue: rgb(110, 132, 163); --novnc-lightblue: rgb(74, 144, 217); --novnc-darkblue: rgb(83, 99, 122); diff --git a/app/styles/input.css b/app/styles/input.css index 1a51e54e..e2468ae4 100644 --- a/app/styles/input.css +++ b/app/styles/input.css @@ -41,7 +41,8 @@ select { vertical-align: middle; margin-top: 0; color: black; - background-color: white; + font-weight: bold; + background-color: var(--novnc-buttongrey); /* Disable Chrome's touch tap highlight */ -webkit-tap-highlight-color: transparent; @@ -58,8 +59,18 @@ select { stroke="rgb(31,31,31)" fill="none" \ stroke-linecap="round" stroke-linejoin="round" /> \ '); + /* FIXME: A bug in Firefox, requires a workaround for the background: + https://bugzilla.mozilla.org/show_bug.cgi?id=1810958 */ + /* The dropdown list will show the select element's background above and + below the options in Firefox. We want the entire dropdown to be white. */ background-color: white; - background-image: var(--select-arrow); + /* However, we don't want the select element to actually show a white + background, so let's place a gradient above it with the color we want. */ + --grey-background: linear-gradient(var(--novnc-buttongrey) 100%, + transparent); + background-image: + var(--select-arrow), + var(--grey-background); background-position: calc(100% - var(--input-xpadding)), left top, left top; background-repeat: no-repeat; padding-right: calc(2*var(--input-xpadding) + 8px); @@ -74,11 +85,14 @@ select:active { \ - '); + '), + var(--grey-background); } option { + /* Prevent Chrome from inheriting background-color from the is opened in Firefox: https://bugzilla.mozilla.org/show_bug.cgi?id=1805406 */ select:active { /* Rotated arrow */ background-image: url('data:image/svg+xml;utf8, \ - \ - \ + \ + \ '), var(--grey-background); } From 633b4c266d8167e565e6f81d9ece8c022b1bfae7 Mon Sep 17 00:00:00 2001 From: Samuel Mannehed Date: Sun, 12 Jan 2025 02:06:51 +0100 Subject: [PATCH 29/70] Redesign checkboxes and radiobuttons Makes them bigger and gets rid of their borders. The change also allowed for some shared styling between them. --- app/styles/input.css | 100 ++++++++++++++++++++++++++++++++----------- 1 file changed, 75 insertions(+), 25 deletions(-) diff --git a/app/styles/input.css b/app/styles/input.css index f206a657..d9b9067c 100644 --- a/app/styles/input.css +++ b/app/styles/input.css @@ -97,35 +97,80 @@ option { } /* - * Checkboxes + * Shared between checkboxes and radiobuttons */ + +input[type=radio], input[type=checkbox] { display: inline-flex; justify-content: center; align-items: center; - background-color: white; - background-image: unset; - border: 1px solid dimgrey; - border-radius: 3px; - width: 13px; - height: 13px; + border-color: transparent; + background-color: var(--novnc-buttongrey); + /* Disable Chrome's touch tap highlight to avoid conflicts with overlay */ + -webkit-tap-highlight-color: transparent; + width: 16px; + --checkradio-height: 16px; + height: var(--checkradio-height); padding: 0; - margin-right: 6px; - vertical-align: bottom; - transition: 0.2s background-color linear; + margin: 0 6px 0 0; + /* Don't have transitions for outline in order to be consistent + with other elements */ + transition: all 0.2s, outline-color 0s, outline-offset 0s; + + /* A transparent outline in order to work around a graphical clipping issue + in WebKit. See bug: https://bugs.webkit.org/show_bug.cgi?id=256003 */ + outline: 1px solid transparent; + position: relative; /* Since ::before & ::after are absolute positioned */ + + /* We want to align with the middle of capital letters, this requires + a workaround. The default behavior is to align the bottom of the element + on top of the text baseline, this is too far up. + We want to push the element down half the difference in height between + it and a capital X. In our font, the height of a capital "X" is 0.698em. + */ + vertical-align: calc(0px - (var(--checkradio-height) - 0.698em) / 2); + /* FIXME: Could write 1cap instead of 0.698em, but it's only supported in + Firefox as of 2023 */ + /* FIXME: We probably want to use round() here, see bug 8148 */ +} +input[type=radio]:focus-visible, +input[type=checkbox]:focus-visible { + outline-color: var(--novnc-lightblue); +} +input[type=checkbox]::before, +input[type=radio]::before { + content: ""; + display: block; /* width & height doesn't work on inline elements */ + transition: inherit; + /* Let's prevent the pseudo-elements from taking up layout space so that + the ::before and ::after pseudo-elements can be in the same place. This + is also required for vertical-align: baseline to work like we want it to + on radio/checkboxes. If the pseudo-elements take up layout space, the + baseline of text inside them will be used instead. */ + position: absolute; +} + +/* + * Checkboxes + */ +input[type=checkbox] { + border-radius: 4px; } input[type=checkbox]:checked { background-color: var(--novnc-blue); - border-color: var(--novnc-blue); } -input[type=checkbox]:checked::after { - content: ""; - display: block; /* width & height doesn't work on inline elements */ - width: 3px; - height: 7px; - border: 1px solid white; +input[type=checkbox]::before { + width: 25%; + height: 55%; + border-style: solid; + border-color: transparent; border-width: 0 2px 2px 0; - transform: rotate(40deg) translateY(-1px); + border-radius: 1px; + transform: translateY(-1px) rotate(35deg); +} +input[type=checkbox]:checked::before { + border-color: white; } /* @@ -133,15 +178,20 @@ input[type=checkbox]:checked::after { */ input[type=radio] { border-radius: 50%; - border: 1px solid dimgrey; - width: 12px; - height: 12px; - padding: 0; - margin-right: 6px; - transition: 0.2s border linear; + border: 1px solid transparent; /* To ensure a smooth transition */ } input[type=radio]:checked { - border: 6px solid var(--novnc-blue); + border: 4px solid var(--novnc-blue); + background-color: white; +} +input[type=radio]::before { + width: inherit; + height: inherit; + border-radius: inherit; + opacity: 0; +} +input[type=radio]:checked::before { + opacity: 1; } /* From 7fdcc66d2c924baa984e83f04f759387ba132747 Mon Sep 17 00:00:00 2001 From: Samuel Mannehed Date: Sun, 12 Jan 2025 02:10:12 +0100 Subject: [PATCH 30/70] Add indeterminate styling to checkboxes and radios This is used when the control is neither checked or unchecked. --- app/styles/input.css | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/app/styles/input.css b/app/styles/input.css index d9b9067c..7aa3dc7f 100644 --- a/app/styles/input.css +++ b/app/styles/input.css @@ -139,7 +139,9 @@ input[type=checkbox]:focus-visible { outline-color: var(--novnc-lightblue); } input[type=checkbox]::before, -input[type=radio]::before { +input[type=checkbox]::after, +input[type=radio]::before, +input[type=radio]::after { content: ""; display: block; /* width & height doesn't work on inline elements */ transition: inherit; @@ -150,6 +152,13 @@ input[type=radio]::before { baseline of text inside them will be used instead. */ position: absolute; } +input[type=checkbox]::after, +input[type=radio]::after { + width: 10px; + height: 2px; + background-color: transparent; + border-radius: 2px; +} /* * Checkboxes @@ -157,7 +166,8 @@ input[type=radio]::before { input[type=checkbox] { border-radius: 4px; } -input[type=checkbox]:checked { +input[type=checkbox]:checked, +input[type=checkbox]:indeterminate { background-color: var(--novnc-blue); } input[type=checkbox]::before { @@ -172,6 +182,9 @@ input[type=checkbox]::before { input[type=checkbox]:checked::before { border-color: white; } +input[type=checkbox]:indeterminate::after { + background-color: white; +} /* * Radiobuttons @@ -193,6 +206,9 @@ input[type=radio]::before { input[type=radio]:checked::before { opacity: 1; } +input[type=radio]:indeterminate::after { + background-color: black; +} /* * Range sliders From 017888c9a8fd07f62403103eec42340233faac44 Mon Sep 17 00:00:00 2001 From: Samuel Mannehed Date: Sun, 12 Jan 2025 02:29:26 +0100 Subject: [PATCH 31/70] Rework how buttons react to :hover and :active Instead of having two different types of effects (hover had a different color, and active had a 3d-effect simulating a pressed button), we now have an increasing activation-level. That means the button goes a bit dark for hover, and then even darker when pressed. There is also a variant that goes lighter for each activation level, that can be used when the initial color is dark. With this change, we can get rid of special :hover and :active styling for the connect button and the control bar buttons. We can use the same activation level principle for all buttons. --- app/styles/base.css | 31 --------- app/styles/input.css | 155 ++++++++++++++++++++++++------------------- 2 files changed, 85 insertions(+), 101 deletions(-) diff --git a/app/styles/base.css b/app/styles/base.css index 3c96933f..fba8981e 100644 --- a/app/styles/base.css +++ b/app/styles/base.css @@ -392,40 +392,15 @@ html { border:1px solid rgba(255, 255, 255, 0.2); border-radius: 6px; background-color: transparent; - background-image: unset; /* we don't want the gradiant from input.css */ } #noVNC_control_bar .noVNC_button.noVNC_selected { border-color: rgba(0, 0, 0, 0.8); background-color: rgba(0, 0, 0, 0.5); } -#noVNC_control_bar .noVNC_button.noVNC_selected:not(:disabled):hover { - border-color: rgba(0, 0, 0, 0.4); - background-color: rgba(0, 0, 0, 0.2); -} -#noVNC_control_bar .noVNC_button:not(:disabled):hover { - background-color: rgba(255, 255, 255, 0.2); -} -#noVNC_control_bar .noVNC_button:not(:disabled):active { - padding-top: 5px; - padding-bottom: 3px; -} #noVNC_control_bar .noVNC_button.noVNC_hidden { display: none !important; } -/* Android browsers don't properly update hover state if touch events are - * intercepted, like they are when clicking on the remote screen. */ -@media (any-pointer: coarse) { - #noVNC_control_bar .noVNC_button:not(:disabled):hover { - background-color: transparent; - } - #noVNC_control_bar .noVNC_button.noVNC_selected:not(:disabled):hover { - border-color: rgba(0, 0, 0, 0.8); - background-color: rgba(0, 0, 0, 0.5); - } -} - - /* Panels */ .noVNC_panel { transform: translateX(25px); @@ -763,12 +738,6 @@ html { background-color: var(--novnc-blue); color: white; - - /* This avoids it jumping around when :active */ - vertical-align: middle; -} -#noVNC_connect_button:hover { - background-color: var(--novnc-darkblue); } #noVNC_connect_button img { diff --git a/app/styles/input.css b/app/styles/input.css index 7aa3dc7f..e4c5b1a1 100644 --- a/app/styles/input.css +++ b/app/styles/input.css @@ -24,6 +24,71 @@ input, input::file-selector-button, button, select, textarea { border-radius: 6px; } +/* + * Button activations + */ + +/* A color overlay that depends on the activation level. The level can then be + set for different states on an element, for example hover and click on a + '), + var(--button-activation-overlay), var(--grey-background); } option { @@ -94,6 +158,7 @@ option { background-color: white; color: black; font-weight: normal; + background-image: var(--button-activation-overlay); } /* @@ -107,6 +172,7 @@ input[type=checkbox] { align-items: center; border-color: transparent; background-color: var(--novnc-buttongrey); + background-image: var(--button-activation-overlay); /* Disable Chrome's touch tap highlight to avoid conflicts with overlay */ -webkit-tap-highlight-color: transparent; width: 16px; @@ -169,6 +235,8 @@ input[type=checkbox] { input[type=checkbox]:checked, input[type=checkbox]:indeterminate { background-color: var(--novnc-blue); + background-image: var(--button-activation-overlay-light); + background-blend-mode: overlay; } input[type=checkbox]::before { width: 25%; @@ -196,11 +264,20 @@ input[type=radio] { input[type=radio]:checked { border: 4px solid var(--novnc-blue); background-color: white; + /* button-activation-overlay should be removed from the radio + element to not interfere with button-activation-overlay-light + that is set on the ::before element. */ + background-image: none; } input[type=radio]::before { width: inherit; height: inherit; border-radius: inherit; + /* We can achieve the highlight overlay effect on border colors by + setting button-activation-overlay-light on an element that stays + on top (z-axis) of the element with a border. */ + background-image: var(--button-activation-overlay-light); + mix-blend-mode: overlay; opacity: 0; } input[type=radio]:checked::before { @@ -238,6 +315,9 @@ input[type=range]::-webkit-slider-thumb { height: 20px; border-radius: 6px; background-color: white; + background-image: var(--button-activation-overlay); + /* Disable Chrome's touch tap highlight to avoid conflicts with overlay */ + -webkit-tap-highlight-color: transparent; border: 1px solid dimgray; margin-top: -7px; } @@ -247,6 +327,7 @@ input[type=range]::-moz-range-thumb { height: 20px; border-radius: 6px; background-color: white; + background-image: var(--button-activation-overlay); border: 1px solid dimgray; margin-top: -7px; } @@ -262,57 +343,6 @@ input::file-selector-button { margin-right: 6px; } -/* - * Hover - */ -input[type=button]:hover, -input[type=color]:hover, -input[type=image]:hover, -input[type=reset]:hover, -input[type=submit]:hover, -input::file-selector-button:hover, -button:hover { - background-color: var(--novnc-lightgrey); -} -select:hover { - background-image: - var(--select-arrow), - linear-gradient(var(--novnc-lightgrey) 100%, - transparent); -} -@media (any-pointer: coarse) { - /* We don't want a hover style after touch input */ - input[type=button]:hover, - input[type=color]:hover, - input[type=image]:hover, - input[type=reset]:hover, - input[type=submit]:hover, - input::file-selector-button:hover, - button:hover { - background-color: var(--novnc-buttongrey); - } - select:hover { - background-image: - var(--select-arrow), - var(--grey-background); - } -} - -/* - * Active (clicked) - */ -input[type=button]:active, -input[type=color]:active, -input[type=image]:active, -input[type=reset]:active, -input[type=submit]:active, -input::file-selector-button:active, -button:active, -select:active { - border-bottom-width: 1px; - margin-top: 1px; -} - /* * Focus (tab) */ @@ -338,21 +368,6 @@ select:disabled, textarea:disabled { opacity: 0.4; } -input[type=button]:disabled, -input[type=color]:disabled, -input[type=image]:disabled, -input[type=reset]:disabled, -input[type=submit]:disabled, -input:disabled::file-selector-button, -button:disabled, -select:disabled { - background-color: var(--novnc-buttongrey); - border-bottom-width: 2px; - margin-top: 0; -} -input[type=file]:disabled { - background-image: none; -} select:disabled { background-image: var(--select-arrow), From 33a2548fcb4d9c437b90b08c6b951755995bd577 Mon Sep 17 00:00:00 2001 From: Samuel Mannehed Date: Sun, 12 Jan 2025 02:49:35 +0100 Subject: [PATCH 32/70] Make buttons flat by removing borders Gives a more clean look that fits well with the new checkboxes and radios. The old border was mostly used to contribute to a 3d-effect, that was used for :active. That :active-styling has been replaced by activation levels. --- app/styles/base.css | 7 ++----- app/styles/input.css | 35 +++++++++++++++++++++++++++-------- 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/app/styles/base.css b/app/styles/base.css index fba8981e..c9e60bd1 100644 --- a/app/styles/base.css +++ b/app/styles/base.css @@ -729,13 +729,10 @@ html { } #noVNC_connect_button { width: 100%; - padding: 5px 30px; - + padding: 6px 30px; cursor: pointer; - - border-color: var(--novnc-darkblue); + border-color: transparent; border-radius: 6px; - background-color: var(--novnc-blue); color: white; } diff --git a/app/styles/input.css b/app/styles/input.css index e4c5b1a1..7608f918 100644 --- a/app/styles/input.css +++ b/app/styles/input.css @@ -13,15 +13,35 @@ } input, input::file-selector-button, button, select, textarea { - /* Respect standard font settings */ - font: inherit; - - /* Disable default rendering */ + padding: 0.5em var(--input-xpadding); + border-radius: 6px; appearance: none; - padding: 0.5em var(--input-xpadding); + /* Respect standard font settings */ + font: inherit; +} + +/* + * Text input + */ + +input:not([type]), +input[type=date], +input[type=datetime-local], +input[type=email], +input[type=month], +input[type=number], +input[type=password], +input[type=search], +input[type=tel], +input[type=text], +input[type=time], +input[type=url], +input[type=week], +textarea { border: 1px solid var(--novnc-lightgrey); - border-radius: 6px; + /* Account for borders on text inputs, buttons dont have borders */ + padding: calc(0.5em - 1px) var(--input-xpadding); } /* @@ -100,7 +120,7 @@ input[type=submit], input::file-selector-button, button, select { - border-bottom-width: 2px; + border: none; color: black; font-weight: bold; background-color: var(--novnc-buttongrey); @@ -170,7 +190,6 @@ input[type=checkbox] { display: inline-flex; justify-content: center; align-items: center; - border-color: transparent; background-color: var(--novnc-buttongrey); background-image: var(--button-activation-overlay); /* Disable Chrome's touch tap highlight to avoid conflicts with overlay */ From 30d46a00fa3e99610f5acc54b2bdc7576bf10e9e Mon Sep 17 00:00:00 2001 From: Samuel Mannehed Date: Sun, 12 Jan 2025 19:50:10 +0100 Subject: [PATCH 33/70] Fix :disabled styling of file-selector-button By applying the rule to the button within the input, we effectively applied the opacity twice - making the button almost disappear. Applying the opacity to the input element is enough. --- app/styles/input.css | 1 - 1 file changed, 1 deletion(-) diff --git a/app/styles/input.css b/app/styles/input.css index 7608f918..dda08b68 100644 --- a/app/styles/input.css +++ b/app/styles/input.css @@ -381,7 +381,6 @@ input[type=file]:focus-visible { * Disabled */ input:disabled, -input:disabled::file-selector-button, button:disabled, select:disabled, textarea:disabled { From ee08032fe74828962f07422eab02ac3115e2a261 Mon Sep 17 00:00:00 2001 From: Samuel Mannehed Date: Sun, 12 Jan 2025 19:52:26 +0100 Subject: [PATCH 34/70] Put specific :disabled rules with its element It makes more sense to group rules per element type. --- app/styles/input.css | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/app/styles/input.css b/app/styles/input.css index dda08b68..d9c35041 100644 --- a/app/styles/input.css +++ b/app/styles/input.css @@ -129,6 +129,11 @@ select { /* Disable Chrome's touch tap highlight */ -webkit-tap-highlight-color: transparent; } +input[type=image]:disabled { + /* See Firefox bug: + https://bugzilla.mozilla.org/show_bug.cgi?id=1798304 */ + cursor: default; +} /* * Select dropdowns @@ -173,6 +178,11 @@ select:active { var(--button-activation-overlay), var(--grey-background); } +select:disabled { + background-image: + var(--select-arrow), + var(--grey-background); +} option { /* Prevent Chrome from inheriting background-color from the is opened in Firefox: - https://bugzilla.mozilla.org/show_bug.cgi?id=1805406 */ -select:active { - /* Rotated arrow */ - background-image: url('data:image/svg+xml;utf8, \ - \ - \ - '), - var(--button-activation-overlay), - var(--grey-background); -} -select:disabled { - background-image: - var(--select-arrow), - var(--grey-background); -} -option { - /* Prevent Chrome from inheriting background-color from the is opened in Firefox: + https://bugzilla.mozilla.org/show_bug.cgi?id=1805406 */ +select:active { + /* Rotated arrow */ + background-image: url('data:image/svg+xml;utf8, \ + \ + \ + '), + var(--button-activation-overlay), + var(--grey-background); +} +select:disabled { + background-image: + var(--select-arrow), + var(--grey-background); +} +option { + /* Prevent Chrome from inheriting background-color from the is opened in Firefox: https://bugzilla.mozilla.org/show_bug.cgi?id=1805406 */ From 7b58cb96bcb9eb9b3620054dcbbce9217a83d9d6 Mon Sep 17 00:00:00 2001 From: Samuel Mannehed Date: Mon, 13 Jan 2025 00:19:56 +0100 Subject: [PATCH 43/70] Add minimum width to buttons This ensures they aren't too small, even if the text label is short. --- app/styles/base.css | 1 + app/styles/input.css | 1 + 2 files changed, 2 insertions(+) diff --git a/app/styles/base.css b/app/styles/base.css index c9e60bd1..aa21ea5c 100644 --- a/app/styles/base.css +++ b/app/styles/base.css @@ -387,6 +387,7 @@ html { /* Control bar buttons */ #noVNC_control_bar .noVNC_button { + min-width: unset; padding: 4px 4px; vertical-align: middle; border:1px solid rgba(255, 255, 255, 0.2); diff --git a/app/styles/input.css b/app/styles/input.css index efc1804f..bd543b0e 100644 --- a/app/styles/input.css +++ b/app/styles/input.css @@ -176,6 +176,7 @@ input[type=submit], input::file-selector-button, button, select { + min-width: 8em; border: none; color: black; font-weight: bold; From 54e76817df2614a21469258c6a271fd5b5bb3a90 Mon Sep 17 00:00:00 2001 From: Samuel Mannehed Date: Mon, 13 Jan 2025 00:22:20 +0100 Subject: [PATCH 44/70] Pointer cursor on buttons & grab on sliders This makes buttons and slider stand out more. --- app/styles/input.css | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/app/styles/input.css b/app/styles/input.css index bd543b0e..95ae3474 100644 --- a/app/styles/input.css +++ b/app/styles/input.css @@ -182,11 +182,18 @@ select { font-weight: bold; background-color: var(--novnc-buttongrey); background-image: var(--button-activation-overlay); - + cursor: pointer; /* Disable Chrome's touch tap highlight */ -webkit-tap-highlight-color: transparent; } -input[type=image]:disabled { +input[type=button]:disabled, +input[type=color]:disabled, +input[type=image]:disabled, +input[type=reset]:disabled, +input[type=submit]:disabled, +input:disabled::file-selector-button, +button:disabled, +select:disabled { /* See Firefox bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1798304 */ cursor: default; @@ -332,6 +339,15 @@ input[type=range] { padding: 0; background: transparent; } +input[type=range]:hover { + cursor: grab; +} +input[type=range]:active { + cursor: grabbing; +} +input[type=range]:disabled { + cursor: default; +} /* -webkit-slider.. & -moz-range.. cant be in selector lists: https://bugs.chromium.org/p/chromium/issues/detail?id=1154623 */ input[type=range]::-webkit-slider-runnable-track { From 2bc505741fc5c508db2d9896e96538c8a64e9e47 Mon Sep 17 00:00:00 2001 From: Samuel Mannehed Date: Mon, 13 Jan 2025 00:28:10 +0100 Subject: [PATCH 45/70] Add styling for color pickers Note that no color picker elements are currently in use, this is for completeness. --- app/styles/input.css | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/app/styles/input.css b/app/styles/input.css index 95ae3474..374b93e4 100644 --- a/app/styles/input.css +++ b/app/styles/input.css @@ -209,6 +209,28 @@ input[type=submit] { overflow: clip; } +/* ------- COLOR PICKERS ------- */ + +input[type=color] { + min-width: unset; + box-sizing: content-box; + width: 1.4em; + height: 1.4em; +} +input[type=color]::-webkit-color-swatch-wrapper { + padding: 0; +} +/* -webkit-color-swatch & -moz-color-swatch cant be in a selector list: + https://bugs.chromium.org/p/chromium/issues/detail?id=1154623 */ +input[type=color]::-webkit-color-swatch { + border: none; + border-radius: 6px; +} +input[type=color]::-moz-color-swatch { + border: none; + border-radius: 6px; +} + /* -- SHARED BETWEEN CHECKBOXES AND RADIOBUTTONS -- */ input[type=radio], From 331ad34d90e0992193d1b7a533fb3edbc91d9bd4 Mon Sep 17 00:00:00 2001 From: Samuel Mannehed Date: Mon, 13 Jan 2025 00:57:25 +0100 Subject: [PATCH 46/70] Make interface airier by increasing line-height Modern interfaces are less cramped, this makes noVNC feel more up to date. Note that this required some adjustments on noVNC_headings and noVNC_connect_button since the text now takes up more height than the images. --- app/styles/base.css | 13 +++++++++++-- app/styles/input.css | 3 ++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/app/styles/base.css b/app/styles/base.css index aa21ea5c..60c092a5 100644 --- a/app/styles/base.css +++ b/app/styles/base.css @@ -31,6 +31,7 @@ :root { font-family: sans-serif; + line-height: 1.6; } body { @@ -477,9 +478,12 @@ html { .noVNC_panel .noVNC_heading { background-color: var(--novnc-blue); border-radius: 6px; - padding: 5px; + padding: 5px 8px; /* Compensate for padding in image */ - padding-right: 8px; + padding-right: 11px; + display: flex; + align-items: center; + gap: 6px; color: white; font-size: 20px; white-space: nowrap; @@ -736,6 +740,11 @@ html { border-radius: 6px; background-color: var(--novnc-blue); color: white; + + display: flex; + justify-content: center; + place-items: center; + gap: 4px; } #noVNC_connect_button img { diff --git a/app/styles/input.css b/app/styles/input.css index 374b93e4..731f36a9 100644 --- a/app/styles/input.css +++ b/app/styles/input.css @@ -23,6 +23,7 @@ input::file-selector-button { /* Respect standard font settings */ font: inherit; + line-height: 1.6; } input:disabled, textarea:disabled, @@ -85,7 +86,7 @@ textarea { /* Make textareas show at minimum one line. This does not work when using box-sizing border-box, in which case, vertical padding and border width needs to be taken into account. */ - min-height: 1em; + min-height: 1lh; vertical-align: baseline; /* Firefox gives "text-bottom" by default */ } From 24f99e548d8135980d49124553321b995489c5bf Mon Sep 17 00:00:00 2001 From: Samuel Mannehed Date: Mon, 13 Jan 2025 01:18:32 +0100 Subject: [PATCH 47/70] Add styling for toggle switches These are a type of checkbox that is suitable for ON/OFF-type switches. --- app/styles/input.css | 66 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 57 insertions(+), 9 deletions(-) diff --git a/app/styles/input.css b/app/styles/input.css index 731f36a9..5eeac940 100644 --- a/app/styles/input.css +++ b/app/styles/input.css @@ -232,7 +232,7 @@ input[type=color]::-moz-color-swatch { border-radius: 6px; } -/* -- SHARED BETWEEN CHECKBOXES AND RADIOBUTTONS -- */ +/* -- SHARED BETWEEN CHECKBOXES, RADIOBUTTONS AND THE TOGGLE CLASS -- */ input[type=radio], input[type=checkbox] { @@ -273,7 +273,7 @@ input[type=checkbox]:focus-visible { outline-color: var(--novnc-lightblue); } input[type=checkbox]::before, -input[type=checkbox]::after, +input[type=checkbox]:not(.toggle)::after, input[type=radio]::before, input[type=radio]::after { content: ""; @@ -286,7 +286,7 @@ input[type=radio]::after { baseline of text inside them will be used instead. */ position: absolute; } -input[type=checkbox]::after, +input[type=checkbox]:not(.toggle)::after, input[type=radio]::after { width: 10px; height: 2px; @@ -296,16 +296,16 @@ input[type=radio]::after { /* ------- CHECKBOXES ------- */ -input[type=checkbox] { +input[type=checkbox]:not(.toggle) { border-radius: 4px; } -input[type=checkbox]:checked, -input[type=checkbox]:indeterminate { +input[type=checkbox]:not(.toggle):checked, +input[type=checkbox]:not(.toggle):indeterminate { background-color: var(--novnc-blue); background-image: var(--button-activation-overlay-light); background-blend-mode: overlay; } -input[type=checkbox]::before { +input[type=checkbox]:not(.toggle)::before { width: 25%; height: 55%; border-style: solid; @@ -314,10 +314,10 @@ input[type=checkbox]::before { border-radius: 1px; transform: translateY(-1px) rotate(35deg); } -input[type=checkbox]:checked::before { +input[type=checkbox]:not(.toggle):checked::before { border-color: white; } -input[type=checkbox]:indeterminate::after { +input[type=checkbox]:not(.toggle):indeterminate::after { background-color: white; } @@ -353,6 +353,54 @@ input[type=radio]:indeterminate::after { background-color: black; } +/* ------- TOGGLE SWITCHES ------- */ + +/* These are meant to be used instead of checkboxes in some cases. If all of + the following critera are true you should use a toggle switch: + + * The choice is a simple ON/OFF or ENABLE/DISABLE + * The choice doesn't give the feeling of "I agree" or "I confirm" + * There are not multiple related & grouped options + */ + +input[type=checkbox].toggle { + display: inline-block; + --checkradio-height: 18px; /* Height value used in calc, see above */ + width: 31px; + cursor: pointer; + user-select: none; + -webkit-user-select: none; + border-radius: 9px; +} +input[type=checkbox].toggle:disabled { + cursor: default; +} +input[type=checkbox].toggle:indeterminate { + background-color: var(--novnc-buttongrey); + background-image: var(--button-activation-overlay); +} +input[type=checkbox].toggle:checked { + background-color: var(--novnc-blue); + background-image: var(--button-activation-overlay-light); + background-blend-mode: overlay; +} +input[type=checkbox].toggle::before { + --circle-diameter: 10px; + --circle-offset: 4px; + width: var(--circle-diameter); + height: var(--circle-diameter); + top: var(--circle-offset); + left: var(--circle-offset); + background: white; + border-radius: 6px; +} +input[type=checkbox].toggle:checked::before { + left: calc(100% - var(--circle-offset) - var(--circle-diameter)); +} +input[type=checkbox].toggle:indeterminate::before { + left: calc(50% - var(--circle-diameter) / 2); +} + /* ------- RANGE SLIDERS ------- */ input[type=range] { From 6c1e7bc50768b07c7cf81628a45bace90f5aa23b Mon Sep 17 00:00:00 2001 From: Samuel Mannehed Date: Mon, 13 Jan 2025 01:19:40 +0100 Subject: [PATCH 48/70] Utilize toggle switch in settings These settings are well suited to use toggle switches. This makes these settings more approachable and user-friendly. --- vnc.html | 36 ++++++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/vnc.html b/vnc.html index ed82b603..304b1a59 100644 --- a/vnc.html +++ b/vnc.html @@ -206,14 +206,26 @@
  • - +
  • - +

  • - +
  • @@ -244,7 +256,11 @@
    WebSocket
    • - +
    • @@ -262,7 +278,11 @@

    • - +
    • @@ -270,7 +290,11 @@

    • - +

    • From 3a5dd22603efddbed6be721a87bca2c2cff106e0 Mon Sep 17 00:00:00 2001 From: Samuel Mannehed Date: Mon, 13 Jan 2025 01:32:57 +0100 Subject: [PATCH 49/70] Add styling for checked options in select boxes --- app/styles/input.css | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/styles/input.css b/app/styles/input.css index 5eeac940..8b5f3ca2 100644 --- a/app/styles/input.css +++ b/app/styles/input.css @@ -515,6 +515,9 @@ select:disabled { var(--select-arrow), var(--grey-background); } +/* Note that styling for
    -
    - - +
    + +
    @@ -383,8 +383,8 @@ -
    - +
    +
    @@ -393,7 +393,7 @@
    - +
    From 6db9dbcf905792570670f255d52b9159fb61a56b Mon Sep 17 00:00:00 2001 From: Samuel Mannehed Date: Tue, 14 Jan 2025 10:29:25 +0100 Subject: [PATCH 55/70] Tweak design of noVNC connect button Make the color contrast with the background and the button more rounded. The goal is to make the button stand out. --- app/styles/base.css | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/styles/base.css b/app/styles/base.css index 64089392..1d61bc46 100644 --- a/app/styles/base.css +++ b/app/styles/base.css @@ -737,9 +737,9 @@ html { } } #noVNC_connect_dlg div { - padding: 12px; + padding: 18px; - background-color: var(--novnc-blue); + background-color: var(--novnc-darkgrey); border-radius: 12px; text-align: center; font-size: 20px; @@ -751,7 +751,7 @@ html { padding: 6px 30px; cursor: pointer; border-color: transparent; - border-radius: 6px; + border-radius: 12px; background-color: var(--novnc-blue); color: white; From 14f9ea5880f32f2a4867006d46c8e871942c698e Mon Sep 17 00:00:00 2001 From: Samuel Mannehed Date: Thu, 16 Jan 2025 11:20:47 +0100 Subject: [PATCH 56/70] Fix settings panel layout on small screens Both labels and inputs protruded outside the panel on for example a phone in portrait mode. This commit fixes that by allowing wrapping and setting a max-width. Since the --input-xpadding variable is now used in two different CSS files, it was moved to constants.css. --- app/styles/base.css | 16 ++++++++++++---- app/styles/constants.css | 6 ++++++ app/styles/input.css | 4 ---- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/app/styles/base.css b/app/styles/base.css index 1d61bc46..96315703 100644 --- a/app/styles/base.css +++ b/app/styles/base.css @@ -464,16 +464,24 @@ html { white-space: nowrap; margin: 5px; } +@media (max-width: 540px) { + /* Allow wrapping on small screens */ + .noVNC_panel label { + white-space: unset; + } +} .noVNC_panel li { margin: 5px; } -.noVNC_panel label > button, -.noVNC_panel label > select, -.noVNC_panel label > textarea, -.noVNC_panel label > input:not([type=checkbox]):not([type=radio]) { +.noVNC_panel button, +.noVNC_panel select, +.noVNC_panel textarea, +.noVNC_panel input:not([type=checkbox]):not([type=radio]) { margin-left: 6px; + /* Prevent inputs in panels from being too wide */ + max-width: calc(100% - 6px - var(--input-xpadding) * 2); } .noVNC_panel .noVNC_heading { diff --git a/app/styles/constants.css b/app/styles/constants.css index daf39616..1123a3ef 100644 --- a/app/styles/constants.css +++ b/app/styles/constants.css @@ -22,3 +22,9 @@ --novnc-green: rgb(0, 128, 0); --novnc-yellow: rgb(255, 255, 0); } + +/* ------ MISC PROPERTIES ------ */ + +:root { + --input-xpadding: 1em; +} diff --git a/app/styles/input.css b/app/styles/input.css index 1be95972..8273d70a 100644 --- a/app/styles/input.css +++ b/app/styles/input.css @@ -7,10 +7,6 @@ /* ------- SHARED BETWEEN INPUT ELEMENTS -------- */ -:root { - --input-xpadding: 1em; -} - input, textarea, button, From 237a34dfb39d8937e4c502618f3749e81f64f72a Mon Sep 17 00:00:00 2001 From: Samuel Mannehed Date: Thu, 23 Jan 2025 13:16:14 +0100 Subject: [PATCH 57/70] Add exceptions for CSS validator false positives Some new CSS incorrectly give errors from validator.w3.org. Issues were opened in that repo, so hopefully we can remove these exceptions soon. I searched for alternative validators, but couldn't find a different one that had a simple API like this one. In order to reliably detect & handle these exceptions we unfortunately need to make the validator output parsing quite a bit more complicated. --- utils/validate | 57 +++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 52 insertions(+), 5 deletions(-) diff --git a/utils/validate b/utils/validate index a6b5507d..3f5cb871 100755 --- a/utils/validate +++ b/utils/validate @@ -28,16 +28,63 @@ for fn in "$@"; do curl --silent \ --header "Content-Type: ${type}; charset=utf-8" \ --data-binary @${fn} \ - https://validator.w3.org/nu/?out=text > $OUT - cat $OUT - echo + "https://validator.w3.org/nu/?out=gnu&level=error&asciiquotes=yes" \ + > $OUT # We don't fail the check for warnings as some warnings are # not relevant for us, and we don't currently have a way to # ignore just those - if grep -q -s -E "^Error:" $OUT; then + while read -r line; do + echo + + line_info=$(echo $line | cut -d ":" -f 2) + start_info=$(echo $line_info | cut -d "-" -f 1) + end_info=$(echo $line_info | cut -d "-" -f 2) + + line_start=$(echo $start_info | cut -d "." -f 1) + col_start=$(echo $start_info | cut -d "." -f 2) + + line_end=$(echo $end_info | cut -d "." -f 1) + col_end=$(echo $end_info | cut -d "." -f 2) + + error=$(echo $line | cut -d ":" -f 4-) + + case $error in + *"\"scrollbar-gutter\": Property \"scrollbar-gutter\" doesn't exist.") + # FIXME: https://github.com/validator/validator/issues/1788 + echo "Ignoring below error on line ${line_start}," \ + "the scrollbar-gutter property actually exist and is widely" \ + "supported:" + echo $error + continue + ;; + *"\"clip-path\": \"path("*) + # FIXME: https://github.com/validator/validator/issues/1786 + echo "Ignoring below error on line ${line_start}," \ + "the path() function is valid for clip-path and is" \ + "widely supported:" + echo $error + continue + ;; + *"Parse Error.") + # FIXME: https://github.com/validator/validator/issues/1786 + lineofselector=$(grep -n "@supports selector(" $fn | cut -d ":" -f 1) + linediff=$((lineofselector-line_start)) + # Only ignore if parse error is within 50 lines of "selector()" + if [ ${linediff#-} -lt 50 ]; then + echo "Ignoring below error on line ${line_start}," \ + "the @supports selector() function should not give a parse" \ + "error:" + echo $error + continue + fi + ;; + esac + echo "ERROR between line ${line_start} (col ${col_start})" \ + "and line ${line_end} (col ${col_end}):" + echo $error RET=1 - fi + done < "$OUT" done rm $OUT From 24835bdda4ff705c38f64616501635a1b6ba81ec Mon Sep 17 00:00:00 2001 From: Samuel Mannehed Date: Thu, 23 Jan 2025 14:22:32 +0100 Subject: [PATCH 58/70] Make the background of expanded settings lighter A very slight change to the background color, to make the contrast better with the light-grey input elements. --- app/styles/base.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/styles/base.css b/app/styles/base.css index 96315703..87bfb45c 100644 --- a/app/styles/base.css +++ b/app/styles/base.css @@ -535,7 +535,7 @@ html { margin: 5px; margin-left: 10px; padding: 5px; - background: rgba(0, 0, 0, 0.05); + background: rgba(0, 0, 0, 0.04); border-radius: 6px; } .noVNC_expander:not(.noVNC_open) ~ * { From b9f172dcdbb8e082de63b36f2ec30a39b7cd37cd Mon Sep 17 00:00:00 2001 From: Alexander Zeijlon Date: Tue, 28 Jan 2025 11:01:53 +0100 Subject: [PATCH 59/70] Update README.md with ExtendedMouseButtons feature --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index c497ad22..4b0b0eac 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,7 @@ profits such as: * Supported VNC encodings: raw, copyrect, rre, hextile, tight, tightPNG, ZRLE, JPEG, Zlib * Supports scaling, clipping and resizing the desktop +* Supports ExtendedMouseButtons pseudo-encoding * Local cursor rendering * Clipboard copy/paste with full Unicode support * Translations From 4e410a0619916970d5027b3ec8c6107d09684bce Mon Sep 17 00:00:00 2001 From: "Samuel Mannehed (ThinLinc team)" Date: Tue, 4 Feb 2025 16:14:31 +0100 Subject: [PATCH 60/70] Use less technical phrasing in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4b0b0eac..0a6d87eb 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ profits such as: * Supported VNC encodings: raw, copyrect, rre, hextile, tight, tightPNG, ZRLE, JPEG, Zlib * Supports scaling, clipping and resizing the desktop -* Supports ExtendedMouseButtons pseudo-encoding +* Supports back & forward mouse buttons * Local cursor rendering * Clipboard copy/paste with full Unicode support * Translations From bbbef2d9fa0f3d8ca301dd233e0f1625be7b19fe Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Wed, 5 Feb 2025 16:11:19 +0100 Subject: [PATCH 61/70] Add helper for ExtendedDesktopSize in tests --- tests/test.rfb.js | 129 ++++++++++++---------------------------------- 1 file changed, 32 insertions(+), 97 deletions(-) diff --git a/tests/test.rfb.js b/tests/test.rfb.js index 8cdd2e36..96f8f696 100644 --- a/tests/test.rfb.js +++ b/tests/test.rfb.js @@ -257,6 +257,29 @@ describe('Remote Frame Buffer protocol client', function () { client._sock._websocket._receiveData(new Uint8Array(data)); } + function sendExtendedDesktopSize(client, reason, result, width, height, screenId, screenFlags) { + let rectInfo = { x: reason, y: result, width: width, height: height, encoding: -308 }; + let rectData = [ + 0x01, // number of screens = 1 + 0x00, 0x00, + 0x00, // padding + (screenId >> 24) & 0xff, + (screenId >> 16) & 0xff, + (screenId >> 8) & 0xff, + screenId & 0xff, + 0x00, 0x00, // screen x + 0x00, 0x00, // screen y + (width >> 8) & 0xff, + width & 0xff, + (height >> 8) & 0xff, + height & 0xff, + (screenFlags >> 24) & 0xff, + (screenFlags >> 16) & 0xff, + (screenFlags >> 8) & 0xff, + screenFlags & 0xff]; + sendFbuMsg([rectInfo], [rectData], client); + } + describe('Connecting/Disconnecting', function () { describe('#RFB (constructor)', function () { let open, attach; @@ -650,16 +673,10 @@ describe('Remote Frame Buffer protocol client', function () { }); it('should update the viewport when the remote session resizes', function () { - // Simple ExtendedDesktopSize FBU message - const incoming = [ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, - 0x00, 0xff, 0x00, 0xff, 0xff, 0xff, 0xfe, 0xcc, - 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, - 0x00, 0x00, 0x00, 0x00 ]; - sinon.spy(client._display, "viewportChangeSize"); - client._sock._websocket._receiveData(new Uint8Array(incoming)); + // Simple ExtendedDesktopSize FBU message + sendExtendedDesktopSize(client, 0, 0, 4, 4, 0x7890abcd, 0x12345678); // The resize will cause scrollbars on the container, this causes a // resize observation in the browsers fakeResizeObserver.fire(); @@ -951,27 +968,7 @@ describe('Remote Frame Buffer protocol client', function () { container.style.height = '80px'; client.scaleViewport = true; - const incoming = [ 0x00, // msg-type=FBU - 0x00, // padding - 0x00, 0x01, // number of rects = 1 - 0x00, 0x00, // reason = server initialized - 0x00, 0x00, // status = no error - 0x00, 0x04, // new width = 4 - 0x00, 0x04, // new height = 4 - 0xff, 0xff, - 0xfe, 0xcc, // enc = (-308) ExtendedDesktopSize - 0x01, // number of screens = 1 - 0x00, 0x00, - 0x00, // padding - 0x78, 0x90, - 0xab, 0xcd, // screen id = 0 - 0x00, 0x00, // screen x = 0 - 0x00, 0x00, // screen y = 0 - 0x00, 0x04, // screen width = 4 - 0x00, 0x04, // screen height = 4 - 0x12, 0x34, - 0x56, 0x78]; // screen flags - client._sock._websocket._receiveData(new Uint8Array(incoming)); + sendExtendedDesktopSize(client, 0, 0, 4, 4, 0x7890abcd, 0x12345678); }); it('should update display scale factor when changing the property', function () { @@ -1039,16 +1036,9 @@ describe('Remote Frame Buffer protocol client', function () { }); it('should update the scaling when the remote session resizes', function () { - // Simple ExtendedDesktopSize FBU message - const incoming = [ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, - 0x00, 0xff, 0x00, 0xff, 0xff, 0xff, 0xfe, 0xcc, - 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, - 0x00, 0x00, 0x00, 0x00 ]; - sinon.spy(client._display, "autoscale"); - client._sock._websocket._receiveData(new Uint8Array(incoming)); + sendExtendedDesktopSize(client, 0, 0, 4, 4, 0x7890abcd, 0x12345678); // The resize will cause scrollbars on the container, this causes a // resize observation in the browsers fakeResizeObserver.fire(); @@ -1080,27 +1070,7 @@ describe('Remote Frame Buffer protocol client', function () { container.style.width = '70px'; container.style.height = '80px'; - const incoming = [ 0x00, // msg-type=FBU - 0x00, // padding - 0x00, 0x01, // number of rects = 1 - 0x00, 0x00, // reason = server initialized - 0x00, 0x00, // status = no error - 0x00, 0x04, // new width = 4 - 0x00, 0x04, // new height = 4 - 0xff, 0xff, - 0xfe, 0xcc, // enc = (-308) ExtendedDesktopSize - 0x01, // number of screens = 1 - 0x00, 0x00, - 0x00, // padding - 0x78, 0x90, - 0xab, 0xcd, // screen id = 0 - 0x00, 0x00, // screen x = 0 - 0x00, 0x00, // screen y = 0 - 0x00, 0x04, // screen width = 4 - 0x00, 0x04, // screen height = 4 - 0x12, 0x34, - 0x56, 0x78]; // screen flags - client._sock._websocket._receiveData(new Uint8Array(incoming)); + sendExtendedDesktopSize(client, 0, 0, 4, 4, 0x7890abcd, 0x12345678); sinon.spy(RFB.messages, "setDesktopSize"); }); @@ -1124,31 +1094,9 @@ describe('Remote Frame Buffer protocol client', function () { container.style.width = '70px'; container.style.height = '80px'; - // Simple ExtendedDesktopSize FBU message - const incoming = [ 0x00, // msg-type=FBU - 0x00, // padding - 0x00, 0x01, // number of rects = 1 - 0x00, 0x00, // reason = server initialized - 0x00, 0x00, // status = no error - 0x00, 0x04, // new width = 4 - 0x00, 0x04, // new height = 4 - 0xff, 0xff, - 0xfe, 0xcc, // enc = (-308) ExtendedDesktopSize - 0x01, // number of screens = 1 - 0x00, 0x00, - 0x00, // padding - 0x78, 0x90, - 0xab, 0xcd, // screen id = 0 - 0x00, 0x00, // screen x = 0 - 0x00, 0x00, // screen y = 0 - 0x00, 0x04, // screen width = 4 - 0x00, 0x04, // screen height = 4 - 0x12, 0x34, - 0x56, 0x78]; // screen flags - // First message should trigger a resize - client._sock._websocket._receiveData(new Uint8Array(incoming)); + sendExtendedDesktopSize(client, 0, 0, 4, 4, 0x7890abcd, 0x12345678); // It should match the current size of the container, // not the reported size from the server @@ -1160,7 +1108,7 @@ describe('Remote Frame Buffer protocol client', function () { // Second message should not trigger a resize - client._sock._websocket._receiveData(new Uint8Array(incoming)); + sendExtendedDesktopSize(client, 0, 0, 4, 4, 0x7890abcd, 0x12345678); expect(RFB.messages.setDesktopSize).to.not.have.been.called; }); @@ -1187,13 +1135,7 @@ describe('Remote Frame Buffer protocol client', function () { sinon.match.object, 40, 50, 0x7890abcd, 0x12345678); // Server responds with the requested size 40x50 - const incoming = [ 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, - 0x00, 0x28, 0x00, 0x32, 0xff, 0xff, 0xfe, 0xcc, - 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x32, - 0x00, 0x00, 0x00, 0x00]; - - client._sock._websocket._receiveData(new Uint8Array(incoming)); + sendExtendedDesktopSize(client, 1, 0, 40, 50, 0x7890abcd, 0x12345678); clock.tick(1000); RFB.messages.setDesktopSize.resetHistory(); @@ -1282,18 +1224,11 @@ describe('Remote Frame Buffer protocol client', function () { }); it('should not try to override a server resize', function () { - // Simple ExtendedDesktopSize FBU message, new size: 100x100 - const incoming = [ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x64, 0x00, 0x64, 0xff, 0xff, 0xfe, 0xcc, - 0x01, 0x00, 0x00, 0x00, 0xab, 0xab, 0xab, 0xab, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04, - 0x11, 0x22, 0x33, 0x44 ]; - // Note that this will cause the browser to display scrollbars // since the framebuffer is 100x100 and the container is 70x80. // The usable space (clientWidth/clientHeight) will be even smaller // due to the scrollbars taking up space. - client._sock._websocket._receiveData(new Uint8Array(incoming)); + sendExtendedDesktopSize(client, 0, 0, 100, 100, 0xabababab, 0x11223344); // The scrollbars cause the ResizeObserver to fire fakeResizeObserver.fire(); clock.tick(1000); From 70446bf74250d85d4b315b1cf3f93a363a0291f6 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Wed, 5 Feb 2025 16:17:56 +0100 Subject: [PATCH 62/70] Make resizeSession setting test more realistic We shouldn't expect a resize request to be sent if the container didn't change size first. --- tests/test.rfb.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/test.rfb.js b/tests/test.rfb.js index 96f8f696..5185ae6e 100644 --- a/tests/test.rfb.js +++ b/tests/test.rfb.js @@ -1082,6 +1082,13 @@ describe('Remote Frame Buffer protocol client', function () { it('should only request a resize when turned on', function () { client.resizeSession = false; expect(RFB.messages.setDesktopSize).to.not.have.been.called; + + container.style.width = '40px'; + container.style.height = '50px'; + fakeResizeObserver.fire(); + clock.tick(1000); + expect(RFB.messages.setDesktopSize).to.not.have.been.called; + client.resizeSession = true; expect(RFB.messages.setDesktopSize).to.have.been.calledOnce; }); From c82178348ab6c67fd3655d1de677c1a0075a2ddb Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Wed, 5 Feb 2025 16:19:00 +0100 Subject: [PATCH 63/70] Include SetDesktopSize responses in tests There might be subtle changes in behaviour, so we should mimic what a real server does. --- tests/test.rfb.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/tests/test.rfb.js b/tests/test.rfb.js index 5185ae6e..9e792704 100644 --- a/tests/test.rfb.js +++ b/tests/test.rfb.js @@ -1070,9 +1070,16 @@ describe('Remote Frame Buffer protocol client', function () { container.style.width = '70px'; container.style.height = '80px'; + sinon.spy(RFB.messages, "setDesktopSize"); + sendExtendedDesktopSize(client, 0, 0, 4, 4, 0x7890abcd, 0x12345678); - sinon.spy(RFB.messages, "setDesktopSize"); + if (RFB.messages.setDesktopSize.calledOnce) { + let width = RFB.messages.setDesktopSize.args[0][1]; + let height = RFB.messages.setDesktopSize.args[0][2]; + sendExtendedDesktopSize(client, 1, 0, width, height, 0x7890abcd, 0x12345678); + RFB.messages.setDesktopSize.resetHistory(); + } }); afterEach(function () { @@ -1111,6 +1118,7 @@ describe('Remote Frame Buffer protocol client', function () { expect(RFB.messages.setDesktopSize).to.have.been.calledWith( sinon.match.object, 70, 80, 0x7890abcd, 0x12345678); + sendExtendedDesktopSize(client, 1, 0, 70, 80, 0x7890abcd, 0x12345678); RFB.messages.setDesktopSize.resetHistory(); // Second message should not trigger a resize @@ -1163,6 +1171,8 @@ describe('Remote Frame Buffer protocol client', function () { expect(RFB.messages.setDesktopSize).to.have.been.calledOnce; expect(RFB.messages.setDesktopSize).to.have.been.calledWith( sinon.match.object, 40, 50, 0x7890abcd, 0x12345678); + + sendExtendedDesktopSize(client, 1, 0, 40, 50, 0x7890abcd, 0x12345678); RFB.messages.setDesktopSize.resetHistory(); container.style.width = '70px'; From 0b5e968e14075519c8ef6ff7201f3355b4536d04 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Wed, 5 Feb 2025 16:33:13 +0100 Subject: [PATCH 64/70] Better resize rate limiting Be more aggressive with resizing, limiting it to once ever 100 ms instead of after a 500 ms idle period. This gives a more responsive user experience. --- core/rfb.js | 57 +++++++++++++++++------ tests/test.rfb.js | 116 +++++++++++++++++++++++++++++++++++++++------- 2 files changed, 141 insertions(+), 32 deletions(-) diff --git a/core/rfb.js b/core/rfb.js index 57f02581..e3266cc8 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -149,6 +149,8 @@ export default class RFB extends EventTargetMixin { this._supportsSetDesktopSize = false; this._screenID = 0; this._screenFlags = 0; + this._pendingRemoteResize = false; + this._lastResize = 0; this._qemuExtKeyEventSupported = false; @@ -736,15 +738,9 @@ export default class RFB extends EventTargetMixin { this._saveExpectedClientSize(); }); - if (this._resizeSession) { - // Request changing the resolution of the remote display to - // the size of the local browser viewport. - - // In order to not send multiple requests before the browser-resize - // is finished we wait 0.5 seconds before sending the request. - clearTimeout(this._resizeTimeout); - this._resizeTimeout = setTimeout(this._requestRemoteResize.bind(this), 500); - } + // Request changing the resolution of the remote display to + // the size of the local browser viewport. + this._requestRemoteResize(); } // Update state of clipping in Display object, and make sure the @@ -794,16 +790,39 @@ export default class RFB extends EventTargetMixin { // Requests a change of remote desktop size. This message is an extension // and may only be sent if we have received an ExtendedDesktopSize message _requestRemoteResize() { - clearTimeout(this._resizeTimeout); - this._resizeTimeout = null; - - if (!this._resizeSession || this._viewOnly || - !this._supportsSetDesktopSize) { + if (!this._resizeSession) { + return; + } + if (this._viewOnly) { + return; + } + if (!this._supportsSetDesktopSize) { return; } + // Rate limit to one pending resize at a time + if (this._pendingRemoteResize) { + return; + } + + // And no more than once every 100ms + if ((Date.now() - this._lastResize) < 100) { + clearTimeout(this._resizeTimeout); + this._resizeTimeout = setTimeout(this._requestRemoteResize.bind(this), + 100 - (Date.now() - this._lastResize)); + return; + } + this._resizeTimeout = null; + const size = this._screenSize(); + // Do we actually change anything? + if (size.w === this._fbWidth && size.h === this._fbHeight) { + return; + } + + this._pendingRemoteResize = true; + this._lastResize = Date.now(); RFB.messages.setDesktopSize(this._sock, Math.floor(size.w), Math.floor(size.h), this._screenID, this._screenFlags); @@ -2913,6 +2932,10 @@ export default class RFB extends EventTargetMixin { * 2 - another client requested the resize */ + if (this._FBU.x === 1) { + this._pendingRemoteResize = false; + } + // We need to handle errors when we requested the resize. if (this._FBU.x === 1 && this._FBU.y !== 0) { let msg = ""; @@ -2945,6 +2968,12 @@ export default class RFB extends EventTargetMixin { this._requestRemoteResize(); } + if (this._FBU.x === 1 && this._FBU.y === 0) { + // We might have resized again whilst waiting for the + // previous request, so check if we are in sync + this._requestRemoteResize(); + } + return true; } diff --git a/tests/test.rfb.js b/tests/test.rfb.js index 9e792704..2a7bbeaa 100644 --- a/tests/test.rfb.js +++ b/tests/test.rfb.js @@ -680,7 +680,6 @@ describe('Remote Frame Buffer protocol client', function () { // The resize will cause scrollbars on the container, this causes a // resize observation in the browsers fakeResizeObserver.fire(); - clock.tick(1000); // FIXME: Display implicitly calls viewportChangeSize() when // resizing the framebuffer, hence calledTwice. @@ -1042,7 +1041,6 @@ describe('Remote Frame Buffer protocol client', function () { // The resize will cause scrollbars on the container, this causes a // resize observation in the browsers fakeResizeObserver.fire(); - clock.tick(1000); expect(client._display.autoscale).to.have.been.calledOnce; expect(client._display.autoscale).to.have.been.calledWith(70, 80); @@ -1079,6 +1077,7 @@ describe('Remote Frame Buffer protocol client', function () { let height = RFB.messages.setDesktopSize.args[0][2]; sendExtendedDesktopSize(client, 1, 0, width, height, 0x7890abcd, 0x12345678); RFB.messages.setDesktopSize.resetHistory(); + clock.tick(10000); } }); @@ -1093,7 +1092,6 @@ describe('Remote Frame Buffer protocol client', function () { container.style.width = '40px'; container.style.height = '50px'; fakeResizeObserver.fire(); - clock.tick(1000); expect(RFB.messages.setDesktopSize).to.not.have.been.called; client.resizeSession = true; @@ -1132,7 +1130,6 @@ describe('Remote Frame Buffer protocol client', function () { container.style.width = '40px'; container.style.height = '50px'; fakeResizeObserver.fire(); - clock.tick(1000); expect(RFB.messages.setDesktopSize).to.have.been.calledOnce; expect(RFB.messages.setDesktopSize).to.have.been.calledWith( @@ -1143,7 +1140,6 @@ describe('Remote Frame Buffer protocol client', function () { container.style.width = '40px'; container.style.height = '50px'; fakeResizeObserver.fire(); - clock.tick(1000); expect(RFB.messages.setDesktopSize).to.have.been.calledOnce; expect(RFB.messages.setDesktopSize).to.have.been.calledWith( @@ -1151,13 +1147,12 @@ describe('Remote Frame Buffer protocol client', function () { // Server responds with the requested size 40x50 sendExtendedDesktopSize(client, 1, 0, 40, 50, 0x7890abcd, 0x12345678); - clock.tick(1000); RFB.messages.setDesktopSize.resetHistory(); // size is still 40x50 - fakeResizeObserver.fire(); clock.tick(1000); + fakeResizeObserver.fire(); expect(RFB.messages.setDesktopSize).to.not.have.been.called; }); @@ -1166,7 +1161,6 @@ describe('Remote Frame Buffer protocol client', function () { container.style.width = '40px'; container.style.height = '50px'; fakeResizeObserver.fire(); - clock.tick(1000); expect(RFB.messages.setDesktopSize).to.have.been.calledOnce; expect(RFB.messages.setDesktopSize).to.have.been.calledWith( @@ -1175,45 +1169,135 @@ describe('Remote Frame Buffer protocol client', function () { sendExtendedDesktopSize(client, 1, 0, 40, 50, 0x7890abcd, 0x12345678); RFB.messages.setDesktopSize.resetHistory(); + clock.tick(1000); container.style.width = '70px'; container.style.height = '80px'; fakeResizeObserver.fire(); - clock.tick(1000); expect(RFB.messages.setDesktopSize).to.have.been.calledOnce; expect(RFB.messages.setDesktopSize).to.have.been.calledWith( sinon.match.object, 70, 80, 0x7890abcd, 0x12345678); }); - it('should not resize until the container size is stable', function () { + it('should rate limit resizes', function () { container.style.width = '20px'; container.style.height = '30px'; fakeResizeObserver.fire(); - clock.tick(400); + + expect(RFB.messages.setDesktopSize).to.have.been.calledOnce; + expect(RFB.messages.setDesktopSize).to.have.been.calledWith( + sinon.match.object, 20, 30, 0x7890abcd, 0x12345678); + + sendExtendedDesktopSize(client, 1, 0, 20, 30, 0x7890abcd, 0x12345678); + RFB.messages.setDesktopSize.resetHistory(); + + clock.tick(20); + + container.style.width = '30px'; + container.style.height = '40px'; + fakeResizeObserver.fire(); expect(RFB.messages.setDesktopSize).to.not.have.been.called; + clock.tick(20); + container.style.width = '40px'; container.style.height = '50px'; fakeResizeObserver.fire(); - clock.tick(400); expect(RFB.messages.setDesktopSize).to.not.have.been.called; - clock.tick(200); + clock.tick(80); expect(RFB.messages.setDesktopSize).to.have.been.calledOnce; expect(RFB.messages.setDesktopSize).to.have.been.calledWith( sinon.match.object, 40, 50, 0x7890abcd, 0x12345678); }); + it('should not have overlapping resize requests', function () { + container.style.width = '40px'; + container.style.height = '50px'; + fakeResizeObserver.fire(); + + expect(RFB.messages.setDesktopSize).to.have.been.calledOnce; + + RFB.messages.setDesktopSize.resetHistory(); + + clock.tick(1000); + container.style.width = '20px'; + container.style.height = '30px'; + fakeResizeObserver.fire(); + + expect(RFB.messages.setDesktopSize).to.not.have.been.called; + }); + + it('should finalize any pending resizes', function () { + container.style.width = '40px'; + container.style.height = '50px'; + fakeResizeObserver.fire(); + + expect(RFB.messages.setDesktopSize).to.have.been.calledOnce; + + RFB.messages.setDesktopSize.resetHistory(); + + clock.tick(1000); + container.style.width = '20px'; + container.style.height = '30px'; + fakeResizeObserver.fire(); + + expect(RFB.messages.setDesktopSize).to.not.have.been.called; + + // Server responds with the requested size 40x50 + sendExtendedDesktopSize(client, 1, 0, 40, 50, 0x7890abcd, 0x12345678); + + expect(RFB.messages.setDesktopSize).to.have.been.calledOnce; + expect(RFB.messages.setDesktopSize).to.have.been.calledWith( + sinon.match.object, 20, 30, 0x7890abcd, 0x12345678); + }); + + it('should not finalize any pending resize if not needed', function () { + container.style.width = '40px'; + container.style.height = '50px'; + fakeResizeObserver.fire(); + + expect(RFB.messages.setDesktopSize).to.have.been.calledOnce; + + RFB.messages.setDesktopSize.resetHistory(); + + // Server responds with the requested size 40x50 + sendExtendedDesktopSize(client, 1, 0, 40, 50, 0x7890abcd, 0x12345678); + + expect(RFB.messages.setDesktopSize).to.not.have.been.called; + }); + + it('should not finalize any pending resizes on errors', function () { + container.style.width = '40px'; + container.style.height = '50px'; + fakeResizeObserver.fire(); + + expect(RFB.messages.setDesktopSize).to.have.been.calledOnce; + + RFB.messages.setDesktopSize.resetHistory(); + + clock.tick(1000); + container.style.width = '20px'; + container.style.height = '30px'; + fakeResizeObserver.fire(); + + expect(RFB.messages.setDesktopSize).to.not.have.been.called; + + // Server failed the requested size 40x50 + sendExtendedDesktopSize(client, 1, 1, 40, 50, 0x7890abcd, 0x12345678); + + expect(RFB.messages.setDesktopSize).to.not.have.been.called; + }); + it('should not resize when resize is disabled', function () { client._resizeSession = false; container.style.width = '40px'; container.style.height = '50px'; fakeResizeObserver.fire(); - clock.tick(1000); expect(RFB.messages.setDesktopSize).to.not.have.been.called; }); @@ -1224,7 +1308,6 @@ describe('Remote Frame Buffer protocol client', function () { container.style.width = '40px'; container.style.height = '50px'; fakeResizeObserver.fire(); - clock.tick(1000); expect(RFB.messages.setDesktopSize).to.not.have.been.called; }); @@ -1235,7 +1318,6 @@ describe('Remote Frame Buffer protocol client', function () { container.style.width = '40px'; container.style.height = '50px'; fakeResizeObserver.fire(); - clock.tick(1000); expect(RFB.messages.setDesktopSize).to.not.have.been.called; }); @@ -1248,7 +1330,6 @@ describe('Remote Frame Buffer protocol client', function () { sendExtendedDesktopSize(client, 0, 0, 100, 100, 0xabababab, 0x11223344); // The scrollbars cause the ResizeObserver to fire fakeResizeObserver.fire(); - clock.tick(1000); expect(RFB.messages.setDesktopSize).to.not.have.been.called; @@ -1256,7 +1337,6 @@ describe('Remote Frame Buffer protocol client', function () { container.style.width = '120px'; container.style.height = '130px'; fakeResizeObserver.fire(); - clock.tick(1000); expect(RFB.messages.setDesktopSize).to.have.been.calledOnce; expect(RFB.messages.setDesktopSize).to.have.been.calledWith( From 7ee792276696ee9cc8f80847506678f9c264e357 Mon Sep 17 00:00:00 2001 From: tianzedavid Date: Thu, 13 Feb 2025 01:00:46 +0800 Subject: [PATCH 65/70] chore: fix some typos Signed-off-by: tianzedavid --- README.md | 2 +- core/util/browser.js | 2 +- snap/local/svc_wrapper.sh | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0a6d87eb..6976a746 100644 --- a/README.md +++ b/README.md @@ -143,7 +143,7 @@ If you want to use certificate files, due to standard snap confinement restricti #### Running noVNC from snap as a service (daemon) The snap package also has the capability to run a 'novnc' service which can be configured to listen on multiple ports connecting to multiple VNC servers -(effectively a service runing multiple instances of novnc). +(effectively a service running multiple instances of novnc). Instructions (with example values): List current services (out-of-box this will be blank): diff --git a/core/util/browser.js b/core/util/browser.js index 2c366765..fc1b77f9 100644 --- a/core/util/browser.js +++ b/core/util/browser.js @@ -13,7 +13,7 @@ import Base64 from '../base64.js'; // Touch detection export let isTouchDevice = ('ontouchstart' in document.documentElement) || - // requried for Chrome debugger + // required for Chrome debugger (document.ontouchstart !== undefined) || // required for MS Surface (navigator.maxTouchPoints > 0) || diff --git a/snap/local/svc_wrapper.sh b/snap/local/svc_wrapper.sh index 77db5394..5c6650a1 100755 --- a/snap/local/svc_wrapper.sh +++ b/snap/local/svc_wrapper.sh @@ -11,7 +11,7 @@ # "vnc": "localhost:5902" #} #} -snapctl get services | jq -c '.[]' | while read service; do # for each service the user sepcified.. +snapctl get services | jq -c '.[]' | while read service; do # for each service the user specified.. # get the important data for the service (listen port, VNC host:port) listen_port="$(echo $service | jq --raw-output '.listen')" vnc_host_port="$(echo $service | jq --raw-output '.vnc')" # --raw-output removes any quotation marks from the output From e8030a9fb124d5c3d5342d966b5cc86280cd4923 Mon Sep 17 00:00:00 2001 From: Alexander Zeijlon Date: Fri, 14 Feb 2025 10:16:08 +0100 Subject: [PATCH 66/70] Update translation template file --- po/noVNC.pot | 164 +++++++++++++++++++++++++-------------------------- 1 file changed, 80 insertions(+), 84 deletions(-) diff --git a/po/noVNC.pot b/po/noVNC.pot index a939d536..7c32a3ed 100644 --- a/po/noVNC.pot +++ b/po/noVNC.pot @@ -6,9 +6,9 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: noVNC 1.5.0\n" +"Project-Id-Version: noVNC 1.6.0\n" "Report-Msgid-Bugs-To: novnc@googlegroups.com\n" -"POT-Creation-Date: 2024-06-03 14:10+0200\n" +"POT-Creation-Date: 2025-02-14 10:14+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,321 +17,317 @@ msgstr "" "Content-Type: text/plain; charset=CHARSET\n" "Content-Transfer-Encoding: 8bit\n" -#: ../app/ui.js:69 +#: ../app/ui.js:84 msgid "" "Running without HTTPS is not recommended, crashes or other issues are likely." msgstr "" -#: ../app/ui.js:410 +#: ../app/ui.js:413 msgid "Connecting..." msgstr "" -#: ../app/ui.js:417 +#: ../app/ui.js:420 msgid "Disconnecting..." msgstr "" -#: ../app/ui.js:423 +#: ../app/ui.js:426 msgid "Reconnecting..." msgstr "" -#: ../app/ui.js:428 +#: ../app/ui.js:431 msgid "Internal error" msgstr "" -#: ../app/ui.js:1026 -msgid "Must set host" -msgstr "" - -#: ../app/ui.js:1052 +#: ../app/ui.js:1079 msgid "Failed to connect to server: " msgstr "" -#: ../app/ui.js:1118 +#: ../app/ui.js:1145 msgid "Connected (encrypted) to " msgstr "" -#: ../app/ui.js:1120 +#: ../app/ui.js:1147 msgid "Connected (unencrypted) to " msgstr "" -#: ../app/ui.js:1143 +#: ../app/ui.js:1170 msgid "Something went wrong, connection is closed" msgstr "" -#: ../app/ui.js:1146 +#: ../app/ui.js:1173 msgid "Failed to connect to server" msgstr "" -#: ../app/ui.js:1158 +#: ../app/ui.js:1185 msgid "Disconnected" msgstr "" -#: ../app/ui.js:1173 +#: ../app/ui.js:1200 msgid "New connection has been rejected with reason: " msgstr "" -#: ../app/ui.js:1176 +#: ../app/ui.js:1203 msgid "New connection has been rejected" msgstr "" -#: ../app/ui.js:1242 +#: ../app/ui.js:1269 msgid "Credentials are required" msgstr "" -#: ../vnc.html:55 +#: ../vnc.html:106 msgid "noVNC encountered an error:" msgstr "" -#: ../vnc.html:65 +#: ../vnc.html:116 msgid "Hide/Show the control bar" msgstr "" -#: ../vnc.html:74 +#: ../vnc.html:125 msgid "Drag" msgstr "" -#: ../vnc.html:74 +#: ../vnc.html:125 msgid "Move/Drag viewport" msgstr "" -#: ../vnc.html:80 +#: ../vnc.html:131 msgid "Keyboard" msgstr "" -#: ../vnc.html:80 +#: ../vnc.html:131 msgid "Show keyboard" msgstr "" -#: ../vnc.html:85 +#: ../vnc.html:136 msgid "Extra keys" msgstr "" -#: ../vnc.html:85 +#: ../vnc.html:136 msgid "Show extra keys" msgstr "" -#: ../vnc.html:90 +#: ../vnc.html:141 msgid "Ctrl" msgstr "" -#: ../vnc.html:90 +#: ../vnc.html:141 msgid "Toggle Ctrl" msgstr "" -#: ../vnc.html:93 +#: ../vnc.html:144 msgid "Alt" msgstr "" -#: ../vnc.html:93 +#: ../vnc.html:144 msgid "Toggle Alt" msgstr "" -#: ../vnc.html:96 +#: ../vnc.html:147 msgid "Toggle Windows" msgstr "" -#: ../vnc.html:96 +#: ../vnc.html:147 msgid "Windows" msgstr "" -#: ../vnc.html:99 +#: ../vnc.html:150 msgid "Send Tab" msgstr "" -#: ../vnc.html:99 +#: ../vnc.html:150 msgid "Tab" msgstr "" -#: ../vnc.html:102 +#: ../vnc.html:153 msgid "Esc" msgstr "" -#: ../vnc.html:102 +#: ../vnc.html:153 msgid "Send Escape" msgstr "" -#: ../vnc.html:105 +#: ../vnc.html:156 msgid "Ctrl+Alt+Del" msgstr "" -#: ../vnc.html:105 +#: ../vnc.html:156 msgid "Send Ctrl-Alt-Del" msgstr "" -#: ../vnc.html:112 +#: ../vnc.html:163 msgid "Shutdown/Reboot" msgstr "" -#: ../vnc.html:112 +#: ../vnc.html:163 msgid "Shutdown/Reboot..." msgstr "" -#: ../vnc.html:118 +#: ../vnc.html:169 msgid "Power" msgstr "" -#: ../vnc.html:120 +#: ../vnc.html:171 msgid "Shutdown" msgstr "" -#: ../vnc.html:121 +#: ../vnc.html:172 msgid "Reboot" msgstr "" -#: ../vnc.html:122 +#: ../vnc.html:173 msgid "Reset" msgstr "" -#: ../vnc.html:127 ../vnc.html:133 +#: ../vnc.html:178 ../vnc.html:184 msgid "Clipboard" msgstr "" -#: ../vnc.html:135 +#: ../vnc.html:186 msgid "Edit clipboard content in the textarea below." msgstr "" -#: ../vnc.html:143 +#: ../vnc.html:194 msgid "Full screen" msgstr "" -#: ../vnc.html:148 ../vnc.html:154 +#: ../vnc.html:199 ../vnc.html:205 msgid "Settings" msgstr "" -#: ../vnc.html:158 +#: ../vnc.html:211 msgid "Shared mode" msgstr "" -#: ../vnc.html:161 +#: ../vnc.html:218 msgid "View only" msgstr "" -#: ../vnc.html:165 +#: ../vnc.html:226 msgid "Clip to window" msgstr "" -#: ../vnc.html:168 +#: ../vnc.html:231 msgid "Scaling mode:" msgstr "" -#: ../vnc.html:170 +#: ../vnc.html:233 msgid "None" msgstr "" -#: ../vnc.html:171 +#: ../vnc.html:234 msgid "Local scaling" msgstr "" -#: ../vnc.html:172 +#: ../vnc.html:235 msgid "Remote resizing" msgstr "" -#: ../vnc.html:177 +#: ../vnc.html:240 msgid "Advanced" msgstr "" -#: ../vnc.html:180 +#: ../vnc.html:243 msgid "Quality:" msgstr "" -#: ../vnc.html:184 +#: ../vnc.html:247 msgid "Compression level:" msgstr "" -#: ../vnc.html:189 +#: ../vnc.html:252 msgid "Repeater ID:" msgstr "" -#: ../vnc.html:193 +#: ../vnc.html:256 msgid "WebSocket" msgstr "" -#: ../vnc.html:196 +#: ../vnc.html:261 msgid "Encrypt" msgstr "" -#: ../vnc.html:199 +#: ../vnc.html:266 msgid "Host:" msgstr "" -#: ../vnc.html:203 +#: ../vnc.html:270 msgid "Port:" msgstr "" -#: ../vnc.html:207 +#: ../vnc.html:274 msgid "Path:" msgstr "" -#: ../vnc.html:214 +#: ../vnc.html:283 msgid "Automatic reconnect" msgstr "" -#: ../vnc.html:217 +#: ../vnc.html:288 msgid "Reconnect delay (ms):" msgstr "" -#: ../vnc.html:222 +#: ../vnc.html:295 msgid "Show dot when no cursor" msgstr "" -#: ../vnc.html:227 +#: ../vnc.html:302 msgid "Logging:" msgstr "" -#: ../vnc.html:236 +#: ../vnc.html:311 msgid "Version:" msgstr "" -#: ../vnc.html:244 +#: ../vnc.html:319 msgid "Disconnect" msgstr "" -#: ../vnc.html:267 +#: ../vnc.html:342 msgid "Connect" msgstr "" -#: ../vnc.html:276 +#: ../vnc.html:351 msgid "Server identity" msgstr "" -#: ../vnc.html:279 +#: ../vnc.html:354 msgid "The server has provided the following identifying information:" msgstr "" -#: ../vnc.html:283 +#: ../vnc.html:357 msgid "Fingerprint:" msgstr "" -#: ../vnc.html:286 +#: ../vnc.html:361 msgid "" "Please verify that the information is correct and press \"Approve\". " "Otherwise press \"Reject\"." msgstr "" -#: ../vnc.html:291 +#: ../vnc.html:366 msgid "Approve" msgstr "" -#: ../vnc.html:292 +#: ../vnc.html:367 msgid "Reject" msgstr "" -#: ../vnc.html:300 +#: ../vnc.html:375 msgid "Credentials" msgstr "" -#: ../vnc.html:304 +#: ../vnc.html:379 msgid "Username:" msgstr "" -#: ../vnc.html:308 +#: ../vnc.html:383 msgid "Password:" msgstr "" -#: ../vnc.html:312 +#: ../vnc.html:387 msgid "Send credentials" msgstr "" -#: ../vnc.html:321 +#: ../vnc.html:396 msgid "Cancel" msgstr "" From 09fc4f7fb99d20a6f796297e990d8c4747eb20a2 Mon Sep 17 00:00:00 2001 From: Alexander Zeijlon Date: Fri, 14 Feb 2025 10:42:16 +0100 Subject: [PATCH 67/70] Update Swedish translations --- po/sv.po | 215 +++++++++++++++++++++++++++---------------------------- 1 file changed, 107 insertions(+), 108 deletions(-) diff --git a/po/sv.po b/po/sv.po index f86c1ef3..aff531ad 100644 --- a/po/sv.po +++ b/po/sv.po @@ -1,312 +1,308 @@ # Swedish translations for noVNC package # Svenska översättningar för paketet noVNC. -# Copyright (C) 2020 The noVNC authors +# Copyright (C) 2025 The noVNC authors # This file is distributed under the same license as the noVNC package. # Samuel Mannehed , 2020. # msgid "" msgstr "" -"Project-Id-Version: noVNC 1.3.0\n" +"Project-Id-Version: noVNC 1.6.0\n" "Report-Msgid-Bugs-To: novnc@googlegroups.com\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" +"POT-Creation-Date: 2025-02-14 10:14+0100\n" +"PO-Revision-Date: 2025-02-14 10:29+0100\n" +"Last-Translator: Alexander Zeijlon \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.4.4\n" +"X-Generator: Poedit 3.5\n" -#: ../app/ui.js:69 +#: ../app/ui.js:84 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 +#: ../app/ui.js:413 msgid "Connecting..." msgstr "Ansluter..." -#: ../app/ui.js:417 +#: ../app/ui.js:420 msgid "Disconnecting..." msgstr "Kopplar ner..." -#: ../app/ui.js:423 +#: ../app/ui.js:426 msgid "Reconnecting..." msgstr "Återansluter..." -#: ../app/ui.js:428 +#: ../app/ui.js:431 msgid "Internal error" msgstr "Internt fel" -#: ../app/ui.js:1026 -msgid "Must set host" -msgstr "Du måste specifiera en värd" - -#: ../app/ui.js:1052 +#: ../app/ui.js:1079 msgid "Failed to connect to server: " msgstr "Misslyckades att ansluta till servern: " -#: ../app/ui.js:1118 +#: ../app/ui.js:1145 msgid "Connected (encrypted) to " msgstr "Ansluten (krypterat) till " -#: ../app/ui.js:1120 +#: ../app/ui.js:1147 msgid "Connected (unencrypted) to " msgstr "Ansluten (okrypterat) till " -#: ../app/ui.js:1143 +#: ../app/ui.js:1170 msgid "Something went wrong, connection is closed" msgstr "Något gick fel, anslutningen avslutades" -#: ../app/ui.js:1146 +#: ../app/ui.js:1173 msgid "Failed to connect to server" msgstr "Misslyckades att ansluta till servern" -#: ../app/ui.js:1158 +#: ../app/ui.js:1185 msgid "Disconnected" msgstr "Frånkopplad" -#: ../app/ui.js:1173 +#: ../app/ui.js:1200 msgid "New connection has been rejected with reason: " msgstr "Ny anslutning har blivit nekad med följande skäl: " -#: ../app/ui.js:1176 +#: ../app/ui.js:1203 msgid "New connection has been rejected" msgstr "Ny anslutning har blivit nekad" -#: ../app/ui.js:1242 +#: ../app/ui.js:1269 msgid "Credentials are required" msgstr "Användaruppgifter krävs" -#: ../vnc.html:55 +#: ../vnc.html:106 msgid "noVNC encountered an error:" msgstr "noVNC stötte på ett problem:" -#: ../vnc.html:65 +#: ../vnc.html:116 msgid "Hide/Show the control bar" msgstr "Göm/Visa kontrollbaren" -#: ../vnc.html:74 +#: ../vnc.html:125 msgid "Drag" msgstr "Dra" -#: ../vnc.html:74 -msgid "Move/Drag Viewport" -msgstr "Flytta/Dra Vyn" +#: ../vnc.html:125 +msgid "Move/Drag viewport" +msgstr "Flytta/Dra vyn" -#: ../vnc.html:80 +#: ../vnc.html:131 msgid "Keyboard" msgstr "Tangentbord" -#: ../vnc.html:80 -msgid "Show Keyboard" -msgstr "Visa Tangentbord" +#: ../vnc.html:131 +msgid "Show keyboard" +msgstr "Visa tangentbord" -#: ../vnc.html:85 +#: ../vnc.html:136 msgid "Extra keys" msgstr "Extraknappar" -#: ../vnc.html:85 -msgid "Show Extra Keys" -msgstr "Visa Extraknappar" +#: ../vnc.html:136 +msgid "Show extra keys" +msgstr "Visa extraknappar" -#: ../vnc.html:90 +#: ../vnc.html:141 msgid "Ctrl" msgstr "Ctrl" -#: ../vnc.html:90 +#: ../vnc.html:141 msgid "Toggle Ctrl" msgstr "Växla Ctrl" -#: ../vnc.html:93 +#: ../vnc.html:144 msgid "Alt" msgstr "Alt" -#: ../vnc.html:93 +#: ../vnc.html:144 msgid "Toggle Alt" msgstr "Växla Alt" -#: ../vnc.html:96 +#: ../vnc.html:147 msgid "Toggle Windows" msgstr "Växla Windows" -#: ../vnc.html:96 +#: ../vnc.html:147 msgid "Windows" msgstr "Windows" -#: ../vnc.html:99 +#: ../vnc.html:150 msgid "Send Tab" msgstr "Skicka Tab" -#: ../vnc.html:99 +#: ../vnc.html:150 msgid "Tab" msgstr "Tab" -#: ../vnc.html:102 +#: ../vnc.html:153 msgid "Esc" msgstr "Esc" -#: ../vnc.html:102 +#: ../vnc.html:153 msgid "Send Escape" msgstr "Skicka Escape" -#: ../vnc.html:105 +#: ../vnc.html:156 msgid "Ctrl+Alt+Del" msgstr "Ctrl+Alt+Del" -#: ../vnc.html:105 +#: ../vnc.html:156 msgid "Send Ctrl-Alt-Del" msgstr "Skicka Ctrl-Alt-Del" -#: ../vnc.html:112 +#: ../vnc.html:163 msgid "Shutdown/Reboot" msgstr "Stäng av/Boota om" -#: ../vnc.html:112 +#: ../vnc.html:163 msgid "Shutdown/Reboot..." msgstr "Stäng av/Boota om..." -#: ../vnc.html:118 +#: ../vnc.html:169 msgid "Power" msgstr "Ström" -#: ../vnc.html:120 +#: ../vnc.html:171 msgid "Shutdown" msgstr "Stäng av" -#: ../vnc.html:121 +#: ../vnc.html:172 msgid "Reboot" msgstr "Boota om" -#: ../vnc.html:122 +#: ../vnc.html:173 msgid "Reset" msgstr "Återställ" -#: ../vnc.html:127 ../vnc.html:133 +#: ../vnc.html:178 ../vnc.html:184 msgid "Clipboard" msgstr "Urklipp" -#: ../vnc.html:135 +#: ../vnc.html:186 msgid "Edit clipboard content in the textarea below." msgstr "Redigera urklippets innehåll i fältet nedan." -#: ../vnc.html:143 +#: ../vnc.html:194 msgid "Full screen" msgstr "Fullskärm" -#: ../vnc.html:148 ../vnc.html:154 +#: ../vnc.html:199 ../vnc.html:205 msgid "Settings" msgstr "Inställningar" -#: ../vnc.html:158 -msgid "Shared Mode" -msgstr "Delat Läge" +#: ../vnc.html:211 +msgid "Shared mode" +msgstr "Delat läge" -#: ../vnc.html:161 -msgid "View Only" -msgstr "Endast Visning" +#: ../vnc.html:218 +msgid "View only" +msgstr "Endast visning" -#: ../vnc.html:165 -msgid "Clip to Window" -msgstr "Begränsa till Fönster" +#: ../vnc.html:226 +msgid "Clip to window" +msgstr "Begränsa till fönster" -#: ../vnc.html:168 +#: ../vnc.html:231 msgid "Scaling mode:" msgstr "Skalningsläge:" -#: ../vnc.html:170 +#: ../vnc.html:233 msgid "None" msgstr "Ingen" -#: ../vnc.html:171 -msgid "Local Scaling" -msgstr "Lokal Skalning" +#: ../vnc.html:234 +msgid "Local scaling" +msgstr "Lokal skalning" -#: ../vnc.html:172 -msgid "Remote Resizing" -msgstr "Ändra Storlek" +#: ../vnc.html:235 +msgid "Remote resizing" +msgstr "Ändra storlek" -#: ../vnc.html:177 +#: ../vnc.html:240 msgid "Advanced" msgstr "Avancerat" -#: ../vnc.html:180 +#: ../vnc.html:243 msgid "Quality:" msgstr "Kvalitet:" -#: ../vnc.html:184 +#: ../vnc.html:247 msgid "Compression level:" msgstr "Kompressionsnivå:" -#: ../vnc.html:189 +#: ../vnc.html:252 msgid "Repeater ID:" msgstr "Repeater-ID:" -#: ../vnc.html:193 +#: ../vnc.html:256 msgid "WebSocket" msgstr "WebSocket" -#: ../vnc.html:196 +#: ../vnc.html:261 msgid "Encrypt" msgstr "Kryptera" -#: ../vnc.html:199 +#: ../vnc.html:266 msgid "Host:" msgstr "Värd:" -#: ../vnc.html:203 +#: ../vnc.html:270 msgid "Port:" msgstr "Port:" -#: ../vnc.html:207 +#: ../vnc.html:274 msgid "Path:" msgstr "Sökväg:" -#: ../vnc.html:214 -msgid "Automatic Reconnect" -msgstr "Automatisk Återanslutning" +#: ../vnc.html:283 +msgid "Automatic reconnect" +msgstr "Automatisk återanslutning" -#: ../vnc.html:217 +#: ../vnc.html:288 msgid "Reconnect delay (ms):" msgstr "Fördröjning (ms):" -#: ../vnc.html:222 +#: ../vnc.html:295 msgid "Show dot when no cursor" msgstr "Visa prick när ingen muspekare finns" -#: ../vnc.html:227 +#: ../vnc.html:302 msgid "Logging:" msgstr "Loggning:" -#: ../vnc.html:236 +#: ../vnc.html:311 msgid "Version:" msgstr "Version:" -#: ../vnc.html:244 +#: ../vnc.html:319 msgid "Disconnect" msgstr "Koppla från" -#: ../vnc.html:267 +#: ../vnc.html:342 msgid "Connect" msgstr "Anslut" -#: ../vnc.html:276 +#: ../vnc.html:351 msgid "Server identity" msgstr "Server-identitet" -#: ../vnc.html:279 +#: ../vnc.html:354 msgid "The server has provided the following identifying information:" msgstr "Servern har gett följande identifierande information:" -#: ../vnc.html:283 +#: ../vnc.html:357 msgid "Fingerprint:" msgstr "Fingeravtryck:" -#: ../vnc.html:286 +#: ../vnc.html:361 msgid "" "Please verify that the information is correct and press \"Approve\". " "Otherwise press \"Reject\"." @@ -314,34 +310,37 @@ msgstr "" "Kontrollera att informationen är korrekt och tryck sedan \"Godkänn\". Tryck " "annars \"Neka\"." -#: ../vnc.html:291 +#: ../vnc.html:366 msgid "Approve" msgstr "Godkänn" -#: ../vnc.html:292 +#: ../vnc.html:367 msgid "Reject" msgstr "Neka" -#: ../vnc.html:300 +#: ../vnc.html:375 msgid "Credentials" msgstr "Användaruppgifter" -#: ../vnc.html:304 +#: ../vnc.html:379 msgid "Username:" msgstr "Användarnamn:" -#: ../vnc.html:308 +#: ../vnc.html:383 msgid "Password:" msgstr "Lösenord:" -#: ../vnc.html:312 -msgid "Send Credentials" -msgstr "Skicka Användaruppgifter" +#: ../vnc.html:387 +msgid "Send credentials" +msgstr "Skicka användaruppgifter" -#: ../vnc.html:321 +#: ../vnc.html:396 msgid "Cancel" msgstr "Avbryt" +#~ msgid "Must set host" +#~ msgstr "Du måste specifiera en värd" + #~ msgid "HTTPS is required for full functionality" #~ msgstr "HTTPS krävs för full funktionalitet" From c3b8cbd3d28c0ceec74c1f5693c6d9cb9c609638 Mon Sep 17 00:00:00 2001 From: Alexander Zeijlon Date: Fri, 14 Feb 2025 11:22:24 +0100 Subject: [PATCH 68/70] Update README.md with H.264 encoding support --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6976a746..3c831be6 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ profits such as: RSA-AES, Tight, VeNCrypt Plain, XVP, Apple's Diffie-Hellman, UltraVNC's MSLogonII * Supported VNC encodings: raw, copyrect, rre, hextile, tight, tightPNG, - ZRLE, JPEG, Zlib + ZRLE, JPEG, Zlib, H.264 * Supports scaling, clipping and resizing the desktop * Supports back & forward mouse buttons * Local cursor rendering From b45f35c6d7b06c1d8641ceb8e7f5ee00c23484eb Mon Sep 17 00:00:00 2001 From: Alexander Zeijlon Date: Fri, 14 Feb 2025 10:47:44 +0100 Subject: [PATCH 69/70] noVNC 1.6.0 beta --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 907cf630..69ba9f5c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@novnc/novnc", - "version": "1.5.0", + "version": "1.6.0-beta", "description": "An HTML5 VNC client", "browser": "lib/rfb", "directories": { From b25675e05290817024fa5ba89581d9cdc12f8992 Mon Sep 17 00:00:00 2001 From: Alexander Zeijlon Date: Fri, 28 Feb 2025 14:42:52 +0100 Subject: [PATCH 70/70] Upgrade to websockify 0.13.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 82d52de4..f743a014 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -42,7 +42,7 @@ parts: - jq websockify: - source: https://github.com/novnc/websockify/archive/v0.12.0.tar.gz + source: https://github.com/novnc/websockify/archive/v0.13.0.tar.gz plugin: python stage-packages: - python3-numpy