diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index d3816280..00000000 --- a/.eslintignore +++ /dev/null @@ -1 +0,0 @@ -**/xtscancodes.js diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index 10e15cec..00000000 --- a/.eslintrc +++ /dev/null @@ -1,54 +0,0 @@ -{ - "env": { - "browser": true, - "es2020": true - }, - "parserOptions": { - "sourceType": "module", - "ecmaVersion": 2020 - }, - "extends": "eslint:recommended", - "rules": { - // Unsafe or confusing stuff that we forbid - - "no-unused-vars": ["error", { "vars": "all", "args": "none", "ignoreRestSiblings": true }], - "no-constant-condition": ["error", { "checkLoops": false }], - "no-var": "error", - "no-useless-constructor": "error", - "object-shorthand": ["error", "methods", { "avoidQuotes": true }], - "prefer-arrow-callback": "error", - "arrow-body-style": ["error", "as-needed", { "requireReturnForObjectLiteral": false } ], - "arrow-parens": ["error", "as-needed", { "requireForBlockBody": true }], - "arrow-spacing": ["error"], - "no-confusing-arrow": ["error", { "allowParens": true }], - - // Enforced coding style - - "brace-style": ["error", "1tbs", { "allowSingleLine": true }], - "indent": ["error", 4, { "SwitchCase": 1, - "VariableDeclarator": "first", - "FunctionDeclaration": { "parameters": "first" }, - "FunctionExpression": { "parameters": "first" }, - "CallExpression": { "arguments": "first" }, - "ArrayExpression": "first", - "ObjectExpression": "first", - "ImportDeclaration": "first", - "ignoreComments": true }], - "comma-spacing": ["error"], - "comma-style": ["error"], - "curly": ["error", "multi-line"], - "func-call-spacing": ["error"], - "func-names": ["error"], - "func-style": ["error", "declaration", { "allowArrowFunctions": true }], - "key-spacing": ["error"], - "keyword-spacing": ["error"], - "no-trailing-spaces": ["error"], - "semi": ["error"], - "space-before-blocks": ["error"], - "space-before-function-paren": ["error", { "anonymous": "always", - "named": "never", - "asyncArrow": "always" }], - "switch-colon-spacing": ["error"], - "camelcase": ["error", { allow: ["^XK_", "^XF86XK_"] }], - } -} diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 84e634d2..a11d3d0a 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -10,18 +10,18 @@ jobs: npm: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - run: | GITREV=$(git rev-parse --short HEAD) echo $GITREV sed -i "s/^\(.*\"version\".*\)\"\([^\"]\+\)\"\(.*\)\$/\1\"\2-g$GITREV\"\3/" package.json if: github.event_name != 'release' - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: # Needs to be explicitly specified for auth to work registry-url: 'https://registry.npmjs.org' - run: npm install - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: npm path: lib @@ -49,7 +49,7 @@ jobs: snap: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - run: | GITREV=$(git rev-parse --short HEAD) echo $GITREV @@ -61,7 +61,7 @@ jobs: sed -i "s/^version:.*/version: '$VERSION'/" snap/snapcraft.yaml - uses: snapcore/action-build@v1 id: snapcraft - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: snap path: ${{ steps.snapcraft.outputs.snap }} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 7cd5b215..540bb990 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -6,14 +6,14 @@ jobs: eslint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 - run: npm update - run: npm run lint html: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 - run: npm update - run: git ls-tree --name-only -r HEAD | grep -E "[.](html|css)$" | xargs ./utils/validate diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1bc1728a..b72195b5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -20,8 +20,8 @@ jobs: fail-fast: false runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 - run: npm update - run: npm run test env: diff --git a/.github/workflows/translate.yml b/.github/workflows/translate.yml index ea6e6bb3..a4da9cbf 100644 --- a/.github/workflows/translate.yml +++ b/.github/workflows/translate.yml @@ -6,8 +6,8 @@ jobs: translate: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 - run: npm update - run: sudo apt-get install gettext - run: make -C po update-pot diff --git a/README.md b/README.md index a771cb43..b95d15e6 100644 --- a/README.md +++ b/README.md @@ -32,8 +32,6 @@ for a more complete list with additional info and links. ### News/help/contact The project website is found at [novnc.com](http://novnc.com). -Notable commits, announcements and news are posted to -[@noVNC](http://www.twitter.com/noVNC). If you are a noVNC developer/integrator/user (or want to be) please join the [noVNC discussion group](https://groups.google.com/forum/?fromgroups#!forum/novnc). @@ -59,7 +57,6 @@ profits such as: [Electronic Frontier Foundation](https://www.eff.org/), [Against Malaria Foundation](http://www.againstmalaria.com/), [Nothing But Nets](http://www.nothingbutnets.net/), etc. -Please tweet [@noVNC](http://www.twitter.com/noVNC) if you do. ### Features diff --git a/app/locale/el.json b/app/locale/el.json index f801251c..4df3e03c 100644 --- a/app/locale/el.json +++ b/app/locale/el.json @@ -1,4 +1,5 @@ { + "HTTPS is required for full functionality": "Το HTTPS είναι απαιτούμενο για πλήρη λειτουργικότητα", "Connecting...": "Συνδέεται...", "Disconnecting...": "Aποσυνδέεται...", "Reconnecting...": "Επανασυνδέεται...", @@ -7,19 +8,15 @@ "Connected (encrypted) to ": "Συνδέθηκε (κρυπτογραφημένα) με το ", "Connected (unencrypted) to ": "Συνδέθηκε (μη κρυπτογραφημένα) με το ", "Something went wrong, connection is closed": "Κάτι πήγε στραβά, η σύνδεση διακόπηκε", + "Failed to connect to server": "Αποτυχία στη σύνδεση με το διακομιστή", "Disconnected": "Αποσυνδέθηκε", "New connection has been rejected with reason: ": "Η νέα σύνδεση απορρίφθηκε διότι: ", "New connection has been rejected": "Η νέα σύνδεση απορρίφθηκε ", - "Password is required": "Απαιτείται ο κωδικός πρόσβασης", + "Credentials are required": "Απαιτούνται διαπιστευτήρια", "noVNC encountered an error:": "το noVNC αντιμετώπισε ένα σφάλμα:", "Hide/Show the control bar": "Απόκρυψη/Εμφάνιση γραμμής ελέγχου", + "Drag": "Σύρσιμο", "Move/Drag Viewport": "Μετακίνηση/Σύρσιμο Θεατού πεδίου", - "viewport drag": "σύρσιμο θεατού πεδίου", - "Active Mouse Button": "Ενεργό Πλήκτρο Ποντικιού", - "No mousebutton": "Χωρίς Πλήκτρο Ποντικιού", - "Left mousebutton": "Αριστερό Πλήκτρο Ποντικιού", - "Middle mousebutton": "Μεσαίο Πλήκτρο Ποντικιού", - "Right mousebutton": "Δεξί Πλήκτρο Ποντικιού", "Keyboard": "Πληκτρολόγιο", "Show Keyboard": "Εμφάνιση Πληκτρολογίου", "Extra keys": "Επιπλέον πλήκτρα", @@ -28,6 +25,8 @@ "Toggle Ctrl": "Εναλλαγή Ctrl", "Alt": "Alt", "Toggle Alt": "Εναλλαγή Alt", + "Toggle Windows": "Εναλλαγή Παράθυρων", + "Windows": "Παράθυρα", "Send Tab": "Αποστολή Tab", "Tab": "Tab", "Esc": "Esc", @@ -41,8 +40,7 @@ "Reboot": "Επανεκκίνηση", "Reset": "Επαναφορά", "Clipboard": "Πρόχειρο", - "Clear": "Καθάρισμα", - "Fullscreen": "Πλήρης Οθόνη", + "Edit clipboard content in the textarea below.": "Επεξεργαστείτε το περιεχόμενο του πρόχειρου στην περιοχή κειμένου παρακάτω.", "Settings": "Ρυθμίσεις", "Shared Mode": "Κοινόχρηστη Λειτουργία", "View Only": "Μόνο Θέαση", @@ -52,6 +50,8 @@ "Local Scaling": "Τοπική Κλιμάκωση", "Remote Resizing": "Απομακρυσμένη Αλλαγή μεγέθους", "Advanced": "Για προχωρημένους", + "Quality:": "Ποιότητα:", + "Compression level:": "Επίπεδο συμπίεσης:", "Repeater ID:": "Repeater ID:", "WebSocket": "WebSocket", "Encrypt": "Κρυπτογράφηση", @@ -60,10 +60,20 @@ "Path:": "Διαδρομή:", "Automatic Reconnect": "Αυτόματη επανασύνδεση", "Reconnect Delay (ms):": "Καθυστέρηση επανασύνδεσης (ms):", + "Show Dot when No Cursor": "Εμφάνιση Τελείας όταν δεν υπάρχει Δρομέας", "Logging:": "Καταγραφή:", + "Version:": "Έκδοση:", "Disconnect": "Αποσύνδεση", "Connect": "Σύνδεση", + "Server identity": "Ταυτότητα Διακομιστή", + "The server has provided the following identifying information:": "Ο διακομιστής παρείχε την ακόλουθη πληροφορία ταυτοποίησης:", + "Fingerprint:": "Δακτυλικό αποτύπωμα:", + "Please verify that the information is correct and press \"Approve\". Otherwise press \"Reject\".": "Παρακαλώ επαληθεύσετε ότι η πληροφορία είναι σωστή και πιέστε \"Αποδοχή\". Αλλιώς πιέστε \"Απόρριψη\".", + "Approve": "Αποδοχή", + "Reject": "Απόρριψη", + "Credentials": "Διαπιστευτήρια", + "Username:": "Κωδικός Χρήστη:", "Password:": "Κωδικός Πρόσβασης:", - "Cancel": "Ακύρωση", - "Canvas not supported.": "Δεν υποστηρίζεται το στοιχείο Canvas" + "Send Credentials": "Αποστολή Διαπιστευτηρίων", + "Cancel": "Ακύρωση" } \ No newline at end of file diff --git a/app/locale/fr.json b/app/locale/fr.json index 22531f73..c0eeec7d 100644 --- a/app/locale/fr.json +++ b/app/locale/fr.json @@ -1,5 +1,4 @@ { - "HTTPS is required for full functionality": "", "Connecting...": "En cours de connexion...", "Disconnecting...": "Déconnexion en cours...", "Reconnecting...": "Reconnexion en cours...", @@ -40,7 +39,8 @@ "Reboot": "Redémarrer", "Reset": "Réinitialiser", "Clipboard": "Presse-papiers", - "Edit clipboard content in the textarea below.": "", + "Clear": "Effacer", + "Fullscreen": "Plein écran", "Settings": "Paramètres", "Shared Mode": "Mode partagé", "View Only": "Afficher uniquement", @@ -65,12 +65,6 @@ "Version:": "Version :", "Disconnect": "Déconnecter", "Connect": "Connecter", - "Server identity": "", - "The server has provided the following identifying information:": "", - "Fingerprint:": "", - "Please verify that the information is correct and press \"Approve\". Otherwise press \"Reject\".": "", - "Approve": "", - "Reject": "", "Username:": "Nom d'utilisateur :", "Password:": "Mot de passe :", "Send Credentials": "Envoyer les identifiants", diff --git a/app/locale/it.json b/app/locale/it.json index 6fd25702..18a7f744 100644 --- a/app/locale/it.json +++ b/app/locale/it.json @@ -14,8 +14,6 @@ "Credentials are required": "Le credenziali sono obbligatorie", "noVNC encountered an error:": "noVNC ha riscontrato un errore:", "Hide/Show the control bar": "Nascondi/Mostra la barra di controllo", - "Drag": "", - "Move/Drag Viewport": "", "Keyboard": "Tastiera", "Show Keyboard": "Mostra tastiera", "Extra keys": "Tasti Aggiuntivi", @@ -44,7 +42,6 @@ "Settings": "Impostazioni", "Shared Mode": "Modalità condivisa", "View Only": "Sola Visualizzazione", - "Clip to Window": "", "Scaling Mode:": "Modalità di ridimensionamento:", "None": "Nessuna", "Local Scaling": "Ridimensionamento Locale", @@ -61,7 +58,6 @@ "Automatic Reconnect": "Riconnessione Automatica", "Reconnect Delay (ms):": "Ritardo Riconnessione (ms):", "Show Dot when No Cursor": "Mostra Punto quando Nessun Cursore", - "Logging:": "", "Version:": "Versione:", "Disconnect": "Disconnetti", "Connect": "Connetti", diff --git a/app/locale/ja.json b/app/locale/ja.json index 43fc5bf3..70fd7a5d 100644 --- a/app/locale/ja.json +++ b/app/locale/ja.json @@ -1,4 +1,5 @@ { + "HTTPS is required for full functionality": "すべての機能を使用するにはHTTPS接続が必要です", "Connecting...": "接続しています...", "Disconnecting...": "切断しています...", "Reconnecting...": "再接続しています...", @@ -21,10 +22,10 @@ "Extra keys": "追加キー", "Show Extra Keys": "追加キーを表示", "Ctrl": "Ctrl", - "Toggle Ctrl": "Ctrl キーを切り替え", + "Toggle Ctrl": "Ctrl キーをトグル", "Alt": "Alt", - "Toggle Alt": "Alt キーを切り替え", - "Toggle Windows": "Windows キーを切り替え", + "Toggle Alt": "Alt キーをトグル", + "Toggle Windows": "Windows キーをトグル", "Windows": "Windows", "Send Tab": "Tab キーを送信", "Tab": "Tab", @@ -39,11 +40,11 @@ "Reboot": "再起動", "Reset": "リセット", "Clipboard": "クリップボード", - "Clear": "クリア", - "Fullscreen": "全画面表示", + "Edit clipboard content in the textarea below.": "以下の入力欄からクリップボードの内容を編集できます。", + "Full Screen": "全画面表示", "Settings": "設定", "Shared Mode": "共有モード", - "View Only": "表示のみ", + "View Only": "表示専用", "Clip to Window": "ウィンドウにクリップ", "Scaling Mode:": "スケーリングモード:", "None": "なし", @@ -60,11 +61,18 @@ "Path:": "パス:", "Automatic Reconnect": "自動再接続", "Reconnect Delay (ms):": "再接続する遅延 (ミリ秒):", - "Show Dot when No Cursor": "カーソルがないときにドットを表示", + "Show Dot when No Cursor": "カーソルがないときにドットを表示する", "Logging:": "ロギング:", "Version:": "バージョン:", "Disconnect": "切断", "Connect": "接続", + "Server identity": "サーバーの識別情報", + "The server has provided the following identifying information:": "サーバーは以下の識別情報を提供しています:", + "Fingerprint:": "フィンガープリント:", + "Please verify that the information is correct and press \"Approve\". Otherwise press \"Reject\".": "この情報が正しい場合は「承認」を、そうでない場合は「拒否」を押してください。", + "Approve": "承認", + "Reject": "拒否", + "Credentials": "資格情報", "Username:": "ユーザー名:", "Password:": "パスワード:", "Send Credentials": "資格情報を送信", diff --git a/app/locale/sv.json b/app/locale/sv.json index 077ef42c..80a400bf 100644 --- a/app/locale/sv.json +++ b/app/locale/sv.json @@ -1,10 +1,11 @@ { - "HTTPS is required for full functionality": "HTTPS krävs för full funktionalitet", + "Running without HTTPS is not recommended, crashes or other issues are likely.": "Det är ej rekommenderat att köra utan HTTPS, krascher och andra problem är troliga.", "Connecting...": "Ansluter...", "Disconnecting...": "Kopplar ner...", "Reconnecting...": "Återansluter...", "Internal error": "Internt fel", "Must set host": "Du måste specifiera en värd", + "Failed to connect to server: ": "Misslyckades att ansluta till servern: ", "Connected (encrypted) to ": "Ansluten (krypterat) till ", "Connected (unencrypted) to ": "Ansluten (okrypterat) till ", "Something went wrong, connection is closed": "Något gick fel, anslutningen avslutades", diff --git a/app/locale/zh_CN.json b/app/locale/zh_CN.json index f0aea9af..3679eadd 100644 --- a/app/locale/zh_CN.json +++ b/app/locale/zh_CN.json @@ -1,69 +1,69 @@ { "Connecting...": "连接中...", + "Connected (encrypted) to ": "已连接(已加密)到", + "Connected (unencrypted) to ": "已连接(未加密)到", "Disconnecting...": "正在断开连接...", - "Reconnecting...": "重新连接中...", - "Internal error": "内部错误", - "Must set host": "请提供主机名", - "Connected (encrypted) to ": "已连接到(加密)", - "Connected (unencrypted) to ": "已连接到(未加密)", - "Something went wrong, connection is closed": "发生错误,连接已关闭", - "Failed to connect to server": "无法连接到服务器", "Disconnected": "已断开连接", - "New connection has been rejected with reason: ": "连接被拒绝,原因:", - "New connection has been rejected": "连接被拒绝", + "Must set host": "必须设置主机", + "Reconnecting...": "重新连接中...", "Password is required": "请提供密码", + "Disconnect timeout": "超时断开", "noVNC encountered an error:": "noVNC 遇到一个错误:", "Hide/Show the control bar": "显示/隐藏控制栏", - "Move/Drag Viewport": "拖放显示范围", - "viewport drag": "显示范围拖放", - "Active Mouse Button": "启动鼠标按鍵", - "No mousebutton": "禁用鼠标按鍵", - "Left mousebutton": "鼠标左鍵", - "Middle mousebutton": "鼠标中鍵", - "Right mousebutton": "鼠标右鍵", + "Move/Drag Viewport": "移动/拖动窗口", + "viewport drag": "窗口拖动", + "Active Mouse Button": "启动鼠标按键", + "No mousebutton": "禁用鼠标按键", + "Left mousebutton": "鼠标左键", + "Middle mousebutton": "鼠标中键", + "Right mousebutton": "鼠标右键", "Keyboard": "键盘", "Show Keyboard": "显示键盘", "Extra keys": "额外按键", "Show Extra Keys": "显示额外按键", "Ctrl": "Ctrl", "Toggle Ctrl": "切换 Ctrl", + "Edit clipboard content in the textarea below.": "在下面的文本区域中编辑剪贴板内容。", "Alt": "Alt", "Toggle Alt": "切换 Alt", "Send Tab": "发送 Tab 键", "Tab": "Tab", "Esc": "Esc", "Send Escape": "发送 Escape 键", - "Ctrl+Alt+Del": "Ctrl-Alt-Del", - "Send Ctrl-Alt-Del": "发送 Ctrl-Alt-Del 键", - "Shutdown/Reboot": "关机/重新启动", - "Shutdown/Reboot...": "关机/重新启动...", + "Ctrl+Alt+Del": "Ctrl+Alt+Del", + "Send Ctrl-Alt-Del": "发送 Ctrl+Alt+Del 键", + "Shutdown/Reboot": "关机/重启", + "Shutdown/Reboot...": "关机/重启...", "Power": "电源", "Shutdown": "关机", - "Reboot": "重新启动", + "Reboot": "重启", "Reset": "重置", "Clipboard": "剪贴板", "Clear": "清除", "Fullscreen": "全屏", "Settings": "设置", + "Encrypt": "加密", "Shared Mode": "分享模式", "View Only": "仅查看", "Clip to Window": "限制/裁切窗口大小", "Scaling Mode:": "缩放模式:", "None": "无", "Local Scaling": "本地缩放", + "Local Downscaling": "降低本地尺寸", "Remote Resizing": "远程调整大小", "Advanced": "高级", + "Local Cursor": "本地光标", "Repeater ID:": "中继站 ID", "WebSocket": "WebSocket", - "Encrypt": "加密", "Host:": "主机:", "Port:": "端口:", "Path:": "路径:", "Automatic Reconnect": "自动重新连接", "Reconnect Delay (ms):": "重新连接间隔 (ms):", "Logging:": "日志级别:", - "Disconnect": "中断连接", + "Disconnect": "断开连接", "Connect": "连接", "Password:": "密码:", - "Cancel": "取消" + "Cancel": "取消", + "Canvas not supported.": "不支持 Canvas。" } \ No newline at end of file diff --git a/app/ui.js b/app/ui.js index 91032c00..10f16a01 100644 --- a/app/ui.js +++ b/app/ui.js @@ -67,7 +67,7 @@ const UI = { // insecure context if (!window.isSecureContext) { // FIXME: This gets hidden when connecting - UI.showStatus(_("HTTPS is required for full functionality"), 'error'); + UI.showStatus(_("Running without HTTPS is not recommended, crashes or other issues are likely."), 'error'); } // Try to fetch version number @@ -1097,10 +1097,18 @@ const UI = { } url += '/' + path; - UI.rfb = new RFB(document.getElementById('noVNC_container'), url, - { shared: UI.getSetting('shared'), - repeaterID: UI.getSetting('repeaterID'), - credentials: { password: password } }); + try { + UI.rfb = new RFB(document.getElementById('noVNC_container'), url, + { shared: UI.getSetting('shared'), + repeaterID: UI.getSetting('repeaterID'), + credentials: { password: password } }); + } catch (exc) { + Log.Error("Failed to connect to server: " + exc); + UI.updateVisualState('disconnected'); + UI.showStatus(_("Failed to connect to server: ") + exc, 'error'); + return; + } + UI.rfb.addEventListener("connect", UI.connectFinished); UI.rfb.addEventListener("disconnect", UI.disconnectFinished); UI.rfb.addEventListener("serververification", UI.serverVerify); diff --git a/app/webutil.js b/app/webutil.js index ecf6d283..6011442c 100644 --- a/app/webutil.js +++ b/app/webutil.js @@ -212,7 +212,7 @@ function localStorageGet(name) { try { r = localStorage.getItem(name); } catch (e) { - if (e instanceof DOMException || !localStorage) { + if (e instanceof DOMException) { logOnce(cookiesMsg); logOnce("'localStorage.getItem(" + name + ")' failed: " + e, "debug"); diff --git a/core/decoders/tight.js b/core/decoders/tight.js index 45622080..8bc977a7 100644 --- a/core/decoders/tight.js +++ b/core/decoders/tight.js @@ -285,7 +285,73 @@ export default class TightDecoder { } _gradientFilter(streamId, x, y, width, height, sock, display, depth) { - throw new Error("Gradient filter not implemented"); + // assume the TPIXEL is 3 bytes long + const uncompressedSize = width * height * 3; + let data; + + if (uncompressedSize === 0) { + return true; + } + + if (uncompressedSize < 12) { + if (sock.rQwait("TIGHT", uncompressedSize)) { + return false; + } + + data = sock.rQshiftBytes(uncompressedSize); + } else { + data = this._readData(sock); + if (data === null) { + return false; + } + + this._zlibs[streamId].setInput(data); + data = this._zlibs[streamId].inflate(uncompressedSize); + this._zlibs[streamId].setInput(null); + } + + let rgbx = new Uint8Array(4 * width * height); + + let rgbxIndex = 0, dataIndex = 0; + let left = new Uint8Array(3); + for (let x = 0; x < width; x++) { + for (let c = 0; c < 3; c++) { + const prediction = left[c]; + const value = data[dataIndex++] + prediction; + rgbx[rgbxIndex++] = value; + left[c] = value; + } + rgbx[rgbxIndex++] = 255; + } + + let upperIndex = 0; + let upper = new Uint8Array(3), + upperleft = new Uint8Array(3); + for (let y = 1; y < height; y++) { + left.fill(0); + upperleft.fill(0); + for (let x = 0; x < width; x++) { + for (let c = 0; c < 3; c++) { + upper[c] = rgbx[upperIndex++]; + let prediction = left[c] + upper[c] - upperleft[c]; + if (prediction < 0) { + prediction = 0; + } else if (prediction > 255) { + prediction = 255; + } + const value = data[dataIndex++] + prediction; + rgbx[rgbxIndex++] = value; + upperleft[c] = upper[c]; + left[c] = value; + } + rgbx[rgbxIndex++] = 255; + upperIndex++; + } + } + + display.blitImage(x, y, width, height, rgbx, 0, false); + + return true; } _readData(sock) { diff --git a/core/deflator.js b/core/deflator.js index fe2a8f70..22f6770b 100644 --- a/core/deflator.js +++ b/core/deflator.js @@ -7,7 +7,7 @@ */ import { deflateInit, deflate } from "../vendor/pako/lib/zlib/deflate.js"; -import { Z_FULL_FLUSH } from "../vendor/pako/lib/zlib/deflate.js"; +import { Z_FULL_FLUSH, Z_DEFAULT_COMPRESSION } from "../vendor/pako/lib/zlib/deflate.js"; import ZStream from "../vendor/pako/lib/zlib/zstream.js"; export default class Deflator { @@ -15,9 +15,8 @@ export default class Deflator { this.strm = new ZStream(); this.chunkSize = 1024 * 10 * 10; this.outputBuffer = new Uint8Array(this.chunkSize); - this.windowBits = 5; - deflateInit(this.strm, this.windowBits); + deflateInit(this.strm, Z_DEFAULT_COMPRESSION); } deflate(inData) { diff --git a/core/encodings.js b/core/encodings.js index 2041b6e0..1a79989d 100644 --- a/core/encodings.js +++ b/core/encodings.js @@ -22,6 +22,7 @@ export const encodings = { pseudoEncodingLastRect: -224, pseudoEncodingCursor: -239, pseudoEncodingQEMUExtendedKeyEvent: -258, + pseudoEncodingQEMULedEvent: -261, pseudoEncodingDesktopName: -307, pseudoEncodingExtendedDesktopSize: -308, pseudoEncodingXvp: -309, diff --git a/core/inflator.js b/core/inflator.js index 4b337607..f851f2a7 100644 --- a/core/inflator.js +++ b/core/inflator.js @@ -14,9 +14,8 @@ export default class Inflate { this.strm = new ZStream(); this.chunkSize = 1024 * 10 * 10; this.strm.output = new Uint8Array(this.chunkSize); - this.windowBits = 5; - inflateInit(this.strm, this.windowBits); + inflateInit(this.strm); } setInput(data) { diff --git a/core/input/keyboard.js b/core/input/keyboard.js index ddb5ce09..68da2312 100644 --- a/core/input/keyboard.js +++ b/core/input/keyboard.js @@ -36,7 +36,7 @@ export default class Keyboard { // ===== PRIVATE METHODS ===== - _sendKeyEvent(keysym, code, down) { + _sendKeyEvent(keysym, code, down, numlock = null, capslock = null) { if (down) { this._keyDownList[code] = keysym; } else { @@ -48,8 +48,9 @@ export default class Keyboard { } Log.Debug("onkeyevent " + (down ? "down" : "up") + - ", keysym: " + keysym, ", code: " + code); - this.onkeyevent(keysym, code, down); + ", keysym: " + keysym, ", code: " + code + + ", numlock: " + numlock + ", capslock: " + capslock); + this.onkeyevent(keysym, code, down, numlock, capslock); } _getKeyCode(e) { @@ -86,6 +87,14 @@ export default class Keyboard { _handleKeyDown(e) { const code = this._getKeyCode(e); let keysym = KeyboardUtil.getKeysym(e); + let numlock = e.getModifierState('NumLock'); + let capslock = e.getModifierState('CapsLock'); + + // getModifierState for NumLock is not supported on mac and ios and always returns false. + // Set to null to indicate unknown/unsupported instead. + if (browser.isMac() || browser.isIOS()) { + numlock = null; + } // Windows doesn't have a proper AltGr, but handles it using // fake Ctrl+Alt. However the remote end might not be Windows, @@ -107,7 +116,7 @@ export default class Keyboard { // key to "AltGraph". keysym = KeyTable.XK_ISO_Level3_Shift; } else { - this._sendKeyEvent(KeyTable.XK_Control_L, "ControlLeft", true); + this._sendKeyEvent(KeyTable.XK_Control_L, "ControlLeft", true, numlock, capslock); } } @@ -118,8 +127,8 @@ export default class Keyboard { // If it's a virtual keyboard then it should be // sufficient to just send press and release right // after each other - this._sendKeyEvent(keysym, code, true); - this._sendKeyEvent(keysym, code, false); + this._sendKeyEvent(keysym, code, true, numlock, capslock); + this._sendKeyEvent(keysym, code, false, numlock, capslock); } stopEvent(e); @@ -157,8 +166,8 @@ export default class Keyboard { // while meta is held down if ((browser.isMac() || browser.isIOS()) && (e.metaKey && code !== 'MetaLeft' && code !== 'MetaRight')) { - this._sendKeyEvent(keysym, code, true); - this._sendKeyEvent(keysym, code, false); + this._sendKeyEvent(keysym, code, true, numlock, capslock); + this._sendKeyEvent(keysym, code, false, numlock, capslock); stopEvent(e); return; } @@ -168,8 +177,8 @@ export default class Keyboard { // which toggles on each press, but not on release. So pretend // it was a quick press and release of the button. if ((browser.isMac() || browser.isIOS()) && (code === 'CapsLock')) { - this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', true); - this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', false); + this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', true, numlock, capslock); + this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', false, numlock, capslock); stopEvent(e); return; } @@ -182,8 +191,8 @@ export default class Keyboard { KeyTable.XK_Hiragana, KeyTable.XK_Romaji ]; if (browser.isWindows() && jpBadKeys.includes(keysym)) { - this._sendKeyEvent(keysym, code, true); - this._sendKeyEvent(keysym, code, false); + this._sendKeyEvent(keysym, code, true, numlock, capslock); + this._sendKeyEvent(keysym, code, false, numlock, capslock); stopEvent(e); return; } @@ -199,7 +208,7 @@ export default class Keyboard { return; } - this._sendKeyEvent(keysym, code, true); + this._sendKeyEvent(keysym, code, true, numlock, capslock); } _handleKeyUp(e) { diff --git a/core/input/util.js b/core/input/util.js index 58f84e55..36b69817 100644 --- a/core/input/util.js +++ b/core/input/util.js @@ -67,7 +67,7 @@ export function getKeycode(evt) { // Get 'KeyboardEvent.key', handling legacy browsers export function getKey(evt) { // Are we getting a proper key value? - if (evt.key !== undefined) { + if ((evt.key !== undefined) && (evt.key !== 'Unidentified')) { // Mozilla isn't fully in sync with the spec yet switch (evt.key) { case 'OS': return 'Meta'; diff --git a/core/rfb.js b/core/rfb.js index 6b663f0a..d77dcac9 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -260,6 +260,8 @@ export default class RFB extends EventTargetMixin { this._keyboard = new Keyboard(this._canvas); this._keyboard.onkeyevent = this._handleKeyEvent.bind(this); + this._remoteCapsLock = null; // Null indicates unknown or irrelevant + this._remoteNumLock = null; this._gestures = new GestureHandler(); @@ -996,11 +998,39 @@ export default class RFB extends EventTargetMixin { } } + _sendPingMessage() { window.parent.postMessage(JSON.stringify({method: 'noVNCPing'}), '*'); } + _handleKeyEvent(keysym, code, down, numlock, capslock) { + // If remote state of capslock is known, and it doesn't match the local led state of + // the keyboard, we send a capslock keypress first to bring it into sync. + // If we just pressed CapsLock, or we toggled it remotely due to it being out of sync + // we clear the remote state so that we don't send duplicate or spurious fixes, + // since it may take some time to receive the new remote CapsLock state. + if (code == 'CapsLock' && down) { + this._remoteCapsLock = null; + } + if (this._remoteCapsLock !== null && capslock !== null && this._remoteCapsLock !== capslock && down) { + Log.Debug("Fixing remote caps lock"); - _handleKeyEvent(keysym, code, down) { + this.sendKey(KeyTable.XK_Caps_Lock, 'CapsLock', true); + this.sendKey(KeyTable.XK_Caps_Lock, 'CapsLock', false); + // We clear the remote capsLock state when we do this to prevent issues with doing this twice + // before we receive an update of the the remote state. + this._remoteCapsLock = null; + } + + // Logic for numlock is exactly the same. + if (code == 'NumLock' && down) { + this._remoteNumLock = null; + } + if (this._remoteNumLock !== null && numlock !== null && this._remoteNumLock !== numlock && down) { + Log.Debug("Fixing remote num lock"); + this.sendKey(KeyTable.XK_Num_Lock, 'NumLock', true); + this.sendKey(KeyTable.XK_Num_Lock, 'NumLock', false); + this._remoteNumLock = null; + } this._sendPingMessage(); this.sendKey(keysym, code, down); } @@ -1938,7 +1968,11 @@ export default class RFB extends EventTargetMixin { _negotiateAuthentication() { switch (this._rfbAuthScheme) { case securityTypeNone: - this._rfbInitState = 'SecurityResult'; + if (this._rfbVersion >= 3.8) { + this._rfbInitState = 'SecurityResult'; + } else { + this._rfbInitState = 'ClientInitialisation'; + } return true; case securityTypeXVP: @@ -1975,13 +2009,6 @@ export default class RFB extends EventTargetMixin { } _handleSecurityResult() { - // There is no security choice, and hence no security result - // until RFB 3.7 - if (this._rfbVersion < 3.7) { - this._rfbInitState = 'ClientInitialisation'; - return true; - } - if (this._sock.rQwait('VNC auth response ', 4)) { return false; } const status = this._sock.rQshift32(); @@ -2117,6 +2144,7 @@ export default class RFB extends EventTargetMixin { encs.push(encodings.pseudoEncodingDesktopSize); encs.push(encodings.pseudoEncodingLastRect); encs.push(encodings.pseudoEncodingQEMUExtendedKeyEvent); + encs.push(encodings.pseudoEncodingQEMULedEvent); encs.push(encodings.pseudoEncodingExtendedDesktopSize); encs.push(encodings.pseudoEncodingXvp); encs.push(encodings.pseudoEncodingFence); @@ -2341,7 +2369,7 @@ export default class RFB extends EventTargetMixin { textData = textData.slice(0, -1); } - textData = textData.replace("\r\n", "\n"); + textData = textData.replaceAll("\r\n", "\n"); this.dispatchEvent(new CustomEvent( "clipboard", @@ -2552,6 +2580,9 @@ export default class RFB extends EventTargetMixin { case encodings.pseudoEncodingExtendedDesktopSize: return this._handleExtendedDesktopSize(); + case encodings.pseudoEncodingQEMULedEvent: + return this._handleLedEvent(); + default: return this._handleDataRect(); } @@ -2729,6 +2760,21 @@ export default class RFB extends EventTargetMixin { return true; } + _handleLedEvent() { + if (this._sock.rQwait("LED Status", 1)) { + return false; + } + + let data = this._sock.rQshift8(); + // ScrollLock state can be retrieved with data & 1. This is currently not needed. + let numLock = data & 2 ? true : false; + let capsLock = data & 4 ? true : false; + this._remoteCapsLock = capsLock; + this._remoteNumLock = numLock; + + return true; + } + _handleExtendedDesktopSize() { if (this._sock.rQwait("ExtendedDesktopSize", 4)) { return false; diff --git a/core/util/cursor.js b/core/util/cursor.js index 3000cf0e..20e75f1b 100644 --- a/core/util/cursor.js +++ b/core/util/cursor.js @@ -69,7 +69,9 @@ export default class Cursor { this._target.removeEventListener('mousemove', this._eventHandlers.mousemove, options); this._target.removeEventListener('mouseup', this._eventHandlers.mouseup, options); - document.body.removeChild(this._canvas); + if (document.contains(this._canvas)) { + document.body.removeChild(this._canvas); + } } this._target = null; diff --git a/docs/novnc_proxy.1 b/docs/novnc_proxy.1 index 11a003b3..259e1b41 100644 --- a/docs/novnc_proxy.1 +++ b/docs/novnc_proxy.1 @@ -3,13 +3,13 @@ .SH NAME novnc_proxy - noVNC proxy server .SH SYNOPSIS -.B novnc_proxy [--listen PORT] [--vnc VNC_HOST:PORT] [--cert CERT] [--ssl-only] +.B novnc_proxy [--listen [HOST:]PORT] [--vnc VNC_HOST:PORT] [--cert CERT] [--ssl-only] Starts the WebSockets proxy and a mini-webserver and provides a cut-and-paste URL to go to. - --listen PORT Port for proxy/webserver to listen on - Default: 6080 + --listen [HOST:]PORT Port for proxy/webserver to listen on + Default: 6080 (on all interfaces) --vnc VNC_HOST:PORT VNC server host:port proxy target Default: localhost:5900 --cert CERT Path to combined cert/key file, or just diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 00000000..c88e7b75 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,102 @@ +import globals from "globals"; +import js from "@eslint/js"; + +export default [ + js.configs.recommended, + { + languageOptions: { + ecmaVersion: 2020, + sourceType: "module", + globals: { + ...globals.browser, + ...globals.es2020, + } + }, + ignores: ["**/xtscancodes.js"], + rules: { + // Unsafe or confusing stuff that we forbid + + "no-unused-vars": ["error", { "vars": "all", + "args": "none", + "ignoreRestSiblings": true, + "caughtErrors": "none" }], + "no-constant-condition": ["error", { "checkLoops": false }], + "no-var": "error", + "no-useless-constructor": "error", + "object-shorthand": ["error", "methods", { "avoidQuotes": true }], + "prefer-arrow-callback": "error", + "arrow-body-style": ["error", "as-needed", { "requireReturnForObjectLiteral": false } ], + "arrow-parens": ["error", "as-needed", { "requireForBlockBody": true }], + "arrow-spacing": ["error"], + "no-confusing-arrow": ["error", { "allowParens": true }], + + // Enforced coding style + + "brace-style": ["error", "1tbs", { "allowSingleLine": true }], + "indent": ["error", 4, { "SwitchCase": 1, + "VariableDeclarator": "first", + "FunctionDeclaration": { "parameters": "first" }, + "FunctionExpression": { "parameters": "first" }, + "CallExpression": { "arguments": "first" }, + "ArrayExpression": "first", + "ObjectExpression": "first", + "ImportDeclaration": "first", + "ignoreComments": true }], + "comma-spacing": ["error"], + "comma-style": ["error"], + "curly": ["error", "multi-line"], + "func-call-spacing": ["error"], + "func-names": ["error"], + "func-style": ["error", "declaration", { "allowArrowFunctions": true }], + "key-spacing": ["error"], + "keyword-spacing": ["error"], + "no-trailing-spaces": ["error"], + "semi": ["error"], + "space-before-blocks": ["error"], + "space-before-function-paren": ["error", { "anonymous": "always", + "named": "never", + "asyncArrow": "always" }], + "switch-colon-spacing": ["error"], + "camelcase": ["error", { "allow": ["^XK_", "^XF86XK_"] }], + "no-console": ["error"], + } + }, + { + files: ["po/po2js", "po/xgettext-html"], + languageOptions: { + globals: { + ...globals.node, + } + }, + rules: { + "no-console": 0, + }, + }, + { + files: ["tests/*"], + languageOptions: { + globals: { + ...globals.node, + ...globals.mocha, + sinon: false, + chai: false, + } + }, + rules: { + "prefer-arrow-callback": 0, + // Too many anonymous callbacks + "func-names": "off", + }, + }, + { + files: ["utils/*"], + languageOptions: { + globals: { + ...globals.node, + } + }, + rules: { + "no-console": 0, + }, + }, +]; diff --git a/package.json b/package.json index 5847887f..9fa8c312 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@novnc/novnc", - "version": "1.4.0", + "version": "1.5.0", "description": "An HTML5 VNC client", "browser": "lib/rfb", "directories": { @@ -14,9 +14,7 @@ "VERSION", "docs/API.md", "docs/LIBRARY.md", - "docs/LICENSE*", - "core", - "vendor/pako" + "docs/LICENSE*" ], "scripts": { "lint": "eslint app core po/po2js po/xgettext-html tests utils", @@ -39,19 +37,14 @@ "homepage": "https://github.com/novnc/noVNC", "devDependencies": { "@babel/core": "latest", - "@babel/plugin-syntax-dynamic-import": "latest", - "@babel/plugin-transform-modules-commonjs": "latest", "@babel/preset-env": "latest", - "@babel/cli": "latest", "babel-plugin-import-redirect": "latest", "browserify": "latest", - "babelify": "latest", - "core-js": "latest", "chai": "latest", "commander": "latest", - "es-module-loader": "latest", "eslint": "latest", "fs-extra": "latest", + "globals": "latest", "jsdom": "latest", "karma": "latest", "karma-mocha": "latest", diff --git a/po/.eslintrc b/po/.eslintrc deleted file mode 100644 index a0157e2a..00000000 --- a/po/.eslintrc +++ /dev/null @@ -1,5 +0,0 @@ -{ - "env": { - "node": true, - }, -} diff --git a/po/el.po b/po/el.po index 5213ae54..de690fe9 100644 --- a/po/el.po +++ b/po/el.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: noVNC 0.6.1\n" "Report-Msgid-Bugs-To: novnc@googlegroups.com\n" -"POT-Creation-Date: 2017-11-17 21:40+0200\n" +"POT-Creation-Date: 2022-12-27 15:24+0100\n" "PO-Revision-Date: 2017-10-11 16:16+0200\n" "Last-Translator: Giannis Kosmas \n" "Language-Team: none\n" @@ -17,273 +17,349 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: ../app/ui.js:404 +#: ../app/ui.js:69 +msgid "HTTPS is required for full functionality" +msgstr "Το HTTPS είναι απαιτούμενο για πλήρη λειτουργικότητα" + +#: ../app/ui.js:410 msgid "Connecting..." msgstr "Συνδέεται..." -#: ../app/ui.js:411 +#: ../app/ui.js:417 msgid "Disconnecting..." msgstr "Aποσυνδέεται..." -#: ../app/ui.js:417 +#: ../app/ui.js:423 msgid "Reconnecting..." msgstr "Επανασυνδέεται..." -#: ../app/ui.js:422 +#: ../app/ui.js:428 msgid "Internal error" msgstr "Εσωτερικό σφάλμα" -#: ../app/ui.js:1019 +#: ../app/ui.js:1026 msgid "Must set host" msgstr "Πρέπει να οριστεί ο διακομιστής" -#: ../app/ui.js:1099 +#: ../app/ui.js:1110 msgid "Connected (encrypted) to " msgstr "Συνδέθηκε (κρυπτογραφημένα) με το " -#: ../app/ui.js:1101 +#: ../app/ui.js:1112 msgid "Connected (unencrypted) to " msgstr "Συνδέθηκε (μη κρυπτογραφημένα) με το " -#: ../app/ui.js:1119 +#: ../app/ui.js:1135 msgid "Something went wrong, connection is closed" msgstr "Κάτι πήγε στραβά, η σύνδεση διακόπηκε" -#: ../app/ui.js:1129 +#: ../app/ui.js:1138 +msgid "Failed to connect to server" +msgstr "Αποτυχία στη σύνδεση με το διακομιστή" + +#: ../app/ui.js:1150 msgid "Disconnected" msgstr "Αποσυνδέθηκε" -#: ../app/ui.js:1142 +#: ../app/ui.js:1165 msgid "New connection has been rejected with reason: " msgstr "Η νέα σύνδεση απορρίφθηκε διότι: " -#: ../app/ui.js:1145 +#: ../app/ui.js:1168 msgid "New connection has been rejected" msgstr "Η νέα σύνδεση απορρίφθηκε " -#: ../app/ui.js:1166 -msgid "Password is required" -msgstr "Απαιτείται ο κωδικός πρόσβασης" +#: ../app/ui.js:1234 +msgid "Credentials are required" +msgstr "Απαιτούνται διαπιστευτήρια" -#: ../vnc.html:89 +#: ../vnc.html:57 msgid "noVNC encountered an error:" msgstr "το noVNC αντιμετώπισε ένα σφάλμα:" -#: ../vnc.html:99 +#: ../vnc.html:67 msgid "Hide/Show the control bar" msgstr "Απόκρυψη/Εμφάνιση γραμμής ελέγχου" -#: ../vnc.html:106 +#: ../vnc.html:76 +msgid "Drag" +msgstr "Σύρσιμο" + +#: ../vnc.html:76 msgid "Move/Drag Viewport" msgstr "Μετακίνηση/Σύρσιμο Θεατού πεδίου" -#: ../vnc.html:106 -msgid "viewport drag" -msgstr "σύρσιμο θεατού πεδίου" - -#: ../vnc.html:112 ../vnc.html:115 ../vnc.html:118 ../vnc.html:121 -msgid "Active Mouse Button" -msgstr "Ενεργό Πλήκτρο Ποντικιού" - -#: ../vnc.html:112 -msgid "No mousebutton" -msgstr "Χωρίς Πλήκτρο Ποντικιού" - -#: ../vnc.html:115 -msgid "Left mousebutton" -msgstr "Αριστερό Πλήκτρο Ποντικιού" - -#: ../vnc.html:118 -msgid "Middle mousebutton" -msgstr "Μεσαίο Πλήκτρο Ποντικιού" - -#: ../vnc.html:121 -msgid "Right mousebutton" -msgstr "Δεξί Πλήκτρο Ποντικιού" - -#: ../vnc.html:124 +#: ../vnc.html:82 msgid "Keyboard" msgstr "Πληκτρολόγιο" -#: ../vnc.html:124 +#: ../vnc.html:82 msgid "Show Keyboard" msgstr "Εμφάνιση Πληκτρολογίου" -#: ../vnc.html:131 +#: ../vnc.html:87 msgid "Extra keys" msgstr "Επιπλέον πλήκτρα" -#: ../vnc.html:131 +#: ../vnc.html:87 msgid "Show Extra Keys" msgstr "Εμφάνιση Επιπλέον Πλήκτρων" -#: ../vnc.html:136 +#: ../vnc.html:92 msgid "Ctrl" msgstr "Ctrl" -#: ../vnc.html:136 +#: ../vnc.html:92 msgid "Toggle Ctrl" msgstr "Εναλλαγή Ctrl" -#: ../vnc.html:139 +#: ../vnc.html:95 msgid "Alt" msgstr "Alt" -#: ../vnc.html:139 +#: ../vnc.html:95 msgid "Toggle Alt" msgstr "Εναλλαγή Alt" -#: ../vnc.html:142 +#: ../vnc.html:98 +msgid "Toggle Windows" +msgstr "Εναλλαγή Παράθυρων" + +#: ../vnc.html:98 +msgid "Windows" +msgstr "Παράθυρα" + +#: ../vnc.html:101 msgid "Send Tab" msgstr "Αποστολή Tab" -#: ../vnc.html:142 +#: ../vnc.html:101 msgid "Tab" msgstr "Tab" -#: ../vnc.html:145 +#: ../vnc.html:104 msgid "Esc" msgstr "Esc" -#: ../vnc.html:145 +#: ../vnc.html:104 msgid "Send Escape" msgstr "Αποστολή Escape" -#: ../vnc.html:148 +#: ../vnc.html:107 msgid "Ctrl+Alt+Del" msgstr "Ctrl+Alt+Del" -#: ../vnc.html:148 +#: ../vnc.html:107 msgid "Send Ctrl-Alt-Del" msgstr "Αποστολή Ctrl-Alt-Del" -#: ../vnc.html:156 +#: ../vnc.html:114 msgid "Shutdown/Reboot" msgstr "Κλείσιμο/Επανεκκίνηση" -#: ../vnc.html:156 +#: ../vnc.html:114 msgid "Shutdown/Reboot..." msgstr "Κλείσιμο/Επανεκκίνηση..." -#: ../vnc.html:162 +#: ../vnc.html:120 msgid "Power" msgstr "Απενεργοποίηση" -#: ../vnc.html:164 +#: ../vnc.html:122 msgid "Shutdown" msgstr "Κλείσιμο" -#: ../vnc.html:165 +#: ../vnc.html:123 msgid "Reboot" msgstr "Επανεκκίνηση" -#: ../vnc.html:166 +#: ../vnc.html:124 msgid "Reset" msgstr "Επαναφορά" -#: ../vnc.html:171 ../vnc.html:177 +#: ../vnc.html:129 ../vnc.html:135 msgid "Clipboard" msgstr "Πρόχειρο" -#: ../vnc.html:181 -msgid "Clear" -msgstr "Καθάρισμα" +#: ../vnc.html:137 +msgid "Edit clipboard content in the textarea below." +msgstr "Επεξεργαστείτε το περιεχόμενο του πρόχειρου στην περιοχή κειμένου παρακάτω." -#: ../vnc.html:187 -msgid "Fullscreen" +#: ../vnc.html:145 +#, fuzzy +msgid "Full Screen" msgstr "Πλήρης Οθόνη" -#: ../vnc.html:192 ../vnc.html:199 +#: ../vnc.html:150 ../vnc.html:156 msgid "Settings" msgstr "Ρυθμίσεις" -#: ../vnc.html:202 +#: ../vnc.html:160 msgid "Shared Mode" msgstr "Κοινόχρηστη Λειτουργία" -#: ../vnc.html:205 +#: ../vnc.html:163 msgid "View Only" msgstr "Μόνο Θέαση" -#: ../vnc.html:209 +#: ../vnc.html:167 msgid "Clip to Window" msgstr "Αποκοπή στο όριο του Παράθυρου" -#: ../vnc.html:212 +#: ../vnc.html:170 msgid "Scaling Mode:" msgstr "Λειτουργία Κλιμάκωσης:" -#: ../vnc.html:214 +#: ../vnc.html:172 msgid "None" msgstr "Καμία" -#: ../vnc.html:215 +#: ../vnc.html:173 msgid "Local Scaling" msgstr "Τοπική Κλιμάκωση" -#: ../vnc.html:216 +#: ../vnc.html:174 msgid "Remote Resizing" msgstr "Απομακρυσμένη Αλλαγή μεγέθους" -#: ../vnc.html:221 +#: ../vnc.html:179 msgid "Advanced" msgstr "Για προχωρημένους" -#: ../vnc.html:224 +#: ../vnc.html:182 +msgid "Quality:" +msgstr "Ποιότητα:" + +#: ../vnc.html:186 +msgid "Compression level:" +msgstr "Επίπεδο συμπίεσης:" + +#: ../vnc.html:191 msgid "Repeater ID:" msgstr "Repeater ID:" -#: ../vnc.html:228 +#: ../vnc.html:195 msgid "WebSocket" msgstr "WebSocket" -#: ../vnc.html:231 +#: ../vnc.html:198 msgid "Encrypt" msgstr "Κρυπτογράφηση" -#: ../vnc.html:234 +#: ../vnc.html:201 msgid "Host:" msgstr "Όνομα διακομιστή:" -#: ../vnc.html:238 +#: ../vnc.html:205 msgid "Port:" msgstr "Πόρτα διακομιστή:" -#: ../vnc.html:242 +#: ../vnc.html:209 msgid "Path:" msgstr "Διαδρομή:" -#: ../vnc.html:249 +#: ../vnc.html:216 msgid "Automatic Reconnect" msgstr "Αυτόματη επανασύνδεση" -#: ../vnc.html:252 +#: ../vnc.html:219 msgid "Reconnect Delay (ms):" msgstr "Καθυστέρηση επανασύνδεσης (ms):" -#: ../vnc.html:258 +#: ../vnc.html:224 +msgid "Show Dot when No Cursor" +msgstr "Εμφάνιση Τελείας όταν δεν υπάρχει Δρομέας" + +#: ../vnc.html:229 msgid "Logging:" msgstr "Καταγραφή:" -#: ../vnc.html:270 +#: ../vnc.html:238 +msgid "Version:" +msgstr "Έκδοση:" + +#: ../vnc.html:246 msgid "Disconnect" msgstr "Αποσύνδεση" -#: ../vnc.html:289 +#: ../vnc.html:269 msgid "Connect" msgstr "Σύνδεση" -#: ../vnc.html:299 +#: ../vnc.html:278 +msgid "Server identity" +msgstr "Ταυτότητα Διακομιστή" + +#: ../vnc.html:281 +msgid "The server has provided the following identifying information:" +msgstr "Ο διακομιστής παρείχε την ακόλουθη πληροφορία ταυτοποίησης:" + +#: ../vnc.html:285 +msgid "Fingerprint:" +msgstr "Δακτυλικό αποτύπωμα:" + +#: ../vnc.html:288 +msgid "" +"Please verify that the information is correct and press \"Approve\". " +"Otherwise press \"Reject\"." +msgstr "" +"Παρακαλώ επαληθεύσετε ότι η πληροφορία είναι σωστή και πιέστε \"Αποδοχή\". " +"Αλλιώς πιέστε \"Απόρριψη\"." + +#: ../vnc.html:293 +msgid "Approve" +msgstr "Αποδοχή" + +#: ../vnc.html:294 +msgid "Reject" +msgstr "Απόρριψη" + +#: ../vnc.html:302 +msgid "Credentials" +msgstr "Διαπιστευτήρια" + +#: ../vnc.html:306 +msgid "Username:" +msgstr "Κωδικός Χρήστη:" + +#: ../vnc.html:310 msgid "Password:" msgstr "Κωδικός Πρόσβασης:" -#: ../vnc.html:313 +#: ../vnc.html:314 +msgid "Send Credentials" +msgstr "Αποστολή Διαπιστευτηρίων" + +#: ../vnc.html:323 msgid "Cancel" msgstr "Ακύρωση" -#: ../vnc.html:329 -msgid "Canvas not supported." -msgstr "Δεν υποστηρίζεται το στοιχείο Canvas" +#~ msgid "Password is required" +#~ msgstr "Απαιτείται ο κωδικός πρόσβασης" + +#~ msgid "viewport drag" +#~ msgstr "σύρσιμο θεατού πεδίου" + +#~ msgid "Active Mouse Button" +#~ msgstr "Ενεργό Πλήκτρο Ποντικιού" + +#~ msgid "No mousebutton" +#~ msgstr "Χωρίς Πλήκτρο Ποντικιού" + +#~ msgid "Left mousebutton" +#~ msgstr "Αριστερό Πλήκτρο Ποντικιού" + +#~ msgid "Middle mousebutton" +#~ msgstr "Μεσαίο Πλήκτρο Ποντικιού" + +#~ msgid "Right mousebutton" +#~ msgstr "Δεξί Πλήκτρο Ποντικιού" + +#~ msgid "Clear" +#~ msgstr "Καθάρισμα" + +#~ msgid "Canvas not supported." +#~ msgstr "Δεν υποστηρίζεται το στοιχείο Canvas" #~ msgid "Disconnect timeout" #~ msgstr "Παρέλευση χρονικού ορίου αποσύνδεσης" diff --git a/po/noVNC.pot b/po/noVNC.pot index 3641e3bc..0f85a82e 100644 --- a/po/noVNC.pot +++ b/po/noVNC.pot @@ -6,9 +6,9 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: noVNC 1.4.0\n" +"Project-Id-Version: noVNC 1.5.0\n" "Report-Msgid-Bugs-To: novnc@googlegroups.com\n" -"POT-Creation-Date: 2022-12-27 15:24+0100\n" +"POT-Creation-Date: 2024-06-03 14:10+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -18,7 +18,8 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" #: ../app/ui.js:69 -msgid "HTTPS is required for full functionality" +msgid "" +"Running without HTTPS is not recommended, crashes or other issues are likely." msgstr "" #: ../app/ui.js:410 @@ -41,292 +42,296 @@ msgstr "" msgid "Must set host" msgstr "" -#: ../app/ui.js:1110 +#: ../app/ui.js:1052 +msgid "Failed to connect to server: " +msgstr "" + +#: ../app/ui.js:1118 msgid "Connected (encrypted) to " msgstr "" -#: ../app/ui.js:1112 +#: ../app/ui.js:1120 msgid "Connected (unencrypted) to " msgstr "" -#: ../app/ui.js:1135 +#: ../app/ui.js:1143 msgid "Something went wrong, connection is closed" msgstr "" -#: ../app/ui.js:1138 +#: ../app/ui.js:1146 msgid "Failed to connect to server" msgstr "" -#: ../app/ui.js:1150 +#: ../app/ui.js:1158 msgid "Disconnected" msgstr "" -#: ../app/ui.js:1165 +#: ../app/ui.js:1173 msgid "New connection has been rejected with reason: " msgstr "" -#: ../app/ui.js:1168 +#: ../app/ui.js:1176 msgid "New connection has been rejected" msgstr "" -#: ../app/ui.js:1234 +#: ../app/ui.js:1242 msgid "Credentials are required" msgstr "" -#: ../vnc.html:57 +#: ../vnc.html:55 msgid "noVNC encountered an error:" msgstr "" -#: ../vnc.html:67 +#: ../vnc.html:65 msgid "Hide/Show the control bar" msgstr "" -#: ../vnc.html:76 +#: ../vnc.html:74 msgid "Drag" msgstr "" -#: ../vnc.html:76 +#: ../vnc.html:74 msgid "Move/Drag Viewport" msgstr "" -#: ../vnc.html:82 +#: ../vnc.html:80 msgid "Keyboard" msgstr "" -#: ../vnc.html:82 +#: ../vnc.html:80 msgid "Show Keyboard" msgstr "" -#: ../vnc.html:87 +#: ../vnc.html:85 msgid "Extra keys" msgstr "" -#: ../vnc.html:87 +#: ../vnc.html:85 msgid "Show Extra Keys" msgstr "" -#: ../vnc.html:92 +#: ../vnc.html:90 msgid "Ctrl" msgstr "" -#: ../vnc.html:92 +#: ../vnc.html:90 msgid "Toggle Ctrl" msgstr "" -#: ../vnc.html:95 +#: ../vnc.html:93 msgid "Alt" msgstr "" -#: ../vnc.html:95 +#: ../vnc.html:93 msgid "Toggle Alt" msgstr "" -#: ../vnc.html:98 +#: ../vnc.html:96 msgid "Toggle Windows" msgstr "" -#: ../vnc.html:98 +#: ../vnc.html:96 msgid "Windows" msgstr "" -#: ../vnc.html:101 +#: ../vnc.html:99 msgid "Send Tab" msgstr "" -#: ../vnc.html:101 +#: ../vnc.html:99 msgid "Tab" msgstr "" -#: ../vnc.html:104 +#: ../vnc.html:102 msgid "Esc" msgstr "" -#: ../vnc.html:104 +#: ../vnc.html:102 msgid "Send Escape" msgstr "" -#: ../vnc.html:107 +#: ../vnc.html:105 msgid "Ctrl+Alt+Del" msgstr "" -#: ../vnc.html:107 +#: ../vnc.html:105 msgid "Send Ctrl-Alt-Del" msgstr "" -#: ../vnc.html:114 +#: ../vnc.html:112 msgid "Shutdown/Reboot" msgstr "" -#: ../vnc.html:114 +#: ../vnc.html:112 msgid "Shutdown/Reboot..." msgstr "" -#: ../vnc.html:120 +#: ../vnc.html:118 msgid "Power" msgstr "" -#: ../vnc.html:122 +#: ../vnc.html:120 msgid "Shutdown" msgstr "" -#: ../vnc.html:123 +#: ../vnc.html:121 msgid "Reboot" msgstr "" -#: ../vnc.html:124 +#: ../vnc.html:122 msgid "Reset" msgstr "" -#: ../vnc.html:129 ../vnc.html:135 +#: ../vnc.html:127 ../vnc.html:133 msgid "Clipboard" msgstr "" -#: ../vnc.html:137 +#: ../vnc.html:135 msgid "Edit clipboard content in the textarea below." msgstr "" -#: ../vnc.html:145 +#: ../vnc.html:143 msgid "Full Screen" msgstr "" -#: ../vnc.html:150 ../vnc.html:156 +#: ../vnc.html:148 ../vnc.html:154 msgid "Settings" msgstr "" -#: ../vnc.html:160 +#: ../vnc.html:158 msgid "Shared Mode" msgstr "" -#: ../vnc.html:163 +#: ../vnc.html:161 msgid "View Only" msgstr "" -#: ../vnc.html:167 +#: ../vnc.html:165 msgid "Clip to Window" msgstr "" -#: ../vnc.html:170 +#: ../vnc.html:168 msgid "Scaling Mode:" msgstr "" -#: ../vnc.html:172 +#: ../vnc.html:170 msgid "None" msgstr "" -#: ../vnc.html:173 +#: ../vnc.html:171 msgid "Local Scaling" msgstr "" -#: ../vnc.html:174 +#: ../vnc.html:172 msgid "Remote Resizing" msgstr "" -#: ../vnc.html:179 +#: ../vnc.html:177 msgid "Advanced" msgstr "" -#: ../vnc.html:182 +#: ../vnc.html:180 msgid "Quality:" msgstr "" -#: ../vnc.html:186 +#: ../vnc.html:184 msgid "Compression level:" msgstr "" -#: ../vnc.html:191 +#: ../vnc.html:189 msgid "Repeater ID:" msgstr "" -#: ../vnc.html:195 +#: ../vnc.html:193 msgid "WebSocket" msgstr "" -#: ../vnc.html:198 +#: ../vnc.html:196 msgid "Encrypt" msgstr "" -#: ../vnc.html:201 +#: ../vnc.html:199 msgid "Host:" msgstr "" -#: ../vnc.html:205 +#: ../vnc.html:203 msgid "Port:" msgstr "" -#: ../vnc.html:209 +#: ../vnc.html:207 msgid "Path:" msgstr "" -#: ../vnc.html:216 +#: ../vnc.html:214 msgid "Automatic Reconnect" msgstr "" -#: ../vnc.html:219 +#: ../vnc.html:217 msgid "Reconnect Delay (ms):" msgstr "" -#: ../vnc.html:224 +#: ../vnc.html:222 msgid "Show Dot when No Cursor" msgstr "" -#: ../vnc.html:229 +#: ../vnc.html:227 msgid "Logging:" msgstr "" -#: ../vnc.html:238 +#: ../vnc.html:236 msgid "Version:" msgstr "" -#: ../vnc.html:246 +#: ../vnc.html:244 msgid "Disconnect" msgstr "" -#: ../vnc.html:269 +#: ../vnc.html:267 msgid "Connect" msgstr "" -#: ../vnc.html:278 +#: ../vnc.html:276 msgid "Server identity" msgstr "" -#: ../vnc.html:281 +#: ../vnc.html:279 msgid "The server has provided the following identifying information:" msgstr "" -#: ../vnc.html:285 +#: ../vnc.html:283 msgid "Fingerprint:" msgstr "" -#: ../vnc.html:288 +#: ../vnc.html:286 msgid "" "Please verify that the information is correct and press \"Approve\". " "Otherwise press \"Reject\"." msgstr "" -#: ../vnc.html:293 +#: ../vnc.html:291 msgid "Approve" msgstr "" -#: ../vnc.html:294 +#: ../vnc.html:292 msgid "Reject" msgstr "" -#: ../vnc.html:302 +#: ../vnc.html:300 msgid "Credentials" msgstr "" -#: ../vnc.html:306 +#: ../vnc.html:304 msgid "Username:" msgstr "" -#: ../vnc.html:310 +#: ../vnc.html:308 msgid "Password:" msgstr "" -#: ../vnc.html:314 +#: ../vnc.html:312 msgid "Send Credentials" msgstr "" -#: ../vnc.html:323 +#: ../vnc.html:321 msgid "Cancel" msgstr "" diff --git a/po/po2js b/po/po2js index fc6e8810..e293deda 100755 --- a/po/po2js +++ b/po/po2js @@ -32,11 +32,13 @@ if (opt.argv.length != 2) { const data = po2json.parseFileSync(opt.argv[0]); -const bodyPart = Object.keys(data).filter(msgid => msgid !== "").map((msgid) => { - if (msgid === "") return; - const msgstr = data[msgid][1]; - return " " + JSON.stringify(msgid) + ": " + JSON.stringify(msgstr); -}).join(",\n"); +const bodyPart = Object.keys(data) + .filter(msgid => msgid !== "") + .filter(msgid => data[msgid][1] !== "") + .map((msgid) => { + const msgstr = data[msgid][1]; + return " " + JSON.stringify(msgid) + ": " + JSON.stringify(msgstr); + }).join(",\n"); const output = "{\n" + bodyPart + "\n}"; diff --git a/po/sv.po b/po/sv.po index 972e4000..85c4e305 100644 --- a/po/sv.po +++ b/po/sv.po @@ -8,20 +8,23 @@ msgid "" msgstr "" "Project-Id-Version: noVNC 1.3.0\n" "Report-Msgid-Bugs-To: novnc@googlegroups.com\n" -"POT-Creation-Date: 2023-01-20 12:54+0100\n" -"PO-Revision-Date: 2023-01-20 12:58+0100\n" -"Last-Translator: Samuel Mannehed \n" +"POT-Creation-Date: 2024-06-03 14:10+0200\n" +"PO-Revision-Date: 2024-06-18 13:52+0200\n" +"Last-Translator: Pierre Ossman \n" "Language-Team: none\n" "Language: sv\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Poedit 3.2.2\n" +"X-Generator: Poedit 3.4.4\n" #: ../app/ui.js:69 -msgid "HTTPS is required for full functionality" -msgstr "HTTPS krävs för full funktionalitet" +msgid "" +"Running without HTTPS is not recommended, crashes or other issues are likely." +msgstr "" +"Det är ej rekommenderat att köra utan HTTPS, krascher och andra problem är " +"troliga." #: ../app/ui.js:410 msgid "Connecting..." @@ -43,35 +46,39 @@ msgstr "Internt fel" msgid "Must set host" msgstr "Du måste specifiera en värd" -#: ../app/ui.js:1110 +#: ../app/ui.js:1052 +msgid "Failed to connect to server: " +msgstr "Misslyckades att ansluta till servern: " + +#: ../app/ui.js:1118 msgid "Connected (encrypted) to " msgstr "Ansluten (krypterat) till " -#: ../app/ui.js:1112 +#: ../app/ui.js:1120 msgid "Connected (unencrypted) to " msgstr "Ansluten (okrypterat) till " -#: ../app/ui.js:1135 +#: ../app/ui.js:1143 msgid "Something went wrong, connection is closed" msgstr "Något gick fel, anslutningen avslutades" -#: ../app/ui.js:1138 +#: ../app/ui.js:1146 msgid "Failed to connect to server" msgstr "Misslyckades att ansluta till servern" -#: ../app/ui.js:1150 +#: ../app/ui.js:1158 msgid "Disconnected" msgstr "Frånkopplad" -#: ../app/ui.js:1165 +#: ../app/ui.js:1173 msgid "New connection has been rejected with reason: " msgstr "Ny anslutning har blivit nekad med följande skäl: " -#: ../app/ui.js:1168 +#: ../app/ui.js:1176 msgid "New connection has been rejected" msgstr "Ny anslutning har blivit nekad" -#: ../app/ui.js:1234 +#: ../app/ui.js:1242 msgid "Credentials are required" msgstr "Användaruppgifter krävs" @@ -304,8 +311,8 @@ msgid "" "Please verify that the information is correct and press \"Approve\". " "Otherwise press \"Reject\"." msgstr "" -"Kontrollera att informationen är korrekt och tryck sedan " -"\"Godkänn\". Tryck annars \"Neka\"." +"Kontrollera att informationen är korrekt och tryck sedan \"Godkänn\". Tryck " +"annars \"Neka\"." #: ../vnc.html:291 msgid "Approve" @@ -335,5 +342,8 @@ msgstr "Skicka Användaruppgifter" msgid "Cancel" msgstr "Avbryt" +#~ msgid "HTTPS is required for full functionality" +#~ msgstr "HTTPS krävs för full funktionalitet" + #~ msgid "Clear" #~ msgstr "Rensa" diff --git a/po/zh_CN.po b/po/zh_CN.po index ede9d441..caae2850 100644 --- a/po/zh_CN.po +++ b/po/zh_CN.po @@ -15,58 +15,42 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: ../app/ui.js:395 +#: ../app/ui.js:430 msgid "Connecting..." msgstr "连接中..." -#: ../app/ui.js:402 +#: ../app/ui.js:438 +msgid "Connected (encrypted) to " +msgstr "已连接(已加密)到" + +#: ../app/ui.js:440 +msgid "Connected (unencrypted) to " +msgstr "已连接(未加密)到" + +#: ../app/ui.js:446 msgid "Disconnecting..." msgstr "正在断开连接..." -#: ../app/ui.js:408 -msgid "Reconnecting..." -msgstr "重新连接中..." - -#: ../app/ui.js:413 -msgid "Internal error" -msgstr "内部错误" - -#: ../app/ui.js:1015 -msgid "Must set host" -msgstr "请提供主机名" - -#: ../app/ui.js:1097 -msgid "Connected (encrypted) to " -msgstr "已连接到(加密)" - -#: ../app/ui.js:1099 -msgid "Connected (unencrypted) to " -msgstr "已连接到(未加密)" - -#: ../app/ui.js:1120 -msgid "Something went wrong, connection is closed" -msgstr "发生错误,连接已关闭" - -#: ../app/ui.js:1123 -msgid "Failed to connect to server" -msgstr "无法连接到服务器" - -#: ../app/ui.js:1133 +#: ../app/ui.js:450 msgid "Disconnected" msgstr "已断开连接" -#: ../app/ui.js:1146 -msgid "New connection has been rejected with reason: " -msgstr "连接被拒绝,原因:" +#: ../app/ui.js:1052 ../core/rfb.js:248 +msgid "Must set host" +msgstr "必须设置主机" -#: ../app/ui.js:1149 -msgid "New connection has been rejected" -msgstr "连接被拒绝" +#: ../app/ui.js:1101 +msgid "Reconnecting..." +msgstr "重新连接中..." -#: ../app/ui.js:1170 +#: ../app/ui.js:1140 msgid "Password is required" msgstr "请提供密码" +#: ../core/rfb.js:548 +msgid "Disconnect timeout" +msgstr "超时断开" + #: ../vnc.html:89 msgid "noVNC encountered an error:" msgstr "noVNC 遇到一个错误:" @@ -77,31 +61,31 @@ msgstr "显示/隐藏控制栏" #: ../vnc.html:106 msgid "Move/Drag Viewport" -msgstr "拖放显示范围" +msgstr "移动/拖动窗口" #: ../vnc.html:106 msgid "viewport drag" -msgstr "显示范围拖放" +msgstr "窗口拖动" #: ../vnc.html:112 ../vnc.html:115 ../vnc.html:118 ../vnc.html:121 msgid "Active Mouse Button" -msgstr "启动鼠标按鍵" +msgstr "启动鼠标按键" #: ../vnc.html:112 msgid "No mousebutton" -msgstr "禁用鼠标按鍵" +msgstr "禁用鼠标按键" #: ../vnc.html:115 msgid "Left mousebutton" -msgstr "鼠标左鍵" +msgstr "鼠标左键" #: ../vnc.html:118 msgid "Middle mousebutton" -msgstr "鼠标中鍵" +msgstr "鼠标中键" #: ../vnc.html:121 msgid "Right mousebutton" -msgstr "鼠标右鍵" +msgstr "鼠标右键" #: ../vnc.html:124 msgid "Keyboard" @@ -127,6 +111,10 @@ msgstr "Ctrl" msgid "Toggle Ctrl" msgstr "切换 Ctrl" +#: ../vnc.html:136 +msgid "Edit clipboard content in the textarea below." +msgstr "在下面的文本区域中编辑剪贴板内容。" + #: ../vnc.html:139 msgid "Alt" msgstr "Alt" @@ -153,19 +141,19 @@ msgstr "发送 Escape 键" #: ../vnc.html:148 msgid "Ctrl+Alt+Del" -msgstr "Ctrl-Alt-Del" +msgstr "Ctrl+Alt+Del" #: ../vnc.html:148 msgid "Send Ctrl-Alt-Del" -msgstr "发送 Ctrl-Alt-Del 键" +msgstr "发送 Ctrl+Alt+Del 键" #: ../vnc.html:156 msgid "Shutdown/Reboot" -msgstr "关机/重新启动" +msgstr "关机/重启" #: ../vnc.html:156 msgid "Shutdown/Reboot..." -msgstr "关机/重新启动..." +msgstr "关机/重启..." #: ../vnc.html:162 msgid "Power" @@ -177,7 +165,7 @@ msgstr "关机" #: ../vnc.html:165 msgid "Reboot" -msgstr "重新启动" +msgstr "重启" #: ../vnc.html:166 msgid "Reset" @@ -199,6 +187,10 @@ msgstr "全屏" msgid "Settings" msgstr "设置" +#: ../vnc.html:200 +msgid "Encrypt" +msgstr "加密" + #: ../vnc.html:202 msgid "Shared Mode" msgstr "分享模式" @@ -224,61 +216,69 @@ msgid "Local Scaling" msgstr "本地缩放" #: ../vnc.html:216 +msgid "Local Downscaling" +msgstr "降低本地尺寸" + +#: ../vnc.html:217 msgid "Remote Resizing" msgstr "远程调整大小" -#: ../vnc.html:221 +#: ../vnc.html:222 msgid "Advanced" msgstr "高级" -#: ../vnc.html:224 +#: ../vnc.html:225 +msgid "Local Cursor" +msgstr "本地光标" + +#: ../vnc.html:229 msgid "Repeater ID:" msgstr "中继站 ID" -#: ../vnc.html:228 +#: ../vnc.html:233 msgid "WebSocket" msgstr "WebSocket" -#: ../vnc.html:231 -msgid "Encrypt" -msgstr "加密" - -#: ../vnc.html:234 +#: ../vnc.html:239 msgid "Host:" msgstr "主机:" -#: ../vnc.html:238 +#: ../vnc.html:243 msgid "Port:" msgstr "端口:" -#: ../vnc.html:242 +#: ../vnc.html:247 msgid "Path:" msgstr "路径:" -#: ../vnc.html:249 +#: ../vnc.html:254 msgid "Automatic Reconnect" msgstr "自动重新连接" -#: ../vnc.html:252 +#: ../vnc.html:257 msgid "Reconnect Delay (ms):" msgstr "重新连接间隔 (ms):" -#: ../vnc.html:258 +#: ../vnc.html:263 msgid "Logging:" msgstr "日志级别:" -#: ../vnc.html:270 +#: ../vnc.html:275 msgid "Disconnect" -msgstr "中断连接" +msgstr "断开连接" -#: ../vnc.html:289 +#: ../vnc.html:294 msgid "Connect" msgstr "连接" -#: ../vnc.html:299 +#: ../vnc.html:304 msgid "Password:" msgstr "密码:" -#: ../vnc.html:313 +#: ../vnc.html:318 msgid "Cancel" msgstr "取消" + +#: ../vnc.html:334 +msgid "Canvas not supported." +msgstr "不支持 Canvas。" \ No newline at end of file diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 02094820..82d52de4 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,5 +1,5 @@ name: novnc -base: core18 # the base snap is the execution environment for this snap +base: core22 # the base snap is the execution environment for this snap version: git summary: Open Source VNC client using HTML5 (WebSockets, Canvas) description: | @@ -42,7 +42,7 @@ parts: - jq websockify: - source: https://github.com/novnc/websockify/archive/v0.11.0.tar.gz + source: https://github.com/novnc/websockify/archive/v0.12.0.tar.gz plugin: python stage-packages: - python3-numpy diff --git a/tests/.eslintrc b/tests/.eslintrc deleted file mode 100644 index 545fa2ed..00000000 --- a/tests/.eslintrc +++ /dev/null @@ -1,15 +0,0 @@ -{ - "env": { - "node": true, - "mocha": true - }, - "globals": { - "chai": false, - "sinon": false - }, - "rules": { - "prefer-arrow-callback": 0, - // Too many anonymous callbacks - "func-names": "off", - } -} diff --git a/tests/test.browser.js b/tests/test.browser.js index 3b2299f6..1beeb48d 100644 --- a/tests/test.browser.js +++ b/tests/test.browser.js @@ -1,4 +1,3 @@ -/* eslint-disable no-console */ const expect = chai.expect; import { isMac, isWindows, isIOS, isAndroid, isChromeOS, diff --git a/tests/test.deflator.js b/tests/test.deflator.js index 12e8a46b..a7e972ec 100644 --- a/tests/test.deflator.js +++ b/tests/test.deflator.js @@ -1,4 +1,3 @@ -/* eslint-disable no-console */ const expect = chai.expect; import { inflateInit, inflate } from "../vendor/pako/lib/zlib/inflate.js"; diff --git a/tests/test.helper.js b/tests/test.helper.js index ff83c539..9995973f 100644 --- a/tests/test.helper.js +++ b/tests/test.helper.js @@ -108,6 +108,8 @@ describe('Helpers', function () { }); it('should use charCode if no key', function () { expect(KeyboardUtil.getKey({charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43})).to.be.equal('Š'); + // Broken Oculus browser + expect(KeyboardUtil.getKey({charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43, key: 'Unidentified'})).to.be.equal('Š'); }); it('should return Unidentified when it cannot map the key', function () { expect(KeyboardUtil.getKey({keycode: 0x42})).to.be.equal('Unidentified'); diff --git a/tests/test.inflator.js b/tests/test.inflator.js index 533bcd86..304e7a0f 100644 --- a/tests/test.inflator.js +++ b/tests/test.inflator.js @@ -1,4 +1,3 @@ -/* eslint-disable no-console */ const expect = chai.expect; import { deflateInit, deflate, Z_FULL_FLUSH } from "../vendor/pako/lib/zlib/deflate.js"; diff --git a/tests/test.int.js b/tests/test.int.js index 954fd279..084d68ab 100644 --- a/tests/test.int.js +++ b/tests/test.int.js @@ -1,4 +1,3 @@ -/* eslint-disable no-console */ const expect = chai.expect; import { toUnsigned32bit, toSigned32bit } from '../core/util/int.js'; diff --git a/tests/test.keyboard.js b/tests/test.keyboard.js index 0d8cac60..efc84c30 100644 --- a/tests/test.keyboard.js +++ b/tests/test.keyboard.js @@ -14,6 +14,10 @@ describe('Key Event Handling', function () { } e.stopPropagation = sinon.spy(); e.preventDefault = sinon.spy(); + e.getModifierState = function (key) { + return e[key]; + }; + return e; } @@ -310,6 +314,50 @@ describe('Key Event Handling', function () { }); }); + describe('Modifier status info', function () { + let origNavigator; + beforeEach(function () { + // window.navigator is a protected read-only property in many + // environments, so we need to redefine it whilst running these + // tests. + origNavigator = Object.getOwnPropertyDescriptor(window, "navigator"); + + Object.defineProperty(window, "navigator", {value: {}}); + }); + + afterEach(function () { + Object.defineProperty(window, "navigator", origNavigator); + }); + + it('should provide caps lock state', function () { + const kbd = new Keyboard(document); + kbd.onkeyevent = sinon.spy(); + kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'A', NumLock: false, CapsLock: true})); + + expect(kbd.onkeyevent).to.have.been.calledOnce; + expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0x41, "KeyA", true, false, true); + }); + + it('should provide num lock state', function () { + const kbd = new Keyboard(document); + kbd.onkeyevent = sinon.spy(); + kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'A', NumLock: true, CapsLock: false})); + + expect(kbd.onkeyevent).to.have.been.calledOnce; + expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0x41, "KeyA", true, true, false); + }); + + it('should have no num lock state on mac', function () { + window.navigator.platform = "Mac"; + const kbd = new Keyboard(document); + kbd.onkeyevent = sinon.spy(); + kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'A', NumLock: false, CapsLock: true})); + + expect(kbd.onkeyevent).to.have.been.calledOnce; + expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0x41, "KeyA", true, null, true); + }); + }); + describe('Japanese IM keys on Windows', function () { let origNavigator; beforeEach(function () { diff --git a/tests/test.rfb.js b/tests/test.rfb.js index bf12a460..62b80ca3 100644 --- a/tests/test.rfb.js +++ b/tests/test.rfb.js @@ -3,7 +3,7 @@ const expect = chai.expect; import RFB from '../core/rfb.js'; import Websock from '../core/websock.js'; import ZStream from "../vendor/pako/lib/zlib/zstream.js"; -import { deflateInit, deflate } from "../vendor/pako/lib/zlib/deflate.js"; +import { deflateInit, deflate, Z_DEFAULT_COMPRESSION } from "../vendor/pako/lib/zlib/deflate.js"; import { encodings } from '../core/encodings.js'; import { toUnsigned32bit } from '../core/util/int.js'; import { encodeUTF8 } from '../core/util/strings.js'; @@ -54,7 +54,7 @@ function deflateWithSize(data) { let strm = new ZStream(); let chunkSize = 1024 * 10 * 10; strm.output = new Uint8Array(chunkSize); - deflateInit(strm, 5); + deflateInit(strm, Z_DEFAULT_COMPRESSION); /* eslint-disable camelcase */ strm.input = unCompData; @@ -1257,6 +1257,14 @@ describe('Remote Frame Buffer Protocol Client', function () { client._sock._websocket._receiveData(new Uint8Array([0, 0, 0, 1])); expect(client._rfbInitState).to.equal('ServerInitialisation'); }); + + it('should transition straight to ServerInitialisation on "no auth" for versions < 3.8', function () { + sendVer('003.007\n', client); + client._sock._websocket._getSentData(); + + sendSecurity(1, client); + expect(client._rfbInitState).to.equal('ServerInitialisation'); + }); }); describe('Authentication', function () { @@ -2230,16 +2238,36 @@ describe('Remote Frame Buffer Protocol Client', function () { }); describe('Legacy SecurityResult', function () { - beforeEach(function () { - sendVer('003.007\n', client); - client._sock._websocket._getSentData(); - sendSecurity(1, client); - client._sock._websocket._getSentData(); - }); - - it('should not include reason in securityfailure event', function () { + it('should not include reason in securityfailure event for versions < 3.7', function () { + client.addEventListener("credentialsrequired", () => { + client.sendCredentials({ password: 'passwd' }); + }); const spy = sinon.spy(); client.addEventListener("securityfailure", spy); + sendVer('003.006\n', client); + client._sock._websocket._receiveData(new Uint8Array([0, 0, 0, 2])); + const challenge = []; + for (let i = 0; i < 16; i++) { challenge[i] = i; } + client._sock._websocket._receiveData(new Uint8Array(challenge)); + + client._sock._websocket._receiveData(new Uint8Array([0, 0, 0, 2])); + expect(spy).to.have.been.calledOnce; + expect(spy.args[0][0].detail.status).to.equal(2); + expect('reason' in spy.args[0][0].detail).to.be.false; + }); + + it('should not include reason in securityfailure event for versions < 3.8', function () { + client.addEventListener("credentialsrequired", () => { + client.sendCredentials({ password: 'passwd' }); + }); + const spy = sinon.spy(); + client.addEventListener("securityfailure", spy); + sendVer('003.007\n', client); + sendSecurity(2, client); + const challenge = []; + for (let i = 0; i < 16; i++) { challenge[i] = i; } + client._sock._websocket._receiveData(new Uint8Array(challenge)); + client._sock._websocket._receiveData(new Uint8Array([0, 0, 0, 2])); expect(spy).to.have.been.calledOnce; expect(spy.args[0][0].detail.status).to.equal(2); @@ -2979,6 +3007,149 @@ describe('Remote Frame Buffer Protocol Client', function () { expect(spy.args[0][0].detail.name).to.equal('som€ nam€'); }); }); + + describe('Caps Lock and Num Lock remote fixup', function () { + function sendLedStateUpdate(state) { + let data = []; + push8(data, state); + sendFbuMsg([{ x: 0, y: 0, width: 0, height: 0, encoding: -261 }], [data], client); + } + + let client; + beforeEach(function () { + client = makeRFB(); + sinon.stub(client, 'sendKey'); + }); + + it('should toggle caps lock if remote caps lock is on and local is off', function () { + sendLedStateUpdate(0b100); + client._handleKeyEvent(0x61, 'KeyA', true, null, false); + + expect(client.sendKey).to.have.been.calledThrice; + expect(client.sendKey.firstCall).to.have.been.calledWith(0xFFE5, "CapsLock", true); + expect(client.sendKey.secondCall).to.have.been.calledWith(0xFFE5, "CapsLock", false); + expect(client.sendKey.thirdCall).to.have.been.calledWith(0x61, "KeyA", true); + }); + + it('should toggle caps lock if remote caps lock is off and local is on', function () { + sendLedStateUpdate(0b011); + client._handleKeyEvent(0x41, 'KeyA', true, null, true); + + expect(client.sendKey).to.have.been.calledThrice; + expect(client.sendKey.firstCall).to.have.been.calledWith(0xFFE5, "CapsLock", true); + expect(client.sendKey.secondCall).to.have.been.calledWith(0xFFE5, "CapsLock", false); + expect(client.sendKey.thirdCall).to.have.been.calledWith(0x41, "KeyA", true); + }); + + it('should not toggle caps lock if remote caps lock is on and local is on', function () { + sendLedStateUpdate(0b100); + client._handleKeyEvent(0x41, 'KeyA', true, null, true); + + expect(client.sendKey).to.have.been.calledOnce; + expect(client.sendKey.firstCall).to.have.been.calledWith(0x41, "KeyA", true); + }); + + it('should not toggle caps lock if remote caps lock is off and local is off', function () { + sendLedStateUpdate(0b011); + client._handleKeyEvent(0x61, 'KeyA', true, null, false); + + expect(client.sendKey).to.have.been.calledOnce; + expect(client.sendKey.firstCall).to.have.been.calledWith(0x61, "KeyA", true); + }); + + it('should not toggle caps lock if the key is caps lock', function () { + sendLedStateUpdate(0b011); + client._handleKeyEvent(0xFFE5, 'CapsLock', true, null, true); + + expect(client.sendKey).to.have.been.calledOnce; + expect(client.sendKey.firstCall).to.have.been.calledWith(0xFFE5, "CapsLock", true); + }); + + it('should toggle caps lock only once', function () { + sendLedStateUpdate(0b100); + client._handleKeyEvent(0x61, 'KeyA', true, null, false); + client._handleKeyEvent(0x61, 'KeyA', true, null, false); + + expect(client.sendKey).to.have.callCount(4); + expect(client.sendKey.firstCall).to.have.been.calledWith(0xFFE5, "CapsLock", true); + expect(client.sendKey.secondCall).to.have.been.calledWith(0xFFE5, "CapsLock", false); + expect(client.sendKey.thirdCall).to.have.been.calledWith(0x61, "KeyA", true); + expect(client.sendKey.lastCall).to.have.been.calledWith(0x61, "KeyA", true); + }); + + it('should retain remote caps lock state on capslock key up', function () { + sendLedStateUpdate(0b100); + client._handleKeyEvent(0xFFE5, 'CapsLock', false, null, true); + + expect(client.sendKey).to.have.been.calledOnce; + expect(client.sendKey.firstCall).to.have.been.calledWith(0xFFE5, "CapsLock", false); + expect(client._remoteCapsLock).to.equal(true); + }); + + it('should toggle num lock if remote num lock is on and local is off', function () { + sendLedStateUpdate(0b010); + client._handleKeyEvent(0xFF9C, 'NumPad1', true, false, null); + + expect(client.sendKey).to.have.been.calledThrice; + expect(client.sendKey.firstCall).to.have.been.calledWith(0xFF7F, "NumLock", true); + expect(client.sendKey.secondCall).to.have.been.calledWith(0xFF7F, "NumLock", false); + expect(client.sendKey.thirdCall).to.have.been.calledWith(0xFF9C, "NumPad1", true); + }); + + it('should toggle num lock if remote num lock is off and local is on', function () { + sendLedStateUpdate(0b101); + client._handleKeyEvent(0xFFB1, 'NumPad1', true, true, null); + + expect(client.sendKey).to.have.been.calledThrice; + expect(client.sendKey.firstCall).to.have.been.calledWith(0xFF7F, "NumLock", true); + expect(client.sendKey.secondCall).to.have.been.calledWith(0xFF7F, "NumLock", false); + expect(client.sendKey.thirdCall).to.have.been.calledWith(0xFFB1, "NumPad1", true); + }); + + it('should not toggle num lock if remote num lock is on and local is on', function () { + sendLedStateUpdate(0b010); + client._handleKeyEvent(0xFFB1, 'NumPad1', true, true, null); + + expect(client.sendKey).to.have.been.calledOnce; + expect(client.sendKey.firstCall).to.have.been.calledWith(0xFFB1, "NumPad1", true); + }); + + it('should not toggle num lock if remote num lock is off and local is off', function () { + sendLedStateUpdate(0b101); + client._handleKeyEvent(0xFF9C, 'NumPad1', true, false, null); + + expect(client.sendKey).to.have.been.calledOnce; + expect(client.sendKey.firstCall).to.have.been.calledWith(0xFF9C, "NumPad1", true); + }); + + it('should not toggle num lock if the key is num lock', function () { + sendLedStateUpdate(0b101); + client._handleKeyEvent(0xFF7F, 'NumLock', true, true, null); + + expect(client.sendKey).to.have.been.calledOnce; + expect(client.sendKey.firstCall).to.have.been.calledWith(0xFF7F, "NumLock", true); + }); + + it('should not toggle num lock if local state is unknown', function () { + sendLedStateUpdate(0b010); + client._handleKeyEvent(0xFFB1, 'NumPad1', true, null, null); + + expect(client.sendKey).to.have.been.calledOnce; + expect(client.sendKey.firstCall).to.have.been.calledWith(0xFFB1, "NumPad1", true); + }); + + it('should toggle num lock only once', function () { + sendLedStateUpdate(0b010); + client._handleKeyEvent(0xFF9C, 'NumPad1', true, false, null); + client._handleKeyEvent(0xFF9C, 'NumPad1', true, false, null); + + expect(client.sendKey).to.have.callCount(4); + expect(client.sendKey.firstCall).to.have.been.calledWith(0xFF7F, "NumLock", true); + expect(client.sendKey.secondCall).to.have.been.calledWith(0xFF7F, "NumLock", false); + expect(client.sendKey.thirdCall).to.have.been.calledWith(0xFF9C, "NumPad1", true); + expect(client.sendKey.lastCall).to.have.been.calledWith(0xFF9C, "NumPad1", true); + }); + }); }); describe('XVP Message Handling', function () { @@ -3092,11 +3263,11 @@ describe('Remote Frame Buffer Protocol Client', function () { }); it('should update clipboard with correct escape characters from a Provide message ', function () { - let expectedData = "Oh\nmy!"; + let expectedData = "Oh\nmy\n!"; let data = [3, 0, 0, 0]; const flags = [0x10, 0x00, 0x00, 0x01]; - let text = encodeUTF8("Oh\r\nmy!\0"); + let text = encodeUTF8("Oh\r\nmy\r\n!\0"); let deflatedText = deflateWithSize(text); diff --git a/tests/test.tight.js b/tests/test.tight.js index b3457a88..141d7b6e 100644 --- a/tests/test.tight.js +++ b/tests/test.tight.js @@ -228,12 +228,59 @@ describe('Tight Decoder', function () { expect(display).to.have.displayed(targetData); }); - it.skip('should handle uncompressed gradient rects', function () { - // Not implemented yet + it('should handle uncompressed gradient rects', function () { + let done; + let blueData = [ 0x40, 0x02, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00 ]; + let greenData = [ 0x40, 0x02, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00 ]; + + done = testDecodeRect(decoder, 0, 0, 2, 1, blueData, display, 24); + expect(done).to.be.true; + done = testDecodeRect(decoder, 0, 1, 2, 1, blueData, display, 24); + expect(done).to.be.true; + done = testDecodeRect(decoder, 2, 0, 2, 1, greenData, display, 24); + expect(done).to.be.true; + done = testDecodeRect(decoder, 2, 1, 2, 1, greenData, display, 24); + expect(done).to.be.true; + done = testDecodeRect(decoder, 0, 2, 2, 1, greenData, display, 24); + expect(done).to.be.true; + done = testDecodeRect(decoder, 0, 3, 2, 1, greenData, display, 24); + expect(done).to.be.true; + done = testDecodeRect(decoder, 2, 2, 2, 1, blueData, display, 24); + expect(done).to.be.true; + done = testDecodeRect(decoder, 2, 3, 2, 1, blueData, display, 24); + expect(done).to.be.true; + + let targetData = new Uint8Array([ + 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, + 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, + 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, + 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255 + ]); + + expect(display).to.have.displayed(targetData); }); - it.skip('should handle compressed gradient rects', function () { - // Not implemented yet + it('should handle compressed gradient rects', function () { + let data = [ + // Control byte + 0x40, 0x02, + // Pixels (compressed) + 0x18, + 0x78, 0x9c, 0x62, 0x60, 0xf8, 0xcf, 0x00, 0x04, + 0xff, 0x19, 0x19, 0xd0, 0x00, 0x44, 0x84, 0xf1, + 0x3f, 0x9a, 0x30, 0x00, 0x00, 0x00, 0xff, 0xff ]; + + let done = testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24); + + let targetData = new Uint8Array([ + 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, + 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, + 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, + 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255 + ]); + + expect(done).to.be.true; + expect(display).to.have.displayed(targetData); }); it('should handle empty copy rects', function () { @@ -275,6 +322,25 @@ describe('Tight Decoder', function () { expect(display).to.have.displayed(targetData); }); + it('should handle empty gradient rects', function () { + display.fillRect(0, 0, 4, 4, [ 0x00, 0x00, 0xff ]); + display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]); + display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]); + + let done = testDecodeRect(decoder, 1, 2, 0, 0, + [ 0x40, 0x02 ], display, 24); + + let targetData = new Uint8Array([ + 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, + 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, + 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, + 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255 + ]); + + expect(done).to.be.true; + expect(display).to.have.displayed(targetData); + }); + it('should handle empty fill rects', function () { display.fillRect(0, 0, 4, 4, [ 0x00, 0x00, 0xff ]); display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]); diff --git a/utils/.eslintrc b/utils/.eslintrc deleted file mode 100644 index b7dc129f..00000000 --- a/utils/.eslintrc +++ /dev/null @@ -1,8 +0,0 @@ -{ - "env": { - "node": true - }, - "rules": { - "no-console": 0 - } -} \ No newline at end of file diff --git a/utils/convert.js b/utils/convert.js index aeba49d9..617f4ed6 100755 --- a/utils/convert.js +++ b/utils/convert.js @@ -1,7 +1,7 @@ #!/usr/bin/env node const path = require('path'); -const program = require('commander'); +const { program } = require('commander'); const fs = require('fs'); const fse = require('fs-extra'); const babel = require('@babel/core'); diff --git a/utils/novnc_proxy b/utils/novnc_proxy index ea3ea706..4b2e3032 100755 --- a/utils/novnc_proxy +++ b/utils/novnc_proxy @@ -8,13 +8,13 @@ usage() { echo "$*" echo fi - echo "Usage: ${NAME} [--listen PORT] [--vnc VNC_HOST:PORT] [--cert CERT] [--ssl-only]" + echo "Usage: ${NAME} [--listen [HOST:]PORT] [--vnc VNC_HOST:PORT] [--cert CERT] [--ssl-only]" echo echo "Starts the WebSockets proxy and a mini-webserver and " echo "provides a cut-and-paste URL to go to." echo - echo " --listen PORT Port for proxy/webserver to listen on" - echo " Default: 6080" + echo " --listen [HOST:]PORT Port for proxy/webserver to listen on" + echo " Default: 6080 (on all interfaces)" echo " --vnc VNC_HOST:PORT VNC server host:port proxy target" echo " Default: localhost:5900" echo " --cert CERT Path to combined cert/key file, or just" @@ -47,14 +47,16 @@ usage() { NAME="$(basename $0)" REAL_NAME="$(readlink -f $0)" HERE="$(cd "$(dirname "$REAL_NAME")" && pwd)" +HOST="" PORT="6080" +LISTEN="$PORT" VNC_DEST="localhost:5900" CERT="" KEY="" WEB="" proxy_pid="" SSLONLY="" -RECORD_ARG="" +RECORD="" SYSLOG_ARG="" HEARTBEAT_ARG="" IDLETIMEOUT_ARG="" @@ -86,14 +88,14 @@ cleanup() { while [ "$*" ]; do param=$1; shift; OPTARG=$1 case $param in - --listen) PORT="${OPTARG}"; shift ;; + --listen) LISTEN="${OPTARG}"; shift ;; --vnc) VNC_DEST="${OPTARG}"; shift ;; --cert) CERT="${OPTARG}"; shift ;; --key) KEY="${OPTARG}"; shift ;; --web) WEB="${OPTARG}"; shift ;; --ssl-only) SSLONLY="--ssl-only" ;; --file-only) FILEONLY_ARG="--file-only" ;; - --record) RECORD_ARG="--record ${OPTARG}"; shift ;; + --record) RECORD="${OPTARG}"; shift ;; --syslog) SYSLOG_ARG="--syslog ${OPTARG}"; shift ;; --heartbeat) HEARTBEAT_ARG="--heartbeat ${OPTARG}"; shift ;; --idle-timeout) IDLETIMEOUT_ARG="--idle-timeout ${OPTARG}"; shift ;; @@ -107,14 +109,23 @@ while [ "$*" ]; do esac done +if [ "$LISTEN" != "$PORT" ]; then + HOST=${LISTEN%:*} + PORT=${LISTEN##*:} + # if no host was given, restore + [ "$HOST" = "$PORT" ] && HOST="" +fi + # Sanity checks -if bash -c "exec 7<>/dev/tcp/localhost/${PORT}" &> /dev/null; then - exec 7<&- - exec 7>&- - die "Port ${PORT} in use. Try --listen PORT" -else - exec 7<&- - exec 7>&- +if [ -z "${HOST}" ]; then + if bash -c "exec 7<>/dev/tcp/localhost/${PORT}" &> /dev/null; then + exec 7<&- + exec 7>&- + die "Port ${PORT} in use. Try --listen PORT" + else + exec 7<&- + exec 7>&- + fi fi trap "cleanup" TERM QUIT INT EXIT @@ -191,9 +202,14 @@ else fi fi -echo "Starting webserver and WebSockets proxy on port ${PORT}" -#${HERE}/websockify --web ${WEB} ${CERT:+--cert ${CERT}} ${PORT} ${VNC_DEST} & -${WEBSOCKIFY} ${SYSLOG_ARG} ${SSLONLY} ${FILEONLY_ARG} --web ${WEB} ${CERT:+--cert ${CERT}} ${KEY:+--key ${KEY}} ${PORT} ${VNC_DEST} ${HEARTBEAT_ARG} ${IDLETIMEOUT_ARG} ${RECORD_ARG} ${TIMEOUT_ARG} ${WEBAUTH_ARG} ${AUTHPLUGIN_ARG} ${AUTHSOURCE_ARG} & +# Make all file paths absolute as websockify changes working directory +WEB=`realpath "${WEB}"` +[ -n "${CERT}" ] && CERT=`realpath "${CERT}"` +[ -n "${KEY}" ] && KEY=`realpath "${KEY}"` +[ -n "${RECORD}" ] && RECORD=`realpath "${RECORD}"` + +echo "Starting webserver and WebSockets proxy on${HOST:+ host ${HOST}} port ${PORT}" +${WEBSOCKIFY} ${SYSLOG_ARG} ${SSLONLY} ${FILEONLY_ARG} --web ${WEB} ${CERT:+--cert ${CERT}} ${KEY:+--key ${KEY}} ${LISTEN} ${VNC_DEST} ${HEARTBEAT_ARG} ${IDLETIMEOUT_ARG} ${RECORD:+--record ${RECORD}} ${TIMEOUT_ARG} ${WEBAUTH_ARG} ${AUTHPLUGIN_ARG} ${AUTHSOURCE_ARG} & proxy_pid="$!" sleep 1 if [ -z "$proxy_pid" ] || ! ps -eo pid= | grep -w "$proxy_pid" > /dev/null; then @@ -202,11 +218,15 @@ if [ -z "$proxy_pid" ] || ! ps -eo pid= | grep -w "$proxy_pid" > /dev/null; then exit 1 fi +if [ -z "$HOST" ]; then + HOST=$(hostname) +fi + echo -e "\n\nNavigate to this URL:\n" if [ "x$SSLONLY" == "x" ]; then - echo -e " http://$(hostname):${PORT}/vnc.html?host=$(hostname)&port=${PORT}\n" + echo -e " http://${HOST}:${PORT}/vnc.html?host=${HOST}&port=${PORT}\n" else - echo -e " https://$(hostname):${PORT}/vnc.html?host=$(hostname)&port=${PORT}\n" + echo -e " https://${HOST}:${PORT}/vnc.html?host=${HOST}&port=${PORT}\n" fi echo -e "Press Ctrl-C to exit\n\n"