Merge remote-tracking branch 'remotes/base/master'

This commit is contained in:
mloginov 2024-07-03 11:50:18 +01:00
commit d109185fa2
45 changed files with 1046 additions and 502 deletions

View File

@ -1 +0,0 @@
**/xtscancodes.js

View File

@ -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_"] }],
}
}

View File

@ -10,18 +10,18 @@ jobs:
npm: npm:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- run: | - run: |
GITREV=$(git rev-parse --short HEAD) GITREV=$(git rev-parse --short HEAD)
echo $GITREV echo $GITREV
sed -i "s/^\(.*\"version\".*\)\"\([^\"]\+\)\"\(.*\)\$/\1\"\2-g$GITREV\"\3/" package.json sed -i "s/^\(.*\"version\".*\)\"\([^\"]\+\)\"\(.*\)\$/\1\"\2-g$GITREV\"\3/" package.json
if: github.event_name != 'release' if: github.event_name != 'release'
- uses: actions/setup-node@v3 - uses: actions/setup-node@v4
with: with:
# Needs to be explicitly specified for auth to work # Needs to be explicitly specified for auth to work
registry-url: 'https://registry.npmjs.org' registry-url: 'https://registry.npmjs.org'
- run: npm install - run: npm install
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v4
with: with:
name: npm name: npm
path: lib path: lib
@ -49,7 +49,7 @@ jobs:
snap: snap:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- run: | - run: |
GITREV=$(git rev-parse --short HEAD) GITREV=$(git rev-parse --short HEAD)
echo $GITREV echo $GITREV
@ -61,7 +61,7 @@ jobs:
sed -i "s/^version:.*/version: '$VERSION'/" snap/snapcraft.yaml sed -i "s/^version:.*/version: '$VERSION'/" snap/snapcraft.yaml
- uses: snapcore/action-build@v1 - uses: snapcore/action-build@v1
id: snapcraft id: snapcraft
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v4
with: with:
name: snap name: snap
path: ${{ steps.snapcraft.outputs.snap }} path: ${{ steps.snapcraft.outputs.snap }}

View File

@ -6,14 +6,14 @@ jobs:
eslint: eslint:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: actions/setup-node@v3 - uses: actions/setup-node@v4
- run: npm update - run: npm update
- run: npm run lint - run: npm run lint
html: html:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: actions/setup-node@v3 - uses: actions/setup-node@v4
- run: npm update - run: npm update
- run: git ls-tree --name-only -r HEAD | grep -E "[.](html|css)$" | xargs ./utils/validate - run: git ls-tree --name-only -r HEAD | grep -E "[.](html|css)$" | xargs ./utils/validate

View File

@ -20,8 +20,8 @@ jobs:
fail-fast: false fail-fast: false
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: actions/setup-node@v3 - uses: actions/setup-node@v4
- run: npm update - run: npm update
- run: npm run test - run: npm run test
env: env:

View File

@ -6,8 +6,8 @@ jobs:
translate: translate:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: actions/setup-node@v3 - uses: actions/setup-node@v4
- run: npm update - run: npm update
- run: sudo apt-get install gettext - run: sudo apt-get install gettext
- run: make -C po update-pot - run: make -C po update-pot

View File

@ -32,8 +32,6 @@ for a more complete list with additional info and links.
### News/help/contact ### News/help/contact
The project website is found at [novnc.com](http://novnc.com). 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 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). [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/), [Electronic Frontier Foundation](https://www.eff.org/),
[Against Malaria Foundation](http://www.againstmalaria.com/), [Against Malaria Foundation](http://www.againstmalaria.com/),
[Nothing But Nets](http://www.nothingbutnets.net/), etc. [Nothing But Nets](http://www.nothingbutnets.net/), etc.
Please tweet [@noVNC](http://www.twitter.com/noVNC) if you do.
### Features ### Features

View File

@ -1,4 +1,5 @@
{ {
"HTTPS is required for full functionality": "Το HTTPS είναι απαιτούμενο για πλήρη λειτουργικότητα",
"Connecting...": "Συνδέεται...", "Connecting...": "Συνδέεται...",
"Disconnecting...": "Aποσυνδέεται...", "Disconnecting...": "Aποσυνδέεται...",
"Reconnecting...": "Επανασυνδέεται...", "Reconnecting...": "Επανασυνδέεται...",
@ -7,19 +8,15 @@
"Connected (encrypted) to ": "Συνδέθηκε (κρυπτογραφημένα) με το ", "Connected (encrypted) to ": "Συνδέθηκε (κρυπτογραφημένα) με το ",
"Connected (unencrypted) to ": "Συνδέθηκε (μη κρυπτογραφημένα) με το ", "Connected (unencrypted) to ": "Συνδέθηκε (μη κρυπτογραφημένα) με το ",
"Something went wrong, connection is closed": "Κάτι πήγε στραβά, η σύνδεση διακόπηκε", "Something went wrong, connection is closed": "Κάτι πήγε στραβά, η σύνδεση διακόπηκε",
"Failed to connect to server": "Αποτυχία στη σύνδεση με το διακομιστή",
"Disconnected": "Αποσυνδέθηκε", "Disconnected": "Αποσυνδέθηκε",
"New connection has been rejected with reason: ": "Η νέα σύνδεση απορρίφθηκε διότι: ", "New connection has been rejected with reason: ": "Η νέα σύνδεση απορρίφθηκε διότι: ",
"New connection has been rejected": "Η νέα σύνδεση απορρίφθηκε ", "New connection has been rejected": "Η νέα σύνδεση απορρίφθηκε ",
"Password is required": "Απαιτείται ο κωδικός πρόσβασης", "Credentials are required": "Απαιτούνται διαπιστευτήρια",
"noVNC encountered an error:": "το noVNC αντιμετώπισε ένα σφάλμα:", "noVNC encountered an error:": "το noVNC αντιμετώπισε ένα σφάλμα:",
"Hide/Show the control bar": "Απόκρυψη/Εμφάνιση γραμμής ελέγχου", "Hide/Show the control bar": "Απόκρυψη/Εμφάνιση γραμμής ελέγχου",
"Drag": "Σύρσιμο",
"Move/Drag Viewport": "Μετακίνηση/Σύρσιμο Θεατού πεδίου", "Move/Drag Viewport": "Μετακίνηση/Σύρσιμο Θεατού πεδίου",
"viewport drag": "σύρσιμο θεατού πεδίου",
"Active Mouse Button": "Ενεργό Πλήκτρο Ποντικιού",
"No mousebutton": "Χωρίς Πλήκτρο Ποντικιού",
"Left mousebutton": "Αριστερό Πλήκτρο Ποντικιού",
"Middle mousebutton": "Μεσαίο Πλήκτρο Ποντικιού",
"Right mousebutton": "Δεξί Πλήκτρο Ποντικιού",
"Keyboard": "Πληκτρολόγιο", "Keyboard": "Πληκτρολόγιο",
"Show Keyboard": "Εμφάνιση Πληκτρολογίου", "Show Keyboard": "Εμφάνιση Πληκτρολογίου",
"Extra keys": "Επιπλέον πλήκτρα", "Extra keys": "Επιπλέον πλήκτρα",
@ -28,6 +25,8 @@
"Toggle Ctrl": "Εναλλαγή Ctrl", "Toggle Ctrl": "Εναλλαγή Ctrl",
"Alt": "Alt", "Alt": "Alt",
"Toggle Alt": "Εναλλαγή Alt", "Toggle Alt": "Εναλλαγή Alt",
"Toggle Windows": "Εναλλαγή Παράθυρων",
"Windows": "Παράθυρα",
"Send Tab": "Αποστολή Tab", "Send Tab": "Αποστολή Tab",
"Tab": "Tab", "Tab": "Tab",
"Esc": "Esc", "Esc": "Esc",
@ -41,8 +40,7 @@
"Reboot": "Επανεκκίνηση", "Reboot": "Επανεκκίνηση",
"Reset": "Επαναφορά", "Reset": "Επαναφορά",
"Clipboard": "Πρόχειρο", "Clipboard": "Πρόχειρο",
"Clear": "Καθάρισμα", "Edit clipboard content in the textarea below.": "Επεξεργαστείτε το περιεχόμενο του πρόχειρου στην περιοχή κειμένου παρακάτω.",
"Fullscreen": "Πλήρης Οθόνη",
"Settings": "Ρυθμίσεις", "Settings": "Ρυθμίσεις",
"Shared Mode": "Κοινόχρηστη Λειτουργία", "Shared Mode": "Κοινόχρηστη Λειτουργία",
"View Only": "Μόνο Θέαση", "View Only": "Μόνο Θέαση",
@ -52,6 +50,8 @@
"Local Scaling": "Τοπική Κλιμάκωση", "Local Scaling": "Τοπική Κλιμάκωση",
"Remote Resizing": "Απομακρυσμένη Αλλαγή μεγέθους", "Remote Resizing": "Απομακρυσμένη Αλλαγή μεγέθους",
"Advanced": "Για προχωρημένους", "Advanced": "Για προχωρημένους",
"Quality:": "Ποιότητα:",
"Compression level:": "Επίπεδο συμπίεσης:",
"Repeater ID:": "Repeater ID:", "Repeater ID:": "Repeater ID:",
"WebSocket": "WebSocket", "WebSocket": "WebSocket",
"Encrypt": "Κρυπτογράφηση", "Encrypt": "Κρυπτογράφηση",
@ -60,10 +60,20 @@
"Path:": "Διαδρομή:", "Path:": "Διαδρομή:",
"Automatic Reconnect": "Αυτόματη επανασύνδεση", "Automatic Reconnect": "Αυτόματη επανασύνδεση",
"Reconnect Delay (ms):": "Καθυστέρηση επανασύνδεσης (ms):", "Reconnect Delay (ms):": "Καθυστέρηση επανασύνδεσης (ms):",
"Show Dot when No Cursor": "Εμφάνιση Τελείας όταν δεν υπάρχει Δρομέας",
"Logging:": "Καταγραφή:", "Logging:": "Καταγραφή:",
"Version:": "Έκδοση:",
"Disconnect": "Αποσύνδεση", "Disconnect": "Αποσύνδεση",
"Connect": "Σύνδεση", "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:": "Κωδικός Πρόσβασης:", "Password:": "Κωδικός Πρόσβασης:",
"Cancel": "Ακύρωση", "Send Credentials": "Αποστολή Διαπιστευτηρίων",
"Canvas not supported.": "Δεν υποστηρίζεται το στοιχείο Canvas" "Cancel": "Ακύρωση"
} }

View File

@ -1,5 +1,4 @@
{ {
"HTTPS is required for full functionality": "",
"Connecting...": "En cours de connexion...", "Connecting...": "En cours de connexion...",
"Disconnecting...": "Déconnexion en cours...", "Disconnecting...": "Déconnexion en cours...",
"Reconnecting...": "Reconnexion en cours...", "Reconnecting...": "Reconnexion en cours...",
@ -40,7 +39,8 @@
"Reboot": "Redémarrer", "Reboot": "Redémarrer",
"Reset": "Réinitialiser", "Reset": "Réinitialiser",
"Clipboard": "Presse-papiers", "Clipboard": "Presse-papiers",
"Edit clipboard content in the textarea below.": "", "Clear": "Effacer",
"Fullscreen": "Plein écran",
"Settings": "Paramètres", "Settings": "Paramètres",
"Shared Mode": "Mode partagé", "Shared Mode": "Mode partagé",
"View Only": "Afficher uniquement", "View Only": "Afficher uniquement",
@ -65,12 +65,6 @@
"Version:": "Version :", "Version:": "Version :",
"Disconnect": "Déconnecter", "Disconnect": "Déconnecter",
"Connect": "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 :", "Username:": "Nom d'utilisateur :",
"Password:": "Mot de passe :", "Password:": "Mot de passe :",
"Send Credentials": "Envoyer les identifiants", "Send Credentials": "Envoyer les identifiants",

View File

@ -14,8 +14,6 @@
"Credentials are required": "Le credenziali sono obbligatorie", "Credentials are required": "Le credenziali sono obbligatorie",
"noVNC encountered an error:": "noVNC ha riscontrato un errore:", "noVNC encountered an error:": "noVNC ha riscontrato un errore:",
"Hide/Show the control bar": "Nascondi/Mostra la barra di controllo", "Hide/Show the control bar": "Nascondi/Mostra la barra di controllo",
"Drag": "",
"Move/Drag Viewport": "",
"Keyboard": "Tastiera", "Keyboard": "Tastiera",
"Show Keyboard": "Mostra tastiera", "Show Keyboard": "Mostra tastiera",
"Extra keys": "Tasti Aggiuntivi", "Extra keys": "Tasti Aggiuntivi",
@ -44,7 +42,6 @@
"Settings": "Impostazioni", "Settings": "Impostazioni",
"Shared Mode": "Modalità condivisa", "Shared Mode": "Modalità condivisa",
"View Only": "Sola Visualizzazione", "View Only": "Sola Visualizzazione",
"Clip to Window": "",
"Scaling Mode:": "Modalità di ridimensionamento:", "Scaling Mode:": "Modalità di ridimensionamento:",
"None": "Nessuna", "None": "Nessuna",
"Local Scaling": "Ridimensionamento Locale", "Local Scaling": "Ridimensionamento Locale",
@ -61,7 +58,6 @@
"Automatic Reconnect": "Riconnessione Automatica", "Automatic Reconnect": "Riconnessione Automatica",
"Reconnect Delay (ms):": "Ritardo Riconnessione (ms):", "Reconnect Delay (ms):": "Ritardo Riconnessione (ms):",
"Show Dot when No Cursor": "Mostra Punto quando Nessun Cursore", "Show Dot when No Cursor": "Mostra Punto quando Nessun Cursore",
"Logging:": "",
"Version:": "Versione:", "Version:": "Versione:",
"Disconnect": "Disconnetti", "Disconnect": "Disconnetti",
"Connect": "Connetti", "Connect": "Connetti",

View File

@ -1,4 +1,5 @@
{ {
"HTTPS is required for full functionality": "すべての機能を使用するにはHTTPS接続が必要です",
"Connecting...": "接続しています...", "Connecting...": "接続しています...",
"Disconnecting...": "切断しています...", "Disconnecting...": "切断しています...",
"Reconnecting...": "再接続しています...", "Reconnecting...": "再接続しています...",
@ -21,10 +22,10 @@
"Extra keys": "追加キー", "Extra keys": "追加キー",
"Show Extra Keys": "追加キーを表示", "Show Extra Keys": "追加キーを表示",
"Ctrl": "Ctrl", "Ctrl": "Ctrl",
"Toggle Ctrl": "Ctrl キーを切り替え", "Toggle Ctrl": "Ctrl キーをトグル",
"Alt": "Alt", "Alt": "Alt",
"Toggle Alt": "Alt キーを切り替え", "Toggle Alt": "Alt キーをトグル",
"Toggle Windows": "Windows キーを切り替え", "Toggle Windows": "Windows キーをトグル",
"Windows": "Windows", "Windows": "Windows",
"Send Tab": "Tab キーを送信", "Send Tab": "Tab キーを送信",
"Tab": "Tab", "Tab": "Tab",
@ -39,11 +40,11 @@
"Reboot": "再起動", "Reboot": "再起動",
"Reset": "リセット", "Reset": "リセット",
"Clipboard": "クリップボード", "Clipboard": "クリップボード",
"Clear": "クリア", "Edit clipboard content in the textarea below.": "以下の入力欄からクリップボードの内容を編集できます。",
"Fullscreen": "全画面表示", "Full Screen": "全画面表示",
"Settings": "設定", "Settings": "設定",
"Shared Mode": "共有モード", "Shared Mode": "共有モード",
"View Only": "表示のみ", "View Only": "表示専用",
"Clip to Window": "ウィンドウにクリップ", "Clip to Window": "ウィンドウにクリップ",
"Scaling Mode:": "スケーリングモード:", "Scaling Mode:": "スケーリングモード:",
"None": "なし", "None": "なし",
@ -60,11 +61,18 @@
"Path:": "パス:", "Path:": "パス:",
"Automatic Reconnect": "自動再接続", "Automatic Reconnect": "自動再接続",
"Reconnect Delay (ms):": "再接続する遅延 (ミリ秒):", "Reconnect Delay (ms):": "再接続する遅延 (ミリ秒):",
"Show Dot when No Cursor": "カーソルがないときにドットを表示", "Show Dot when No Cursor": "カーソルがないときにドットを表示する",
"Logging:": "ロギング:", "Logging:": "ロギング:",
"Version:": "バージョン:", "Version:": "バージョン:",
"Disconnect": "切断", "Disconnect": "切断",
"Connect": "接続", "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:": "ユーザー名:", "Username:": "ユーザー名:",
"Password:": "パスワード:", "Password:": "パスワード:",
"Send Credentials": "資格情報を送信", "Send Credentials": "資格情報を送信",

View File

@ -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...", "Connecting...": "Ansluter...",
"Disconnecting...": "Kopplar ner...", "Disconnecting...": "Kopplar ner...",
"Reconnecting...": "Återansluter...", "Reconnecting...": "Återansluter...",
"Internal error": "Internt fel", "Internal error": "Internt fel",
"Must set host": "Du måste specifiera en värd", "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 (encrypted) to ": "Ansluten (krypterat) till ",
"Connected (unencrypted) to ": "Ansluten (okrypterat) till ", "Connected (unencrypted) to ": "Ansluten (okrypterat) till ",
"Something went wrong, connection is closed": "Något gick fel, anslutningen avslutades", "Something went wrong, connection is closed": "Något gick fel, anslutningen avslutades",

View File

@ -1,69 +1,69 @@
{ {
"Connecting...": "连接中...", "Connecting...": "连接中...",
"Connected (encrypted) to ": "已连接(已加密)到",
"Connected (unencrypted) to ": "已连接(未加密)到",
"Disconnecting...": "正在断开连接...", "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": "已断开连接", "Disconnected": "已断开连接",
"New connection has been rejected with reason: ": "连接被拒绝,原因:", "Must set host": "必须设置主机",
"New connection has been rejected": "连接被拒绝", "Reconnecting...": "重新连接中...",
"Password is required": "请提供密码", "Password is required": "请提供密码",
"Disconnect timeout": "超时断开",
"noVNC encountered an error:": "noVNC 遇到一个错误:", "noVNC encountered an error:": "noVNC 遇到一个错误:",
"Hide/Show the control bar": "显示/隐藏控制栏", "Hide/Show the control bar": "显示/隐藏控制栏",
"Move/Drag Viewport": "拖放显示范围", "Move/Drag Viewport": "移动/拖动窗口",
"viewport drag": "显示范围拖放", "viewport drag": "窗口拖动",
"Active Mouse Button": "启动鼠标按", "Active Mouse Button": "启动鼠标按",
"No mousebutton": "禁用鼠标按", "No mousebutton": "禁用鼠标按",
"Left mousebutton": "鼠标左", "Left mousebutton": "鼠标左",
"Middle mousebutton": "鼠标中", "Middle mousebutton": "鼠标中",
"Right mousebutton": "鼠标右", "Right mousebutton": "鼠标右",
"Keyboard": "键盘", "Keyboard": "键盘",
"Show Keyboard": "显示键盘", "Show Keyboard": "显示键盘",
"Extra keys": "额外按键", "Extra keys": "额外按键",
"Show Extra Keys": "显示额外按键", "Show Extra Keys": "显示额外按键",
"Ctrl": "Ctrl", "Ctrl": "Ctrl",
"Toggle Ctrl": "切换 Ctrl", "Toggle Ctrl": "切换 Ctrl",
"Edit clipboard content in the textarea below.": "在下面的文本区域中编辑剪贴板内容。",
"Alt": "Alt", "Alt": "Alt",
"Toggle Alt": "切换 Alt", "Toggle Alt": "切换 Alt",
"Send Tab": "发送 Tab 键", "Send Tab": "发送 Tab 键",
"Tab": "Tab", "Tab": "Tab",
"Esc": "Esc", "Esc": "Esc",
"Send Escape": "发送 Escape 键", "Send Escape": "发送 Escape 键",
"Ctrl+Alt+Del": "Ctrl-Alt-Del", "Ctrl+Alt+Del": "Ctrl+Alt+Del",
"Send Ctrl-Alt-Del": "发送 Ctrl-Alt-Del 键", "Send Ctrl-Alt-Del": "发送 Ctrl+Alt+Del 键",
"Shutdown/Reboot": "关机/重", "Shutdown/Reboot": "关机/重启",
"Shutdown/Reboot...": "关机/重...", "Shutdown/Reboot...": "关机/重启...",
"Power": "电源", "Power": "电源",
"Shutdown": "关机", "Shutdown": "关机",
"Reboot": "重", "Reboot": "重启",
"Reset": "重置", "Reset": "重置",
"Clipboard": "剪贴板", "Clipboard": "剪贴板",
"Clear": "清除", "Clear": "清除",
"Fullscreen": "全屏", "Fullscreen": "全屏",
"Settings": "设置", "Settings": "设置",
"Encrypt": "加密",
"Shared Mode": "分享模式", "Shared Mode": "分享模式",
"View Only": "仅查看", "View Only": "仅查看",
"Clip to Window": "限制/裁切窗口大小", "Clip to Window": "限制/裁切窗口大小",
"Scaling Mode:": "缩放模式:", "Scaling Mode:": "缩放模式:",
"None": "无", "None": "无",
"Local Scaling": "本地缩放", "Local Scaling": "本地缩放",
"Local Downscaling": "降低本地尺寸",
"Remote Resizing": "远程调整大小", "Remote Resizing": "远程调整大小",
"Advanced": "高级", "Advanced": "高级",
"Local Cursor": "本地光标",
"Repeater ID:": "中继站 ID", "Repeater ID:": "中继站 ID",
"WebSocket": "WebSocket", "WebSocket": "WebSocket",
"Encrypt": "加密",
"Host:": "主机:", "Host:": "主机:",
"Port:": "端口:", "Port:": "端口:",
"Path:": "路径:", "Path:": "路径:",
"Automatic Reconnect": "自动重新连接", "Automatic Reconnect": "自动重新连接",
"Reconnect Delay (ms):": "重新连接间隔 (ms)", "Reconnect Delay (ms):": "重新连接间隔 (ms)",
"Logging:": "日志级别:", "Logging:": "日志级别:",
"Disconnect": "断连接", "Disconnect": "连接",
"Connect": "连接", "Connect": "连接",
"Password:": "密码:", "Password:": "密码:",
"Cancel": "取消" "Cancel": "取消",
"Canvas not supported.": "不支持 Canvas。"
} }

View File

@ -67,7 +67,7 @@ const UI = {
// insecure context // insecure context
if (!window.isSecureContext) { if (!window.isSecureContext) {
// FIXME: This gets hidden when connecting // 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 // Try to fetch version number
@ -1097,10 +1097,18 @@ const UI = {
} }
url += '/' + path; url += '/' + path;
try {
UI.rfb = new RFB(document.getElementById('noVNC_container'), url, UI.rfb = new RFB(document.getElementById('noVNC_container'), url,
{ shared: UI.getSetting('shared'), { shared: UI.getSetting('shared'),
repeaterID: UI.getSetting('repeaterID'), repeaterID: UI.getSetting('repeaterID'),
credentials: { password: password } }); 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("connect", UI.connectFinished);
UI.rfb.addEventListener("disconnect", UI.disconnectFinished); UI.rfb.addEventListener("disconnect", UI.disconnectFinished);
UI.rfb.addEventListener("serververification", UI.serverVerify); UI.rfb.addEventListener("serververification", UI.serverVerify);

View File

@ -212,7 +212,7 @@ function localStorageGet(name) {
try { try {
r = localStorage.getItem(name); r = localStorage.getItem(name);
} catch (e) { } catch (e) {
if (e instanceof DOMException || !localStorage) { if (e instanceof DOMException) {
logOnce(cookiesMsg); logOnce(cookiesMsg);
logOnce("'localStorage.getItem(" + name + ")' failed: " + e, logOnce("'localStorage.getItem(" + name + ")' failed: " + e,
"debug"); "debug");

View File

@ -285,7 +285,73 @@ export default class TightDecoder {
} }
_gradientFilter(streamId, x, y, width, height, sock, display, depth) { _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) { _readData(sock) {

View File

@ -7,7 +7,7 @@
*/ */
import { deflateInit, deflate } from "../vendor/pako/lib/zlib/deflate.js"; 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"; import ZStream from "../vendor/pako/lib/zlib/zstream.js";
export default class Deflator { export default class Deflator {
@ -15,9 +15,8 @@ export default class Deflator {
this.strm = new ZStream(); this.strm = new ZStream();
this.chunkSize = 1024 * 10 * 10; this.chunkSize = 1024 * 10 * 10;
this.outputBuffer = new Uint8Array(this.chunkSize); this.outputBuffer = new Uint8Array(this.chunkSize);
this.windowBits = 5;
deflateInit(this.strm, this.windowBits); deflateInit(this.strm, Z_DEFAULT_COMPRESSION);
} }
deflate(inData) { deflate(inData) {

View File

@ -22,6 +22,7 @@ export const encodings = {
pseudoEncodingLastRect: -224, pseudoEncodingLastRect: -224,
pseudoEncodingCursor: -239, pseudoEncodingCursor: -239,
pseudoEncodingQEMUExtendedKeyEvent: -258, pseudoEncodingQEMUExtendedKeyEvent: -258,
pseudoEncodingQEMULedEvent: -261,
pseudoEncodingDesktopName: -307, pseudoEncodingDesktopName: -307,
pseudoEncodingExtendedDesktopSize: -308, pseudoEncodingExtendedDesktopSize: -308,
pseudoEncodingXvp: -309, pseudoEncodingXvp: -309,

View File

@ -14,9 +14,8 @@ export default class Inflate {
this.strm = new ZStream(); this.strm = new ZStream();
this.chunkSize = 1024 * 10 * 10; this.chunkSize = 1024 * 10 * 10;
this.strm.output = new Uint8Array(this.chunkSize); this.strm.output = new Uint8Array(this.chunkSize);
this.windowBits = 5;
inflateInit(this.strm, this.windowBits); inflateInit(this.strm);
} }
setInput(data) { setInput(data) {

View File

@ -36,7 +36,7 @@ export default class Keyboard {
// ===== PRIVATE METHODS ===== // ===== PRIVATE METHODS =====
_sendKeyEvent(keysym, code, down) { _sendKeyEvent(keysym, code, down, numlock = null, capslock = null) {
if (down) { if (down) {
this._keyDownList[code] = keysym; this._keyDownList[code] = keysym;
} else { } else {
@ -48,8 +48,9 @@ export default class Keyboard {
} }
Log.Debug("onkeyevent " + (down ? "down" : "up") + Log.Debug("onkeyevent " + (down ? "down" : "up") +
", keysym: " + keysym, ", code: " + code); ", keysym: " + keysym, ", code: " + code +
this.onkeyevent(keysym, code, down); ", numlock: " + numlock + ", capslock: " + capslock);
this.onkeyevent(keysym, code, down, numlock, capslock);
} }
_getKeyCode(e) { _getKeyCode(e) {
@ -86,6 +87,14 @@ export default class Keyboard {
_handleKeyDown(e) { _handleKeyDown(e) {
const code = this._getKeyCode(e); const code = this._getKeyCode(e);
let keysym = KeyboardUtil.getKeysym(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 // Windows doesn't have a proper AltGr, but handles it using
// fake Ctrl+Alt. However the remote end might not be Windows, // fake Ctrl+Alt. However the remote end might not be Windows,
@ -107,7 +116,7 @@ export default class Keyboard {
// key to "AltGraph". // key to "AltGraph".
keysym = KeyTable.XK_ISO_Level3_Shift; keysym = KeyTable.XK_ISO_Level3_Shift;
} else { } 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 // If it's a virtual keyboard then it should be
// sufficient to just send press and release right // sufficient to just send press and release right
// after each other // after each other
this._sendKeyEvent(keysym, code, true); this._sendKeyEvent(keysym, code, true, numlock, capslock);
this._sendKeyEvent(keysym, code, false); this._sendKeyEvent(keysym, code, false, numlock, capslock);
} }
stopEvent(e); stopEvent(e);
@ -157,8 +166,8 @@ export default class Keyboard {
// while meta is held down // while meta is held down
if ((browser.isMac() || browser.isIOS()) && if ((browser.isMac() || browser.isIOS()) &&
(e.metaKey && code !== 'MetaLeft' && code !== 'MetaRight')) { (e.metaKey && code !== 'MetaLeft' && code !== 'MetaRight')) {
this._sendKeyEvent(keysym, code, true); this._sendKeyEvent(keysym, code, true, numlock, capslock);
this._sendKeyEvent(keysym, code, false); this._sendKeyEvent(keysym, code, false, numlock, capslock);
stopEvent(e); stopEvent(e);
return; return;
} }
@ -168,8 +177,8 @@ export default class Keyboard {
// which toggles on each press, but not on release. So pretend // which toggles on each press, but not on release. So pretend
// it was a quick press and release of the button. // it was a quick press and release of the button.
if ((browser.isMac() || browser.isIOS()) && (code === 'CapsLock')) { if ((browser.isMac() || browser.isIOS()) && (code === 'CapsLock')) {
this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', true); this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', true, numlock, capslock);
this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', false); this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', false, numlock, capslock);
stopEvent(e); stopEvent(e);
return; return;
} }
@ -182,8 +191,8 @@ export default class Keyboard {
KeyTable.XK_Hiragana, KeyTable.XK_Hiragana,
KeyTable.XK_Romaji ]; KeyTable.XK_Romaji ];
if (browser.isWindows() && jpBadKeys.includes(keysym)) { if (browser.isWindows() && jpBadKeys.includes(keysym)) {
this._sendKeyEvent(keysym, code, true); this._sendKeyEvent(keysym, code, true, numlock, capslock);
this._sendKeyEvent(keysym, code, false); this._sendKeyEvent(keysym, code, false, numlock, capslock);
stopEvent(e); stopEvent(e);
return; return;
} }
@ -199,7 +208,7 @@ export default class Keyboard {
return; return;
} }
this._sendKeyEvent(keysym, code, true); this._sendKeyEvent(keysym, code, true, numlock, capslock);
} }
_handleKeyUp(e) { _handleKeyUp(e) {

View File

@ -67,7 +67,7 @@ export function getKeycode(evt) {
// Get 'KeyboardEvent.key', handling legacy browsers // Get 'KeyboardEvent.key', handling legacy browsers
export function getKey(evt) { export function getKey(evt) {
// Are we getting a proper key value? // 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 // Mozilla isn't fully in sync with the spec yet
switch (evt.key) { switch (evt.key) {
case 'OS': return 'Meta'; case 'OS': return 'Meta';

View File

@ -260,6 +260,8 @@ export default class RFB extends EventTargetMixin {
this._keyboard = new Keyboard(this._canvas); this._keyboard = new Keyboard(this._canvas);
this._keyboard.onkeyevent = this._handleKeyEvent.bind(this); this._keyboard.onkeyevent = this._handleKeyEvent.bind(this);
this._remoteCapsLock = null; // Null indicates unknown or irrelevant
this._remoteNumLock = null;
this._gestures = new GestureHandler(); this._gestures = new GestureHandler();
@ -996,11 +998,39 @@ export default class RFB extends EventTargetMixin {
} }
} }
_sendPingMessage() { _sendPingMessage() {
window.parent.postMessage(JSON.stringify({method: 'noVNCPing'}), '*'); 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._sendPingMessage();
this.sendKey(keysym, code, down); this.sendKey(keysym, code, down);
} }
@ -1938,7 +1968,11 @@ export default class RFB extends EventTargetMixin {
_negotiateAuthentication() { _negotiateAuthentication() {
switch (this._rfbAuthScheme) { switch (this._rfbAuthScheme) {
case securityTypeNone: case securityTypeNone:
if (this._rfbVersion >= 3.8) {
this._rfbInitState = 'SecurityResult'; this._rfbInitState = 'SecurityResult';
} else {
this._rfbInitState = 'ClientInitialisation';
}
return true; return true;
case securityTypeXVP: case securityTypeXVP:
@ -1975,13 +2009,6 @@ export default class RFB extends EventTargetMixin {
} }
_handleSecurityResult() { _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; } if (this._sock.rQwait('VNC auth response ', 4)) { return false; }
const status = this._sock.rQshift32(); const status = this._sock.rQshift32();
@ -2117,6 +2144,7 @@ export default class RFB extends EventTargetMixin {
encs.push(encodings.pseudoEncodingDesktopSize); encs.push(encodings.pseudoEncodingDesktopSize);
encs.push(encodings.pseudoEncodingLastRect); encs.push(encodings.pseudoEncodingLastRect);
encs.push(encodings.pseudoEncodingQEMUExtendedKeyEvent); encs.push(encodings.pseudoEncodingQEMUExtendedKeyEvent);
encs.push(encodings.pseudoEncodingQEMULedEvent);
encs.push(encodings.pseudoEncodingExtendedDesktopSize); encs.push(encodings.pseudoEncodingExtendedDesktopSize);
encs.push(encodings.pseudoEncodingXvp); encs.push(encodings.pseudoEncodingXvp);
encs.push(encodings.pseudoEncodingFence); encs.push(encodings.pseudoEncodingFence);
@ -2341,7 +2369,7 @@ export default class RFB extends EventTargetMixin {
textData = textData.slice(0, -1); textData = textData.slice(0, -1);
} }
textData = textData.replace("\r\n", "\n"); textData = textData.replaceAll("\r\n", "\n");
this.dispatchEvent(new CustomEvent( this.dispatchEvent(new CustomEvent(
"clipboard", "clipboard",
@ -2552,6 +2580,9 @@ export default class RFB extends EventTargetMixin {
case encodings.pseudoEncodingExtendedDesktopSize: case encodings.pseudoEncodingExtendedDesktopSize:
return this._handleExtendedDesktopSize(); return this._handleExtendedDesktopSize();
case encodings.pseudoEncodingQEMULedEvent:
return this._handleLedEvent();
default: default:
return this._handleDataRect(); return this._handleDataRect();
} }
@ -2729,6 +2760,21 @@ export default class RFB extends EventTargetMixin {
return true; 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() { _handleExtendedDesktopSize() {
if (this._sock.rQwait("ExtendedDesktopSize", 4)) { if (this._sock.rQwait("ExtendedDesktopSize", 4)) {
return false; return false;

View File

@ -69,8 +69,10 @@ export default class Cursor {
this._target.removeEventListener('mousemove', this._eventHandlers.mousemove, options); this._target.removeEventListener('mousemove', this._eventHandlers.mousemove, options);
this._target.removeEventListener('mouseup', this._eventHandlers.mouseup, options); this._target.removeEventListener('mouseup', this._eventHandlers.mouseup, options);
if (document.contains(this._canvas)) {
document.body.removeChild(this._canvas); document.body.removeChild(this._canvas);
} }
}
this._target = null; this._target = null;
} }

View File

@ -3,13 +3,13 @@
.SH NAME .SH NAME
novnc_proxy - noVNC proxy server novnc_proxy - noVNC proxy server
.SH SYNOPSIS .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 Starts the WebSockets proxy and a mini-webserver and
provides a cut-and-paste URL to go to. provides a cut-and-paste URL to go to.
--listen PORT Port for proxy/webserver to listen on --listen [HOST:]PORT Port for proxy/webserver to listen on
Default: 6080 Default: 6080 (on all interfaces)
--vnc VNC_HOST:PORT VNC server host:port proxy target --vnc VNC_HOST:PORT VNC server host:port proxy target
Default: localhost:5900 Default: localhost:5900
--cert CERT Path to combined cert/key file, or just --cert CERT Path to combined cert/key file, or just

102
eslint.config.mjs Normal file
View File

@ -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,
},
},
];

View File

@ -1,6 +1,6 @@
{ {
"name": "@novnc/novnc", "name": "@novnc/novnc",
"version": "1.4.0", "version": "1.5.0",
"description": "An HTML5 VNC client", "description": "An HTML5 VNC client",
"browser": "lib/rfb", "browser": "lib/rfb",
"directories": { "directories": {
@ -14,9 +14,7 @@
"VERSION", "VERSION",
"docs/API.md", "docs/API.md",
"docs/LIBRARY.md", "docs/LIBRARY.md",
"docs/LICENSE*", "docs/LICENSE*"
"core",
"vendor/pako"
], ],
"scripts": { "scripts": {
"lint": "eslint app core po/po2js po/xgettext-html tests utils", "lint": "eslint app core po/po2js po/xgettext-html tests utils",
@ -39,19 +37,14 @@
"homepage": "https://github.com/novnc/noVNC", "homepage": "https://github.com/novnc/noVNC",
"devDependencies": { "devDependencies": {
"@babel/core": "latest", "@babel/core": "latest",
"@babel/plugin-syntax-dynamic-import": "latest",
"@babel/plugin-transform-modules-commonjs": "latest",
"@babel/preset-env": "latest", "@babel/preset-env": "latest",
"@babel/cli": "latest",
"babel-plugin-import-redirect": "latest", "babel-plugin-import-redirect": "latest",
"browserify": "latest", "browserify": "latest",
"babelify": "latest",
"core-js": "latest",
"chai": "latest", "chai": "latest",
"commander": "latest", "commander": "latest",
"es-module-loader": "latest",
"eslint": "latest", "eslint": "latest",
"fs-extra": "latest", "fs-extra": "latest",
"globals": "latest",
"jsdom": "latest", "jsdom": "latest",
"karma": "latest", "karma": "latest",
"karma-mocha": "latest", "karma-mocha": "latest",

View File

@ -1,5 +0,0 @@
{
"env": {
"node": true,
},
}

262
po/el.po
View File

@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: noVNC 0.6.1\n" "Project-Id-Version: noVNC 0.6.1\n"
"Report-Msgid-Bugs-To: novnc@googlegroups.com\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" "PO-Revision-Date: 2017-10-11 16:16+0200\n"
"Last-Translator: Giannis Kosmas <kosmasgiannis@gmail.com>\n" "Last-Translator: Giannis Kosmas <kosmasgiannis@gmail.com>\n"
"Language-Team: none\n" "Language-Team: none\n"
@ -17,273 +17,349 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\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..." msgid "Connecting..."
msgstr "Συνδέεται..." msgstr "Συνδέεται..."
#: ../app/ui.js:411 #: ../app/ui.js:417
msgid "Disconnecting..." msgid "Disconnecting..."
msgstr "Aποσυνδέεται..." msgstr "Aποσυνδέεται..."
#: ../app/ui.js:417 #: ../app/ui.js:423
msgid "Reconnecting..." msgid "Reconnecting..."
msgstr "Επανασυνδέεται..." msgstr "Επανασυνδέεται..."
#: ../app/ui.js:422 #: ../app/ui.js:428
msgid "Internal error" msgid "Internal error"
msgstr "Εσωτερικό σφάλμα" msgstr "Εσωτερικό σφάλμα"
#: ../app/ui.js:1019 #: ../app/ui.js:1026
msgid "Must set host" msgid "Must set host"
msgstr "Πρέπει να οριστεί ο διακομιστής" msgstr "Πρέπει να οριστεί ο διακομιστής"
#: ../app/ui.js:1099 #: ../app/ui.js:1110
msgid "Connected (encrypted) to " msgid "Connected (encrypted) to "
msgstr "Συνδέθηκε (κρυπτογραφημένα) με το " msgstr "Συνδέθηκε (κρυπτογραφημένα) με το "
#: ../app/ui.js:1101 #: ../app/ui.js:1112
msgid "Connected (unencrypted) to " msgid "Connected (unencrypted) to "
msgstr "Συνδέθηκε (μη κρυπτογραφημένα) με το " msgstr "Συνδέθηκε (μη κρυπτογραφημένα) με το "
#: ../app/ui.js:1119 #: ../app/ui.js:1135
msgid "Something went wrong, connection is closed" msgid "Something went wrong, connection is closed"
msgstr "Κάτι πήγε στραβά, η σύνδεση διακόπηκε" msgstr "Κάτι πήγε στραβά, η σύνδεση διακόπηκε"
#: ../app/ui.js:1129 #: ../app/ui.js:1138
msgid "Failed to connect to server"
msgstr "Αποτυχία στη σύνδεση με το διακομιστή"
#: ../app/ui.js:1150
msgid "Disconnected" msgid "Disconnected"
msgstr "Αποσυνδέθηκε" msgstr "Αποσυνδέθηκε"
#: ../app/ui.js:1142 #: ../app/ui.js:1165
msgid "New connection has been rejected with reason: " msgid "New connection has been rejected with reason: "
msgstr "Η νέα σύνδεση απορρίφθηκε διότι: " msgstr "Η νέα σύνδεση απορρίφθηκε διότι: "
#: ../app/ui.js:1145 #: ../app/ui.js:1168
msgid "New connection has been rejected" msgid "New connection has been rejected"
msgstr "Η νέα σύνδεση απορρίφθηκε " msgstr "Η νέα σύνδεση απορρίφθηκε "
#: ../app/ui.js:1166 #: ../app/ui.js:1234
msgid "Password is required" msgid "Credentials are required"
msgstr "Απαιτείται ο κωδικός πρόσβασης" msgstr "Απαιτούνται διαπιστευτήρια"
#: ../vnc.html:89 #: ../vnc.html:57
msgid "noVNC encountered an error:" msgid "noVNC encountered an error:"
msgstr "το noVNC αντιμετώπισε ένα σφάλμα:" msgstr "το noVNC αντιμετώπισε ένα σφάλμα:"
#: ../vnc.html:99 #: ../vnc.html:67
msgid "Hide/Show the control bar" msgid "Hide/Show the control bar"
msgstr "Απόκρυψη/Εμφάνιση γραμμής ελέγχου" msgstr "Απόκρυψη/Εμφάνιση γραμμής ελέγχου"
#: ../vnc.html:106 #: ../vnc.html:76
msgid "Drag"
msgstr "Σύρσιμο"
#: ../vnc.html:76
msgid "Move/Drag Viewport" msgid "Move/Drag Viewport"
msgstr "Μετακίνηση/Σύρσιμο Θεατού πεδίου" msgstr "Μετακίνηση/Σύρσιμο Θεατού πεδίου"
#: ../vnc.html:106 #: ../vnc.html:82
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
msgid "Keyboard" msgid "Keyboard"
msgstr "Πληκτρολόγιο" msgstr "Πληκτρολόγιο"
#: ../vnc.html:124 #: ../vnc.html:82
msgid "Show Keyboard" msgid "Show Keyboard"
msgstr "Εμφάνιση Πληκτρολογίου" msgstr "Εμφάνιση Πληκτρολογίου"
#: ../vnc.html:131 #: ../vnc.html:87
msgid "Extra keys" msgid "Extra keys"
msgstr "Επιπλέον πλήκτρα" msgstr "Επιπλέον πλήκτρα"
#: ../vnc.html:131 #: ../vnc.html:87
msgid "Show Extra Keys" msgid "Show Extra Keys"
msgstr "Εμφάνιση Επιπλέον Πλήκτρων" msgstr "Εμφάνιση Επιπλέον Πλήκτρων"
#: ../vnc.html:136 #: ../vnc.html:92
msgid "Ctrl" msgid "Ctrl"
msgstr "Ctrl" msgstr "Ctrl"
#: ../vnc.html:136 #: ../vnc.html:92
msgid "Toggle Ctrl" msgid "Toggle Ctrl"
msgstr "Εναλλαγή Ctrl" msgstr "Εναλλαγή Ctrl"
#: ../vnc.html:139 #: ../vnc.html:95
msgid "Alt" msgid "Alt"
msgstr "Alt" msgstr "Alt"
#: ../vnc.html:139 #: ../vnc.html:95
msgid "Toggle Alt" msgid "Toggle Alt"
msgstr "Εναλλαγή 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" msgid "Send Tab"
msgstr "Αποστολή Tab" msgstr "Αποστολή Tab"
#: ../vnc.html:142 #: ../vnc.html:101
msgid "Tab" msgid "Tab"
msgstr "Tab" msgstr "Tab"
#: ../vnc.html:145 #: ../vnc.html:104
msgid "Esc" msgid "Esc"
msgstr "Esc" msgstr "Esc"
#: ../vnc.html:145 #: ../vnc.html:104
msgid "Send Escape" msgid "Send Escape"
msgstr "Αποστολή Escape" msgstr "Αποστολή Escape"
#: ../vnc.html:148 #: ../vnc.html:107
msgid "Ctrl+Alt+Del" msgid "Ctrl+Alt+Del"
msgstr "Ctrl+Alt+Del" msgstr "Ctrl+Alt+Del"
#: ../vnc.html:148 #: ../vnc.html:107
msgid "Send Ctrl-Alt-Del" msgid "Send Ctrl-Alt-Del"
msgstr "Αποστολή Ctrl-Alt-Del" msgstr "Αποστολή Ctrl-Alt-Del"
#: ../vnc.html:156 #: ../vnc.html:114
msgid "Shutdown/Reboot" msgid "Shutdown/Reboot"
msgstr "Κλείσιμο/Επανεκκίνηση" msgstr "Κλείσιμο/Επανεκκίνηση"
#: ../vnc.html:156 #: ../vnc.html:114
msgid "Shutdown/Reboot..." msgid "Shutdown/Reboot..."
msgstr "Κλείσιμο/Επανεκκίνηση..." msgstr "Κλείσιμο/Επανεκκίνηση..."
#: ../vnc.html:162 #: ../vnc.html:120
msgid "Power" msgid "Power"
msgstr "Απενεργοποίηση" msgstr "Απενεργοποίηση"
#: ../vnc.html:164 #: ../vnc.html:122
msgid "Shutdown" msgid "Shutdown"
msgstr "Κλείσιμο" msgstr "Κλείσιμο"
#: ../vnc.html:165 #: ../vnc.html:123
msgid "Reboot" msgid "Reboot"
msgstr "Επανεκκίνηση" msgstr "Επανεκκίνηση"
#: ../vnc.html:166 #: ../vnc.html:124
msgid "Reset" msgid "Reset"
msgstr "Επαναφορά" msgstr "Επαναφορά"
#: ../vnc.html:171 ../vnc.html:177 #: ../vnc.html:129 ../vnc.html:135
msgid "Clipboard" msgid "Clipboard"
msgstr "Πρόχειρο" msgstr "Πρόχειρο"
#: ../vnc.html:181 #: ../vnc.html:137
msgid "Clear" msgid "Edit clipboard content in the textarea below."
msgstr "Καθάρισμα" msgstr "Επεξεργαστείτε το περιεχόμενο του πρόχειρου στην περιοχή κειμένου παρακάτω."
#: ../vnc.html:187 #: ../vnc.html:145
msgid "Fullscreen" #, fuzzy
msgid "Full Screen"
msgstr "Πλήρης Οθόνη" msgstr "Πλήρης Οθόνη"
#: ../vnc.html:192 ../vnc.html:199 #: ../vnc.html:150 ../vnc.html:156
msgid "Settings" msgid "Settings"
msgstr "Ρυθμίσεις" msgstr "Ρυθμίσεις"
#: ../vnc.html:202 #: ../vnc.html:160
msgid "Shared Mode" msgid "Shared Mode"
msgstr "Κοινόχρηστη Λειτουργία" msgstr "Κοινόχρηστη Λειτουργία"
#: ../vnc.html:205 #: ../vnc.html:163
msgid "View Only" msgid "View Only"
msgstr "Μόνο Θέαση" msgstr "Μόνο Θέαση"
#: ../vnc.html:209 #: ../vnc.html:167
msgid "Clip to Window" msgid "Clip to Window"
msgstr "Αποκοπή στο όριο του Παράθυρου" msgstr "Αποκοπή στο όριο του Παράθυρου"
#: ../vnc.html:212 #: ../vnc.html:170
msgid "Scaling Mode:" msgid "Scaling Mode:"
msgstr "Λειτουργία Κλιμάκωσης:" msgstr "Λειτουργία Κλιμάκωσης:"
#: ../vnc.html:214 #: ../vnc.html:172
msgid "None" msgid "None"
msgstr "Καμία" msgstr "Καμία"
#: ../vnc.html:215 #: ../vnc.html:173
msgid "Local Scaling" msgid "Local Scaling"
msgstr "Τοπική Κλιμάκωση" msgstr "Τοπική Κλιμάκωση"
#: ../vnc.html:216 #: ../vnc.html:174
msgid "Remote Resizing" msgid "Remote Resizing"
msgstr "Απομακρυσμένη Αλλαγή μεγέθους" msgstr "Απομακρυσμένη Αλλαγή μεγέθους"
#: ../vnc.html:221 #: ../vnc.html:179
msgid "Advanced" msgid "Advanced"
msgstr "Για προχωρημένους" msgstr "Για προχωρημένους"
#: ../vnc.html:224 #: ../vnc.html:182
msgid "Quality:"
msgstr "Ποιότητα:"
#: ../vnc.html:186
msgid "Compression level:"
msgstr "Επίπεδο συμπίεσης:"
#: ../vnc.html:191
msgid "Repeater ID:" msgid "Repeater ID:"
msgstr "Repeater ID:" msgstr "Repeater ID:"
#: ../vnc.html:228 #: ../vnc.html:195
msgid "WebSocket" msgid "WebSocket"
msgstr "WebSocket" msgstr "WebSocket"
#: ../vnc.html:231 #: ../vnc.html:198
msgid "Encrypt" msgid "Encrypt"
msgstr "Κρυπτογράφηση" msgstr "Κρυπτογράφηση"
#: ../vnc.html:234 #: ../vnc.html:201
msgid "Host:" msgid "Host:"
msgstr "Όνομα διακομιστή:" msgstr "Όνομα διακομιστή:"
#: ../vnc.html:238 #: ../vnc.html:205
msgid "Port:" msgid "Port:"
msgstr "Πόρτα διακομιστή:" msgstr "Πόρτα διακομιστή:"
#: ../vnc.html:242 #: ../vnc.html:209
msgid "Path:" msgid "Path:"
msgstr "Διαδρομή:" msgstr "Διαδρομή:"
#: ../vnc.html:249 #: ../vnc.html:216
msgid "Automatic Reconnect" msgid "Automatic Reconnect"
msgstr "Αυτόματη επανασύνδεση" msgstr "Αυτόματη επανασύνδεση"
#: ../vnc.html:252 #: ../vnc.html:219
msgid "Reconnect Delay (ms):" msgid "Reconnect Delay (ms):"
msgstr "Καθυστέρηση επανασύνδεσης (ms):" msgstr "Καθυστέρηση επανασύνδεσης (ms):"
#: ../vnc.html:258 #: ../vnc.html:224
msgid "Show Dot when No Cursor"
msgstr "Εμφάνιση Τελείας όταν δεν υπάρχει Δρομέας"
#: ../vnc.html:229
msgid "Logging:" msgid "Logging:"
msgstr "Καταγραφή:" msgstr "Καταγραφή:"
#: ../vnc.html:270 #: ../vnc.html:238
msgid "Version:"
msgstr "Έκδοση:"
#: ../vnc.html:246
msgid "Disconnect" msgid "Disconnect"
msgstr "Αποσύνδεση" msgstr "Αποσύνδεση"
#: ../vnc.html:289 #: ../vnc.html:269
msgid "Connect" msgid "Connect"
msgstr "Σύνδεση" 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:" msgid "Password:"
msgstr "Κωδικός Πρόσβασης:" msgstr "Κωδικός Πρόσβασης:"
#: ../vnc.html:313 #: ../vnc.html:314
msgid "Send Credentials"
msgstr "Αποστολή Διαπιστευτηρίων"
#: ../vnc.html:323
msgid "Cancel" msgid "Cancel"
msgstr "Ακύρωση" msgstr "Ακύρωση"
#: ../vnc.html:329 #~ msgid "Password is required"
msgid "Canvas not supported." #~ msgstr "Απαιτείται ο κωδικός πρόσβασης"
msgstr "Δεν υποστηρίζεται το στοιχείο Canvas"
#~ 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" #~ msgid "Disconnect timeout"
#~ msgstr "Παρέλευση χρονικού ορίου αποσύνδεσης" #~ msgstr "Παρέλευση χρονικού ορίου αποσύνδεσης"

View File

@ -6,9 +6,9 @@
#, fuzzy #, fuzzy
msgid "" msgid ""
msgstr "" 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" "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" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -18,7 +18,8 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
#: ../app/ui.js:69 #: ../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 "" msgstr ""
#: ../app/ui.js:410 #: ../app/ui.js:410
@ -41,292 +42,296 @@ msgstr ""
msgid "Must set host" msgid "Must set host"
msgstr "" msgstr ""
#: ../app/ui.js:1110 #: ../app/ui.js:1052
msgid "Failed to connect to server: "
msgstr ""
#: ../app/ui.js:1118
msgid "Connected (encrypted) to " msgid "Connected (encrypted) to "
msgstr "" msgstr ""
#: ../app/ui.js:1112 #: ../app/ui.js:1120
msgid "Connected (unencrypted) to " msgid "Connected (unencrypted) to "
msgstr "" msgstr ""
#: ../app/ui.js:1135 #: ../app/ui.js:1143
msgid "Something went wrong, connection is closed" msgid "Something went wrong, connection is closed"
msgstr "" msgstr ""
#: ../app/ui.js:1138 #: ../app/ui.js:1146
msgid "Failed to connect to server" msgid "Failed to connect to server"
msgstr "" msgstr ""
#: ../app/ui.js:1150 #: ../app/ui.js:1158
msgid "Disconnected" msgid "Disconnected"
msgstr "" msgstr ""
#: ../app/ui.js:1165 #: ../app/ui.js:1173
msgid "New connection has been rejected with reason: " msgid "New connection has been rejected with reason: "
msgstr "" msgstr ""
#: ../app/ui.js:1168 #: ../app/ui.js:1176
msgid "New connection has been rejected" msgid "New connection has been rejected"
msgstr "" msgstr ""
#: ../app/ui.js:1234 #: ../app/ui.js:1242
msgid "Credentials are required" msgid "Credentials are required"
msgstr "" msgstr ""
#: ../vnc.html:57 #: ../vnc.html:55
msgid "noVNC encountered an error:" msgid "noVNC encountered an error:"
msgstr "" msgstr ""
#: ../vnc.html:67 #: ../vnc.html:65
msgid "Hide/Show the control bar" msgid "Hide/Show the control bar"
msgstr "" msgstr ""
#: ../vnc.html:76 #: ../vnc.html:74
msgid "Drag" msgid "Drag"
msgstr "" msgstr ""
#: ../vnc.html:76 #: ../vnc.html:74
msgid "Move/Drag Viewport" msgid "Move/Drag Viewport"
msgstr "" msgstr ""
#: ../vnc.html:82 #: ../vnc.html:80
msgid "Keyboard" msgid "Keyboard"
msgstr "" msgstr ""
#: ../vnc.html:82 #: ../vnc.html:80
msgid "Show Keyboard" msgid "Show Keyboard"
msgstr "" msgstr ""
#: ../vnc.html:87 #: ../vnc.html:85
msgid "Extra keys" msgid "Extra keys"
msgstr "" msgstr ""
#: ../vnc.html:87 #: ../vnc.html:85
msgid "Show Extra Keys" msgid "Show Extra Keys"
msgstr "" msgstr ""
#: ../vnc.html:92 #: ../vnc.html:90
msgid "Ctrl" msgid "Ctrl"
msgstr "" msgstr ""
#: ../vnc.html:92 #: ../vnc.html:90
msgid "Toggle Ctrl" msgid "Toggle Ctrl"
msgstr "" msgstr ""
#: ../vnc.html:95 #: ../vnc.html:93
msgid "Alt" msgid "Alt"
msgstr "" msgstr ""
#: ../vnc.html:95 #: ../vnc.html:93
msgid "Toggle Alt" msgid "Toggle Alt"
msgstr "" msgstr ""
#: ../vnc.html:98 #: ../vnc.html:96
msgid "Toggle Windows" msgid "Toggle Windows"
msgstr "" msgstr ""
#: ../vnc.html:98 #: ../vnc.html:96
msgid "Windows" msgid "Windows"
msgstr "" msgstr ""
#: ../vnc.html:101 #: ../vnc.html:99
msgid "Send Tab" msgid "Send Tab"
msgstr "" msgstr ""
#: ../vnc.html:101 #: ../vnc.html:99
msgid "Tab" msgid "Tab"
msgstr "" msgstr ""
#: ../vnc.html:104 #: ../vnc.html:102
msgid "Esc" msgid "Esc"
msgstr "" msgstr ""
#: ../vnc.html:104 #: ../vnc.html:102
msgid "Send Escape" msgid "Send Escape"
msgstr "" msgstr ""
#: ../vnc.html:107 #: ../vnc.html:105
msgid "Ctrl+Alt+Del" msgid "Ctrl+Alt+Del"
msgstr "" msgstr ""
#: ../vnc.html:107 #: ../vnc.html:105
msgid "Send Ctrl-Alt-Del" msgid "Send Ctrl-Alt-Del"
msgstr "" msgstr ""
#: ../vnc.html:114 #: ../vnc.html:112
msgid "Shutdown/Reboot" msgid "Shutdown/Reboot"
msgstr "" msgstr ""
#: ../vnc.html:114 #: ../vnc.html:112
msgid "Shutdown/Reboot..." msgid "Shutdown/Reboot..."
msgstr "" msgstr ""
#: ../vnc.html:120 #: ../vnc.html:118
msgid "Power" msgid "Power"
msgstr "" msgstr ""
#: ../vnc.html:122 #: ../vnc.html:120
msgid "Shutdown" msgid "Shutdown"
msgstr "" msgstr ""
#: ../vnc.html:123 #: ../vnc.html:121
msgid "Reboot" msgid "Reboot"
msgstr "" msgstr ""
#: ../vnc.html:124 #: ../vnc.html:122
msgid "Reset" msgid "Reset"
msgstr "" msgstr ""
#: ../vnc.html:129 ../vnc.html:135 #: ../vnc.html:127 ../vnc.html:133
msgid "Clipboard" msgid "Clipboard"
msgstr "" msgstr ""
#: ../vnc.html:137 #: ../vnc.html:135
msgid "Edit clipboard content in the textarea below." msgid "Edit clipboard content in the textarea below."
msgstr "" msgstr ""
#: ../vnc.html:145 #: ../vnc.html:143
msgid "Full Screen" msgid "Full Screen"
msgstr "" msgstr ""
#: ../vnc.html:150 ../vnc.html:156 #: ../vnc.html:148 ../vnc.html:154
msgid "Settings" msgid "Settings"
msgstr "" msgstr ""
#: ../vnc.html:160 #: ../vnc.html:158
msgid "Shared Mode" msgid "Shared Mode"
msgstr "" msgstr ""
#: ../vnc.html:163 #: ../vnc.html:161
msgid "View Only" msgid "View Only"
msgstr "" msgstr ""
#: ../vnc.html:167 #: ../vnc.html:165
msgid "Clip to Window" msgid "Clip to Window"
msgstr "" msgstr ""
#: ../vnc.html:170 #: ../vnc.html:168
msgid "Scaling Mode:" msgid "Scaling Mode:"
msgstr "" msgstr ""
#: ../vnc.html:172 #: ../vnc.html:170
msgid "None" msgid "None"
msgstr "" msgstr ""
#: ../vnc.html:173 #: ../vnc.html:171
msgid "Local Scaling" msgid "Local Scaling"
msgstr "" msgstr ""
#: ../vnc.html:174 #: ../vnc.html:172
msgid "Remote Resizing" msgid "Remote Resizing"
msgstr "" msgstr ""
#: ../vnc.html:179 #: ../vnc.html:177
msgid "Advanced" msgid "Advanced"
msgstr "" msgstr ""
#: ../vnc.html:182 #: ../vnc.html:180
msgid "Quality:" msgid "Quality:"
msgstr "" msgstr ""
#: ../vnc.html:186 #: ../vnc.html:184
msgid "Compression level:" msgid "Compression level:"
msgstr "" msgstr ""
#: ../vnc.html:191 #: ../vnc.html:189
msgid "Repeater ID:" msgid "Repeater ID:"
msgstr "" msgstr ""
#: ../vnc.html:195 #: ../vnc.html:193
msgid "WebSocket" msgid "WebSocket"
msgstr "" msgstr ""
#: ../vnc.html:198 #: ../vnc.html:196
msgid "Encrypt" msgid "Encrypt"
msgstr "" msgstr ""
#: ../vnc.html:201 #: ../vnc.html:199
msgid "Host:" msgid "Host:"
msgstr "" msgstr ""
#: ../vnc.html:205 #: ../vnc.html:203
msgid "Port:" msgid "Port:"
msgstr "" msgstr ""
#: ../vnc.html:209 #: ../vnc.html:207
msgid "Path:" msgid "Path:"
msgstr "" msgstr ""
#: ../vnc.html:216 #: ../vnc.html:214
msgid "Automatic Reconnect" msgid "Automatic Reconnect"
msgstr "" msgstr ""
#: ../vnc.html:219 #: ../vnc.html:217
msgid "Reconnect Delay (ms):" msgid "Reconnect Delay (ms):"
msgstr "" msgstr ""
#: ../vnc.html:224 #: ../vnc.html:222
msgid "Show Dot when No Cursor" msgid "Show Dot when No Cursor"
msgstr "" msgstr ""
#: ../vnc.html:229 #: ../vnc.html:227
msgid "Logging:" msgid "Logging:"
msgstr "" msgstr ""
#: ../vnc.html:238 #: ../vnc.html:236
msgid "Version:" msgid "Version:"
msgstr "" msgstr ""
#: ../vnc.html:246 #: ../vnc.html:244
msgid "Disconnect" msgid "Disconnect"
msgstr "" msgstr ""
#: ../vnc.html:269 #: ../vnc.html:267
msgid "Connect" msgid "Connect"
msgstr "" msgstr ""
#: ../vnc.html:278 #: ../vnc.html:276
msgid "Server identity" msgid "Server identity"
msgstr "" msgstr ""
#: ../vnc.html:281 #: ../vnc.html:279
msgid "The server has provided the following identifying information:" msgid "The server has provided the following identifying information:"
msgstr "" msgstr ""
#: ../vnc.html:285 #: ../vnc.html:283
msgid "Fingerprint:" msgid "Fingerprint:"
msgstr "" msgstr ""
#: ../vnc.html:288 #: ../vnc.html:286
msgid "" msgid ""
"Please verify that the information is correct and press \"Approve\". " "Please verify that the information is correct and press \"Approve\". "
"Otherwise press \"Reject\"." "Otherwise press \"Reject\"."
msgstr "" msgstr ""
#: ../vnc.html:293 #: ../vnc.html:291
msgid "Approve" msgid "Approve"
msgstr "" msgstr ""
#: ../vnc.html:294 #: ../vnc.html:292
msgid "Reject" msgid "Reject"
msgstr "" msgstr ""
#: ../vnc.html:302 #: ../vnc.html:300
msgid "Credentials" msgid "Credentials"
msgstr "" msgstr ""
#: ../vnc.html:306 #: ../vnc.html:304
msgid "Username:" msgid "Username:"
msgstr "" msgstr ""
#: ../vnc.html:310 #: ../vnc.html:308
msgid "Password:" msgid "Password:"
msgstr "" msgstr ""
#: ../vnc.html:314 #: ../vnc.html:312
msgid "Send Credentials" msgid "Send Credentials"
msgstr "" msgstr ""
#: ../vnc.html:323 #: ../vnc.html:321
msgid "Cancel" msgid "Cancel"
msgstr "" msgstr ""

View File

@ -32,8 +32,10 @@ if (opt.argv.length != 2) {
const data = po2json.parseFileSync(opt.argv[0]); const data = po2json.parseFileSync(opt.argv[0]);
const bodyPart = Object.keys(data).filter(msgid => msgid !== "").map((msgid) => { const bodyPart = Object.keys(data)
if (msgid === "") return; .filter(msgid => msgid !== "")
.filter(msgid => data[msgid][1] !== "")
.map((msgid) => {
const msgstr = data[msgid][1]; const msgstr = data[msgid][1];
return " " + JSON.stringify(msgid) + ": " + JSON.stringify(msgstr); return " " + JSON.stringify(msgid) + ": " + JSON.stringify(msgstr);
}).join(",\n"); }).join(",\n");

View File

@ -8,20 +8,23 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: noVNC 1.3.0\n" "Project-Id-Version: noVNC 1.3.0\n"
"Report-Msgid-Bugs-To: novnc@googlegroups.com\n" "Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
"POT-Creation-Date: 2023-01-20 12:54+0100\n" "POT-Creation-Date: 2024-06-03 14:10+0200\n"
"PO-Revision-Date: 2023-01-20 12:58+0100\n" "PO-Revision-Date: 2024-06-18 13:52+0200\n"
"Last-Translator: Samuel Mannehed <samuel@cendio.se>\n" "Last-Translator: Pierre Ossman <ossman@cendio.se>\n"
"Language-Team: none\n" "Language-Team: none\n"
"Language: sv\n" "Language: sv\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\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 #: ../app/ui.js:69
msgid "HTTPS is required for full functionality" msgid ""
msgstr "HTTPS krävs för full funktionalitet" "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:410
msgid "Connecting..." msgid "Connecting..."
@ -43,35 +46,39 @@ msgstr "Internt fel"
msgid "Must set host" msgid "Must set host"
msgstr "Du måste specifiera en värd" 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 " msgid "Connected (encrypted) to "
msgstr "Ansluten (krypterat) till " msgstr "Ansluten (krypterat) till "
#: ../app/ui.js:1112 #: ../app/ui.js:1120
msgid "Connected (unencrypted) to " msgid "Connected (unencrypted) to "
msgstr "Ansluten (okrypterat) till " msgstr "Ansluten (okrypterat) till "
#: ../app/ui.js:1135 #: ../app/ui.js:1143
msgid "Something went wrong, connection is closed" msgid "Something went wrong, connection is closed"
msgstr "Något gick fel, anslutningen avslutades" msgstr "Något gick fel, anslutningen avslutades"
#: ../app/ui.js:1138 #: ../app/ui.js:1146
msgid "Failed to connect to server" msgid "Failed to connect to server"
msgstr "Misslyckades att ansluta till servern" msgstr "Misslyckades att ansluta till servern"
#: ../app/ui.js:1150 #: ../app/ui.js:1158
msgid "Disconnected" msgid "Disconnected"
msgstr "Frånkopplad" msgstr "Frånkopplad"
#: ../app/ui.js:1165 #: ../app/ui.js:1173
msgid "New connection has been rejected with reason: " msgid "New connection has been rejected with reason: "
msgstr "Ny anslutning har blivit nekad med följande skäl: " 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" msgid "New connection has been rejected"
msgstr "Ny anslutning har blivit nekad" msgstr "Ny anslutning har blivit nekad"
#: ../app/ui.js:1234 #: ../app/ui.js:1242
msgid "Credentials are required" msgid "Credentials are required"
msgstr "Användaruppgifter krävs" msgstr "Användaruppgifter krävs"
@ -304,8 +311,8 @@ msgid ""
"Please verify that the information is correct and press \"Approve\". " "Please verify that the information is correct and press \"Approve\". "
"Otherwise press \"Reject\"." "Otherwise press \"Reject\"."
msgstr "" msgstr ""
"Kontrollera att informationen är korrekt och tryck sedan " "Kontrollera att informationen är korrekt och tryck sedan \"Godkänn\". Tryck "
"\"Godkänn\". Tryck annars \"Neka\"." "annars \"Neka\"."
#: ../vnc.html:291 #: ../vnc.html:291
msgid "Approve" msgid "Approve"
@ -335,5 +342,8 @@ msgstr "Skicka Användaruppgifter"
msgid "Cancel" msgid "Cancel"
msgstr "Avbryt" msgstr "Avbryt"
#~ msgid "HTTPS is required for full functionality"
#~ msgstr "HTTPS krävs för full funktionalitet"
#~ msgid "Clear" #~ msgid "Clear"
#~ msgstr "Rensa" #~ msgstr "Rensa"

View File

@ -15,58 +15,42 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
#: ../app/ui.js:395 #: ../app/ui.js:430
msgid "Connecting..." msgid "Connecting..."
msgstr "连接中..." 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..." msgid "Disconnecting..."
msgstr "正在断开连接..." msgstr "正在断开连接..."
#: ../app/ui.js:408 #: ../app/ui.js:450
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
msgid "Disconnected" msgid "Disconnected"
msgstr "已断开连接" msgstr "已断开连接"
#: ../app/ui.js:1146 #: ../app/ui.js:1052 ../core/rfb.js:248
msgid "New connection has been rejected with reason: " msgid "Must set host"
msgstr "连接被拒绝,原因:" msgstr "必须设置主机"
#: ../app/ui.js:1149 #: ../app/ui.js:1101
msgid "New connection has been rejected" msgid "Reconnecting..."
msgstr "连接被拒绝" msgstr "重新连接中..."
#: ../app/ui.js:1170 #: ../app/ui.js:1140
msgid "Password is required" msgid "Password is required"
msgstr "请提供密码" msgstr "请提供密码"
#: ../core/rfb.js:548
msgid "Disconnect timeout"
msgstr "超时断开"
#: ../vnc.html:89 #: ../vnc.html:89
msgid "noVNC encountered an error:" msgid "noVNC encountered an error:"
msgstr "noVNC 遇到一个错误:" msgstr "noVNC 遇到一个错误:"
@ -77,31 +61,31 @@ msgstr "显示/隐藏控制栏"
#: ../vnc.html:106 #: ../vnc.html:106
msgid "Move/Drag Viewport" msgid "Move/Drag Viewport"
msgstr "拖放显示范围" msgstr "移动/拖动窗口"
#: ../vnc.html:106 #: ../vnc.html:106
msgid "viewport drag" msgid "viewport drag"
msgstr "显示范围拖放" msgstr "窗口拖动"
#: ../vnc.html:112 ../vnc.html:115 ../vnc.html:118 ../vnc.html:121 #: ../vnc.html:112 ../vnc.html:115 ../vnc.html:118 ../vnc.html:121
msgid "Active Mouse Button" msgid "Active Mouse Button"
msgstr "启动鼠标按" msgstr "启动鼠标按"
#: ../vnc.html:112 #: ../vnc.html:112
msgid "No mousebutton" msgid "No mousebutton"
msgstr "禁用鼠标按" msgstr "禁用鼠标按"
#: ../vnc.html:115 #: ../vnc.html:115
msgid "Left mousebutton" msgid "Left mousebutton"
msgstr "鼠标左" msgstr "鼠标左"
#: ../vnc.html:118 #: ../vnc.html:118
msgid "Middle mousebutton" msgid "Middle mousebutton"
msgstr "鼠标中" msgstr "鼠标中"
#: ../vnc.html:121 #: ../vnc.html:121
msgid "Right mousebutton" msgid "Right mousebutton"
msgstr "鼠标右" msgstr "鼠标右"
#: ../vnc.html:124 #: ../vnc.html:124
msgid "Keyboard" msgid "Keyboard"
@ -127,6 +111,10 @@ msgstr "Ctrl"
msgid "Toggle Ctrl" msgid "Toggle Ctrl"
msgstr "切换 Ctrl" msgstr "切换 Ctrl"
#: ../vnc.html:136
msgid "Edit clipboard content in the textarea below."
msgstr "在下面的文本区域中编辑剪贴板内容。"
#: ../vnc.html:139 #: ../vnc.html:139
msgid "Alt" msgid "Alt"
msgstr "Alt" msgstr "Alt"
@ -153,19 +141,19 @@ msgstr "发送 Escape 键"
#: ../vnc.html:148 #: ../vnc.html:148
msgid "Ctrl+Alt+Del" msgid "Ctrl+Alt+Del"
msgstr "Ctrl-Alt-Del" msgstr "Ctrl+Alt+Del"
#: ../vnc.html:148 #: ../vnc.html:148
msgid "Send Ctrl-Alt-Del" msgid "Send Ctrl-Alt-Del"
msgstr "发送 Ctrl-Alt-Del 键" msgstr "发送 Ctrl+Alt+Del 键"
#: ../vnc.html:156 #: ../vnc.html:156
msgid "Shutdown/Reboot" msgid "Shutdown/Reboot"
msgstr "关机/重" msgstr "关机/重启"
#: ../vnc.html:156 #: ../vnc.html:156
msgid "Shutdown/Reboot..." msgid "Shutdown/Reboot..."
msgstr "关机/重..." msgstr "关机/重启..."
#: ../vnc.html:162 #: ../vnc.html:162
msgid "Power" msgid "Power"
@ -177,7 +165,7 @@ msgstr "关机"
#: ../vnc.html:165 #: ../vnc.html:165
msgid "Reboot" msgid "Reboot"
msgstr "重" msgstr "重启"
#: ../vnc.html:166 #: ../vnc.html:166
msgid "Reset" msgid "Reset"
@ -199,6 +187,10 @@ msgstr "全屏"
msgid "Settings" msgid "Settings"
msgstr "设置" msgstr "设置"
#: ../vnc.html:200
msgid "Encrypt"
msgstr "加密"
#: ../vnc.html:202 #: ../vnc.html:202
msgid "Shared Mode" msgid "Shared Mode"
msgstr "分享模式" msgstr "分享模式"
@ -224,61 +216,69 @@ msgid "Local Scaling"
msgstr "本地缩放" msgstr "本地缩放"
#: ../vnc.html:216 #: ../vnc.html:216
msgid "Local Downscaling"
msgstr "降低本地尺寸"
#: ../vnc.html:217
msgid "Remote Resizing" msgid "Remote Resizing"
msgstr "远程调整大小" msgstr "远程调整大小"
#: ../vnc.html:221 #: ../vnc.html:222
msgid "Advanced" msgid "Advanced"
msgstr "高级" msgstr "高级"
#: ../vnc.html:224 #: ../vnc.html:225
msgid "Local Cursor"
msgstr "本地光标"
#: ../vnc.html:229
msgid "Repeater ID:" msgid "Repeater ID:"
msgstr "中继站 ID" msgstr "中继站 ID"
#: ../vnc.html:228 #: ../vnc.html:233
msgid "WebSocket" msgid "WebSocket"
msgstr "WebSocket" msgstr "WebSocket"
#: ../vnc.html:231 #: ../vnc.html:239
msgid "Encrypt"
msgstr "加密"
#: ../vnc.html:234
msgid "Host:" msgid "Host:"
msgstr "主机:" msgstr "主机:"
#: ../vnc.html:238 #: ../vnc.html:243
msgid "Port:" msgid "Port:"
msgstr "端口:" msgstr "端口:"
#: ../vnc.html:242 #: ../vnc.html:247
msgid "Path:" msgid "Path:"
msgstr "路径:" msgstr "路径:"
#: ../vnc.html:249 #: ../vnc.html:254
msgid "Automatic Reconnect" msgid "Automatic Reconnect"
msgstr "自动重新连接" msgstr "自动重新连接"
#: ../vnc.html:252 #: ../vnc.html:257
msgid "Reconnect Delay (ms):" msgid "Reconnect Delay (ms):"
msgstr "重新连接间隔 (ms)" msgstr "重新连接间隔 (ms)"
#: ../vnc.html:258 #: ../vnc.html:263
msgid "Logging:" msgid "Logging:"
msgstr "日志级别:" msgstr "日志级别:"
#: ../vnc.html:270 #: ../vnc.html:275
msgid "Disconnect" msgid "Disconnect"
msgstr "断连接" msgstr "断连接"
#: ../vnc.html:289 #: ../vnc.html:294
msgid "Connect" msgid "Connect"
msgstr "连接" msgstr "连接"
#: ../vnc.html:299 #: ../vnc.html:304
msgid "Password:" msgid "Password:"
msgstr "密码:" msgstr "密码:"
#: ../vnc.html:313 #: ../vnc.html:318
msgid "Cancel" msgid "Cancel"
msgstr "取消" msgstr "取消"
#: ../vnc.html:334
msgid "Canvas not supported."
msgstr "不支持 Canvas。"

View File

@ -1,5 +1,5 @@
name: novnc 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 version: git
summary: Open Source VNC client using HTML5 (WebSockets, Canvas) summary: Open Source VNC client using HTML5 (WebSockets, Canvas)
description: | description: |
@ -42,7 +42,7 @@ parts:
- jq - jq
websockify: 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 plugin: python
stage-packages: stage-packages:
- python3-numpy - python3-numpy

View File

@ -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",
}
}

View File

@ -1,4 +1,3 @@
/* eslint-disable no-console */
const expect = chai.expect; const expect = chai.expect;
import { isMac, isWindows, isIOS, isAndroid, isChromeOS, import { isMac, isWindows, isIOS, isAndroid, isChromeOS,

View File

@ -1,4 +1,3 @@
/* eslint-disable no-console */
const expect = chai.expect; const expect = chai.expect;
import { inflateInit, inflate } from "../vendor/pako/lib/zlib/inflate.js"; import { inflateInit, inflate } from "../vendor/pako/lib/zlib/inflate.js";

View File

@ -108,6 +108,8 @@ describe('Helpers', function () {
}); });
it('should use charCode if no key', function () { it('should use charCode if no key', function () {
expect(KeyboardUtil.getKey({charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43})).to.be.equal('Š'); 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 () { it('should return Unidentified when it cannot map the key', function () {
expect(KeyboardUtil.getKey({keycode: 0x42})).to.be.equal('Unidentified'); expect(KeyboardUtil.getKey({keycode: 0x42})).to.be.equal('Unidentified');

View File

@ -1,4 +1,3 @@
/* eslint-disable no-console */
const expect = chai.expect; const expect = chai.expect;
import { deflateInit, deflate, Z_FULL_FLUSH } from "../vendor/pako/lib/zlib/deflate.js"; import { deflateInit, deflate, Z_FULL_FLUSH } from "../vendor/pako/lib/zlib/deflate.js";

View File

@ -1,4 +1,3 @@
/* eslint-disable no-console */
const expect = chai.expect; const expect = chai.expect;
import { toUnsigned32bit, toSigned32bit } from '../core/util/int.js'; import { toUnsigned32bit, toSigned32bit } from '../core/util/int.js';

View File

@ -14,6 +14,10 @@ describe('Key Event Handling', function () {
} }
e.stopPropagation = sinon.spy(); e.stopPropagation = sinon.spy();
e.preventDefault = sinon.spy(); e.preventDefault = sinon.spy();
e.getModifierState = function (key) {
return e[key];
};
return e; 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 () { describe('Japanese IM keys on Windows', function () {
let origNavigator; let origNavigator;
beforeEach(function () { beforeEach(function () {

View File

@ -3,7 +3,7 @@ const expect = chai.expect;
import RFB from '../core/rfb.js'; import RFB from '../core/rfb.js';
import Websock from '../core/websock.js'; import Websock from '../core/websock.js';
import ZStream from "../vendor/pako/lib/zlib/zstream.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 { encodings } from '../core/encodings.js';
import { toUnsigned32bit } from '../core/util/int.js'; import { toUnsigned32bit } from '../core/util/int.js';
import { encodeUTF8 } from '../core/util/strings.js'; import { encodeUTF8 } from '../core/util/strings.js';
@ -54,7 +54,7 @@ function deflateWithSize(data) {
let strm = new ZStream(); let strm = new ZStream();
let chunkSize = 1024 * 10 * 10; let chunkSize = 1024 * 10 * 10;
strm.output = new Uint8Array(chunkSize); strm.output = new Uint8Array(chunkSize);
deflateInit(strm, 5); deflateInit(strm, Z_DEFAULT_COMPRESSION);
/* eslint-disable camelcase */ /* eslint-disable camelcase */
strm.input = unCompData; strm.input = unCompData;
@ -1257,6 +1257,14 @@ describe('Remote Frame Buffer Protocol Client', function () {
client._sock._websocket._receiveData(new Uint8Array([0, 0, 0, 1])); client._sock._websocket._receiveData(new Uint8Array([0, 0, 0, 1]));
expect(client._rfbInitState).to.equal('ServerInitialisation'); 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 () { describe('Authentication', function () {
@ -2230,16 +2238,36 @@ describe('Remote Frame Buffer Protocol Client', function () {
}); });
describe('Legacy SecurityResult', function () { describe('Legacy SecurityResult', function () {
beforeEach(function () { it('should not include reason in securityfailure event for versions < 3.7', function () {
sendVer('003.007\n', client); client.addEventListener("credentialsrequired", () => {
client._sock._websocket._getSentData(); client.sendCredentials({ password: 'passwd' });
sendSecurity(1, client);
client._sock._websocket._getSentData();
}); });
it('should not include reason in securityfailure event', function () {
const spy = sinon.spy(); const spy = sinon.spy();
client.addEventListener("securityfailure", 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])); client._sock._websocket._receiveData(new Uint8Array([0, 0, 0, 2]));
expect(spy).to.have.been.calledOnce; expect(spy).to.have.been.calledOnce;
expect(spy.args[0][0].detail.status).to.equal(2); 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€'); 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 () { 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 () { 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]; let data = [3, 0, 0, 0];
const flags = [0x10, 0x00, 0x00, 0x01]; 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); let deflatedText = deflateWithSize(text);

View File

@ -228,12 +228,59 @@ describe('Tight Decoder', function () {
expect(display).to.have.displayed(targetData); expect(display).to.have.displayed(targetData);
}); });
it.skip('should handle uncompressed gradient rects', function () { it('should handle uncompressed gradient rects', function () {
// Not implemented yet 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 () { it('should handle compressed gradient rects', function () {
// Not implemented yet 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 () { it('should handle empty copy rects', function () {
@ -275,6 +322,25 @@ describe('Tight Decoder', function () {
expect(display).to.have.displayed(targetData); 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 () { it('should handle empty fill rects', function () {
display.fillRect(0, 0, 4, 4, [ 0x00, 0x00, 0xff ]); display.fillRect(0, 0, 4, 4, [ 0x00, 0x00, 0xff ]);
display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]); display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);

View File

@ -1,8 +0,0 @@
{
"env": {
"node": true
},
"rules": {
"no-console": 0
}
}

View File

@ -1,7 +1,7 @@
#!/usr/bin/env node #!/usr/bin/env node
const path = require('path'); const path = require('path');
const program = require('commander'); const { program } = require('commander');
const fs = require('fs'); const fs = require('fs');
const fse = require('fs-extra'); const fse = require('fs-extra');
const babel = require('@babel/core'); const babel = require('@babel/core');

View File

@ -8,13 +8,13 @@ usage() {
echo "$*" echo "$*"
echo echo
fi 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
echo "Starts the WebSockets proxy and a mini-webserver and " echo "Starts the WebSockets proxy and a mini-webserver and "
echo "provides a cut-and-paste URL to go to." echo "provides a cut-and-paste URL to go to."
echo echo
echo " --listen PORT Port for proxy/webserver to listen on" echo " --listen [HOST:]PORT Port for proxy/webserver to listen on"
echo " Default: 6080" echo " Default: 6080 (on all interfaces)"
echo " --vnc VNC_HOST:PORT VNC server host:port proxy target" echo " --vnc VNC_HOST:PORT VNC server host:port proxy target"
echo " Default: localhost:5900" echo " Default: localhost:5900"
echo " --cert CERT Path to combined cert/key file, or just" echo " --cert CERT Path to combined cert/key file, or just"
@ -47,14 +47,16 @@ usage() {
NAME="$(basename $0)" NAME="$(basename $0)"
REAL_NAME="$(readlink -f $0)" REAL_NAME="$(readlink -f $0)"
HERE="$(cd "$(dirname "$REAL_NAME")" && pwd)" HERE="$(cd "$(dirname "$REAL_NAME")" && pwd)"
HOST=""
PORT="6080" PORT="6080"
LISTEN="$PORT"
VNC_DEST="localhost:5900" VNC_DEST="localhost:5900"
CERT="" CERT=""
KEY="" KEY=""
WEB="" WEB=""
proxy_pid="" proxy_pid=""
SSLONLY="" SSLONLY=""
RECORD_ARG="" RECORD=""
SYSLOG_ARG="" SYSLOG_ARG=""
HEARTBEAT_ARG="" HEARTBEAT_ARG=""
IDLETIMEOUT_ARG="" IDLETIMEOUT_ARG=""
@ -86,14 +88,14 @@ cleanup() {
while [ "$*" ]; do while [ "$*" ]; do
param=$1; shift; OPTARG=$1 param=$1; shift; OPTARG=$1
case $param in case $param in
--listen) PORT="${OPTARG}"; shift ;; --listen) LISTEN="${OPTARG}"; shift ;;
--vnc) VNC_DEST="${OPTARG}"; shift ;; --vnc) VNC_DEST="${OPTARG}"; shift ;;
--cert) CERT="${OPTARG}"; shift ;; --cert) CERT="${OPTARG}"; shift ;;
--key) KEY="${OPTARG}"; shift ;; --key) KEY="${OPTARG}"; shift ;;
--web) WEB="${OPTARG}"; shift ;; --web) WEB="${OPTARG}"; shift ;;
--ssl-only) SSLONLY="--ssl-only" ;; --ssl-only) SSLONLY="--ssl-only" ;;
--file-only) FILEONLY_ARG="--file-only" ;; --file-only) FILEONLY_ARG="--file-only" ;;
--record) RECORD_ARG="--record ${OPTARG}"; shift ;; --record) RECORD="${OPTARG}"; shift ;;
--syslog) SYSLOG_ARG="--syslog ${OPTARG}"; shift ;; --syslog) SYSLOG_ARG="--syslog ${OPTARG}"; shift ;;
--heartbeat) HEARTBEAT_ARG="--heartbeat ${OPTARG}"; shift ;; --heartbeat) HEARTBEAT_ARG="--heartbeat ${OPTARG}"; shift ;;
--idle-timeout) IDLETIMEOUT_ARG="--idle-timeout ${OPTARG}"; shift ;; --idle-timeout) IDLETIMEOUT_ARG="--idle-timeout ${OPTARG}"; shift ;;
@ -107,7 +109,15 @@ while [ "$*" ]; do
esac esac
done done
if [ "$LISTEN" != "$PORT" ]; then
HOST=${LISTEN%:*}
PORT=${LISTEN##*:}
# if no host was given, restore
[ "$HOST" = "$PORT" ] && HOST=""
fi
# Sanity checks # Sanity checks
if [ -z "${HOST}" ]; then
if bash -c "exec 7<>/dev/tcp/localhost/${PORT}" &> /dev/null; then if bash -c "exec 7<>/dev/tcp/localhost/${PORT}" &> /dev/null; then
exec 7<&- exec 7<&-
exec 7>&- exec 7>&-
@ -116,6 +126,7 @@ else
exec 7<&- exec 7<&-
exec 7>&- exec 7>&-
fi fi
fi
trap "cleanup" TERM QUIT INT EXIT trap "cleanup" TERM QUIT INT EXIT
@ -191,9 +202,14 @@ else
fi fi
fi fi
echo "Starting webserver and WebSockets proxy on port ${PORT}" # Make all file paths absolute as websockify changes working directory
#${HERE}/websockify --web ${WEB} ${CERT:+--cert ${CERT}} ${PORT} ${VNC_DEST} & WEB=`realpath "${WEB}"`
${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} & [ -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="$!" proxy_pid="$!"
sleep 1 sleep 1
if [ -z "$proxy_pid" ] || ! ps -eo pid= | grep -w "$proxy_pid" > /dev/null; then 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 exit 1
fi fi
if [ -z "$HOST" ]; then
HOST=$(hostname)
fi
echo -e "\n\nNavigate to this URL:\n" echo -e "\n\nNavigate to this URL:\n"
if [ "x$SSLONLY" == "x" ]; then 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 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 fi
echo -e "Press Ctrl-C to exit\n\n" echo -e "Press Ctrl-C to exit\n\n"