├── .gitmodules ├── defaults.json ├── mandatory.json ├── snap ├── hooks │ └── configure ├── local │ └── svc_wrapper.sh └── snapcraft.yaml ├── app ├── sounds │ ├── bell.mp3 │ ├── bell.oga │ └── CREDITS ├── images │ ├── icons │ │ ├── novnc.ico │ │ ├── novnc-ios-40.png │ │ ├── novnc-ios-58.png │ │ ├── novnc-ios-60.png │ │ ├── novnc-ios-80.png │ │ ├── novnc-ios-87.png │ │ ├── novnc-ios-120.png │ │ ├── novnc-ios-152.png │ │ ├── novnc-ios-167.png │ │ ├── novnc-ios-180.png │ │ └── Makefile │ ├── windows.svg │ ├── handle.svg │ ├── tab.svg │ ├── expander.svg │ ├── settings.svg │ ├── error.svg │ ├── fullscreen.svg │ ├── info.svg │ ├── ctrlaltdel.svg │ ├── connect.svg │ ├── alt.svg │ ├── warning.svg │ └── power.svg ├── styles │ ├── Orbitron700.ttf │ ├── Orbitron700.woff │ └── constants.css ├── locale │ ├── README │ ├── zh_TW.json │ ├── ko.json │ ├── es.json │ ├── tr.json │ ├── it.json │ ├── cs.json │ ├── ru.json │ ├── pt_BR.json │ ├── ja.json │ ├── zh_CN.json │ ├── de.json │ ├── pl.json │ ├── sv.json │ ├── hu.json │ └── fr.json └── error-handler.js ├── docs ├── rfbproto-3.3.pdf ├── rfbproto-3.7.pdf ├── rfbproto-3.8.pdf ├── notes ├── flash_policy.txt ├── LIBRARY.md ├── LICENSE.BSD-2-Clause ├── LICENSE.BSD-3-Clause ├── novnc_proxy.1 ├── links └── rfb_notes ├── .gitignore ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── feature_request.md │ └── bug_report.md └── workflows │ ├── translate.yml │ ├── lint.yml │ ├── test.yml │ └── deploy.yml ├── vendor └── pako │ ├── README.md │ ├── lib │ ├── zlib │ │ ├── messages.js │ │ ├── adler32.js │ │ ├── crc32.js │ │ ├── zstream.js │ │ ├── gzheader.js │ │ └── constants.js │ └── utils │ │ └── common.js │ └── LICENSE ├── core ├── util │ ├── int.js │ ├── strings.js │ ├── element.js │ ├── eventtarget.js │ └── logging.js ├── decoders │ ├── copyrect.js │ ├── tightpng.js │ ├── rre.js │ ├── zlib.js │ └── raw.js ├── crypto │ ├── bigint.js │ ├── dh.js │ └── crypto.js ├── encodings.js ├── inflator.js ├── clipboard.js ├── deflator.js └── input │ └── vkeys.js ├── AUTHORS ├── utils ├── b64-to-binary.pl ├── README.md ├── u2x11 ├── validate └── genkeysymdef.js ├── tests ├── test.int.js ├── vnc_playback.html ├── test.base64.js ├── test.deflator.js ├── test.zlib.js ├── fake.websocket.js ├── test.util.js ├── test.copyrect.js ├── test.inflator.js └── test.clipboard.js ├── po ├── Makefile ├── po2js └── xgettext-html ├── package.json ├── LICENSE.txt ├── karma.conf.cjs └── eslint.config.mjs /.gitmodules: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /defaults.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /mandatory.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /snap/hooks/configure: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | snapctl restart novnc.novncsvc 4 | -------------------------------------------------------------------------------- /app/sounds/bell.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/novnc/noVNC/HEAD/app/sounds/bell.mp3 -------------------------------------------------------------------------------- /app/sounds/bell.oga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/novnc/noVNC/HEAD/app/sounds/bell.oga -------------------------------------------------------------------------------- /docs/rfbproto-3.3.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/novnc/noVNC/HEAD/docs/rfbproto-3.3.pdf -------------------------------------------------------------------------------- /docs/rfbproto-3.7.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/novnc/noVNC/HEAD/docs/rfbproto-3.7.pdf -------------------------------------------------------------------------------- /docs/rfbproto-3.8.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/novnc/noVNC/HEAD/docs/rfbproto-3.8.pdf -------------------------------------------------------------------------------- /app/images/icons/novnc.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/novnc/noVNC/HEAD/app/images/icons/novnc.ico -------------------------------------------------------------------------------- /app/styles/Orbitron700.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/novnc/noVNC/HEAD/app/styles/Orbitron700.ttf -------------------------------------------------------------------------------- /app/styles/Orbitron700.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/novnc/noVNC/HEAD/app/styles/Orbitron700.woff -------------------------------------------------------------------------------- /app/images/icons/novnc-ios-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/novnc/noVNC/HEAD/app/images/icons/novnc-ios-40.png -------------------------------------------------------------------------------- /app/images/icons/novnc-ios-58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/novnc/noVNC/HEAD/app/images/icons/novnc-ios-58.png -------------------------------------------------------------------------------- /app/images/icons/novnc-ios-60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/novnc/noVNC/HEAD/app/images/icons/novnc-ios-60.png -------------------------------------------------------------------------------- /app/images/icons/novnc-ios-80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/novnc/noVNC/HEAD/app/images/icons/novnc-ios-80.png -------------------------------------------------------------------------------- /app/images/icons/novnc-ios-87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/novnc/noVNC/HEAD/app/images/icons/novnc-ios-87.png -------------------------------------------------------------------------------- /app/locale/README: -------------------------------------------------------------------------------- 1 | DO NOT MODIFY THE FILES IN THIS FOLDER, THEY ARE AUTOMATICALLY GENERATED FROM THE PO-FILES. 2 | -------------------------------------------------------------------------------- /app/images/icons/novnc-ios-120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/novnc/noVNC/HEAD/app/images/icons/novnc-ios-120.png -------------------------------------------------------------------------------- /app/images/icons/novnc-ios-152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/novnc/noVNC/HEAD/app/images/icons/novnc-ios-152.png -------------------------------------------------------------------------------- /app/images/icons/novnc-ios-167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/novnc/noVNC/HEAD/app/images/icons/novnc-ios-167.png -------------------------------------------------------------------------------- /app/images/icons/novnc-ios-180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/novnc/noVNC/HEAD/app/images/icons/novnc-ios-180.png -------------------------------------------------------------------------------- /docs/notes: -------------------------------------------------------------------------------- 1 | Rebuilding inflator.js 2 | 3 | - Download pako from npm 4 | - Install browserify using npm 5 | - browserify core/inflator.mod.js -o core/inflator.js -s Inflator 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.o 3 | tests/data_*.js 4 | utils/rebind.so 5 | utils/websockify 6 | /node_modules 7 | /build 8 | /lib 9 | recordings 10 | *.swp 11 | *~ 12 | noVNC-*.tgz 13 | -------------------------------------------------------------------------------- /app/sounds/CREDITS: -------------------------------------------------------------------------------- 1 | bell 2 | Copyright: Dr. Richard Boulanger et al 3 | URL: http://www.archive.org/details/Berklee44v12 4 | License: CC-BY Attribution 3.0 Unported 5 | -------------------------------------------------------------------------------- /docs/flash_policy.txt: -------------------------------------------------------------------------------- 1 | Manual setup: 2 | 3 | DATA="echo \'\'" 4 | /usr/bin/socat -T 1 TCP-L:843,reuseaddr,fork,crlf SYSTEM:"$DATA" 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Question or discussion 4 | url: https://groups.google.com/forum/?fromgroups#!forum/novnc 5 | about: Ask a question or start a discussion 6 | -------------------------------------------------------------------------------- /vendor/pako/README.md: -------------------------------------------------------------------------------- 1 | This is an ES6-modules-compatible version of 2 | https://github.com/nodeca/pako, based on pako version 1.0.3. 3 | 4 | It's more-or-less a direct translation of the original, with unused parts 5 | removed, and the dynamic support for non-typed arrays removed (since ES6 6 | modules don't work well with dynamic exports). 7 | -------------------------------------------------------------------------------- /core/util/int.js: -------------------------------------------------------------------------------- 1 | /* 2 | * noVNC: HTML5 VNC client 3 | * Copyright (C) 2020 The noVNC authors 4 | * Licensed under MPL 2.0 (see LICENSE.txt) 5 | * 6 | * See README.md for usage and integration instructions. 7 | */ 8 | 9 | export function toUnsigned32bit(toConvert) { 10 | return toConvert >>> 0; 11 | } 12 | 13 | export function toSigned32bit(toConvert) { 14 | return toConvert | 0; 15 | } 16 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | maintainers: 2 | - Samuel Mannehed for Cendio AB (@samhed) 3 | - Pierre Ossman for Cendio AB (@CendioOssman) 4 | maintainersEmeritus: 5 | - Joel Martin (@kanaka) 6 | - Solly Ross (@directxman12) 7 | - @astrand 8 | contributors: 9 | # There are a bunch of people that should be here. 10 | # If you want to be on this list, feel free send a PR 11 | # to add yourself. 12 | - jalf 13 | - NTT corp. 14 | -------------------------------------------------------------------------------- /.github/workflows/translate.yml: -------------------------------------------------------------------------------- 1 | name: Translate 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | translate: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v4 10 | - uses: actions/setup-node@v4 11 | - run: npm update 12 | - run: sudo apt-get install gettext 13 | - run: make -C po update-pot 14 | - run: make -C po update-po 15 | - run: make -C po update-js 16 | -------------------------------------------------------------------------------- /utils/b64-to-binary.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | use MIME::Base64; 3 | 4 | for (<>) { 5 | unless (/^'([{}])(\d+)\1(.+?)',$/) { 6 | print; 7 | next; 8 | } 9 | 10 | my ($dir, $amt, $b64) = ($1, $2, $3); 11 | 12 | my $decoded = MIME::Base64::decode($b64) or die "Could not base64-decode line `$_`"; 13 | 14 | my $decoded_escaped = join "", map { "\\x$_" } unpack("(H2)*", $decoded); 15 | 16 | print "'${dir}${amt}${dir}${decoded_escaped}',\n"; 17 | } 18 | -------------------------------------------------------------------------------- /tests/test.int.js: -------------------------------------------------------------------------------- 1 | import { toUnsigned32bit, toSigned32bit } from '../core/util/int.js'; 2 | 3 | describe('Integer casting', function () { 4 | it('should cast unsigned to signed', function () { 5 | let expected = 4294967286; 6 | expect(toUnsigned32bit(-10)).to.equal(expected); 7 | }); 8 | 9 | it('should cast signed to unsigned', function () { 10 | let expected = -10; 11 | expect(toSigned32bit(4294967286)).to.equal(expected); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | eslint: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v4 10 | - uses: actions/setup-node@v4 11 | - run: npm update 12 | - run: npm run lint 13 | html: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | - uses: actions/setup-node@v4 18 | - run: npm update 19 | - run: git ls-tree --name-only -r HEAD | grep -E "[.](html|css)$" | xargs ./utils/validate 20 | -------------------------------------------------------------------------------- /utils/README.md: -------------------------------------------------------------------------------- 1 | ## WebSockets Proxy/Bridge 2 | 3 | Websockify has been forked out into its own project. `novnc_proxy` will 4 | automatically download it here if it is not already present and not 5 | installed as system-wide. 6 | 7 | For more detailed description and usage information please refer to 8 | the [websockify README](https://github.com/novnc/websockify/blob/master/README.md). 9 | 10 | The other versions of websockify (C, Node.js) and the associated test 11 | programs have been moved to 12 | [websockify](https://github.com/novnc/websockify). Websockify was 13 | formerly named wsproxy. 14 | 15 | -------------------------------------------------------------------------------- /vendor/pako/lib/zlib/messages.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 2: 'need dictionary', /* Z_NEED_DICT 2 */ 3 | 1: 'stream end', /* Z_STREAM_END 1 */ 4 | 0: '', /* Z_OK 0 */ 5 | '-1': 'file error', /* Z_ERRNO (-1) */ 6 | '-2': 'stream error', /* Z_STREAM_ERROR (-2) */ 7 | '-3': 'data error', /* Z_DATA_ERROR (-3) */ 8 | '-4': 'insufficient memory', /* Z_MEM_ERROR (-4) */ 9 | '-5': 'buffer error', /* Z_BUF_ERROR (-5) */ 10 | '-6': 'incompatible version' /* Z_VERSION_ERROR (-6) */ 11 | }; 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | **Is your feature request related to a problem? Please describe.** 8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Describe alternatives you've considered** 14 | A clear and concise description of any alternative solutions or features you've considered. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. 18 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | strategy: 8 | matrix: 9 | os: 10 | - ubuntu-latest 11 | - windows-latest 12 | browser: 13 | - ChromeHeadless 14 | - FirefoxHeadless 15 | include: 16 | - os: macos-latest 17 | browser: Safari 18 | - os: windows-latest 19 | browser: EdgeHeadless 20 | fail-fast: false 21 | runs-on: ${{ matrix.os }} 22 | steps: 23 | - uses: actions/checkout@v4 24 | - uses: actions/setup-node@v4 25 | - run: npm update 26 | - run: npm run test 27 | env: 28 | TEST_BROWSER_NAME: ${{ matrix.browser }} 29 | -------------------------------------------------------------------------------- /core/decoders/copyrect.js: -------------------------------------------------------------------------------- 1 | /* 2 | * noVNC: HTML5 VNC client 3 | * Copyright (C) 2019 The noVNC authors 4 | * Licensed under MPL 2.0 (see LICENSE.txt) 5 | * 6 | * See README.md for usage and integration instructions. 7 | * 8 | */ 9 | 10 | export default class CopyRectDecoder { 11 | decodeRect(x, y, width, height, sock, display, depth) { 12 | if (sock.rQwait("COPYRECT", 4)) { 13 | return false; 14 | } 15 | 16 | let deltaX = sock.rQshift16(); 17 | let deltaY = sock.rQshift16(); 18 | 19 | if ((width === 0) || (height === 0)) { 20 | return true; 21 | } 22 | 23 | display.copyImage(deltaX, deltaY, x, y, width, height); 24 | 25 | return true; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/vnc_playback.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | VNC playback 5 | 6 | 7 | 8 | 9 | Iterations:   10 | Perftest:  11 | Realtime:   12 | 13 |   14 | 15 |

16 | 17 | Results:
18 | 19 | 20 |

21 | 22 |
23 |
Loading
24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /vendor/pako/lib/zlib/adler32.js: -------------------------------------------------------------------------------- 1 | // Note: adler32 takes 12% for level 0 and 2% for level 6. 2 | // It doesn't worth to make additional optimizationa as in original. 3 | // Small size is preferable. 4 | 5 | export default function adler32(adler, buf, len, pos) { 6 | var s1 = (adler & 0xffff) |0, 7 | s2 = ((adler >>> 16) & 0xffff) |0, 8 | n = 0; 9 | 10 | while (len !== 0) { 11 | // Set limit ~ twice less than 5552, to keep 12 | // s2 in 31-bits, because we force signed ints. 13 | // in other case %= will fail. 14 | n = len > 2000 ? 2000 : len; 15 | len -= n; 16 | 17 | do { 18 | s1 = (s1 + buf[pos++]) |0; 19 | s2 = (s2 + s1) |0; 20 | } while (--n); 21 | 22 | s1 %= 65521; 23 | s2 %= 65521; 24 | } 25 | 26 | return (s1 | (s2 << 16)) |0; 27 | } 28 | -------------------------------------------------------------------------------- /core/decoders/tightpng.js: -------------------------------------------------------------------------------- 1 | /* 2 | * noVNC: HTML5 VNC client 3 | * Copyright (C) 2019 The noVNC authors 4 | * Licensed under MPL 2.0 (see LICENSE.txt) 5 | * 6 | * See README.md for usage and integration instructions. 7 | * 8 | */ 9 | 10 | import TightDecoder from './tight.js'; 11 | 12 | export default class TightPNGDecoder extends TightDecoder { 13 | _pngRect(x, y, width, height, sock, display, depth) { 14 | let data = this._readData(sock); 15 | if (data === null) { 16 | return false; 17 | } 18 | 19 | display.imageRect(x, y, width, height, "image/png", data); 20 | 21 | return true; 22 | } 23 | 24 | _basicRect(ctl, x, y, width, height, sock, display, depth) { 25 | throw new Error("BasicCompression received in TightPNG rect"); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /core/util/strings.js: -------------------------------------------------------------------------------- 1 | /* 2 | * noVNC: HTML5 VNC client 3 | * Copyright (C) 2019 The noVNC authors 4 | * Licensed under MPL 2.0 (see LICENSE.txt) 5 | * 6 | * See README.md for usage and integration instructions. 7 | */ 8 | 9 | // Decode from UTF-8 10 | export function decodeUTF8(utf8string, allowLatin1=false) { 11 | try { 12 | return decodeURIComponent(escape(utf8string)); 13 | } catch (e) { 14 | if (e instanceof URIError) { 15 | if (allowLatin1) { 16 | // If we allow Latin1 we can ignore any decoding fails 17 | // and in these cases return the original string 18 | return utf8string; 19 | } 20 | } 21 | throw e; 22 | } 23 | } 24 | 25 | // Encode to UTF-8 26 | export function encodeUTF8(DOMString) { 27 | return unescape(encodeURIComponent(DOMString)); 28 | } 29 | -------------------------------------------------------------------------------- /core/util/element.js: -------------------------------------------------------------------------------- 1 | /* 2 | * noVNC: HTML5 VNC client 3 | * Copyright (C) 2020 The noVNC authors 4 | * Licensed under MPL 2.0 (see LICENSE.txt) 5 | * 6 | * See README.md for usage and integration instructions. 7 | */ 8 | 9 | /* 10 | * HTML element utility functions 11 | */ 12 | 13 | export function clientToElement(x, y, elem) { 14 | const bounds = elem.getBoundingClientRect(); 15 | let pos = { x: 0, y: 0 }; 16 | // Clip to target bounds 17 | if (x < bounds.left) { 18 | pos.x = 0; 19 | } else if (x >= bounds.right) { 20 | pos.x = bounds.width - 1; 21 | } else { 22 | pos.x = x - bounds.left; 23 | } 24 | if (y < bounds.top) { 25 | pos.y = 0; 26 | } else if (y >= bounds.bottom) { 27 | pos.y = bounds.height - 1; 28 | } else { 29 | pos.y = y - bounds.top; 30 | } 31 | return pos; 32 | } 33 | -------------------------------------------------------------------------------- /app/styles/constants.css: -------------------------------------------------------------------------------- 1 | /* 2 | * noVNC general CSS constant variables 3 | * Copyright (C) 2025 The noVNC authors 4 | * noVNC is licensed under the MPL 2.0 (see LICENSE.txt) 5 | * This file is licensed under the 2-Clause BSD license (see LICENSE.txt). 6 | */ 7 | 8 | /* ---------- COLORS ----------- */ 9 | 10 | :root { 11 | --novnc-grey: rgb(128, 128, 128); 12 | --novnc-lightgrey: rgb(192, 192, 192); 13 | --novnc-darkgrey: rgb(92, 92, 92); 14 | 15 | /* Transparent to make button colors adapt to the background */ 16 | --novnc-buttongrey: rgba(192, 192, 192, 0.5); 17 | 18 | --novnc-blue: rgb(110, 132, 163); 19 | --novnc-lightblue: rgb(74, 144, 217); 20 | --novnc-darkblue: rgb(83, 99, 122); 21 | 22 | --novnc-green: rgb(0, 128, 0); 23 | --novnc-yellow: rgb(255, 255, 0); 24 | } 25 | 26 | /* ------ MISC PROPERTIES ------ */ 27 | 28 | :root { 29 | --input-xpadding: 1em; 30 | } 31 | -------------------------------------------------------------------------------- /vendor/pako/lib/zlib/crc32.js: -------------------------------------------------------------------------------- 1 | // Note: we can't get significant speed boost here. 2 | // So write code to minimize size - no pregenerated tables 3 | // and array tools dependencies. 4 | 5 | 6 | // Use ordinary array, since untyped makes no boost here 7 | export default function makeTable() { 8 | var c, table = []; 9 | 10 | for (var n = 0; n < 256; n++) { 11 | c = n; 12 | for (var k = 0; k < 8; k++) { 13 | c = ((c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1)); 14 | } 15 | table[n] = c; 16 | } 17 | 18 | return table; 19 | } 20 | 21 | // Create table on load. Just 255 signed longs. Not a problem. 22 | var crcTable = makeTable(); 23 | 24 | 25 | function crc32(crc, buf, len, pos) { 26 | var t = crcTable, 27 | end = pos + len; 28 | 29 | crc ^= -1; 30 | 31 | for (var i = pos; i < end; i++) { 32 | crc = (crc >>> 8) ^ t[(crc ^ buf[i]) & 0xFF]; 33 | } 34 | 35 | return (crc ^ (-1)); // >>> 0; 36 | } 37 | -------------------------------------------------------------------------------- /vendor/pako/lib/zlib/zstream.js: -------------------------------------------------------------------------------- 1 | export default function ZStream() { 2 | /* next input byte */ 3 | this.input = null; // JS specific, because we have no pointers 4 | this.next_in = 0; 5 | /* number of bytes available at input */ 6 | this.avail_in = 0; 7 | /* total number of input bytes read so far */ 8 | this.total_in = 0; 9 | /* next output byte should be put there */ 10 | this.output = null; // JS specific, because we have no pointers 11 | this.next_out = 0; 12 | /* remaining free space at output */ 13 | this.avail_out = 0; 14 | /* total number of bytes output so far */ 15 | this.total_out = 0; 16 | /* last error message, NULL if no error */ 17 | this.msg = ''/*Z_NULL*/; 18 | /* not visible by applications */ 19 | this.state = null; 20 | /* best guess about the data type: binary or text */ 21 | this.data_type = 2/*Z_UNKNOWN*/; 22 | /* adler32 value of the uncompressed data */ 23 | this.adler = 0; 24 | } 25 | -------------------------------------------------------------------------------- /core/crypto/bigint.js: -------------------------------------------------------------------------------- 1 | export function modPow(b, e, m) { 2 | let r = 1n; 3 | b = b % m; 4 | while (e > 0n) { 5 | if ((e & 1n) === 1n) { 6 | r = (r * b) % m; 7 | } 8 | e = e >> 1n; 9 | b = (b * b) % m; 10 | } 11 | return r; 12 | } 13 | 14 | export function bigIntToU8Array(bigint, padLength=0) { 15 | let hex = bigint.toString(16); 16 | if (padLength === 0) { 17 | padLength = Math.ceil(hex.length / 2); 18 | } 19 | hex = hex.padStart(padLength * 2, '0'); 20 | const length = hex.length / 2; 21 | const arr = new Uint8Array(length); 22 | for (let i = 0; i < length; i++) { 23 | arr[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16); 24 | } 25 | return arr; 26 | } 27 | 28 | export function u8ArrayToBigInt(arr) { 29 | let hex = '0x'; 30 | for (let i = 0; i < arr.length; i++) { 31 | hex += arr[i].toString(16).padStart(2, '0'); 32 | } 33 | return BigInt(hex); 34 | } 35 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | **Describe the bug** 8 | A clear and concise description of what the bug is. 9 | 10 | **To reproduce** 11 | Steps to reproduce the behavior: 12 | 1. Go to '...' 13 | 2. Click on '....' 14 | 3. Scroll down to '....' 15 | 4. See error 16 | 17 | **Expected behavior** 18 | A clear and concise description of what you expected to happen. 19 | 20 | **Screenshots** 21 | If applicable, add screenshots to help explain your problem. 22 | 23 | **Client (please complete the following information):** 24 | - OS: [e.g. iOS] 25 | - Browser: [e.g. chrome, safari] 26 | - Browser version: [e.g. 22] 27 | 28 | **Server (please complete the following information):** 29 | - noVNC version: [e.g. 1.0.0 or git commit id] 30 | - VNC server: [e.g. QEMU, TigerVNC] 31 | - WebSocket proxy: [e.g. websockify] 32 | 33 | **Additional context** 34 | Add any other context about the problem here. 35 | -------------------------------------------------------------------------------- /utils/u2x11: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Convert "U+..." commented entries in /usr/include/X11/keysymdef.h 4 | # into JavaScript for use by noVNC. Note this is likely to produce 5 | # a few duplicate properties with clashing values, that will need 6 | # resolving manually. 7 | # 8 | # Colin Dean 9 | # 10 | 11 | regex="^#define[ \t]+XK_[A-Za-z0-9_]+[ \t]+0x([0-9a-fA-F]+)[ \t]+\/\*[ \t]+U\+([0-9a-fA-F]+)[ \t]+[^*]+.[ \t]+\*\/[ \t]*$" 12 | echo "unicodeTable = {" 13 | while read line; do 14 | if echo "${line}" | egrep -qs "${regex}"; then 15 | 16 | x11=$(echo "${line}" | sed -r "s/${regex}/\1/") 17 | vnc=$(echo "${line}" | sed -r "s/${regex}/\2/") 18 | 19 | if echo "${vnc}" | egrep -qs "^00[2-9A-F][0-9A-F]$"; then 20 | : # skip ISO Latin-1 (U+0020 to U+00FF) as 1-to-1 mapping 21 | else 22 | # note 1-to-1 is possible (e.g. for Euro symbol, U+20AC) 23 | echo " 0x${vnc} : 0x${x11}," 24 | fi 25 | fi 26 | done < /usr/include/X11/keysymdef.h | uniq 27 | echo "};" 28 | 29 | -------------------------------------------------------------------------------- /core/util/eventtarget.js: -------------------------------------------------------------------------------- 1 | /* 2 | * noVNC: HTML5 VNC client 3 | * Copyright (C) 2019 The noVNC authors 4 | * Licensed under MPL 2.0 (see LICENSE.txt) 5 | * 6 | * See README.md for usage and integration instructions. 7 | */ 8 | 9 | export default class EventTargetMixin { 10 | constructor() { 11 | this._listeners = new Map(); 12 | } 13 | 14 | addEventListener(type, callback) { 15 | if (!this._listeners.has(type)) { 16 | this._listeners.set(type, new Set()); 17 | } 18 | this._listeners.get(type).add(callback); 19 | } 20 | 21 | removeEventListener(type, callback) { 22 | if (this._listeners.has(type)) { 23 | this._listeners.get(type).delete(callback); 24 | } 25 | } 26 | 27 | dispatchEvent(event) { 28 | if (!this._listeners.has(event.type)) { 29 | return true; 30 | } 31 | this._listeners.get(event.type) 32 | .forEach(callback => callback.call(this, event)); 33 | return !event.defaultPrevented; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /po/Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | .PHONY: update-po update-js update-pot 3 | .PHONY: FORCE 4 | 5 | LINGUAS := cs de el es fr hr hu it ja ko nl pl pt_BR ru sv tr zh_CN zh_TW 6 | 7 | VERSION := $(shell grep '"version"' ../package.json | cut -d '"' -f 4) 8 | 9 | POFILES := $(addsuffix .po,$(LINGUAS)) 10 | JSONFILES := $(addprefix ../app/locale/,$(addsuffix .json,$(LINGUAS))) 11 | 12 | update-po: $(POFILES) 13 | update-js: $(JSONFILES) 14 | 15 | %.po: FORCE 16 | msgmerge --update --lang=$* $@ noVNC.pot 17 | ../app/locale/%.json: FORCE 18 | ./po2js $*.po $@ 19 | 20 | update-pot: 21 | xgettext --output=noVNC.js.pot \ 22 | --copyright-holder="The noVNC authors" \ 23 | --package-name="noVNC" \ 24 | --package-version="$(VERSION)" \ 25 | --msgid-bugs-address="novnc@googlegroups.com" \ 26 | --add-comments=TRANSLATORS: \ 27 | --from-code=UTF-8 \ 28 | --sort-by-file \ 29 | ../app/*.js \ 30 | ../core/*.js \ 31 | ../core/input/*.js 32 | ./xgettext-html --output=noVNC.html.pot \ 33 | ../vnc.html 34 | msgcat --output-file=noVNC.pot \ 35 | --sort-by-file noVNC.js.pot noVNC.html.pot 36 | rm -f noVNC.js.pot noVNC.html.pot 37 | -------------------------------------------------------------------------------- /vendor/pako/LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (C) 2014-2016 by Vitaly Puzrin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/LIBRARY.md: -------------------------------------------------------------------------------- 1 | # Using the noVNC JavaScript library 2 | 3 | This document describes how to make use of the noVNC JavaScript library for 4 | integration in your own VNC client application. If you wish to embed the more 5 | complete noVNC application with its included user interface then please see 6 | our [embedding documentation](EMBEDDING.md). 7 | 8 | ## API 9 | 10 | The API of noVNC consists of a single object called `RFB`. The formal 11 | documentation for that object can be found in our [API documentation](API.md). 12 | 13 | ## Example 14 | 15 | noVNC includes a small example application called `vnc_lite.html`. This does 16 | not make use of all the features of noVNC, but is a good start to see how to 17 | do things. 18 | 19 | ## Conversion of modules 20 | 21 | noVNC is written using ECMAScript 6 modules. This is not supported by older 22 | versions of Node.js. To use noVNC with those older versions of Node.js the 23 | library must first be converted. 24 | 25 | Fortunately noVNC includes a script to handle this conversion. Please follow 26 | the following steps: 27 | 28 | 1. Install Node.js 29 | 2. Run `npm install` in the noVNC directory 30 | 31 | The result of the conversion is available in the `lib/` directory. 32 | -------------------------------------------------------------------------------- /vendor/pako/lib/utils/common.js: -------------------------------------------------------------------------------- 1 | // reduce buffer size, avoiding mem copy 2 | export function shrinkBuf (buf, size) { 3 | if (buf.length === size) { return buf; } 4 | if (buf.subarray) { return buf.subarray(0, size); } 5 | buf.length = size; 6 | return buf; 7 | }; 8 | 9 | 10 | export function arraySet (dest, src, src_offs, len, dest_offs) { 11 | if (src.subarray && dest.subarray) { 12 | dest.set(src.subarray(src_offs, src_offs + len), dest_offs); 13 | return; 14 | } 15 | // Fallback to ordinary array 16 | for (var i = 0; i < len; i++) { 17 | dest[dest_offs + i] = src[src_offs + i]; 18 | } 19 | } 20 | 21 | // Join array of chunks to single array. 22 | export function flattenChunks (chunks) { 23 | var i, l, len, pos, chunk, result; 24 | 25 | // calculate data length 26 | len = 0; 27 | for (i = 0, l = chunks.length; i < l; i++) { 28 | len += chunks[i].length; 29 | } 30 | 31 | // join chunks 32 | result = new Uint8Array(len); 33 | pos = 0; 34 | for (i = 0, l = chunks.length; i < l; i++) { 35 | chunk = chunks[i]; 36 | result.set(chunk, pos); 37 | pos += chunk.length; 38 | } 39 | 40 | return result; 41 | } 42 | 43 | export var Buf8 = Uint8Array; 44 | export var Buf16 = Uint16Array; 45 | export var Buf32 = Int32Array; 46 | -------------------------------------------------------------------------------- /core/decoders/rre.js: -------------------------------------------------------------------------------- 1 | /* 2 | * noVNC: HTML5 VNC client 3 | * Copyright (C) 2019 The noVNC authors 4 | * Licensed under MPL 2.0 (see LICENSE.txt) 5 | * 6 | * See README.md for usage and integration instructions. 7 | * 8 | */ 9 | 10 | export default class RREDecoder { 11 | constructor() { 12 | this._subrects = 0; 13 | } 14 | 15 | decodeRect(x, y, width, height, sock, display, depth) { 16 | if (this._subrects === 0) { 17 | if (sock.rQwait("RRE", 4 + 4)) { 18 | return false; 19 | } 20 | 21 | this._subrects = sock.rQshift32(); 22 | 23 | let color = sock.rQshiftBytes(4); // Background 24 | display.fillRect(x, y, width, height, color); 25 | } 26 | 27 | while (this._subrects > 0) { 28 | if (sock.rQwait("RRE", 4 + 8)) { 29 | return false; 30 | } 31 | 32 | let color = sock.rQshiftBytes(4); 33 | let sx = sock.rQshift16(); 34 | let sy = sock.rQshift16(); 35 | let swidth = sock.rQshift16(); 36 | let sheight = sock.rQshift16(); 37 | display.fillRect(x + sx, y + sy, swidth, sheight, color); 38 | 39 | this._subrects--; 40 | } 41 | 42 | return true; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /snap/local/svc_wrapper.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # `snapctl get services` returns a JSON array, example: 4 | #{ 5 | #"n6801": { 6 | # "listen": 6801, 7 | # "vnc": "localhost:5901" 8 | #}, 9 | #"n6802": { 10 | # "listen": 6802, 11 | # "vnc": "localhost:5902" 12 | #} 13 | #} 14 | snapctl get services | jq -c '.[]' | while read service; do # for each service the user specified.. 15 | # get the important data for the service (listen port, VNC host:port) 16 | listen_port="$(echo $service | jq --raw-output '.listen')" 17 | vnc_host_port="$(echo $service | jq --raw-output '.vnc')" # --raw-output removes any quotation marks from the output 18 | 19 | # check whether those values are valid 20 | expr "$listen_port" : '^[0-9]\+$' > /dev/null 21 | listen_port_valid=$? 22 | if [ ! $listen_port_valid ] || [ -z "$vnc_host_port" ]; then 23 | # invalid values mean the service is disabled, do nothing except for printing a message (logged in /var/log/system or systemd journal) 24 | echo "novnc: not starting service ${service} with listen_port ${listen_port} and vnc_host_port ${vnc_host_port}" 25 | else 26 | # start (and fork with '&') the service using the specified listen port and VNC host:port 27 | $SNAP/novnc_proxy --listen $listen_port --vnc $vnc_host_port & 28 | fi 29 | done 30 | -------------------------------------------------------------------------------- /tests/test.base64.js: -------------------------------------------------------------------------------- 1 | import Base64 from '../core/base64.js'; 2 | 3 | describe('Base64 tools', function () { 4 | "use strict"; 5 | 6 | const BIN_ARR = new Array(256); 7 | for (let i = 0; i < 256; i++) { 8 | BIN_ARR[i] = i; 9 | } 10 | 11 | const B64_STR = "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w=="; 12 | 13 | 14 | describe('encode', function () { 15 | it('should encode a binary string into Base64', function () { 16 | const encoded = Base64.encode(BIN_ARR); 17 | expect(encoded).to.equal(B64_STR); 18 | }); 19 | }); 20 | 21 | describe('decode', function () { 22 | it('should decode a Base64 string into a normal string', function () { 23 | const decoded = Base64.decode(B64_STR); 24 | expect(decoded).to.deep.equal(BIN_ARR); 25 | }); 26 | 27 | it('should throw an error if we have extra characters at the end of the string', function () { 28 | expect(() => Base64.decode(B64_STR+'abcdef')).to.throw(Error); 29 | }); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /docs/LICENSE.BSD-2-Clause: -------------------------------------------------------------------------------- 1 | Copyright (c) , 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 17 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | -------------------------------------------------------------------------------- /vendor/pako/lib/zlib/gzheader.js: -------------------------------------------------------------------------------- 1 | export default function GZheader() { 2 | /* true if compressed data believed to be text */ 3 | this.text = 0; 4 | /* modification time */ 5 | this.time = 0; 6 | /* extra flags (not used when writing a gzip file) */ 7 | this.xflags = 0; 8 | /* operating system */ 9 | this.os = 0; 10 | /* pointer to extra field or Z_NULL if none */ 11 | this.extra = null; 12 | /* extra field length (valid if extra != Z_NULL) */ 13 | this.extra_len = 0; // Actually, we don't need it in JS, 14 | // but leave for few code modifications 15 | 16 | // 17 | // Setup limits is not necessary because in js we should not preallocate memory 18 | // for inflate use constant limit in 65536 bytes 19 | // 20 | 21 | /* space at extra (only when reading header) */ 22 | // this.extra_max = 0; 23 | /* pointer to zero-terminated file name or Z_NULL */ 24 | this.name = ''; 25 | /* space at name (only when reading header) */ 26 | // this.name_max = 0; 27 | /* pointer to zero-terminated comment or Z_NULL */ 28 | this.comment = ''; 29 | /* space at comment (only when reading header) */ 30 | // this.comm_max = 0; 31 | /* true if there was or will be a header crc */ 32 | this.hcrc = 0; 33 | /* true when done reading gzip header (not used when writing a gzip file) */ 34 | this.done = false; 35 | } 36 | -------------------------------------------------------------------------------- /core/decoders/zlib.js: -------------------------------------------------------------------------------- 1 | /* 2 | * noVNC: HTML5 VNC client 3 | * Copyright (C) 2024 The noVNC authors 4 | * Licensed under MPL 2.0 (see LICENSE.txt) 5 | * 6 | * See README.md for usage and integration instructions. 7 | * 8 | */ 9 | 10 | import Inflator from "../inflator.js"; 11 | 12 | export default class ZlibDecoder { 13 | constructor() { 14 | this._zlib = new Inflator(); 15 | this._length = 0; 16 | } 17 | 18 | decodeRect(x, y, width, height, sock, display, depth) { 19 | if ((width === 0) || (height === 0)) { 20 | return true; 21 | } 22 | 23 | if (this._length === 0) { 24 | if (sock.rQwait("ZLIB", 4)) { 25 | return false; 26 | } 27 | 28 | this._length = sock.rQshift32(); 29 | } 30 | 31 | if (sock.rQwait("ZLIB", this._length)) { 32 | return false; 33 | } 34 | 35 | let data = new Uint8Array(sock.rQshiftBytes(this._length, false)); 36 | this._length = 0; 37 | 38 | this._zlib.setInput(data); 39 | data = this._zlib.inflate(width * height * 4); 40 | this._zlib.setInput(null); 41 | 42 | // Max sure the image is fully opaque 43 | for (let i = 0; i < width * height; i++) { 44 | data[i * 4 + 3] = 255; 45 | } 46 | 47 | display.blitImage(x, y, width, height, data, 0); 48 | 49 | return true; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/images/icons/Makefile: -------------------------------------------------------------------------------- 1 | BROWSER_SIZES := 16 24 32 48 64 2 | #ANDROID_SIZES := 72 96 144 192 3 | # FIXME: The ICO is limited to 8 icons due to a Chrome bug: 4 | # https://bugs.chromium.org/p/chromium/issues/detail?id=1381393 5 | ANDROID_SIZES := 96 144 192 6 | WEB_ICON_SIZES := $(BROWSER_SIZES) $(ANDROID_SIZES) 7 | 8 | #IOS_1X_SIZES := 20 29 40 76 # No such devices exist anymore 9 | IOS_2X_SIZES := 40 58 80 120 152 167 10 | IOS_3X_SIZES := 60 87 120 180 11 | ALL_IOS_SIZES := $(IOS_1X_SIZES) $(IOS_2X_SIZES) $(IOS_3X_SIZES) 12 | 13 | ALL_ICONS := \ 14 | $(ALL_IOS_SIZES:%=novnc-ios-%.png) \ 15 | novnc.ico 16 | 17 | all: $(ALL_ICONS) 18 | 19 | # Our testing shows that the ICO file need to be sorted in largest to 20 | # smallest to get the apporpriate behviour 21 | WEB_ICON_SIZES_REVERSE := $(shell echo $(WEB_ICON_SIZES) | tr ' ' '\n' | sort -nr | tr '\n' ' ') 22 | WEB_BASE_ICONS := $(WEB_ICON_SIZES_REVERSE:%=novnc-%.png) 23 | .INTERMEDIATE: $(WEB_BASE_ICONS) 24 | 25 | novnc.ico: $(WEB_BASE_ICONS) 26 | convert $(WEB_BASE_ICONS) "$@" 27 | 28 | # General conversion 29 | novnc-%.png: novnc-icon.svg 30 | convert -depth 8 -background transparent \ 31 | -size $*x$* "$(lastword $^)" "$@" 32 | 33 | # iOS icons use their own SVG 34 | novnc-ios-%.png: novnc-ios-icon.svg 35 | convert -depth 8 -background transparent \ 36 | -size $*x$* "$(lastword $^)" "$@" 37 | 38 | # The smallest sizes are generated using a different SVG 39 | novnc-16.png novnc-24.png novnc-32.png: novnc-icon-sm.svg 40 | 41 | clean: 42 | rm -f *.png 43 | -------------------------------------------------------------------------------- /vendor/pako/lib/zlib/constants.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 3 | /* Allowed flush values; see deflate() and inflate() below for details */ 4 | Z_NO_FLUSH: 0, 5 | Z_PARTIAL_FLUSH: 1, 6 | Z_SYNC_FLUSH: 2, 7 | Z_FULL_FLUSH: 3, 8 | Z_FINISH: 4, 9 | Z_BLOCK: 5, 10 | Z_TREES: 6, 11 | 12 | /* Return codes for the compression/decompression functions. Negative values 13 | * are errors, positive values are used for special but normal events. 14 | */ 15 | Z_OK: 0, 16 | Z_STREAM_END: 1, 17 | Z_NEED_DICT: 2, 18 | Z_ERRNO: -1, 19 | Z_STREAM_ERROR: -2, 20 | Z_DATA_ERROR: -3, 21 | //Z_MEM_ERROR: -4, 22 | Z_BUF_ERROR: -5, 23 | //Z_VERSION_ERROR: -6, 24 | 25 | /* compression levels */ 26 | Z_NO_COMPRESSION: 0, 27 | Z_BEST_SPEED: 1, 28 | Z_BEST_COMPRESSION: 9, 29 | Z_DEFAULT_COMPRESSION: -1, 30 | 31 | 32 | Z_FILTERED: 1, 33 | Z_HUFFMAN_ONLY: 2, 34 | Z_RLE: 3, 35 | Z_FIXED: 4, 36 | Z_DEFAULT_STRATEGY: 0, 37 | 38 | /* Possible values of the data_type field (though see inflate()) */ 39 | Z_BINARY: 0, 40 | Z_TEXT: 1, 41 | //Z_ASCII: 1, // = Z_TEXT (deprecated) 42 | Z_UNKNOWN: 2, 43 | 44 | /* The deflate compression method */ 45 | Z_DEFLATED: 8 46 | //Z_NULL: null // Use -1 or null inline, depending on var type 47 | }; 48 | -------------------------------------------------------------------------------- /po/po2js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* 3 | * ps2js: gettext .po to noVNC .js converter 4 | * Copyright (C) 2018 The noVNC authors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | import { program } from 'commander'; 21 | import fs from 'fs'; 22 | import pofile from "pofile"; 23 | 24 | program 25 | .argument('') 26 | .argument('') 27 | .parse(process.argv); 28 | 29 | let data = fs.readFileSync(program.args[0], "utf8"); 30 | let po = pofile.parse(data); 31 | 32 | const bodyPart = po.items 33 | .filter(item => item.msgid !== "") 34 | .filter(item => item.msgstr[0] !== "") 35 | .filter(item => !item.flags.fuzzy) 36 | .filter(item => !item.obsolete) 37 | .map(item => " " + JSON.stringify(item.msgid) + ": " + JSON.stringify(item.msgstr[0])) 38 | .join(",\n"); 39 | 40 | const output = "{\n" + bodyPart + "\n}"; 41 | 42 | fs.writeFileSync(program.args[1], output); 43 | -------------------------------------------------------------------------------- /core/util/logging.js: -------------------------------------------------------------------------------- 1 | /* 2 | * noVNC: HTML5 VNC client 3 | * Copyright (C) 2019 The noVNC authors 4 | * Licensed under MPL 2.0 (see LICENSE.txt) 5 | * 6 | * See README.md for usage and integration instructions. 7 | */ 8 | 9 | /* 10 | * Logging/debug routines 11 | */ 12 | 13 | let _logLevel = 'warn'; 14 | 15 | let Debug = () => {}; 16 | let Info = () => {}; 17 | let Warn = () => {}; 18 | let Error = () => {}; 19 | 20 | export function initLogging(level) { 21 | if (typeof level === 'undefined') { 22 | level = _logLevel; 23 | } else { 24 | _logLevel = level; 25 | } 26 | 27 | Debug = Info = Warn = Error = () => {}; 28 | 29 | if (typeof window.console !== "undefined") { 30 | /* eslint-disable no-console, no-fallthrough */ 31 | switch (level) { 32 | case 'debug': 33 | Debug = console.debug.bind(window.console); 34 | case 'info': 35 | Info = console.info.bind(window.console); 36 | case 'warn': 37 | Warn = console.warn.bind(window.console); 38 | case 'error': 39 | Error = console.error.bind(window.console); 40 | case 'none': 41 | break; 42 | default: 43 | throw new window.Error("invalid logging type '" + level + "'"); 44 | } 45 | /* eslint-enable no-console, no-fallthrough */ 46 | } 47 | } 48 | 49 | export function getLogging() { 50 | return _logLevel; 51 | } 52 | 53 | export { Debug, Info, Warn, Error }; 54 | 55 | // Initialize logging level 56 | initLogging(); 57 | -------------------------------------------------------------------------------- /docs/LICENSE.BSD-3-Clause: -------------------------------------------------------------------------------- 1 | Copyright (c) , 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of the nor the 12 | names of its contributors may be used to endorse or promote products 13 | derived from this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /docs/novnc_proxy.1: -------------------------------------------------------------------------------- 1 | .TH novnc_proxy 1 "June 25, 2020" "version 1.2.0" "USER COMMANDS" 2 | 3 | .SH NAME 4 | novnc_proxy - noVNC proxy server 5 | .SH SYNOPSIS 6 | .B novnc_proxy [--listen [HOST:]PORT] [--vnc VNC_HOST:PORT] [--cert CERT] [--ssl-only] 7 | 8 | Starts the WebSockets proxy and a mini-webserver and 9 | provides a cut-and-paste URL to go to. 10 | 11 | --listen [HOST:]PORT Port for proxy/webserver to listen on 12 | Default: 6080 (on all interfaces) 13 | --vnc VNC_HOST:PORT VNC server host:port proxy target 14 | Default: localhost:5900 15 | --cert CERT Path to combined cert/key file, or just 16 | the cert file if used with --key 17 | Default: self.pem 18 | --key KEY Path to key file, when not combined with cert 19 | --web WEB Path to web files (e.g. vnc.html) 20 | Default: ./ 21 | --ssl-only Disable non-https connections. 22 | 23 | --record FILE Record traffic to FILE.session.js 24 | 25 | --syslog SERVER Can be local socket such as /dev/log, or a UDP host:port pair. 26 | 27 | --heartbeat SEC send a ping to the client every SEC seconds 28 | --timeout SEC after SEC seconds exit when not connected 29 | --idle-timeout SEC server exits after SEC seconds if there are no 30 | active connections 31 | 32 | .SH AUTHOR 33 | The noVNC authors 34 | https://github.com/novnc/noVNC 35 | 36 | .SH SEE ALSO 37 | websockify(1), nova-novncproxy(1) 38 | -------------------------------------------------------------------------------- /snap/snapcraft.yaml: -------------------------------------------------------------------------------- 1 | name: novnc 2 | base: core22 # the base snap is the execution environment for this snap 3 | version: git 4 | summary: Open Source VNC client using HTML5 (WebSockets, Canvas) 5 | description: | 6 | Open Source VNC client using HTML5 (WebSockets, Canvas). 7 | noVNC is both a VNC client JavaScript library as well as an 8 | application built on top of that library. noVNC runs well in any 9 | modern browser including mobile browsers (iOS and Android). 10 | 11 | grade: stable 12 | confinement: strict 13 | 14 | parts: 15 | novnc: 16 | source: . 17 | plugin: dump 18 | organize: 19 | utils/novnc_proxy: / 20 | stage: 21 | - vnc.html 22 | - app 23 | - core/**/*.js 24 | - vendor/**/*.js 25 | - novnc_proxy 26 | 27 | novnc-deps: 28 | plugin: nil 29 | stage-packages: 30 | - bash 31 | 32 | svc-script: 33 | source: snap/local 34 | plugin: dump 35 | stage: 36 | - svc_wrapper.sh 37 | 38 | svc-script-deps: 39 | plugin: nil 40 | stage-packages: 41 | - bash 42 | - jq 43 | 44 | websockify: 45 | source: https://github.com/novnc/websockify/archive/v0.13.0.tar.gz 46 | plugin: python 47 | stage-packages: 48 | - python3-numpy 49 | 50 | hooks: 51 | configure: 52 | plugs: [network, network-bind] 53 | 54 | apps: 55 | novnc: 56 | command: ./novnc_proxy 57 | plugs: [network, network-bind] 58 | novncsvc: 59 | command: ./svc_wrapper.sh 60 | daemon: forking 61 | plugs: [network, network-bind] 62 | -------------------------------------------------------------------------------- /core/crypto/dh.js: -------------------------------------------------------------------------------- 1 | import { modPow, bigIntToU8Array, u8ArrayToBigInt } from "./bigint.js"; 2 | 3 | class DHPublicKey { 4 | constructor(key) { 5 | this._key = key; 6 | } 7 | 8 | get algorithm() { 9 | return { name: "DH" }; 10 | } 11 | 12 | exportKey() { 13 | return this._key; 14 | } 15 | } 16 | 17 | export class DHCipher { 18 | constructor() { 19 | this._g = null; 20 | this._p = null; 21 | this._gBigInt = null; 22 | this._pBigInt = null; 23 | this._privateKey = null; 24 | } 25 | 26 | get algorithm() { 27 | return { name: "DH" }; 28 | } 29 | 30 | static generateKey(algorithm, _extractable) { 31 | const cipher = new DHCipher; 32 | cipher._generateKey(algorithm); 33 | return { privateKey: cipher, publicKey: new DHPublicKey(cipher._publicKey) }; 34 | } 35 | 36 | _generateKey(algorithm) { 37 | const g = algorithm.g; 38 | const p = algorithm.p; 39 | this._keyBytes = p.length; 40 | this._gBigInt = u8ArrayToBigInt(g); 41 | this._pBigInt = u8ArrayToBigInt(p); 42 | this._privateKey = window.crypto.getRandomValues(new Uint8Array(this._keyBytes)); 43 | this._privateKeyBigInt = u8ArrayToBigInt(this._privateKey); 44 | this._publicKey = bigIntToU8Array(modPow( 45 | this._gBigInt, this._privateKeyBigInt, this._pBigInt), this._keyBytes); 46 | } 47 | 48 | deriveBits(algorithm, length) { 49 | const bytes = Math.ceil(length / 8); 50 | const pkey = new Uint8Array(algorithm.public); 51 | const len = bytes > this._keyBytes ? bytes : this._keyBytes; 52 | const secret = modPow(u8ArrayToBigInt(pkey), this._privateKeyBigInt, this._pBigInt); 53 | return bigIntToU8Array(secret, len).slice(0, len); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /core/decoders/raw.js: -------------------------------------------------------------------------------- 1 | /* 2 | * noVNC: HTML5 VNC client 3 | * Copyright (C) 2019 The noVNC authors 4 | * Licensed under MPL 2.0 (see LICENSE.txt) 5 | * 6 | * See README.md for usage and integration instructions. 7 | * 8 | */ 9 | 10 | export default class RawDecoder { 11 | constructor() { 12 | this._lines = 0; 13 | } 14 | 15 | decodeRect(x, y, width, height, sock, display, depth) { 16 | if ((width === 0) || (height === 0)) { 17 | return true; 18 | } 19 | 20 | if (this._lines === 0) { 21 | this._lines = height; 22 | } 23 | 24 | const pixelSize = depth == 8 ? 1 : 4; 25 | const bytesPerLine = width * pixelSize; 26 | 27 | while (this._lines > 0) { 28 | if (sock.rQwait("RAW", bytesPerLine)) { 29 | return false; 30 | } 31 | 32 | const curY = y + (height - this._lines); 33 | 34 | let data = sock.rQshiftBytes(bytesPerLine, false); 35 | 36 | // Convert data if needed 37 | if (depth == 8) { 38 | const newdata = new Uint8Array(width * 4); 39 | for (let i = 0; i < width; i++) { 40 | newdata[i * 4 + 0] = ((data[i] >> 0) & 0x3) * 255 / 3; 41 | newdata[i * 4 + 1] = ((data[i] >> 2) & 0x3) * 255 / 3; 42 | newdata[i * 4 + 2] = ((data[i] >> 4) & 0x3) * 255 / 3; 43 | newdata[i * 4 + 3] = 255; 44 | } 45 | data = newdata; 46 | } 47 | 48 | // Max sure the image is fully opaque 49 | for (let i = 0; i < width; i++) { 50 | data[i * 4 + 3] = 255; 51 | } 52 | 53 | display.blitImage(x, curY, width, 1, data, 0); 54 | this._lines--; 55 | } 56 | 57 | return true; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@novnc/novnc", 3 | "version": "1.6.0", 4 | "description": "An HTML5 VNC client", 5 | "type": "module", 6 | "files": [ 7 | "core", 8 | "vendor", 9 | "AUTHORS", 10 | "VERSION", 11 | "docs/API.md", 12 | "docs/LIBRARY.md", 13 | "docs/LICENSE*" 14 | ], 15 | "exports": "./core/rfb.js", 16 | "scripts": { 17 | "lint": "eslint app core po/po2js po/xgettext-html tests utils", 18 | "test": "karma start karma.conf.cjs" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/novnc/noVNC.git" 23 | }, 24 | "author": "Joel Martin (https://github.com/kanaka)", 25 | "contributors": [ 26 | "Samuel Mannehed (https://github.com/samhed)", 27 | "Pierre Ossman (https://github.com/CendioOssman)" 28 | ], 29 | "license": "MPL-2.0", 30 | "bugs": { 31 | "url": "https://github.com/novnc/noVNC/issues" 32 | }, 33 | "homepage": "https://github.com/novnc/noVNC", 34 | "devDependencies": { 35 | "@babel/core": "latest", 36 | "@babel/preset-env": "latest", 37 | "babel-plugin-import-redirect": "latest", 38 | "browserify": "latest", 39 | "chai": "latest", 40 | "commander": "latest", 41 | "eslint": "latest", 42 | "fs-extra": "latest", 43 | "globals": "latest", 44 | "jsdom": "latest", 45 | "karma": "latest", 46 | "karma-mocha": "latest", 47 | "karma-chrome-launcher": "latest", 48 | "@chiragrupani/karma-chromium-edge-launcher": "latest", 49 | "karma-firefox-launcher": "latest", 50 | "karma-ie-launcher": "latest", 51 | "karma-mocha-reporter": "latest", 52 | "karma-safari-launcher": "latest", 53 | "karma-script-launcher": "latest", 54 | "mocha": "latest", 55 | "pofile": "latest", 56 | "sinon": "latest", 57 | "sinon-chai": "latest" 58 | }, 59 | "dependencies": {}, 60 | "keywords": [ 61 | "vnc", 62 | "rfb", 63 | "novnc", 64 | "websockify" 65 | ] 66 | } 67 | -------------------------------------------------------------------------------- /core/encodings.js: -------------------------------------------------------------------------------- 1 | /* 2 | * noVNC: HTML5 VNC client 3 | * Copyright (C) 2019 The noVNC authors 4 | * Licensed under MPL 2.0 (see LICENSE.txt) 5 | * 6 | * See README.md for usage and integration instructions. 7 | */ 8 | 9 | export const encodings = { 10 | encodingRaw: 0, 11 | encodingCopyRect: 1, 12 | encodingRRE: 2, 13 | encodingHextile: 5, 14 | encodingZlib: 6, 15 | encodingTight: 7, 16 | encodingZRLE: 16, 17 | encodingTightPNG: -260, 18 | encodingJPEG: 21, 19 | encodingH264: 50, 20 | 21 | pseudoEncodingQualityLevel9: -23, 22 | pseudoEncodingQualityLevel0: -32, 23 | pseudoEncodingDesktopSize: -223, 24 | pseudoEncodingLastRect: -224, 25 | pseudoEncodingCursor: -239, 26 | pseudoEncodingQEMUExtendedKeyEvent: -258, 27 | pseudoEncodingQEMULedEvent: -261, 28 | pseudoEncodingDesktopName: -307, 29 | pseudoEncodingExtendedDesktopSize: -308, 30 | pseudoEncodingXvp: -309, 31 | pseudoEncodingFence: -312, 32 | pseudoEncodingContinuousUpdates: -313, 33 | pseudoEncodingExtendedMouseButtons: -316, 34 | pseudoEncodingCompressLevel9: -247, 35 | pseudoEncodingCompressLevel0: -256, 36 | pseudoEncodingVMwareCursor: 0x574d5664, 37 | pseudoEncodingExtendedClipboard: 0xc0a1e5ce 38 | }; 39 | 40 | export function encodingName(num) { 41 | switch (num) { 42 | case encodings.encodingRaw: return "Raw"; 43 | case encodings.encodingCopyRect: return "CopyRect"; 44 | case encodings.encodingRRE: return "RRE"; 45 | case encodings.encodingHextile: return "Hextile"; 46 | case encodings.encodingZlib: return "Zlib"; 47 | case encodings.encodingTight: return "Tight"; 48 | case encodings.encodingZRLE: return "ZRLE"; 49 | case encodings.encodingTightPNG: return "TightPNG"; 50 | case encodings.encodingJPEG: return "JPEG"; 51 | case encodings.encodingH264: return "H.264"; 52 | default: return "[unknown encoding " + num + "]"; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /core/inflator.js: -------------------------------------------------------------------------------- 1 | /* 2 | * noVNC: HTML5 VNC client 3 | * Copyright (C) 2020 The noVNC authors 4 | * Licensed under MPL 2.0 (see LICENSE.txt) 5 | * 6 | * See README.md for usage and integration instructions. 7 | */ 8 | 9 | import { inflateInit, inflate, inflateReset } from "../vendor/pako/lib/zlib/inflate.js"; 10 | import ZStream from "../vendor/pako/lib/zlib/zstream.js"; 11 | 12 | export default class Inflate { 13 | constructor() { 14 | this.strm = new ZStream(); 15 | this.chunkSize = 1024 * 10 * 10; 16 | this.strm.output = new Uint8Array(this.chunkSize); 17 | 18 | inflateInit(this.strm); 19 | } 20 | 21 | setInput(data) { 22 | if (!data) { 23 | //FIXME: flush remaining data. 24 | /* eslint-disable camelcase */ 25 | this.strm.input = null; 26 | this.strm.avail_in = 0; 27 | this.strm.next_in = 0; 28 | } else { 29 | this.strm.input = data; 30 | this.strm.avail_in = this.strm.input.length; 31 | this.strm.next_in = 0; 32 | /* eslint-enable camelcase */ 33 | } 34 | } 35 | 36 | inflate(expected) { 37 | // resize our output buffer if it's too small 38 | // (we could just use multiple chunks, but that would cause an extra 39 | // allocation each time to flatten the chunks) 40 | if (expected > this.chunkSize) { 41 | this.chunkSize = expected; 42 | this.strm.output = new Uint8Array(this.chunkSize); 43 | } 44 | 45 | /* eslint-disable camelcase */ 46 | this.strm.next_out = 0; 47 | this.strm.avail_out = expected; 48 | /* eslint-enable camelcase */ 49 | 50 | let ret = inflate(this.strm, 0); // Flush argument not used. 51 | if (ret < 0) { 52 | throw new Error("zlib inflate failed"); 53 | } 54 | 55 | if (this.strm.next_out != expected) { 56 | throw new Error("Incomplete zlib block"); 57 | } 58 | 59 | return new Uint8Array(this.strm.output.buffer, 0, this.strm.next_out); 60 | } 61 | 62 | reset() { 63 | inflateReset(this.strm); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /core/clipboard.js: -------------------------------------------------------------------------------- 1 | /* 2 | * noVNC: HTML5 VNC client 3 | * Copyright (c) 2025 The noVNC authors 4 | * Licensed under MPL 2.0 or any later version (see LICENSE.txt) 5 | */ 6 | 7 | import * as Log from './util/logging.js'; 8 | import { browserAsyncClipboardSupport } from './util/browser.js'; 9 | 10 | export default class AsyncClipboard { 11 | constructor(target) { 12 | this._target = target || null; 13 | 14 | this._isAvailable = null; 15 | 16 | this._eventHandlers = { 17 | 'focus': this._handleFocus.bind(this), 18 | }; 19 | 20 | // ===== EVENT HANDLERS ===== 21 | 22 | this.onpaste = () => {}; 23 | } 24 | 25 | // ===== PRIVATE METHODS ===== 26 | 27 | async _ensureAvailable() { 28 | if (this._isAvailable !== null) return this._isAvailable; 29 | try { 30 | const status = await browserAsyncClipboardSupport(); 31 | this._isAvailable = (status === 'available'); 32 | } catch { 33 | this._isAvailable = false; 34 | } 35 | return this._isAvailable; 36 | } 37 | 38 | async _handleFocus(event) { 39 | if (!(await this._ensureAvailable())) return; 40 | try { 41 | const text = await navigator.clipboard.readText(); 42 | this.onpaste(text); 43 | } catch (error) { 44 | Log.Error("Clipboard read failed: ", error); 45 | } 46 | } 47 | 48 | // ===== PUBLIC METHODS ===== 49 | 50 | writeClipboard(text) { 51 | // Can lazily check cached availability 52 | if (!this._isAvailable) return false; 53 | navigator.clipboard.writeText(text) 54 | .catch(error => Log.Error("Clipboard write failed: ", error)); 55 | return true; 56 | } 57 | 58 | grab() { 59 | if (!this._target) return; 60 | this._ensureAvailable() 61 | .then((isAvailable) => { 62 | if (isAvailable) { 63 | this._target.addEventListener('focus', this._eventHandlers.focus); 64 | } 65 | }); 66 | } 67 | 68 | ungrab() { 69 | if (!this._target) return; 70 | this._target.removeEventListener('focus', this._eventHandlers.focus); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /app/locale/zh_TW.json: -------------------------------------------------------------------------------- 1 | { 2 | "Connecting...": "連線中...", 3 | "Disconnecting...": "正在中斷連線...", 4 | "Reconnecting...": "重新連線中...", 5 | "Internal error": "內部錯誤", 6 | "Must set host": "請提供主機資訊", 7 | "Connected (encrypted) to ": "已加密連線到", 8 | "Connected (unencrypted) to ": "未加密連線到", 9 | "Something went wrong, connection is closed": "發生錯誤,連線已關閉", 10 | "Failed to connect to server": "無法連線到伺服器", 11 | "Disconnected": "連線已中斷", 12 | "New connection has been rejected with reason: ": "連線被拒絕,原因:", 13 | "New connection has been rejected": "連線被拒絕", 14 | "Password is required": "請提供密碼", 15 | "noVNC encountered an error:": "noVNC 遇到一個錯誤:", 16 | "Hide/Show the control bar": "顯示/隱藏控制列", 17 | "Move/Drag viewport": "拖放顯示範圍", 18 | "viewport drag": "顯示範圍拖放", 19 | "Active Mouse Button": "啟用滑鼠按鍵", 20 | "No mousebutton": "無滑鼠按鍵", 21 | "Left mousebutton": "滑鼠左鍵", 22 | "Middle mousebutton": "滑鼠中鍵", 23 | "Right mousebutton": "滑鼠右鍵", 24 | "Keyboard": "鍵盤", 25 | "Show keyboard": "顯示鍵盤", 26 | "Extra keys": "額外按鍵", 27 | "Show extra keys": "顯示額外按鍵", 28 | "Ctrl": "Ctrl", 29 | "Toggle Ctrl": "切換 Ctrl", 30 | "Alt": "Alt", 31 | "Toggle Alt": "切換 Alt", 32 | "Send Tab": "送出 Tab 鍵", 33 | "Tab": "Tab", 34 | "Esc": "Esc", 35 | "Send Escape": "送出 Escape 鍵", 36 | "Ctrl+Alt+Del": "Ctrl-Alt-Del", 37 | "Send Ctrl-Alt-Del": "送出 Ctrl-Alt-Del 快捷鍵", 38 | "Shutdown/Reboot": "關機/重新啟動", 39 | "Shutdown/Reboot...": "關機/重新啟動...", 40 | "Power": "電源", 41 | "Shutdown": "關機", 42 | "Reboot": "重新啟動", 43 | "Reset": "重設", 44 | "Clipboard": "剪貼簿", 45 | "Clear": "清除", 46 | "Fullscreen": "全螢幕", 47 | "Settings": "設定", 48 | "Shared mode": "分享模式", 49 | "View only": "僅檢視", 50 | "Clip to window": "限制/裁切視窗大小", 51 | "Scaling mode:": "縮放模式:", 52 | "None": "無", 53 | "Local scaling": "本機縮放", 54 | "Remote resizing": "遠端調整大小", 55 | "Advanced": "進階", 56 | "Repeater ID:": "中繼站 ID", 57 | "WebSocket": "WebSocket", 58 | "Encrypt": "加密", 59 | "Host:": "主機:", 60 | "Port:": "連接埠:", 61 | "Path:": "路徑:", 62 | "Automatic reconnect": "自動重新連線", 63 | "Reconnect delay (ms):": "重新連線間隔 (ms):", 64 | "Logging:": "日誌級別:", 65 | "Disconnect": "中斷連線", 66 | "Connect": "連線", 67 | "Password:": "密碼:", 68 | "Cancel": "取消" 69 | } -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | noVNC is Copyright (C) 2022 The noVNC authors 2 | (./AUTHORS) 3 | 4 | The noVNC core library files are licensed under the MPL 2.0 (Mozilla 5 | Public License 2.0). The noVNC core library is composed of the 6 | Javascript code necessary for full noVNC operation. This includes (but 7 | is not limited to): 8 | 9 | core/**/*.js 10 | app/*.js 11 | test/playback.js 12 | 13 | The HTML, CSS, font and images files that included with the noVNC 14 | source distibution (or repository) are not considered part of the 15 | noVNC core library and are licensed under more permissive licenses. 16 | The intent is to allow easy integration of noVNC into existing web 17 | sites and web applications. 18 | 19 | The HTML, CSS, font and image files are licensed as follows: 20 | 21 | *.html : 2-Clause BSD license 22 | 23 | app/styles/*.css : 2-Clause BSD license 24 | 25 | app/styles/Orbitron* : SIL Open Font License 1.1 26 | (Copyright 2009 Matt McInerney) 27 | 28 | app/images/ : Creative Commons Attribution-ShareAlike 29 | http://creativecommons.org/licenses/by-sa/3.0/ 30 | 31 | Some portions of noVNC are copyright to their individual authors. 32 | Please refer to the individual source files and/or to the noVNC commit 33 | history: https://github.com/novnc/noVNC/commits/master 34 | 35 | The are several files and projects that have been incorporated into 36 | the noVNC core library. Here is a list of those files and the original 37 | licenses (all MPL 2.0 compatible): 38 | 39 | core/base64.js : MPL 2.0 40 | 41 | core/des.js : Various BSD style licenses 42 | 43 | vendor/pako/ : MIT 44 | 45 | Any other files not mentioned above are typically marked with 46 | a copyright/license header at the top of the file. The default noVNC 47 | license is MPL-2.0. 48 | 49 | The following license texts are included: 50 | 51 | docs/LICENSE.MPL-2.0 52 | docs/LICENSE.OFL-1.1 53 | docs/LICENSE.BSD-3-Clause (New BSD) 54 | docs/LICENSE.BSD-2-Clause (Simplified BSD / FreeBSD) 55 | vendor/pako/LICENSE (MIT) 56 | 57 | Or alternatively the license texts may be found here: 58 | 59 | http://www.mozilla.org/MPL/2.0/ 60 | http://scripts.sil.org/OFL 61 | http://en.wikipedia.org/wiki/BSD_licenses 62 | https://opensource.org/licenses/MIT 63 | -------------------------------------------------------------------------------- /app/locale/ko.json: -------------------------------------------------------------------------------- 1 | { 2 | "Connecting...": "연결중...", 3 | "Disconnecting...": "연결 해제중...", 4 | "Reconnecting...": "재연결중...", 5 | "Internal error": "내부 오류", 6 | "Must set host": "호스트는 설정되어야 합니다.", 7 | "Connected (encrypted) to ": "다음과 (암호화되어) 연결되었습니다:", 8 | "Connected (unencrypted) to ": "다음과 (암호화 없이) 연결되었습니다:", 9 | "Something went wrong, connection is closed": "무언가 잘못되었습니다, 연결이 닫혔습니다.", 10 | "Failed to connect to server": "서버에 연결하지 못했습니다.", 11 | "Disconnected": "연결이 해제되었습니다.", 12 | "New connection has been rejected with reason: ": "새 연결이 다음 이유로 거부되었습니다:", 13 | "New connection has been rejected": "새 연결이 거부되었습니다.", 14 | "Password is required": "비밀번호가 필요합니다.", 15 | "noVNC encountered an error:": "noVNC에 오류가 발생했습니다:", 16 | "Hide/Show the control bar": "컨트롤 바 숨기기/보이기", 17 | "Move/Drag viewport": "움직이기/드래그 뷰포트", 18 | "viewport drag": "뷰포트 드래그", 19 | "Active Mouse Button": "마우스 버튼 활성화", 20 | "No mousebutton": "마우스 버튼 없음", 21 | "Left mousebutton": "왼쪽 마우스 버튼", 22 | "Middle mousebutton": "중간 마우스 버튼", 23 | "Right mousebutton": "오른쪽 마우스 버튼", 24 | "Keyboard": "키보드", 25 | "Show keyboard": "키보드 보이기", 26 | "Extra keys": "기타 키들", 27 | "Show extra keys": "기타 키들 보이기", 28 | "Ctrl": "Ctrl", 29 | "Toggle Ctrl": "Ctrl 켜기/끄기", 30 | "Alt": "Alt", 31 | "Toggle Alt": "Alt 켜기/끄기", 32 | "Send Tab": "Tab 보내기", 33 | "Tab": "Tab", 34 | "Esc": "Esc", 35 | "Send Escape": "Esc 보내기", 36 | "Ctrl+Alt+Del": "Ctrl+Alt+Del", 37 | "Send Ctrl-Alt-Del": "Ctrl+Alt+Del 보내기", 38 | "Shutdown/Reboot": "셧다운/리붓", 39 | "Shutdown/Reboot...": "셧다운/리붓...", 40 | "Power": "전원", 41 | "Shutdown": "셧다운", 42 | "Reboot": "리붓", 43 | "Reset": "리셋", 44 | "Clipboard": "클립보드", 45 | "Clear": "지우기", 46 | "Fullscreen": "전체화면", 47 | "Settings": "설정", 48 | "Shared mode": "공유 모드", 49 | "View only": "보기 전용", 50 | "Clip to window": "창에 클립", 51 | "Scaling mode:": "스케일링 모드:", 52 | "None": "없음", 53 | "Local scaling": "로컬 스케일링", 54 | "Remote resizing": "원격 크기 조절", 55 | "Advanced": "고급", 56 | "Repeater ID:": "중계 ID", 57 | "WebSocket": "웹소켓", 58 | "Encrypt": "암호화", 59 | "Host:": "호스트:", 60 | "Port:": "포트:", 61 | "Path:": "위치:", 62 | "Automatic reconnect": "자동 재연결", 63 | "Reconnect delay (ms):": "재연결 지연 시간 (ms)", 64 | "Logging:": "로깅", 65 | "Disconnect": "연결 해제", 66 | "Connect": "연결", 67 | "Password:": "비밀번호:", 68 | "Send Password": "비밀번호 전송", 69 | "Cancel": "취소" 70 | } -------------------------------------------------------------------------------- /docs/links: -------------------------------------------------------------------------------- 1 | New tight PNG protocol: 2 | http://wiki.qemu.org/VNC_Tight_PNG 3 | http://xf.iksaif.net/blog/index.php?post/2010/06/14/QEMU:-Tight-PNG-and-some-profiling 4 | 5 | RFB protocol and extensions: 6 | http://tigervnc.org/cgi-bin/rfbproto 7 | 8 | Canvas Browser Compatibility: 9 | http://philip.html5.org/tests/canvas/suite/tests/results.html 10 | 11 | WebSockets API standard: 12 | http://www.whatwg.org/specs/web-apps/current-work/complete.html#websocket 13 | http://dev.w3.org/html5/websockets/ 14 | http://www.ietf.org/id/draft-ietf-hybi-thewebsocketprotocol-00.txt 15 | 16 | Browser Keyboard Events detailed: 17 | http://unixpapa.com/js/key.html 18 | 19 | ActionScript (Flash) WebSocket implementation: 20 | http://github.com/gimite/web-socket-js 21 | 22 | ActionScript (Flash) crypto/TLS library: 23 | http://code.google.com/p/as3crypto 24 | http://github.com/lyokato/as3crypto_patched 25 | 26 | TLS Protocol: 27 | http://en.wikipedia.org/wiki/Transport_Layer_Security 28 | 29 | Generate self-signed certificate: 30 | http://docs.python.org/dev/library/ssl.html#certificates 31 | 32 | Cursor appearance/style (for Cursor pseudo-encoding): 33 | http://en.wikipedia.org/wiki/ICO_(file_format) 34 | http://www.daubnet.com/en/file-format-cur 35 | https://developer.mozilla.org/en/Using_URL_values_for_the_cursor_property 36 | http://www.fileformat.info/format/bmp/egff.htm 37 | 38 | Icon/Cursor file format: 39 | http://msdn.microsoft.com/en-us/library/ms997538 40 | http://msdn.microsoft.com/en-us/library/aa921550.aspx 41 | http://msdn.microsoft.com/en-us/library/aa930622.aspx 42 | 43 | 44 | RDP Protocol specification: 45 | http://msdn.microsoft.com/en-us/library/cc240445(v=PROT.10).aspx 46 | 47 | 48 | Related projects: 49 | 50 | guacamole: http://guacamole.sourceforge.net/ 51 | 52 | - Web client, but Java servlet does pre-processing 53 | 54 | jsvnc: http://code.google.com/p/jsvnc/ 55 | 56 | - No releases 57 | 58 | webvnc: http://code.google.com/p/webvnc/ 59 | 60 | - Jetty web server gateway, no updates since April 2008. 61 | 62 | RealVNC Java applet: http://www.realvnc.com/support/javavncviewer.html 63 | 64 | - Java applet 65 | 66 | Flashlight-VNC: http://www.wizhelp.com/flashlight-vnc/ 67 | 68 | - Adobe Flash implementation 69 | 70 | FVNC: http://osflash.org/fvnc 71 | 72 | - Adbove Flash implementation 73 | 74 | CanVNC: http://canvnc.sourceforge.net/ 75 | 76 | - HTML client with REST to VNC python proxy. Mostly vapor. 77 | -------------------------------------------------------------------------------- /app/images/windows.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image/svg+xml 51 | 56 | 57 | -------------------------------------------------------------------------------- /utils/validate: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | RET=0 6 | 7 | OUT=`mktemp` 8 | 9 | for fn in "$@"; do 10 | echo "Validating $fn..." 11 | echo 12 | 13 | case $fn in 14 | *.html) 15 | type="text/html" 16 | ;; 17 | *.css) 18 | type="text/css" 19 | ;; 20 | *) 21 | echo "Unknown format!" 22 | echo 23 | RET=1 24 | continue 25 | ;; 26 | esac 27 | 28 | curl --silent \ 29 | --header "Content-Type: ${type}; charset=utf-8" \ 30 | --data-binary @${fn} \ 31 | "https://validator.w3.org/nu/?out=gnu&level=error&asciiquotes=yes" \ 32 | > $OUT 33 | 34 | # We don't fail the check for warnings as some warnings are 35 | # not relevant for us, and we don't currently have a way to 36 | # ignore just those 37 | while read -r line; do 38 | echo 39 | 40 | line_info=$(echo $line | cut -d ":" -f 2) 41 | start_info=$(echo $line_info | cut -d "-" -f 1) 42 | end_info=$(echo $line_info | cut -d "-" -f 2) 43 | 44 | line_start=$(echo $start_info | cut -d "." -f 1) 45 | col_start=$(echo $start_info | cut -d "." -f 2) 46 | 47 | line_end=$(echo $end_info | cut -d "." -f 1) 48 | col_end=$(echo $end_info | cut -d "." -f 2) 49 | 50 | error=$(echo $line | cut -d ":" -f 4-) 51 | 52 | case $error in 53 | *"\"scrollbar-gutter\": Property \"scrollbar-gutter\" doesn't exist.") 54 | # FIXME: https://github.com/validator/validator/issues/1788 55 | echo "Ignoring below error on line ${line_start}," \ 56 | "the scrollbar-gutter property actually exist and is widely" \ 57 | "supported:" 58 | echo $error 59 | continue 60 | ;; 61 | *"\"clip-path\": \"path("*) 62 | # FIXME: https://github.com/validator/validator/issues/1786 63 | echo "Ignoring below error on line ${line_start}," \ 64 | "the path() function is valid for clip-path and is" \ 65 | "widely supported:" 66 | echo $error 67 | continue 68 | ;; 69 | *"Parse Error.") 70 | # FIXME: https://github.com/validator/validator/issues/1786 71 | lineofselector=$(grep -n "@supports selector(" $fn | cut -d ":" -f 1) 72 | linediff=$((lineofselector-line_start)) 73 | # Only ignore if parse error is within 50 lines of "selector()" 74 | if [ ${linediff#-} -lt 50 ]; then 75 | echo "Ignoring below error on line ${line_start}," \ 76 | "the @supports selector() function should not give a parse" \ 77 | "error:" 78 | echo $error 79 | continue 80 | fi 81 | ;; 82 | esac 83 | echo "ERROR between line ${line_start} (col ${col_start})" \ 84 | "and line ${line_end} (col ${col_end}):" 85 | echo $error 86 | RET=1 87 | done < "$OUT" 88 | done 89 | 90 | rm $OUT 91 | 92 | exit $RET 93 | -------------------------------------------------------------------------------- /tests/test.deflator.js: -------------------------------------------------------------------------------- 1 | import { inflateInit, inflate } from "../vendor/pako/lib/zlib/inflate.js"; 2 | import ZStream from "../vendor/pako/lib/zlib/zstream.js"; 3 | import Deflator from "../core/deflator.js"; 4 | 5 | function _inflator(compText, expected) { 6 | let strm = new ZStream(); 7 | let chunkSize = 1024 * 10 * 10; 8 | strm.output = new Uint8Array(chunkSize); 9 | 10 | inflateInit(strm, 5); 11 | 12 | if (expected > chunkSize) { 13 | chunkSize = expected; 14 | strm.output = new Uint8Array(chunkSize); 15 | } 16 | 17 | /* eslint-disable camelcase */ 18 | strm.input = compText; 19 | strm.avail_in = strm.input.length; 20 | strm.next_in = 0; 21 | 22 | strm.next_out = 0; 23 | strm.avail_out = expected.length; 24 | /* eslint-enable camelcase */ 25 | 26 | let ret = inflate(strm, 0); 27 | 28 | // Check that return code is not an error 29 | expect(ret).to.be.greaterThan(-1); 30 | 31 | return new Uint8Array(strm.output.buffer, 0, strm.next_out); 32 | } 33 | 34 | describe('Deflate data', function () { 35 | 36 | it('should be able to deflate messages', function () { 37 | let deflator = new Deflator(); 38 | 39 | let text = "123asdf"; 40 | let preText = new Uint8Array(text.length); 41 | for (let i = 0; i < preText.length; i++) { 42 | preText[i] = text.charCodeAt(i); 43 | } 44 | 45 | let compText = deflator.deflate(preText); 46 | 47 | let inflatedText = _inflator(compText, text.length); 48 | expect(inflatedText).to.array.equal(preText); 49 | 50 | }); 51 | 52 | it('should be able to deflate large messages', function () { 53 | let deflator = new Deflator(); 54 | 55 | /* Generate a big string with random characters. Used because 56 | repetition of letters might be deflated more effectively than 57 | random ones. */ 58 | let text = ""; 59 | let characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; 60 | for (let i = 0; i < 300000; i++) { 61 | text += characters.charAt(Math.floor(Math.random() * characters.length)); 62 | } 63 | 64 | let preText = new Uint8Array(text.length); 65 | for (let i = 0; i < preText.length; i++) { 66 | preText[i] = text.charCodeAt(i); 67 | } 68 | 69 | let compText = deflator.deflate(preText); 70 | 71 | //Check that the compressed size is expected size 72 | expect(compText.length).to.be.greaterThan((1024 * 10 * 10) * 2); 73 | 74 | let inflatedText = _inflator(compText, text.length); 75 | 76 | expect(inflatedText).to.array.equal(preText); 77 | 78 | }); 79 | }); 80 | -------------------------------------------------------------------------------- /app/images/handle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 54 | 57 | 58 | 60 | 61 | 63 | image/svg+xml 64 | 66 | 67 | 68 | 69 | 70 | 75 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /app/locale/es.json: -------------------------------------------------------------------------------- 1 | { 2 | "Connecting...": "Conectando...", 3 | "Connected (encrypted) to ": "Conectado (con encriptación) a", 4 | "Connected (unencrypted) to ": "Conectado (sin encriptación) a", 5 | "Disconnecting...": "Desconectando...", 6 | "Disconnected": "Desconectado", 7 | "Must set host": "Se debe configurar el host", 8 | "Reconnecting...": "Reconectando...", 9 | "Password is required": "La contraseña es obligatoria", 10 | "Disconnect timeout": "Tiempo de desconexión agotado", 11 | "noVNC encountered an error:": "noVNC ha encontrado un error:", 12 | "Hide/Show the control bar": "Ocultar/Mostrar la barra de control", 13 | "Move/Drag viewport": "Mover/Arrastrar la ventana", 14 | "viewport drag": "Arrastrar la ventana", 15 | "Active Mouse Button": "Botón activo del ratón", 16 | "No mousebutton": "Ningún botón del ratón", 17 | "Left mousebutton": "Botón izquierdo del ratón", 18 | "Middle mousebutton": "Botón central del ratón", 19 | "Right mousebutton": "Botón derecho del ratón", 20 | "Keyboard": "Teclado", 21 | "Show keyboard": "Mostrar teclado", 22 | "Extra keys": "Teclas adicionales", 23 | "Show Extra Keys": "Mostrar Teclas Adicionales", 24 | "Ctrl": "Ctrl", 25 | "Toggle Ctrl": "Pulsar/Soltar Ctrl", 26 | "Alt": "Alt", 27 | "Toggle Alt": "Pulsar/Soltar Alt", 28 | "Send Tab": "Enviar Tabulación", 29 | "Tab": "Tabulación", 30 | "Esc": "Esc", 31 | "Send Escape": "Enviar Escape", 32 | "Ctrl+Alt+Del": "Ctrl+Alt+Del", 33 | "Send Ctrl-Alt-Del": "Enviar Ctrl+Alt+Del", 34 | "Shutdown/Reboot": "Apagar/Reiniciar", 35 | "Shutdown/Reboot...": "Apagar/Reiniciar...", 36 | "Power": "Encender", 37 | "Shutdown": "Apagar", 38 | "Reboot": "Reiniciar", 39 | "Reset": "Restablecer", 40 | "Clipboard": "Portapapeles", 41 | "Clear": "Vaciar", 42 | "Fullscreen": "Pantalla Completa", 43 | "Settings": "Configuraciones", 44 | "Encrypt": "Encriptar", 45 | "Shared Mode": "Modo Compartido", 46 | "View only": "Solo visualización", 47 | "Clip to window": "Recortar al tamaño de la ventana", 48 | "Scaling mode:": "Modo de escalado:", 49 | "None": "Ninguno", 50 | "Local Scaling": "Escalado Local", 51 | "Local Downscaling": "Reducción de escala local", 52 | "Remote resizing": "Cambio de tamaño remoto", 53 | "Advanced": "Avanzado", 54 | "Local Cursor": "Cursor Local", 55 | "Repeater ID:": "ID del Repetidor:", 56 | "WebSocket": "WebSocket", 57 | "Host:": "Host:", 58 | "Port:": "Puerto:", 59 | "Path:": "Ruta:", 60 | "Automatic reconnect": "Reconexión automática", 61 | "Reconnect delay (ms):": "Retraso en la reconexión (ms):", 62 | "Logging:": "Registrando:", 63 | "Disconnect": "Desconectar", 64 | "Connect": "Conectar", 65 | "Password:": "Contraseña:", 66 | "Cancel": "Cancelar", 67 | "Canvas not supported.": "Canvas no soportado." 68 | } -------------------------------------------------------------------------------- /core/deflator.js: -------------------------------------------------------------------------------- 1 | /* 2 | * noVNC: HTML5 VNC client 3 | * Copyright (C) 2020 The noVNC authors 4 | * Licensed under MPL 2.0 (see LICENSE.txt) 5 | * 6 | * See README.md for usage and integration instructions. 7 | */ 8 | 9 | import { deflateInit, deflate } from "../vendor/pako/lib/zlib/deflate.js"; 10 | import { Z_FULL_FLUSH, Z_DEFAULT_COMPRESSION } from "../vendor/pako/lib/zlib/deflate.js"; 11 | import ZStream from "../vendor/pako/lib/zlib/zstream.js"; 12 | 13 | export default class Deflator { 14 | constructor() { 15 | this.strm = new ZStream(); 16 | this.chunkSize = 1024 * 10 * 10; 17 | this.outputBuffer = new Uint8Array(this.chunkSize); 18 | 19 | deflateInit(this.strm, Z_DEFAULT_COMPRESSION); 20 | } 21 | 22 | deflate(inData) { 23 | /* eslint-disable camelcase */ 24 | this.strm.input = inData; 25 | this.strm.avail_in = this.strm.input.length; 26 | this.strm.next_in = 0; 27 | this.strm.output = this.outputBuffer; 28 | this.strm.avail_out = this.chunkSize; 29 | this.strm.next_out = 0; 30 | /* eslint-enable camelcase */ 31 | 32 | let lastRet = deflate(this.strm, Z_FULL_FLUSH); 33 | let outData = new Uint8Array(this.strm.output.buffer, 0, this.strm.next_out); 34 | 35 | if (lastRet < 0) { 36 | throw new Error("zlib deflate failed"); 37 | } 38 | 39 | if (this.strm.avail_in > 0) { 40 | // Read chunks until done 41 | 42 | let chunks = [outData]; 43 | let totalLen = outData.length; 44 | do { 45 | /* eslint-disable camelcase */ 46 | this.strm.output = new Uint8Array(this.chunkSize); 47 | this.strm.next_out = 0; 48 | this.strm.avail_out = this.chunkSize; 49 | /* eslint-enable camelcase */ 50 | 51 | lastRet = deflate(this.strm, Z_FULL_FLUSH); 52 | 53 | if (lastRet < 0) { 54 | throw new Error("zlib deflate failed"); 55 | } 56 | 57 | let chunk = new Uint8Array(this.strm.output.buffer, 0, this.strm.next_out); 58 | totalLen += chunk.length; 59 | chunks.push(chunk); 60 | } while (this.strm.avail_in > 0); 61 | 62 | // Combine chunks into a single data 63 | 64 | let newData = new Uint8Array(totalLen); 65 | let offset = 0; 66 | 67 | for (let i = 0; i < chunks.length; i++) { 68 | newData.set(chunks[i], offset); 69 | offset += chunks[i].length; 70 | } 71 | 72 | outData = newData; 73 | } 74 | 75 | /* eslint-disable camelcase */ 76 | this.strm.input = null; 77 | this.strm.avail_in = 0; 78 | this.strm.next_in = 0; 79 | /* eslint-enable camelcase */ 80 | 81 | return outData; 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /app/locale/tr.json: -------------------------------------------------------------------------------- 1 | { 2 | "Connecting...": "Bağlanıyor...", 3 | "Disconnecting...": "Bağlantı kesiliyor...", 4 | "Reconnecting...": "Yeniden bağlantı kuruluyor...", 5 | "Internal error": "İç hata", 6 | "Must set host": "Sunucuyu kur", 7 | "Connected (encrypted) to ": "Bağlı (şifrelenmiş)", 8 | "Connected (unencrypted) to ": "Bağlandı (şifrelenmemiş)", 9 | "Something went wrong, connection is closed": "Bir şeyler ters gitti, bağlantı kesildi", 10 | "Disconnected": "Bağlantı kesildi", 11 | "New connection has been rejected with reason: ": "Bağlantı aşağıdaki nedenlerden dolayı reddedildi: ", 12 | "New connection has been rejected": "Bağlantı reddedildi", 13 | "Password is required": "Şifre gerekli", 14 | "noVNC encountered an error:": "Bir hata oluştu:", 15 | "Hide/Show the control bar": "Denetim masasını Gizle/Göster", 16 | "Move/Drag Viewport": "Görünümü Taşı/Sürükle", 17 | "viewport drag": "Görüntü penceresini sürükle", 18 | "Active Mouse Button": "Aktif Fare Düğmesi", 19 | "No mousebutton": "Fare düğmesi yok", 20 | "Left mousebutton": "Farenin sol düğmesi", 21 | "Middle mousebutton": "Farenin orta düğmesi", 22 | "Right mousebutton": "Farenin sağ düğmesi", 23 | "Keyboard": "Klavye", 24 | "Show Keyboard": "Klavye Düzenini Göster", 25 | "Extra keys": "Ekstra tuşlar", 26 | "Show extra keys": "Ekstra tuşları göster", 27 | "Ctrl": "Ctrl", 28 | "Toggle Ctrl": "Ctrl Değiştir ", 29 | "Alt": "Alt", 30 | "Toggle Alt": "Alt Değiştir", 31 | "Send Tab": "Sekme Gönder", 32 | "Tab": "Sekme", 33 | "Esc": "Esc", 34 | "Send Escape": "Boşluk Gönder", 35 | "Ctrl+Alt+Del": "Ctrl + Alt + Del", 36 | "Send Ctrl-Alt-Del": "Ctrl-Alt-Del Gönder", 37 | "Shutdown/Reboot": "Kapat/Yeniden Başlat", 38 | "Shutdown/Reboot...": "Kapat/Yeniden Başlat...", 39 | "Power": "Güç", 40 | "Shutdown": "Kapat", 41 | "Reboot": "Yeniden Başlat", 42 | "Reset": "Sıfırla", 43 | "Clipboard": "Pano", 44 | "Clear": "Temizle", 45 | "Fullscreen": "Tam Ekran", 46 | "Settings": "Ayarlar", 47 | "Shared Mode": "Paylaşım Modu", 48 | "View Only": "Sadece Görüntüle", 49 | "Clip to Window": "Pencereye Tıkla", 50 | "Scaling Mode:": "Ölçekleme Modu:", 51 | "None": "Bilinmeyen", 52 | "Local Scaling": "Yerel Ölçeklendirme", 53 | "Remote Resizing": "Uzaktan Yeniden Boyutlandırma", 54 | "Advanced": "Gelişmiş", 55 | "Repeater ID:": "Tekralayıcı ID:", 56 | "WebSocket": "WebSocket", 57 | "Encrypt": "Şifrele", 58 | "Host:": "Ana makine:", 59 | "Port:": "Port:", 60 | "Path:": "Yol:", 61 | "Automatic Reconnect": "Otomatik Yeniden Bağlan", 62 | "Reconnect Delay (ms):": "Yeniden Bağlanma Süreci (ms):", 63 | "Logging:": "Giriş yapılıyor:", 64 | "Disconnect": "Bağlantıyı Kes", 65 | "Connect": "Bağlan", 66 | "Password:": "Parola:", 67 | "Cancel": "Vazgeç", 68 | "Canvas not supported.": "Tuval desteklenmiyor." 69 | } -------------------------------------------------------------------------------- /app/error-handler.js: -------------------------------------------------------------------------------- 1 | /* 2 | * noVNC: HTML5 VNC client 3 | * Copyright (C) 2019 The noVNC authors 4 | * Licensed under MPL 2.0 (see LICENSE.txt) 5 | * 6 | * See README.md for usage and integration instructions. 7 | */ 8 | 9 | // Fallback for all uncaught errors 10 | function handleError(event, err) { 11 | try { 12 | const msg = document.getElementById('noVNC_fallback_errormsg'); 13 | 14 | // Work around Firefox bug: 15 | // https://bugzilla.mozilla.org/show_bug.cgi?id=1685038 16 | if (event.message === "ResizeObserver loop completed with undelivered notifications.") { 17 | return false; 18 | } 19 | 20 | // Only show the initial error 21 | if (msg.hasChildNodes()) { 22 | return false; 23 | } 24 | 25 | let div = document.createElement("div"); 26 | div.classList.add('noVNC_message'); 27 | div.appendChild(document.createTextNode(event.message)); 28 | msg.appendChild(div); 29 | 30 | if (event.filename) { 31 | div = document.createElement("div"); 32 | div.className = 'noVNC_location'; 33 | let text = event.filename; 34 | if (event.lineno !== undefined) { 35 | text += ":" + event.lineno; 36 | if (event.colno !== undefined) { 37 | text += ":" + event.colno; 38 | } 39 | } 40 | div.appendChild(document.createTextNode(text)); 41 | msg.appendChild(div); 42 | } 43 | 44 | if (err && err.stack) { 45 | div = document.createElement("div"); 46 | div.className = 'noVNC_stack'; 47 | div.appendChild(document.createTextNode(err.stack)); 48 | msg.appendChild(div); 49 | } 50 | 51 | document.getElementById('noVNC_fallback_error') 52 | .classList.add("noVNC_open"); 53 | 54 | } catch (exc) { 55 | document.write("noVNC encountered an error."); 56 | } 57 | 58 | // Try to disable keyboard interaction, best effort 59 | try { 60 | // Remove focus from the currently focused element in order to 61 | // prevent keyboard interaction from continuing 62 | if (document.activeElement) { document.activeElement.blur(); } 63 | 64 | // Don't let any element be focusable when showing the error 65 | let keyboardFocusable = 'a[href], button, input, textarea, select, details, [tabindex]'; 66 | document.querySelectorAll(keyboardFocusable).forEach((elem) => { 67 | elem.setAttribute("tabindex", "-1"); 68 | }); 69 | } catch (exc) { 70 | // Do nothing 71 | } 72 | 73 | // Don't return true since this would prevent the error 74 | // from being printed to the browser console. 75 | return false; 76 | } 77 | 78 | window.addEventListener('error', evt => handleError(evt, evt.error)); 79 | window.addEventListener('unhandledrejection', evt => handleError(evt.reason, evt.reason)); 80 | -------------------------------------------------------------------------------- /app/locale/it.json: -------------------------------------------------------------------------------- 1 | { 2 | "Connecting...": "Connessione in corso...", 3 | "Disconnecting...": "Disconnessione...", 4 | "Reconnecting...": "Riconnessione...", 5 | "Internal error": "Errore interno", 6 | "Must set host": "Devi impostare l'host", 7 | "Connected (encrypted) to ": "Connesso (crittografato) a ", 8 | "Connected (unencrypted) to ": "Connesso (non crittografato) a", 9 | "Something went wrong, connection is closed": "Qualcosa è andato storto, la connessione è stata chiusa", 10 | "Failed to connect to server": "Impossibile connettersi al server", 11 | "Disconnected": "Disconnesso", 12 | "New connection has been rejected with reason: ": "La nuova connessione è stata rifiutata con motivo: ", 13 | "New connection has been rejected": "La nuova connessione è stata rifiutata", 14 | "Credentials are required": "Le credenziali sono obbligatorie", 15 | "noVNC encountered an error:": "noVNC ha riscontrato un errore:", 16 | "Hide/Show the control bar": "Nascondi/Mostra la barra di controllo", 17 | "Keyboard": "Tastiera", 18 | "Show keyboard": "Mostra tastiera", 19 | "Extra keys": "Tasti Aggiuntivi", 20 | "Show Extra Keys": "Mostra Tasti Aggiuntivi", 21 | "Ctrl": "Ctrl", 22 | "Toggle Ctrl": "Tieni premuto Ctrl", 23 | "Alt": "Alt", 24 | "Toggle Alt": "Tieni premuto Alt", 25 | "Toggle Windows": "Tieni premuto Windows", 26 | "Windows": "Windows", 27 | "Send Tab": "Invia Tab", 28 | "Tab": "Tab", 29 | "Esc": "Esc", 30 | "Send Escape": "Invia Esc", 31 | "Ctrl+Alt+Del": "Ctrl+Alt+Canc", 32 | "Send Ctrl-Alt-Del": "Invia Ctrl-Alt-Canc", 33 | "Shutdown/Reboot": "Spegnimento/Riavvio", 34 | "Shutdown/Reboot...": "Spegnimento/Riavvio...", 35 | "Power": "Alimentazione", 36 | "Shutdown": "Spegnimento", 37 | "Reboot": "Riavvio", 38 | "Reset": "Reset", 39 | "Clipboard": "Clipboard", 40 | "Clear": "Pulisci", 41 | "Fullscreen": "Schermo intero", 42 | "Settings": "Impostazioni", 43 | "Shared mode": "Modalità condivisa", 44 | "View Only": "Sola Visualizzazione", 45 | "Scaling mode:": "Modalità di ridimensionamento:", 46 | "None": "Nessuna", 47 | "Local Scaling": "Ridimensionamento Locale", 48 | "Remote Resizing": "Ridimensionamento Remoto", 49 | "Advanced": "Avanzate", 50 | "Quality:": "Qualità:", 51 | "Compression level:": "Livello Compressione:", 52 | "Repeater ID:": "ID Ripetitore:", 53 | "WebSocket": "WebSocket", 54 | "Encrypt": "Crittografa", 55 | "Host:": "Host:", 56 | "Port:": "Porta:", 57 | "Path:": "Percorso:", 58 | "Automatic Reconnect": "Riconnessione Automatica", 59 | "Reconnect Delay (ms):": "Ritardo Riconnessione (ms):", 60 | "Show Dot when No Cursor": "Mostra Punto quando Nessun Cursore", 61 | "Version:": "Versione:", 62 | "Disconnect": "Disconnetti", 63 | "Connect": "Connetti", 64 | "Username:": "Utente:", 65 | "Password:": "Password:", 66 | "Send Credentials": "Invia Credenziale", 67 | "Cancel": "Annulla" 68 | } -------------------------------------------------------------------------------- /app/locale/cs.json: -------------------------------------------------------------------------------- 1 | { 2 | "Connecting...": "Připojení...", 3 | "Disconnecting...": "Odpojení...", 4 | "Reconnecting...": "Obnova připojení...", 5 | "Internal error": "Vnitřní chyba", 6 | "Must set host": "Hostitel musí být nastavení", 7 | "Connected (encrypted) to ": "Připojení (šifrované) k ", 8 | "Connected (unencrypted) to ": "Připojení (nešifrované) k ", 9 | "Something went wrong, connection is closed": "Něco se pokazilo, odpojeno", 10 | "Failed to connect to server": "Chyba připojení k serveru", 11 | "Disconnected": "Odpojeno", 12 | "New connection has been rejected with reason: ": "Nové připojení bylo odmítnuto s odůvodněním: ", 13 | "New connection has been rejected": "Nové připojení bylo odmítnuto", 14 | "Password is required": "Je vyžadováno heslo", 15 | "noVNC encountered an error:": "noVNC narazilo na chybu:", 16 | "Hide/Show the control bar": "Skrýt/zobrazit ovládací panel", 17 | "Move/Drag viewport": "Přesunout/přetáhnout výřez", 18 | "viewport drag": "přesun výřezu", 19 | "Active Mouse Button": "Aktivní tlačítka myši", 20 | "No mousebutton": "Žádné", 21 | "Left mousebutton": "Levé tlačítko myši", 22 | "Middle mousebutton": "Prostřední tlačítko myši", 23 | "Right mousebutton": "Pravé tlačítko myši", 24 | "Keyboard": "Klávesnice", 25 | "Show keyboard": "Zobrazit klávesnici", 26 | "Extra keys": "Extra klávesy", 27 | "Show extra keys": "Zobrazit extra klávesy", 28 | "Ctrl": "Ctrl", 29 | "Toggle Ctrl": "Přepnout Ctrl", 30 | "Alt": "Alt", 31 | "Toggle Alt": "Přepnout Alt", 32 | "Send Tab": "Odeslat tabulátor", 33 | "Tab": "Tab", 34 | "Esc": "Esc", 35 | "Send Escape": "Odeslat Esc", 36 | "Ctrl+Alt+Del": "Ctrl+Alt+Del", 37 | "Send Ctrl-Alt-Del": "Poslat Ctrl-Alt-Del", 38 | "Shutdown/Reboot": "Vypnutí/Restart", 39 | "Shutdown/Reboot...": "Vypnutí/Restart...", 40 | "Power": "Napájení", 41 | "Shutdown": "Vypnout", 42 | "Reboot": "Restart", 43 | "Reset": "Reset", 44 | "Clipboard": "Schránka", 45 | "Clear": "Vymazat", 46 | "Fullscreen": "Celá obrazovka", 47 | "Settings": "Nastavení", 48 | "Shared mode": "Sdílený režim", 49 | "View only": "Pouze prohlížení", 50 | "Clip to window": "Přizpůsobit oknu", 51 | "Scaling mode:": "Přizpůsobení velikosti", 52 | "None": "Žádné", 53 | "Local scaling": "Místní", 54 | "Remote resizing": "Vzdálené", 55 | "Advanced": "Pokročilé", 56 | "Repeater ID:": "ID opakovače", 57 | "WebSocket": "WebSocket", 58 | "Encrypt": "Šifrování:", 59 | "Host:": "Hostitel:", 60 | "Port:": "Port:", 61 | "Path:": "Cesta", 62 | "Automatic reconnect": "Automatická obnova připojení", 63 | "Reconnect delay (ms):": "Zpoždění připojení (ms)", 64 | "Show dot when no cursor": "Tečka místo chybějícího kurzoru myši", 65 | "Logging:": "Logování:", 66 | "Disconnect": "Odpojit", 67 | "Connect": "Připojit", 68 | "Password:": "Heslo", 69 | "Send Password": "Odeslat heslo", 70 | "Cancel": "Zrušit" 71 | } -------------------------------------------------------------------------------- /karma.conf.cjs: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | 3 | // The Safari launcher is broken, so construct our own 4 | function SafariBrowser(id, baseBrowserDecorator, args) { 5 | baseBrowserDecorator(this); 6 | 7 | this._start = function(url) { 8 | this._execCommand('/usr/bin/open', ['-W', '-n', '-a', 'Safari', url]); 9 | } 10 | } 11 | 12 | SafariBrowser.prototype = { 13 | name: 'Safari' 14 | } 15 | 16 | module.exports = (config) => { 17 | let browsers = []; 18 | 19 | if (process.env.TEST_BROWSER_NAME) { 20 | browsers = process.env.TEST_BROWSER_NAME.split(','); 21 | } 22 | 23 | const my_conf = { 24 | 25 | // base path that will be used to resolve all patterns (eg. files, exclude) 26 | basePath: '', 27 | 28 | // frameworks to use 29 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 30 | frameworks: ['mocha'], 31 | 32 | // list of files / patterns to load in the browser 33 | files: [ 34 | // node modules 35 | { pattern: 'node_modules/chai/**', included: false }, 36 | { pattern: 'node_modules/sinon/**', included: false }, 37 | { pattern: 'node_modules/sinon-chai/**', included: false }, 38 | // modules to test 39 | { pattern: 'app/localization.js', included: false, type: 'module' }, 40 | { pattern: 'app/webutil.js', included: false, type: 'module' }, 41 | { pattern: 'core/**/*.js', included: false, type: 'module' }, 42 | { pattern: 'vendor/pako/**/*.js', included: false, type: 'module' }, 43 | // tests 44 | { pattern: 'tests/test.*.js', type: 'module' }, 45 | // test support files 46 | { pattern: 'tests/fake.*.js', included: false, type: 'module' }, 47 | { pattern: 'tests/assertions.js', type: 'module' }, 48 | ], 49 | 50 | client: { 51 | mocha: { 52 | // replace Karma debug page with mocha display 53 | 'reporter': 'html', 54 | 'ui': 'bdd' 55 | } 56 | }, 57 | 58 | // list of files to exclude 59 | exclude: [ 60 | ], 61 | 62 | plugins: [ 63 | 'karma-*', 64 | '@chiragrupani/karma-chromium-edge-launcher', 65 | { 'launcher:Safari': [ 'type', SafariBrowser ] }, 66 | ], 67 | 68 | // start these browsers 69 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 70 | browsers: browsers, 71 | 72 | // test results reporter to use 73 | // possible values: 'dots', 'progress' 74 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 75 | reporters: ['mocha'], 76 | 77 | 78 | // level of logging 79 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 80 | logLevel: config.LOG_INFO, 81 | 82 | 83 | // enable / disable watching file and executing tests whenever any file changes 84 | autoWatch: false, 85 | 86 | // Continuous Integration mode 87 | // if true, Karma captures browsers, runs the tests and exits 88 | singleRun: true, 89 | }; 90 | 91 | config.set(my_conf); 92 | }; 93 | -------------------------------------------------------------------------------- /app/locale/ru.json: -------------------------------------------------------------------------------- 1 | { 2 | "Connecting...": "Подключение...", 3 | "Disconnecting...": "Отключение...", 4 | "Reconnecting...": "Переподключение...", 5 | "Internal error": "Внутренняя ошибка", 6 | "Must set host": "Задайте имя сервера или IP", 7 | "Connected (encrypted) to ": "Подключено (с шифрованием) к ", 8 | "Connected (unencrypted) to ": "Подключено (без шифрования) к ", 9 | "Something went wrong, connection is closed": "Что-то пошло не так, подключение разорвано", 10 | "Failed to connect to server": "Ошибка подключения к серверу", 11 | "Disconnected": "Отключено", 12 | "New connection has been rejected with reason: ": "Новое соединение отклонено по причине: ", 13 | "New connection has been rejected": "Новое соединение отклонено", 14 | "Credentials are required": "Требуются учетные данные", 15 | "noVNC encountered an error:": "Ошибка noVNC: ", 16 | "Hide/Show the control bar": "Скрыть/Показать контрольную панель", 17 | "Drag": "Переместить", 18 | "Move/Drag viewport": "Переместить окно", 19 | "Keyboard": "Клавиатура", 20 | "Show keyboard": "Показать клавиатуру", 21 | "Extra keys": "Дополнительные Кнопки", 22 | "Show Extra Keys": "Показать Дополнительные Кнопки", 23 | "Ctrl": "Ctrl", 24 | "Toggle Ctrl": "Зажать Ctrl", 25 | "Alt": "Alt", 26 | "Toggle Alt": "Зажать Alt", 27 | "Toggle Windows": "Зажать Windows", 28 | "Windows": "Вкладка", 29 | "Send Tab": "Передать нажатие Tab", 30 | "Tab": "Tab", 31 | "Esc": "Esc", 32 | "Send Escape": "Передать нажатие Escape", 33 | "Ctrl+Alt+Del": "Ctrl+Alt+Del", 34 | "Send Ctrl-Alt-Del": "Передать нажатие Ctrl-Alt-Del", 35 | "Shutdown/Reboot": "Выключить/Перезагрузить", 36 | "Shutdown/Reboot...": "Выключить/Перезагрузить...", 37 | "Power": "Питание", 38 | "Shutdown": "Выключить", 39 | "Reboot": "Перезагрузить", 40 | "Reset": "Сброс", 41 | "Clipboard": "Буфер обмена", 42 | "Clear": "Очистить", 43 | "Fullscreen": "Во весь экран", 44 | "Settings": "Настройки", 45 | "Shared mode": "Общий режим", 46 | "View Only": "Только Просмотр", 47 | "Clip to window": "В окно", 48 | "Scaling mode:": "Масштаб:", 49 | "None": "Нет", 50 | "Local scaling": "Локальный масштаб", 51 | "Remote resizing": "Удаленная перенастройка размера", 52 | "Advanced": "Дополнительно", 53 | "Quality:": "Качество", 54 | "Compression level:": "Уровень Сжатия", 55 | "Repeater ID:": "Идентификатор ID:", 56 | "WebSocket": "WebSocket", 57 | "Encrypt": "Шифрование", 58 | "Host:": "Сервер:", 59 | "Port:": "Порт:", 60 | "Path:": "Путь:", 61 | "Automatic reconnect": "Автоматическое переподключение", 62 | "Reconnect delay (ms):": "Задержка переподключения (мс):", 63 | "Show dot when no cursor": "Показать точку вместо курсора", 64 | "Logging:": "Лог:", 65 | "Version:": "Версия", 66 | "Disconnect": "Отключение", 67 | "Connect": "Подключение", 68 | "Username:": "Имя Пользователя", 69 | "Password:": "Пароль:", 70 | "Send Credentials": "Передача Учетных Данных", 71 | "Cancel": "Выход" 72 | } -------------------------------------------------------------------------------- /core/input/vkeys.js: -------------------------------------------------------------------------------- 1 | /* 2 | * noVNC: HTML5 VNC client 3 | * Copyright (C) 2018 The noVNC authors 4 | * Licensed under MPL 2.0 or any later version (see LICENSE.txt) 5 | */ 6 | 7 | /* 8 | * Mapping between Microsoft® Windows® Virtual-Key codes and 9 | * HTML key codes. 10 | */ 11 | 12 | export default { 13 | 0x08: 'Backspace', 14 | 0x09: 'Tab', 15 | 0x0a: 'NumpadClear', 16 | 0x0d: 'Enter', 17 | 0x10: 'ShiftLeft', 18 | 0x11: 'ControlLeft', 19 | 0x12: 'AltLeft', 20 | 0x13: 'Pause', 21 | 0x14: 'CapsLock', 22 | 0x15: 'Lang1', 23 | 0x19: 'Lang2', 24 | 0x1b: 'Escape', 25 | 0x1c: 'Convert', 26 | 0x1d: 'NonConvert', 27 | 0x20: 'Space', 28 | 0x21: 'PageUp', 29 | 0x22: 'PageDown', 30 | 0x23: 'End', 31 | 0x24: 'Home', 32 | 0x25: 'ArrowLeft', 33 | 0x26: 'ArrowUp', 34 | 0x27: 'ArrowRight', 35 | 0x28: 'ArrowDown', 36 | 0x29: 'Select', 37 | 0x2c: 'PrintScreen', 38 | 0x2d: 'Insert', 39 | 0x2e: 'Delete', 40 | 0x2f: 'Help', 41 | 0x30: 'Digit0', 42 | 0x31: 'Digit1', 43 | 0x32: 'Digit2', 44 | 0x33: 'Digit3', 45 | 0x34: 'Digit4', 46 | 0x35: 'Digit5', 47 | 0x36: 'Digit6', 48 | 0x37: 'Digit7', 49 | 0x38: 'Digit8', 50 | 0x39: 'Digit9', 51 | 0x5b: 'MetaLeft', 52 | 0x5c: 'MetaRight', 53 | 0x5d: 'ContextMenu', 54 | 0x5f: 'Sleep', 55 | 0x60: 'Numpad0', 56 | 0x61: 'Numpad1', 57 | 0x62: 'Numpad2', 58 | 0x63: 'Numpad3', 59 | 0x64: 'Numpad4', 60 | 0x65: 'Numpad5', 61 | 0x66: 'Numpad6', 62 | 0x67: 'Numpad7', 63 | 0x68: 'Numpad8', 64 | 0x69: 'Numpad9', 65 | 0x6a: 'NumpadMultiply', 66 | 0x6b: 'NumpadAdd', 67 | 0x6c: 'NumpadDecimal', 68 | 0x6d: 'NumpadSubtract', 69 | 0x6e: 'NumpadDecimal', // Duplicate, because buggy on Windows 70 | 0x6f: 'NumpadDivide', 71 | 0x70: 'F1', 72 | 0x71: 'F2', 73 | 0x72: 'F3', 74 | 0x73: 'F4', 75 | 0x74: 'F5', 76 | 0x75: 'F6', 77 | 0x76: 'F7', 78 | 0x77: 'F8', 79 | 0x78: 'F9', 80 | 0x79: 'F10', 81 | 0x7a: 'F11', 82 | 0x7b: 'F12', 83 | 0x7c: 'F13', 84 | 0x7d: 'F14', 85 | 0x7e: 'F15', 86 | 0x7f: 'F16', 87 | 0x80: 'F17', 88 | 0x81: 'F18', 89 | 0x82: 'F19', 90 | 0x83: 'F20', 91 | 0x84: 'F21', 92 | 0x85: 'F22', 93 | 0x86: 'F23', 94 | 0x87: 'F24', 95 | 0x90: 'NumLock', 96 | 0x91: 'ScrollLock', 97 | 0xa6: 'BrowserBack', 98 | 0xa7: 'BrowserForward', 99 | 0xa8: 'BrowserRefresh', 100 | 0xa9: 'BrowserStop', 101 | 0xaa: 'BrowserSearch', 102 | 0xab: 'BrowserFavorites', 103 | 0xac: 'BrowserHome', 104 | 0xad: 'AudioVolumeMute', 105 | 0xae: 'AudioVolumeDown', 106 | 0xaf: 'AudioVolumeUp', 107 | 0xb0: 'MediaTrackNext', 108 | 0xb1: 'MediaTrackPrevious', 109 | 0xb2: 'MediaStop', 110 | 0xb3: 'MediaPlayPause', 111 | 0xb4: 'LaunchMail', 112 | 0xb5: 'MediaSelect', 113 | 0xb6: 'LaunchApp1', 114 | 0xb7: 'LaunchApp2', 115 | 0xe1: 'AltRight', // Only when it is AltGraph 116 | }; 117 | -------------------------------------------------------------------------------- /app/locale/pt_BR.json: -------------------------------------------------------------------------------- 1 | { 2 | "Connecting...": "Conectando...", 3 | "Disconnecting...": "Desconectando...", 4 | "Reconnecting...": "Reconectando...", 5 | "Internal error": "Erro interno", 6 | "Must set host": "É necessário definir o host", 7 | "Connected (encrypted) to ": "Conectado (com criptografia) a ", 8 | "Connected (unencrypted) to ": "Conectado (sem criptografia) a ", 9 | "Something went wrong, connection is closed": "Algo deu errado. A conexão foi encerrada.", 10 | "Failed to connect to server": "Falha ao conectar-se ao servidor", 11 | "Disconnected": "Desconectado", 12 | "New connection has been rejected with reason: ": "A nova conexão foi rejeitada pelo motivo: ", 13 | "New connection has been rejected": "A nova conexão foi rejeitada", 14 | "Credentials are required": "Credenciais são obrigatórias", 15 | "noVNC encountered an error:": "O noVNC encontrou um erro:", 16 | "Hide/Show the control bar": "Esconder/mostrar a barra de controles", 17 | "Drag": "Arrastar", 18 | "Move/Drag viewport": "Mover/arrastar a janela", 19 | "Keyboard": "Teclado", 20 | "Show keyboard": "Mostrar teclado", 21 | "Extra keys": "Teclas adicionais", 22 | "Show extra keys": "Mostrar teclas adicionais", 23 | "Ctrl": "Ctrl", 24 | "Toggle Ctrl": "Pressionar/soltar Ctrl", 25 | "Alt": "Alt", 26 | "Toggle Alt": "Pressionar/soltar Alt", 27 | "Toggle Windows": "Pressionar/soltar Windows", 28 | "Windows": "Windows", 29 | "Send Tab": "Enviar Tab", 30 | "Tab": "Tab", 31 | "Esc": "Esc", 32 | "Send Escape": "Enviar Esc", 33 | "Ctrl+Alt+Del": "Ctrl+Alt+Del", 34 | "Send Ctrl-Alt-Del": "Enviar Ctrl-Alt-Del", 35 | "Shutdown/Reboot": "Desligar/reiniciar", 36 | "Shutdown/Reboot...": "Desligar/reiniciar...", 37 | "Power": "Ligar", 38 | "Shutdown": "Desligar", 39 | "Reboot": "Reiniciar", 40 | "Reset": "Reiniciar (forçado)", 41 | "Clipboard": "Área de transferência", 42 | "Clear": "Limpar", 43 | "Fullscreen": "Tela cheia", 44 | "Settings": "Configurações", 45 | "Shared mode": "Modo compartilhado", 46 | "View only": "Apenas visualizar", 47 | "Clip to window": "Recortar à janela", 48 | "Scaling mode:": "Modo de dimensionamento:", 49 | "None": "Nenhum", 50 | "Local scaling": "Local", 51 | "Remote resizing": "Remoto", 52 | "Advanced": "Avançado", 53 | "Quality:": "Qualidade:", 54 | "Compression level:": "Nível de compressão:", 55 | "Repeater ID:": "ID do repetidor:", 56 | "WebSocket": "WebSocket", 57 | "Encrypt": "Criptografar", 58 | "Host:": "Host:", 59 | "Port:": "Porta:", 60 | "Path:": "Caminho:", 61 | "Automatic reconnect": "Reconexão automática", 62 | "Reconnect delay (ms):": "Atraso da reconexão (ms)", 63 | "Show dot when no cursor": "Mostrar ponto quando não há cursor", 64 | "Logging:": "Registros:", 65 | "Version:": "Versão:", 66 | "Disconnect": "Desconectar", 67 | "Connect": "Conectar", 68 | "Username:": "Nome de usuário:", 69 | "Password:": "Senha:", 70 | "Send credentials": "Enviar credenciais", 71 | "Cancel": "Cancelar" 72 | } -------------------------------------------------------------------------------- /app/locale/ja.json: -------------------------------------------------------------------------------- 1 | { 2 | "Running without HTTPS is not recommended, crashes or other issues are likely.": "HTTPS接続なしで実行することは推奨されません。クラッシュしたりその他の問題が発生したりする可能性があります。", 3 | "Connecting...": "接続しています...", 4 | "Disconnecting...": "切断しています...", 5 | "Reconnecting...": "再接続しています...", 6 | "Internal error": "内部エラー", 7 | "Must set host": "ホストを設定する必要があります", 8 | "Failed to connect to server: ": "サーバーへの接続に失敗しました: ", 9 | "Connected (encrypted) to ": "接続しました (暗号化済み): ", 10 | "Connected (unencrypted) to ": "接続しました (暗号化されていません): ", 11 | "Something went wrong, connection is closed": "問題が発生したため、接続が閉じられました", 12 | "Failed to connect to server": "サーバーへの接続に失敗しました", 13 | "Disconnected": "切断しました", 14 | "New connection has been rejected with reason: ": "新規接続は次の理由で拒否されました: ", 15 | "New connection has been rejected": "新規接続は拒否されました", 16 | "Credentials are required": "資格情報が必要です", 17 | "noVNC encountered an error:": "noVNC でエラーが発生しました:", 18 | "Hide/Show the control bar": "コントロールバーを隠す/表示する", 19 | "Drag": "ドラッグ", 20 | "Move/Drag viewport": "ビューポートを移動/ドラッグ", 21 | "Keyboard": "キーボード", 22 | "Show keyboard": "キーボードを表示", 23 | "Extra keys": "追加キー", 24 | "Show extra keys": "追加キーを表示", 25 | "Ctrl": "Ctrl", 26 | "Toggle Ctrl": "Ctrl キーをトグル", 27 | "Alt": "Alt", 28 | "Toggle Alt": "Alt キーをトグル", 29 | "Toggle Windows": "Windows キーをトグル", 30 | "Windows": "Windows", 31 | "Send Tab": "Tab キーを送信", 32 | "Tab": "Tab", 33 | "Esc": "Esc", 34 | "Send Escape": "Escape キーを送信", 35 | "Ctrl+Alt+Del": "Ctrl+Alt+Del", 36 | "Send Ctrl-Alt-Del": "Ctrl-Alt-Del を送信", 37 | "Shutdown/Reboot": "シャットダウン/再起動", 38 | "Shutdown/Reboot...": "シャットダウン/再起動...", 39 | "Power": "電源", 40 | "Shutdown": "シャットダウン", 41 | "Reboot": "再起動", 42 | "Reset": "リセット", 43 | "Clipboard": "クリップボード", 44 | "Edit clipboard content in the textarea below.": "以下の入力欄からクリップボードの内容を編集できます。", 45 | "Full screen": "全画面表示", 46 | "Settings": "設定", 47 | "Shared mode": "共有モード", 48 | "View only": "表示専用", 49 | "Clip to window": "ウィンドウにクリップ", 50 | "Scaling mode:": "スケーリングモード:", 51 | "None": "なし", 52 | "Local scaling": "ローカルでスケーリング", 53 | "Remote resizing": "リモートでリサイズ", 54 | "Advanced": "高度", 55 | "Quality:": "品質:", 56 | "Compression level:": "圧縮レベル:", 57 | "Repeater ID:": "リピーター ID:", 58 | "WebSocket": "WebSocket", 59 | "Encrypt": "暗号化", 60 | "Host:": "ホスト:", 61 | "Port:": "ポート:", 62 | "Path:": "パス:", 63 | "Automatic reconnect": "自動再接続", 64 | "Reconnect delay (ms):": "再接続する遅延 (ミリ秒):", 65 | "Show dot when no cursor": "カーソルがないときにドットを表示する", 66 | "Logging:": "ロギング:", 67 | "Version:": "バージョン:", 68 | "Disconnect": "切断", 69 | "Connect": "接続", 70 | "Server identity": "サーバーの識別情報", 71 | "The server has provided the following identifying information:": "サーバーは以下の識別情報を提供しています:", 72 | "Fingerprint:": "フィンガープリント:", 73 | "Please verify that the information is correct and press \"Approve\". Otherwise press \"Reject\".": "この情報が正しい場合は「承認」を、そうでない場合は「拒否」を押してください。", 74 | "Approve": "承認", 75 | "Reject": "拒否", 76 | "Credentials": "資格情報", 77 | "Username:": "ユーザー名:", 78 | "Password:": "パスワード:", 79 | "Send credentials": "資格情報を送信", 80 | "Cancel": "キャンセル" 81 | } -------------------------------------------------------------------------------- /app/images/tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 54 | 57 | 58 | 60 | 61 | 63 | image/svg+xml 64 | 66 | 67 | 68 | 69 | 70 | 75 | 80 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /app/images/expander.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 42 | 45 | 46 | 48 | 49 | 51 | image/svg+xml 52 | 54 | 55 | 56 | 57 | 58 | 63 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /app/images/settings.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 49 | 52 | 53 | 55 | 56 | 58 | image/svg+xml 59 | 61 | 62 | 63 | 64 | 65 | 70 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /tests/test.zlib.js: -------------------------------------------------------------------------------- 1 | import Websock from '../core/websock.js'; 2 | import Display from '../core/display.js'; 3 | 4 | import ZlibDecoder from '../core/decoders/zlib.js'; 5 | 6 | import FakeWebSocket from './fake.websocket.js'; 7 | 8 | function testDecodeRect(decoder, x, y, width, height, data, display, depth) { 9 | let sock; 10 | let done = false; 11 | 12 | sock = new Websock; 13 | sock.open("ws://example.com"); 14 | 15 | sock.on('message', () => { 16 | done = decoder.decodeRect(x, y, width, height, sock, display, depth); 17 | }); 18 | 19 | // Empty messages are filtered at multiple layers, so we need to 20 | // do a direct call 21 | if (data.length === 0) { 22 | done = decoder.decodeRect(x, y, width, height, sock, display, depth); 23 | } else { 24 | sock._websocket._receiveData(new Uint8Array(data)); 25 | } 26 | 27 | display.flip(); 28 | 29 | return done; 30 | } 31 | 32 | describe('Zlib decoder', function () { 33 | let decoder; 34 | let display; 35 | 36 | before(FakeWebSocket.replace); 37 | after(FakeWebSocket.restore); 38 | 39 | beforeEach(function () { 40 | decoder = new ZlibDecoder(); 41 | display = new Display(document.createElement('canvas')); 42 | display.resize(4, 4); 43 | }); 44 | 45 | it('should handle the Zlib encoding', function () { 46 | let done; 47 | 48 | let zlibData = new Uint8Array([ 49 | 0x00, 0x00, 0x00, 0x23, /* length */ 50 | 0x78, 0x01, 0xfa, 0xcf, 0x00, 0x04, 0xff, 0x61, 0x04, 0x90, 0x01, 0x41, 0x50, 0xc1, 0xff, 0x0c, 51 | 0xef, 0x40, 0x02, 0xef, 0xfe, 0x33, 0xac, 0x02, 0xe2, 0xd5, 0x40, 0x8c, 0xce, 0x07, 0x00, 0x00, 52 | 0x00, 0xff, 0xff, 53 | ]); 54 | done = testDecodeRect(decoder, 0, 0, 4, 4, zlibData, display, 24); 55 | expect(done).to.be.true; 56 | 57 | let targetData = new Uint8ClampedArray([ 58 | 0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 59 | 0x00, 0xff, 0x00, 255, 0xff, 0x00, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 60 | 0xee, 0x00, 0xff, 255, 0x00, 0xee, 0xff, 255, 0xaa, 0xee, 0xff, 255, 0xab, 0xee, 0xff, 255, 61 | 0xee, 0x00, 0xff, 255, 0x00, 0xee, 0xff, 255, 0xaa, 0xee, 0xff, 255, 0xab, 0xee, 0xff, 255 62 | ]); 63 | 64 | expect(display).to.have.displayed(targetData); 65 | }); 66 | 67 | it('should handle empty rects', function () { 68 | display.fillRect(0, 0, 4, 4, [0x00, 0x00, 0xff]); 69 | display.fillRect(2, 0, 2, 2, [0x00, 0xff, 0x00]); 70 | display.fillRect(0, 2, 2, 2, [0x00, 0xff, 0x00]); 71 | 72 | let done = testDecodeRect(decoder, 1, 2, 0, 0, [], display, 24); 73 | 74 | let targetData = new Uint8Array([ 75 | 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 76 | 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 77 | 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 78 | 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255 79 | ]); 80 | 81 | expect(done).to.be.true; 82 | expect(display).to.have.displayed(targetData); 83 | }); 84 | }); 85 | -------------------------------------------------------------------------------- /core/crypto/crypto.js: -------------------------------------------------------------------------------- 1 | import { AESECBCipher, AESEAXCipher } from "./aes.js"; 2 | import { DESCBCCipher, DESECBCipher } from "./des.js"; 3 | import { RSACipher } from "./rsa.js"; 4 | import { DHCipher } from "./dh.js"; 5 | import { MD5 } from "./md5.js"; 6 | 7 | // A single interface for the cryptographic algorithms not supported by SubtleCrypto. 8 | // Both synchronous and asynchronous implmentations are allowed. 9 | class LegacyCrypto { 10 | constructor() { 11 | this._algorithms = { 12 | "AES-ECB": AESECBCipher, 13 | "AES-EAX": AESEAXCipher, 14 | "DES-ECB": DESECBCipher, 15 | "DES-CBC": DESCBCCipher, 16 | "RSA-PKCS1-v1_5": RSACipher, 17 | "DH": DHCipher, 18 | "MD5": MD5, 19 | }; 20 | } 21 | 22 | encrypt(algorithm, key, data) { 23 | if (key.algorithm.name !== algorithm.name) { 24 | throw new Error("algorithm does not match"); 25 | } 26 | if (typeof key.encrypt !== "function") { 27 | throw new Error("key does not support encryption"); 28 | } 29 | return key.encrypt(algorithm, data); 30 | } 31 | 32 | decrypt(algorithm, key, data) { 33 | if (key.algorithm.name !== algorithm.name) { 34 | throw new Error("algorithm does not match"); 35 | } 36 | if (typeof key.decrypt !== "function") { 37 | throw new Error("key does not support encryption"); 38 | } 39 | return key.decrypt(algorithm, data); 40 | } 41 | 42 | importKey(format, keyData, algorithm, extractable, keyUsages) { 43 | if (format !== "raw") { 44 | throw new Error("key format is not supported"); 45 | } 46 | const alg = this._algorithms[algorithm.name]; 47 | if (typeof alg === "undefined" || typeof alg.importKey !== "function") { 48 | throw new Error("algorithm is not supported"); 49 | } 50 | return alg.importKey(keyData, algorithm, extractable, keyUsages); 51 | } 52 | 53 | generateKey(algorithm, extractable, keyUsages) { 54 | const alg = this._algorithms[algorithm.name]; 55 | if (typeof alg === "undefined" || typeof alg.generateKey !== "function") { 56 | throw new Error("algorithm is not supported"); 57 | } 58 | return alg.generateKey(algorithm, extractable, keyUsages); 59 | } 60 | 61 | exportKey(format, key) { 62 | if (format !== "raw") { 63 | throw new Error("key format is not supported"); 64 | } 65 | if (typeof key.exportKey !== "function") { 66 | throw new Error("key does not support exportKey"); 67 | } 68 | return key.exportKey(); 69 | } 70 | 71 | digest(algorithm, data) { 72 | const alg = this._algorithms[algorithm]; 73 | if (typeof alg !== "function") { 74 | throw new Error("algorithm is not supported"); 75 | } 76 | return alg(data); 77 | } 78 | 79 | deriveBits(algorithm, key, length) { 80 | if (key.algorithm.name !== algorithm.name) { 81 | throw new Error("algorithm does not match"); 82 | } 83 | if (typeof key.deriveBits !== "function") { 84 | throw new Error("key does not support deriveBits"); 85 | } 86 | return key.deriveBits(algorithm, length); 87 | } 88 | } 89 | 90 | export default new LegacyCrypto; 91 | -------------------------------------------------------------------------------- /tests/fake.websocket.js: -------------------------------------------------------------------------------- 1 | import Base64 from '../core/base64.js'; 2 | 3 | export default class FakeWebSocket { 4 | constructor(uri, protocols) { 5 | this.url = uri; 6 | this.binaryType = "arraybuffer"; 7 | this.extensions = ""; 8 | 9 | this.onerror = null; 10 | this.onmessage = null; 11 | this.onopen = null; 12 | 13 | if (!protocols || typeof protocols === 'string') { 14 | this.protocol = protocols; 15 | } else { 16 | this.protocol = protocols[0]; 17 | } 18 | 19 | this._sendQueue = new Uint8Array(20000); 20 | 21 | this.readyState = FakeWebSocket.CONNECTING; 22 | this.bufferedAmount = 0; 23 | 24 | this._isFake = true; 25 | } 26 | 27 | close(code, reason) { 28 | this.readyState = FakeWebSocket.CLOSED; 29 | if (this.onclose) { 30 | this.onclose(new CloseEvent("close", { 'code': code, 'reason': reason, 'wasClean': true })); 31 | } 32 | } 33 | 34 | send(data) { 35 | if (this.protocol == 'base64') { 36 | data = Base64.decode(data); 37 | } else { 38 | data = new Uint8Array(data); 39 | } 40 | if (this.bufferedAmount + data.length > this._sendQueue.length) { 41 | let newlen = this._sendQueue.length; 42 | while (this.bufferedAmount + data.length > newlen) { 43 | newlen *= 2; 44 | } 45 | let newbuf = new Uint8Array(newlen); 46 | newbuf.set(this._sendQueue); 47 | this._sendQueue = newbuf; 48 | } 49 | this._sendQueue.set(data, this.bufferedAmount); 50 | this.bufferedAmount += data.length; 51 | } 52 | 53 | _getSentData() { 54 | const res = this._sendQueue.slice(0, this.bufferedAmount); 55 | this.bufferedAmount = 0; 56 | return res; 57 | } 58 | 59 | _open() { 60 | this.readyState = FakeWebSocket.OPEN; 61 | if (this.onopen) { 62 | this.onopen(new Event('open')); 63 | } 64 | } 65 | 66 | _receiveData(data) { 67 | if (data.length < 4096) { 68 | // Break apart the data to expose bugs where we assume data is 69 | // neatly packaged 70 | for (let i = 0;i < data.length;i++) { 71 | let buf = data.slice(i, i+1); 72 | this.onmessage(new MessageEvent("message", { 'data': buf.buffer })); 73 | } 74 | } else { 75 | this.onmessage(new MessageEvent("message", { 'data': data.buffer })); 76 | } 77 | } 78 | } 79 | 80 | FakeWebSocket.OPEN = WebSocket.OPEN; 81 | FakeWebSocket.CONNECTING = WebSocket.CONNECTING; 82 | FakeWebSocket.CLOSING = WebSocket.CLOSING; 83 | FakeWebSocket.CLOSED = WebSocket.CLOSED; 84 | 85 | FakeWebSocket._isFake = true; 86 | 87 | FakeWebSocket.replace = () => { 88 | if (!WebSocket._isFake) { 89 | const realVersion = WebSocket; 90 | // eslint-disable-next-line no-global-assign 91 | WebSocket = FakeWebSocket; 92 | FakeWebSocket._realVersion = realVersion; 93 | } 94 | }; 95 | 96 | FakeWebSocket.restore = () => { 97 | if (WebSocket._isFake) { 98 | // eslint-disable-next-line no-global-assign 99 | WebSocket = WebSocket._realVersion; 100 | } 101 | }; 102 | -------------------------------------------------------------------------------- /app/images/error.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 54 | 57 | 58 | 60 | 61 | 63 | image/svg+xml 64 | 66 | 67 | 68 | 69 | 70 | 75 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /app/locale/zh_CN.json: -------------------------------------------------------------------------------- 1 | { 2 | "Running without HTTPS is not recommended, crashes or other issues are likely.": "不建议在没有 HTTPS 的情况下运行,可能会出现崩溃或出现其他问题。", 3 | "Connecting...": "连接中...", 4 | "Disconnecting...": "正在断开连接...", 5 | "Reconnecting...": "重新连接中...", 6 | "Internal error": "内部错误", 7 | "Must set host": "必须设置主机", 8 | "Failed to connect to server: ": "无法连接到服务器:", 9 | "Connected (encrypted) to ": "已连接(已加密)到", 10 | "Connected (unencrypted) to ": "已连接(未加密)到", 11 | "Something went wrong, connection is closed": "出了点问题,连接已关闭", 12 | "Failed to connect to server": "无法连接到服务器", 13 | "Disconnected": "已断开连接", 14 | "New connection has been rejected with reason: ": "新连接被拒绝,原因如下:", 15 | "New connection has been rejected": "新连接已被拒绝", 16 | "Credentials are required": "需要凭证", 17 | "noVNC encountered an error:": "noVNC 遇到一个错误:", 18 | "Hide/Show the control bar": "显示/隐藏控制栏", 19 | "Drag": "拖动", 20 | "Move/Drag viewport": "移动/拖动窗口", 21 | "Keyboard": "键盘", 22 | "Show keyboard": "显示键盘", 23 | "Extra keys": "额外按键", 24 | "Show extra keys": "显示额外按键", 25 | "Ctrl": "Ctrl", 26 | "Toggle Ctrl": "切换 Ctrl", 27 | "Alt": "Alt", 28 | "Toggle Alt": "切换 Alt", 29 | "Toggle Windows": "切换窗口", 30 | "Windows": "窗口", 31 | "Send Tab": "发送 Tab 键", 32 | "Tab": "Tab", 33 | "Esc": "Esc", 34 | "Send Escape": "发送 Escape 键", 35 | "Ctrl+Alt+Del": "Ctrl+Alt+Del", 36 | "Send Ctrl-Alt-Del": "发送 Ctrl+Alt+Del 键", 37 | "Shutdown/Reboot": "关机/重启", 38 | "Shutdown/Reboot...": "关机/重启...", 39 | "Power": "电源", 40 | "Shutdown": "关机", 41 | "Reboot": "重启", 42 | "Reset": "重置", 43 | "Clipboard": "剪贴板", 44 | "Edit clipboard content in the textarea below.": "在下面的文本区域中编辑剪贴板内容。", 45 | "Full screen": "全屏", 46 | "Settings": "设置", 47 | "Shared mode": "分享模式", 48 | "View only": "仅查看", 49 | "Clip to window": "限制/裁切窗口大小", 50 | "Scaling mode:": "缩放模式:", 51 | "None": "无", 52 | "Local scaling": "本地缩放", 53 | "Remote resizing": "远程调整大小", 54 | "Advanced": "高级", 55 | "Quality:": "品质:", 56 | "Compression level:": "压缩级别:", 57 | "Repeater ID:": "中继站 ID", 58 | "WebSocket": "WebSocket", 59 | "Encrypt": "加密", 60 | "Host:": "主机:", 61 | "Port:": "端口:", 62 | "Path:": "路径:", 63 | "Automatic reconnect": "自动重新连接", 64 | "Reconnect delay (ms):": "重新连接间隔 (ms):", 65 | "Show dot when no cursor": "无光标时显示点", 66 | "Logging:": "日志级别:", 67 | "Version:": "版本:", 68 | "Disconnect": "断开连接", 69 | "Connect": "连接", 70 | "Server identity": "服务器身份", 71 | "The server has provided the following identifying information:": "服务器提供了以下识别信息:", 72 | "Fingerprint:": "指纹:", 73 | "Please verify that the information is correct and press \"Approve\". Otherwise press \"Reject\".": "请核实信息是否正确,并按 “同意”,否则按 “拒绝”。", 74 | "Approve": "同意", 75 | "Reject": "拒绝", 76 | "Credentials": "凭证", 77 | "Username:": "用户名:", 78 | "Password:": "密码:", 79 | "Send credentials": "发送凭证", 80 | "Cancel": "取消", 81 | "Password is required": "请提供密码", 82 | "Disconnect timeout": "超时断开", 83 | "viewport drag": "窗口拖动", 84 | "Active Mouse Button": "启动鼠标按键", 85 | "No mousebutton": "禁用鼠标按键", 86 | "Left mousebutton": "鼠标左键", 87 | "Middle mousebutton": "鼠标中键", 88 | "Right mousebutton": "鼠标右键", 89 | "Clear": "清除", 90 | "Local Downscaling": "降低本地尺寸", 91 | "Local Cursor": "本地光标", 92 | "Canvas not supported.": "不支持 Canvas。" 93 | } -------------------------------------------------------------------------------- /app/locale/de.json: -------------------------------------------------------------------------------- 1 | { 2 | "Connecting...": "Verbinden...", 3 | "Disconnecting...": "Verbindung trennen...", 4 | "Reconnecting...": "Verbindung wiederherstellen...", 5 | "Internal error": "Interner Fehler", 6 | "Must set host": "Richten Sie den Server ein", 7 | "Connected (encrypted) to ": "Verbunden mit (verschlüsselt) ", 8 | "Connected (unencrypted) to ": "Verbunden mit (unverschlüsselt) ", 9 | "Something went wrong, connection is closed": "Etwas lief schief, Verbindung wurde getrennt", 10 | "Disconnected": "Verbindung zum Server getrennt", 11 | "New connection has been rejected with reason: ": "Verbindung wurde aus folgendem Grund abgelehnt: ", 12 | "New connection has been rejected": "Verbindung wurde abgelehnt", 13 | "Password is required": "Passwort ist erforderlich", 14 | "noVNC encountered an error:": "Ein Fehler ist aufgetreten:", 15 | "Hide/Show the control bar": "Kontrollleiste verstecken/anzeigen", 16 | "Move/Drag viewport": "Ansichtsfenster verschieben/ziehen", 17 | "viewport drag": "Ansichtsfenster ziehen", 18 | "Active Mouse Button": "Aktive Maustaste", 19 | "No mousebutton": "Keine Maustaste", 20 | "Left mousebutton": "Linke Maustaste", 21 | "Middle mousebutton": "Mittlere Maustaste", 22 | "Right mousebutton": "Rechte Maustaste", 23 | "Keyboard": "Tastatur", 24 | "Show keyboard": "Tastatur anzeigen", 25 | "Extra keys": "Zusatztasten", 26 | "Show extra keys": "Zusatztasten anzeigen", 27 | "Ctrl": "Strg", 28 | "Toggle Ctrl": "Strg umschalten", 29 | "Alt": "Alt", 30 | "Toggle Alt": "Alt umschalten", 31 | "Send Tab": "Tab senden", 32 | "Tab": "Tab", 33 | "Esc": "Esc", 34 | "Send Escape": "Escape senden", 35 | "Ctrl+Alt+Del": "Strg+Alt+Entf", 36 | "Send Ctrl-Alt-Del": "Strg+Alt+Entf senden", 37 | "Shutdown/Reboot": "Herunterfahren/Neustarten", 38 | "Shutdown/Reboot...": "Herunterfahren/Neustarten...", 39 | "Power": "Energie", 40 | "Shutdown": "Herunterfahren", 41 | "Reboot": "Neustarten", 42 | "Reset": "Zurücksetzen", 43 | "Clipboard": "Zwischenablage", 44 | "Clear": "Löschen", 45 | "Fullscreen": "Vollbild", 46 | "Settings": "Einstellungen", 47 | "Shared mode": "Geteilter Modus", 48 | "View only": "Nur betrachten", 49 | "Clip to window": "Auf Fenster begrenzen", 50 | "Scaling mode:": "Skalierungsmodus:", 51 | "None": "Keiner", 52 | "Local scaling": "Lokales skalieren", 53 | "Remote resizing": "Serverseitiges skalieren", 54 | "Advanced": "Erweitert", 55 | "Repeater ID:": "Repeater ID:", 56 | "WebSocket": "WebSocket", 57 | "Encrypt": "Verschlüsselt", 58 | "Host:": "Server:", 59 | "Port:": "Port:", 60 | "Path:": "Pfad:", 61 | "Automatic reconnect": "Automatisch wiederverbinden", 62 | "Reconnect delay (ms):": "Wiederverbindungsverzögerung (ms):", 63 | "Logging:": "Protokollierung:", 64 | "Disconnect": "Verbindung trennen", 65 | "Connect": "Verbinden", 66 | "Password:": "Passwort:", 67 | "Cancel": "Abbrechen", 68 | "Canvas not supported.": "Canvas nicht unterstützt.", 69 | "Disconnect timeout": "Zeitüberschreitung beim Trennen", 70 | "Local Downscaling": "Lokales herunterskalieren", 71 | "Local Cursor": "Lokaler Mauszeiger", 72 | "Forcing clipping mode since scrollbars aren't supported by IE in fullscreen": "'Clipping-Modus' aktiviert, Scrollbalken in 'IE-Vollbildmodus' werden nicht unterstützt", 73 | "True Color": "True Color" 74 | } -------------------------------------------------------------------------------- /app/images/fullscreen.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 53 | 56 | 57 | 59 | 60 | 62 | image/svg+xml 63 | 65 | 66 | 67 | 68 | 69 | 74 | 82 | 87 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /app/images/info.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 54 | 57 | 58 | 60 | 61 | 63 | image/svg+xml 64 | 66 | 67 | 68 | 69 | 70 | 75 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /app/locale/pl.json: -------------------------------------------------------------------------------- 1 | { 2 | "Connecting...": "Łączenie...", 3 | "Disconnecting...": "Rozłączanie...", 4 | "Reconnecting...": "Łączenie...", 5 | "Internal error": "Błąd wewnętrzny", 6 | "Must set host": "Host i port są wymagane", 7 | "Connected (encrypted) to ": "Połączenie (szyfrowane) z ", 8 | "Connected (unencrypted) to ": "Połączenie (nieszyfrowane) z ", 9 | "Something went wrong, connection is closed": "Coś poszło źle, połączenie zostało zamknięte", 10 | "Disconnected": "Rozłączony", 11 | "New connection has been rejected with reason: ": "Nowe połączenie zostało odrzucone z powodu: ", 12 | "New connection has been rejected": "Nowe połączenie zostało odrzucone", 13 | "Password is required": "Hasło jest wymagane", 14 | "noVNC encountered an error:": "noVNC napotkało błąd:", 15 | "Hide/Show the control bar": "Pokaż/Ukryj pasek ustawień", 16 | "Move/Drag Viewport": "Ruszaj/Przeciągaj Viewport", 17 | "viewport drag": "przeciągnij viewport", 18 | "Active Mouse Button": "Aktywny Przycisk Myszy", 19 | "No mousebutton": "Brak przycisku myszy", 20 | "Left mousebutton": "Lewy przycisk myszy", 21 | "Middle mousebutton": "Środkowy przycisk myszy", 22 | "Right mousebutton": "Prawy przycisk myszy", 23 | "Keyboard": "Klawiatura", 24 | "Show keyboard": "Pokaż klawiaturę", 25 | "Extra keys": "Przyciski dodatkowe", 26 | "Show extra keys": "Pokaż przyciski dodatkowe", 27 | "Ctrl": "Ctrl", 28 | "Toggle Ctrl": "Przełącz Ctrl", 29 | "Alt": "Alt", 30 | "Toggle Alt": "Przełącz Alt", 31 | "Send Tab": "Wyślij Tab", 32 | "Tab": "Tab", 33 | "Esc": "Esc", 34 | "Send Escape": "Wyślij Escape", 35 | "Ctrl+Alt+Del": "Ctrl+Alt+Del", 36 | "Send Ctrl-Alt-Del": "Wyślij Ctrl-Alt-Del", 37 | "Shutdown/Reboot": "Wyłącz/Uruchom ponownie", 38 | "Shutdown/Reboot...": "Wyłącz/Uruchom ponownie...", 39 | "Power": "Włączony", 40 | "Shutdown": "Wyłącz", 41 | "Reboot": "Uruchom ponownie", 42 | "Reset": "Resetuj", 43 | "Clipboard": "Schowek", 44 | "Clear": "Wyczyść", 45 | "Fullscreen": "Pełny ekran", 46 | "Settings": "Ustawienia", 47 | "Shared Mode": "Tryb Współdzielenia", 48 | "View Only": "Tylko Podgląd", 49 | "Clip to Window": "Przytnij do Okna", 50 | "Scaling Mode:": "Tryb Skalowania:", 51 | "None": "Brak", 52 | "Local scaling": "Skalowanie lokalne", 53 | "Remote resizing": "Skalowanie zdalne", 54 | "Advanced": "Zaawansowane", 55 | "Repeater ID:": "ID Repeatera:", 56 | "WebSocket": "WebSocket", 57 | "Encrypt": "Szyfrowanie", 58 | "Host:": "Host:", 59 | "Port:": "Port:", 60 | "Path:": "Ścieżka:", 61 | "Automatic reconnect": "Automatycznie wznawiaj połączenie", 62 | "Reconnect delay (ms):": "Opóźnienie wznawiania (ms):", 63 | "Logging:": "Poziom logowania:", 64 | "Disconnect": "Rozłącz", 65 | "Connect": "Połącz", 66 | "Password:": "Hasło:", 67 | "Cancel": "Anuluj", 68 | "Canvas not supported.": "Element Canvas nie jest wspierany.", 69 | "Disconnect timeout": "Timeout rozłączenia", 70 | "Local Downscaling": "Downscaling lokalny", 71 | "Local Cursor": "Lokalny kursor", 72 | "Forcing clipping mode since scrollbars aren't supported by IE in fullscreen": "Wymuszam clipping mode ponieważ paski przewijania nie są wspierane przez IE w trybie pełnoekranowym", 73 | "True Color": "True Color", 74 | "Style:": "Styl:", 75 | "default": "domyślny", 76 | "Apply": "Zapisz", 77 | "Connection": "Połączenie", 78 | "Token:": "Token:", 79 | "Send Password": "Wyślij Hasło" 80 | } -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | push: 5 | pull_request: 6 | release: 7 | types: [published] 8 | 9 | jobs: 10 | npm: 11 | runs-on: ubuntu-latest 12 | permissions: 13 | id-token: write 14 | contents: read 15 | steps: 16 | - uses: actions/checkout@v4 17 | - run: | 18 | GITREV=$(git rev-parse --short HEAD) 19 | echo $GITREV 20 | sed -i "s/^\(.*\"version\".*\)\"\([^\"]\+\)\"\(.*\)\$/\1\"\2-g$GITREV\"\3/" package.json 21 | if: github.event_name != 'release' 22 | - uses: actions/setup-node@v4 23 | with: 24 | # Node 24 is needed to get npm > 11.5.1, which is a requirement for 25 | # OIDC auth. 26 | node-version: 24 27 | # Needs to be explicitly specified for auth to work 28 | registry-url: 'https://registry.npmjs.org' 29 | - run: npm install 30 | - uses: actions/upload-artifact@v4 31 | with: 32 | name: npm 33 | path: lib 34 | - run: npm publish --access public 35 | if: | 36 | github.repository == 'novnc/noVNC' && 37 | github.event_name == 'release' && 38 | !github.event.release.prerelease 39 | - run: npm publish --access public --tag beta 40 | if: | 41 | github.repository == 'novnc/noVNC' && 42 | github.event_name == 'release' && 43 | github.event.release.prerelease 44 | - run: npm publish --access public --tag dev 45 | if: | 46 | github.repository == 'novnc/noVNC' && 47 | github.event_name == 'push' && 48 | github.event.ref == 'refs/heads/master' 49 | snap: 50 | runs-on: ubuntu-latest 51 | steps: 52 | - uses: actions/checkout@v4 53 | - run: | 54 | GITREV=$(git rev-parse --short HEAD) 55 | echo $GITREV 56 | sed -i "s/^\(.*\"version\".*\)\"\([^\"]\+\)\"\(.*\)\$/\1\"\2-g$GITREV\"\3/" package.json 57 | if: github.event_name != 'release' 58 | - run: | 59 | VERSION=$(grep '"version"' package.json | cut -d '"' -f 4) 60 | echo $VERSION 61 | sed -i "s/^version:.*/version: '$VERSION'/" snap/snapcraft.yaml 62 | - uses: snapcore/action-build@v1 63 | id: snapcraft 64 | - uses: actions/upload-artifact@v4 65 | with: 66 | name: snap 67 | path: ${{ steps.snapcraft.outputs.snap }} 68 | - uses: snapcore/action-publish@v1 69 | with: 70 | snap: ${{ steps.snapcraft.outputs.snap }} 71 | release: stable 72 | env: 73 | SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_LOGIN }} 74 | if: | 75 | github.repository == 'novnc/noVNC' && 76 | github.event_name == 'release' && 77 | !github.event.release.prerelease 78 | - uses: snapcore/action-publish@v1 79 | with: 80 | snap: ${{ steps.snapcraft.outputs.snap }} 81 | release: beta 82 | env: 83 | SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_LOGIN }} 84 | if: | 85 | github.repository == 'novnc/noVNC' && 86 | github.event_name == 'release' && 87 | github.event.release.prerelease 88 | - uses: snapcore/action-publish@v1 89 | with: 90 | snap: ${{ steps.snapcraft.outputs.snap }} 91 | release: edge 92 | env: 93 | SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_LOGIN }} 94 | if: | 95 | github.repository == 'novnc/noVNC' && 96 | github.event_name == 'push' && 97 | github.event.ref == 'refs/heads/master' 98 | -------------------------------------------------------------------------------- /tests/test.util.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | import * as Log from '../core/util/logging.js'; 3 | import { encodeUTF8, decodeUTF8 } from '../core/util/strings.js'; 4 | 5 | describe('Utils', function () { 6 | "use strict"; 7 | 8 | describe('logging functions', function () { 9 | beforeEach(function () { 10 | sinon.spy(console, 'log'); 11 | sinon.spy(console, 'debug'); 12 | sinon.spy(console, 'warn'); 13 | sinon.spy(console, 'error'); 14 | sinon.spy(console, 'info'); 15 | }); 16 | 17 | afterEach(function () { 18 | console.log.restore(); 19 | console.debug.restore(); 20 | console.warn.restore(); 21 | console.error.restore(); 22 | console.info.restore(); 23 | Log.initLogging(); 24 | }); 25 | 26 | it('should use noop for levels lower than the min level', function () { 27 | Log.initLogging('warn'); 28 | Log.Debug('hi'); 29 | Log.Info('hello'); 30 | expect(console.log).to.not.have.been.called; 31 | }); 32 | 33 | it('should use console.debug for Debug', function () { 34 | Log.initLogging('debug'); 35 | Log.Debug('dbg'); 36 | expect(console.debug).to.have.been.calledWith('dbg'); 37 | }); 38 | 39 | it('should use console.info for Info', function () { 40 | Log.initLogging('debug'); 41 | Log.Info('inf'); 42 | expect(console.info).to.have.been.calledWith('inf'); 43 | }); 44 | 45 | it('should use console.warn for Warn', function () { 46 | Log.initLogging('warn'); 47 | Log.Warn('wrn'); 48 | expect(console.warn).to.have.been.called; 49 | expect(console.warn).to.have.been.calledWith('wrn'); 50 | }); 51 | 52 | it('should use console.error for Error', function () { 53 | Log.initLogging('error'); 54 | Log.Error('err'); 55 | expect(console.error).to.have.been.called; 56 | expect(console.error).to.have.been.calledWith('err'); 57 | }); 58 | }); 59 | 60 | describe('string functions', function () { 61 | it('should decode UTF-8 to DOMString correctly', function () { 62 | const utf8string = '\xd0\x9f'; 63 | const domstring = decodeUTF8(utf8string); 64 | expect(domstring).to.equal("П"); 65 | }); 66 | 67 | it('should encode DOMString to UTF-8 correctly', function () { 68 | const domstring = "åäöa"; 69 | const utf8string = encodeUTF8(domstring); 70 | expect(utf8string).to.equal('\xc3\xa5\xc3\xa4\xc3\xb6\x61'); 71 | }); 72 | 73 | it('should allow Latin-1 strings if allowLatin1 is set when decoding', function () { 74 | const latin1string = '\xe5\xe4\xf6'; 75 | expect(() => decodeUTF8(latin1string)).to.throw(Error); 76 | expect(decodeUTF8(latin1string, true)).to.equal('åäö'); 77 | }); 78 | }); 79 | 80 | // TODO(directxman12): test the conf_default and conf_defaults methods 81 | // TODO(directxman12): test the event methods (addEvent, removeEvent, stopEvent) 82 | // TODO(directxman12): figure out a good way to test getPosition and getEventPosition 83 | // TODO(directxman12): figure out how to test the browser detection functions properly 84 | // (we can't really test them against the browsers, except for Gecko 85 | // via PhantomJS, the default test driver) 86 | }); 87 | /* eslint-enable no-console */ 88 | -------------------------------------------------------------------------------- /tests/test.copyrect.js: -------------------------------------------------------------------------------- 1 | import Websock from '../core/websock.js'; 2 | import Display from '../core/display.js'; 3 | 4 | import CopyRectDecoder from '../core/decoders/copyrect.js'; 5 | 6 | import FakeWebSocket from './fake.websocket.js'; 7 | 8 | function testDecodeRect(decoder, x, y, width, height, data, display, depth) { 9 | let sock; 10 | let done = false; 11 | 12 | sock = new Websock; 13 | sock.open("ws://example.com"); 14 | 15 | sock.on('message', () => { 16 | done = decoder.decodeRect(x, y, width, height, sock, display, depth); 17 | }); 18 | 19 | // Empty messages are filtered at multiple layers, so we need to 20 | // do a direct call 21 | if (data.length === 0) { 22 | done = decoder.decodeRect(x, y, width, height, sock, display, depth); 23 | } else { 24 | sock._websocket._receiveData(new Uint8Array(data)); 25 | } 26 | 27 | display.flip(); 28 | 29 | return done; 30 | } 31 | 32 | describe('CopyRect decoder', function () { 33 | let decoder; 34 | let display; 35 | 36 | before(FakeWebSocket.replace); 37 | after(FakeWebSocket.restore); 38 | 39 | beforeEach(function () { 40 | decoder = new CopyRectDecoder(); 41 | display = new Display(document.createElement('canvas')); 42 | display.resize(4, 4); 43 | }); 44 | 45 | it('should handle the CopyRect encoding', function () { 46 | // seed some initial data to copy 47 | display.fillRect(0, 0, 4, 4, [ 0x11, 0x22, 0x33 ]); 48 | display.fillRect(0, 0, 2, 2, [ 0x00, 0x00, 0xff ]); 49 | display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]); 50 | 51 | let done; 52 | done = testDecodeRect(decoder, 0, 2, 2, 2, 53 | [0x00, 0x02, 0x00, 0x00], 54 | display, 24); 55 | expect(done).to.be.true; 56 | done = testDecodeRect(decoder, 2, 2, 2, 2, 57 | [0x00, 0x00, 0x00, 0x00], 58 | display, 24); 59 | expect(done).to.be.true; 60 | 61 | let targetData = new Uint8Array([ 62 | 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 63 | 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 64 | 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 65 | 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255 66 | ]); 67 | 68 | expect(display).to.have.displayed(targetData); 69 | }); 70 | 71 | it('should handle empty rects', function () { 72 | display.fillRect(0, 0, 4, 4, [ 0x00, 0x00, 0xff ]); 73 | display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]); 74 | display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]); 75 | 76 | let done = testDecodeRect(decoder, 1, 2, 0, 0, 77 | [0x00, 0x00, 0x00, 0x00], 78 | display, 24); 79 | 80 | let targetData = new Uint8Array([ 81 | 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 82 | 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 83 | 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 84 | 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255 85 | ]); 86 | 87 | expect(done).to.be.true; 88 | expect(display).to.have.displayed(targetData); 89 | }); 90 | }); 91 | -------------------------------------------------------------------------------- /app/images/ctrlaltdel.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 54 | 57 | 58 | 60 | 61 | 63 | image/svg+xml 64 | 66 | 67 | 68 | 69 | 70 | 75 | 83 | 91 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /app/images/connect.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 53 | 56 | 57 | 59 | 60 | 62 | image/svg+xml 63 | 65 | 66 | 67 | 68 | 69 | 74 | 77 | 83 | 89 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /utils/genkeysymdef.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* 3 | * genkeysymdef: X11 keysymdef.h to JavaScript converter 4 | * Copyright (C) 2018 The noVNC authors 5 | * Licensed under MPL 2.0 (see LICENSE.txt) 6 | */ 7 | 8 | "use strict"; 9 | 10 | import fs from 'fs'; 11 | 12 | let showHelp = process.argv.length === 2; 13 | let filename; 14 | 15 | for (let i = 2; i < process.argv.length; ++i) { 16 | switch (process.argv[i]) { 17 | case "--help": 18 | case "-h": 19 | showHelp = true; 20 | break; 21 | case "--file": 22 | case "-f": 23 | default: 24 | filename = process.argv[i]; 25 | } 26 | } 27 | 28 | if (!filename) { 29 | showHelp = true; 30 | console.log("Error: No filename specified\n"); 31 | } 32 | 33 | if (showHelp) { 34 | console.log("Parses a *nix keysymdef.h to generate Unicode code point mappings"); 35 | console.log("Usage: node parse.js [options] filename:"); 36 | console.log(" -h [ --help ] Produce this help message"); 37 | console.log(" filename The keysymdef.h file to parse"); 38 | process.exit(0); 39 | } 40 | 41 | const buf = fs.readFileSync(filename); 42 | const str = buf.toString('utf8'); 43 | 44 | const re = /^#define XK_([a-zA-Z_0-9]+)\s+0x([0-9a-fA-F]+)\s*(\/\*\s*(.*)\s*\*\/)?\s*$/m; 45 | 46 | const arr = str.split('\n'); 47 | 48 | const codepoints = {}; 49 | 50 | for (let i = 0; i < arr.length; ++i) { 51 | const result = re.exec(arr[i]); 52 | if (result) { 53 | const keyname = result[1]; 54 | const keysym = parseInt(result[2], 16); 55 | const remainder = result[3]; 56 | 57 | const unicodeRes = /U\+([0-9a-fA-F]+)/.exec(remainder); 58 | if (unicodeRes) { 59 | const unicode = parseInt(unicodeRes[1], 16); 60 | // The first entry is the preferred one 61 | if (!codepoints[unicode]) { 62 | codepoints[unicode] = { keysym: keysym, name: keyname }; 63 | } 64 | } 65 | } 66 | } 67 | 68 | let out = 69 | "/*\n" + 70 | " * Mapping from Unicode codepoints to X11/RFB keysyms\n" + 71 | " *\n" + 72 | " * This file was automatically generated from keysymdef.h\n" + 73 | " * DO NOT EDIT!\n" + 74 | " */\n" + 75 | "\n" + 76 | "/* Functions at the bottom */\n" + 77 | "\n" + 78 | "const codepoints = {\n"; 79 | 80 | function toHex(num) { 81 | let s = num.toString(16); 82 | if (s.length < 4) { 83 | s = ("0000" + s).slice(-4); 84 | } 85 | return "0x" + s; 86 | } 87 | 88 | for (let codepoint in codepoints) { 89 | codepoint = parseInt(codepoint); 90 | 91 | // Latin-1? 92 | if ((codepoint >= 0x20) && (codepoint <= 0xff)) { 93 | continue; 94 | } 95 | 96 | // Handled by the general Unicode mapping? 97 | if ((codepoint | 0x01000000) === codepoints[codepoint].keysym) { 98 | continue; 99 | } 100 | 101 | out += " " + toHex(codepoint) + ": " + 102 | toHex(codepoints[codepoint].keysym) + 103 | ", // XK_" + codepoints[codepoint].name + "\n"; 104 | } 105 | 106 | out += 107 | "};\n" + 108 | "\n" + 109 | "export default {\n" + 110 | " lookup(u) {\n" + 111 | " // Latin-1 is one-to-one mapping\n" + 112 | " if ((u >= 0x20) && (u <= 0xff)) {\n" + 113 | " return u;\n" + 114 | " }\n" + 115 | "\n" + 116 | " // Lookup table (fairly random)\n" + 117 | " const keysym = codepoints[u];\n" + 118 | " if (keysym !== undefined) {\n" + 119 | " return keysym;\n" + 120 | " }\n" + 121 | "\n" + 122 | " // General mapping as final fallback\n" + 123 | " return 0x01000000 | u;\n" + 124 | " },\n" + 125 | "};"; 126 | 127 | console.log(out); 128 | -------------------------------------------------------------------------------- /tests/test.inflator.js: -------------------------------------------------------------------------------- 1 | import { deflateInit, deflate, Z_FULL_FLUSH } from "../vendor/pako/lib/zlib/deflate.js"; 2 | import ZStream from "../vendor/pako/lib/zlib/zstream.js"; 3 | import Inflator from "../core/inflator.js"; 4 | 5 | function _deflator(data) { 6 | let strm = new ZStream(); 7 | 8 | deflateInit(strm, 5); 9 | 10 | /* eslint-disable camelcase */ 11 | strm.input = data; 12 | strm.avail_in = strm.input.length; 13 | strm.next_in = 0; 14 | /* eslint-enable camelcase */ 15 | 16 | let chunks = []; 17 | let totalLen = 0; 18 | while (strm.avail_in > 0) { 19 | /* eslint-disable camelcase */ 20 | strm.output = new Uint8Array(1024 * 10 * 10); 21 | strm.avail_out = strm.output.length; 22 | strm.next_out = 0; 23 | /* eslint-enable camelcase */ 24 | 25 | let ret = deflate(strm, Z_FULL_FLUSH); 26 | 27 | // Check that return code is not an error 28 | expect(ret).to.be.greaterThan(-1); 29 | 30 | let chunk = new Uint8Array(strm.output.buffer, 0, strm.next_out); 31 | totalLen += chunk.length; 32 | chunks.push(chunk); 33 | } 34 | 35 | // Combine chunks into a single data 36 | 37 | let outData = new Uint8Array(totalLen); 38 | let offset = 0; 39 | 40 | for (let i = 0; i < chunks.length; i++) { 41 | outData.set(chunks[i], offset); 42 | offset += chunks[i].length; 43 | } 44 | 45 | return outData; 46 | } 47 | 48 | describe('Inflate data', function () { 49 | 50 | it('should be able to inflate messages', function () { 51 | let inflator = new Inflator(); 52 | 53 | let text = "123asdf"; 54 | let preText = new Uint8Array(text.length); 55 | for (let i = 0; i < preText.length; i++) { 56 | preText[i] = text.charCodeAt(i); 57 | } 58 | 59 | let compText = _deflator(preText); 60 | 61 | inflator.setInput(compText); 62 | let inflatedText = inflator.inflate(preText.length); 63 | 64 | expect(inflatedText).to.array.equal(preText); 65 | 66 | }); 67 | 68 | it('should be able to inflate large messages', function () { 69 | let inflator = new Inflator(); 70 | 71 | /* Generate a big string with random characters. Used because 72 | repetition of letters might be deflated more effectively than 73 | random ones. */ 74 | let text = ""; 75 | let characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; 76 | for (let i = 0; i < 300000; i++) { 77 | text += characters.charAt(Math.floor(Math.random() * characters.length)); 78 | } 79 | 80 | let preText = new Uint8Array(text.length); 81 | for (let i = 0; i < preText.length; i++) { 82 | preText[i] = text.charCodeAt(i); 83 | } 84 | 85 | let compText = _deflator(preText); 86 | 87 | //Check that the compressed size is expected size 88 | expect(compText.length).to.be.greaterThan((1024 * 10 * 10) * 2); 89 | 90 | inflator.setInput(compText); 91 | let inflatedText = inflator.inflate(preText.length); 92 | 93 | expect(inflatedText).to.array.equal(preText); 94 | }); 95 | 96 | it('should throw an error on insufficient data', function () { 97 | let inflator = new Inflator(); 98 | 99 | let text = "123asdf"; 100 | let preText = new Uint8Array(text.length); 101 | for (let i = 0; i < preText.length; i++) { 102 | preText[i] = text.charCodeAt(i); 103 | } 104 | 105 | let compText = _deflator(preText); 106 | 107 | inflator.setInput(compText); 108 | expect(() => inflator.inflate(preText.length * 2)).to.throw(); 109 | }); 110 | }); 111 | -------------------------------------------------------------------------------- /app/images/alt.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 54 | 57 | 58 | 60 | 61 | 63 | image/svg+xml 64 | 66 | 67 | 68 | 69 | 70 | 75 | 78 | 82 | 86 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /app/locale/sv.json: -------------------------------------------------------------------------------- 1 | { 2 | "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.", 3 | "Connecting...": "Ansluter...", 4 | "Disconnecting...": "Kopplar ner...", 5 | "Reconnecting...": "Återansluter...", 6 | "Internal error": "Internt fel", 7 | "Failed to connect to server: ": "Misslyckades att ansluta till servern: ", 8 | "Connected (encrypted) to ": "Ansluten (krypterat) till ", 9 | "Connected (unencrypted) to ": "Ansluten (okrypterat) till ", 10 | "Something went wrong, connection is closed": "Något gick fel, anslutningen avslutades", 11 | "Failed to connect to server": "Misslyckades att ansluta till servern", 12 | "Disconnected": "Frånkopplad", 13 | "New connection has been rejected with reason: ": "Ny anslutning har blivit nekad med följande skäl: ", 14 | "New connection has been rejected": "Ny anslutning har blivit nekad", 15 | "Credentials are required": "Användaruppgifter krävs", 16 | "noVNC encountered an error:": "noVNC stötte på ett problem:", 17 | "Hide/Show the control bar": "Göm/Visa kontrollbaren", 18 | "Drag": "Dra", 19 | "Move/Drag viewport": "Flytta/Dra vyn", 20 | "Keyboard": "Tangentbord", 21 | "Show keyboard": "Visa tangentbord", 22 | "Extra keys": "Extraknappar", 23 | "Show extra keys": "Visa extraknappar", 24 | "Ctrl": "Ctrl", 25 | "Toggle Ctrl": "Växla Ctrl", 26 | "Alt": "Alt", 27 | "Toggle Alt": "Växla Alt", 28 | "Toggle Windows": "Växla Windows", 29 | "Windows": "Windows", 30 | "Send Tab": "Skicka Tab", 31 | "Tab": "Tab", 32 | "Esc": "Esc", 33 | "Send Escape": "Skicka Escape", 34 | "Ctrl+Alt+Del": "Ctrl+Alt+Del", 35 | "Send Ctrl-Alt-Del": "Skicka Ctrl-Alt-Del", 36 | "Shutdown/Reboot": "Stäng av/Boota om", 37 | "Shutdown/Reboot...": "Stäng av/Boota om...", 38 | "Power": "Ström", 39 | "Shutdown": "Stäng av", 40 | "Reboot": "Boota om", 41 | "Reset": "Återställ", 42 | "Clipboard": "Urklipp", 43 | "Edit clipboard content in the textarea below.": "Redigera urklippets innehåll i fältet nedan.", 44 | "Full screen": "Fullskärm", 45 | "Settings": "Inställningar", 46 | "Shared mode": "Delat läge", 47 | "View only": "Endast visning", 48 | "Clip to window": "Begränsa till fönster", 49 | "Scaling mode:": "Skalningsläge:", 50 | "None": "Ingen", 51 | "Local scaling": "Lokal skalning", 52 | "Remote resizing": "Ändra storlek", 53 | "Advanced": "Avancerat", 54 | "Quality:": "Kvalitet:", 55 | "Compression level:": "Kompressionsnivå:", 56 | "Repeater ID:": "Repeater-ID:", 57 | "WebSocket": "WebSocket", 58 | "Encrypt": "Kryptera", 59 | "Host:": "Värd:", 60 | "Port:": "Port:", 61 | "Path:": "Sökväg:", 62 | "Automatic reconnect": "Automatisk återanslutning", 63 | "Reconnect delay (ms):": "Fördröjning (ms):", 64 | "Show dot when no cursor": "Visa prick när ingen muspekare finns", 65 | "Logging:": "Loggning:", 66 | "Version:": "Version:", 67 | "Disconnect": "Koppla från", 68 | "Connect": "Anslut", 69 | "Server identity": "Server-identitet", 70 | "The server has provided the following identifying information:": "Servern har gett följande identifierande information:", 71 | "Fingerprint:": "Fingeravtryck:", 72 | "Please verify that the information is correct and press \"Approve\". Otherwise press \"Reject\".": "Kontrollera att informationen är korrekt och tryck sedan \"Godkänn\". Tryck annars \"Neka\".", 73 | "Approve": "Godkänn", 74 | "Reject": "Neka", 75 | "Credentials": "Användaruppgifter", 76 | "Username:": "Användarnamn:", 77 | "Password:": "Lösenord:", 78 | "Send credentials": "Skicka användaruppgifter", 79 | "Cancel": "Avbryt", 80 | "Must set host": "Du måste specifiera en värd", 81 | "HTTPS is required for full functionality": "HTTPS krävs för full funktionalitet", 82 | "Clear": "Rensa" 83 | } -------------------------------------------------------------------------------- /app/locale/hu.json: -------------------------------------------------------------------------------- 1 | { 2 | "Running without HTTPS is not recommended, crashes or other issues are likely.": "HTTPS nélkül futtatni nem ajánlott, összeomlások vagy más problémák várhatók.", 3 | "Connecting...": "Kapcsolódás...", 4 | "Disconnecting...": "Kapcsolat bontása...", 5 | "Reconnecting...": "Újrakapcsolódás...", 6 | "Internal error": "Belső hiba", 7 | "Failed to connect to server: ": "Nem sikerült csatlakozni a szerverhez: ", 8 | "Connected (encrypted) to ": "Kapcsolódva (titkosítva) ehhez: ", 9 | "Connected (unencrypted) to ": "Kapcsolódva (titkosítatlanul) ehhez: ", 10 | "Something went wrong, connection is closed": "Valami hiba történt, a kapcsolat lezárult", 11 | "Failed to connect to server": "Nem sikerült csatlakozni a szerverhez", 12 | "Disconnected": "Kapcsolat bontva", 13 | "New connection has been rejected with reason: ": "Az új kapcsolat elutasítva, indok: ", 14 | "New connection has been rejected": "Az új kapcsolat elutasítva", 15 | "Credentials are required": "Hitelesítő adatok szükségesek", 16 | "noVNC encountered an error:": "A noVNC hibát észlelt:", 17 | "Hide/Show the control bar": "Vezérlősáv elrejtése/megjelenítése", 18 | "Drag": "Húzás", 19 | "Move/Drag viewport": "Nézet mozgatása/húzása", 20 | "Keyboard": "Billentyűzet", 21 | "Show keyboard": "Billentyűzet megjelenítése", 22 | "Extra keys": "Extra billentyűk", 23 | "Show extra keys": "Extra billentyűk megjelenítése", 24 | "Ctrl": "Ctrl", 25 | "Toggle Ctrl": "Ctrl lenyomása/felengedése", 26 | "Alt": "Alt", 27 | "Toggle Alt": "Alt lenyomása/felengedése", 28 | "Toggle Windows": "Windows lenyomása/felengedése", 29 | "Windows": "Windows", 30 | "Send Tab": "Tab küldése", 31 | "Tab": "Tab", 32 | "Esc": "Esc", 33 | "Send Escape": "Escape küldése", 34 | "Ctrl+Alt+Del": "Ctrl+Alt+Del", 35 | "Send Ctrl-Alt-Del": "Ctrl-Alt-Del küldése", 36 | "Shutdown/Reboot": "Leállítás/Újraindítás", 37 | "Shutdown/Reboot...": "Leállítás/Újraindítás...", 38 | "Power": "Bekapcsolás", 39 | "Shutdown": "Leállítás", 40 | "Reboot": "Újraindítás", 41 | "Reset": "Reset", 42 | "Clipboard": "Vágólap", 43 | "Edit clipboard content in the textarea below.": "Itt tudod módosítani a vágólap tartalmát.", 44 | "Full screen": "Teljes képernyő", 45 | "Settings": "Beállítások", 46 | "Shared mode": "Megosztott mód", 47 | "View only": "Csak megtekintés", 48 | "Clip to window": "Ablakhoz igazítás", 49 | "Scaling mode:": "Méretezési mód:", 50 | "None": "Nincs", 51 | "Local scaling": "Helyi méretezés", 52 | "Remote resizing": "Távoli átméretezés", 53 | "Advanced": "Speciális", 54 | "Quality:": "Minőség:", 55 | "Compression level:": "Tömörítési szint:", 56 | "Repeater ID:": "Ismétlő azonosító:", 57 | "WebSocket": "WebSocket", 58 | "Encrypt": "Titkosítás", 59 | "Host:": "Hoszt:", 60 | "Port:": "Port:", 61 | "Path:": "Útvonal:", 62 | "Automatic reconnect": "Automatikus újracsatlakozás", 63 | "Reconnect delay (ms):": "Újracsatlakozás késleltetése (ms):", 64 | "Show dot when no cursor": "Kurzor hiányában pont mutatása", 65 | "Logging:": "Naplózás:", 66 | "Version:": "Verzió:", 67 | "Disconnect": "Kapcsolat bontása", 68 | "Connect": "Csatlakozás", 69 | "Server identity": "Szerver azonosító", 70 | "The server has provided the following identifying information:": "A szerver a következő azonosító információt adta meg:", 71 | "Fingerprint:": "Ujjlenyomat:", 72 | "Please verify that the information is correct and press \"Approve\". Otherwise press \"Reject\".": "Ellenőrizze, hogy az információ helyes-e és nyomja meg a \"Jóváhagyás\" gombot. Ellenkező esetben nyomja meg az \"Elutasítás\" gombot.", 73 | "Approve": "Jóváhagyás", 74 | "Reject": "Elutasítás", 75 | "Credentials": "Hitelesítő adatok", 76 | "Username:": "Felhasználónév:", 77 | "Password:": "Jelszó:", 78 | "Send credentials": "Hitelesítő adatok küldése", 79 | "Cancel": "Mégse" 80 | } -------------------------------------------------------------------------------- /app/locale/fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "Running without HTTPS is not recommended, crashes or other issues are likely.": "Lancer sans HTTPS n'est pas recommandé, crashs ou autres problèmes en vue.", 3 | "Connecting...": "En cours de connexion...", 4 | "Disconnecting...": "Déconnexion en cours...", 5 | "Reconnecting...": "Reconnexion en cours...", 6 | "Internal error": "Erreur interne", 7 | "Failed to connect to server: ": "Échec de connexion au serveur ", 8 | "Connected (encrypted) to ": "Connecté (chiffré) à ", 9 | "Connected (unencrypted) to ": "Connecté (non chiffré) à ", 10 | "Something went wrong, connection is closed": "Quelque chose s'est mal passé, la connexion a été fermée", 11 | "Failed to connect to server": "Échec de connexion au serveur", 12 | "Disconnected": "Déconnecté", 13 | "New connection has been rejected with reason: ": "Une nouvelle connexion a été rejetée avec motif : ", 14 | "New connection has been rejected": "Une nouvelle connexion a été rejetée", 15 | "Credentials are required": "Les identifiants sont requis", 16 | "noVNC encountered an error:": "noVNC a rencontré une erreur :", 17 | "Hide/Show the control bar": "Masquer/Afficher la barre de contrôle", 18 | "Drag": "Faire glisser", 19 | "Move/Drag viewport": "Déplacer la fenêtre de visualisation", 20 | "Keyboard": "Clavier", 21 | "Show keyboard": "Afficher le clavier", 22 | "Extra keys": "Touches supplémentaires", 23 | "Show extra keys": "Afficher les touches supplémentaires", 24 | "Ctrl": "Ctrl", 25 | "Toggle Ctrl": "Basculer Ctrl", 26 | "Alt": "Alt", 27 | "Toggle Alt": "Basculer Alt", 28 | "Toggle Windows": "Basculer Windows", 29 | "Windows": "Fenêtre", 30 | "Send Tab": "Envoyer Tab", 31 | "Tab": "Tabulation", 32 | "Esc": "Esc", 33 | "Send Escape": "Envoyer Escape", 34 | "Ctrl+Alt+Del": "Ctrl+Alt+Del", 35 | "Send Ctrl-Alt-Del": "Envoyer Ctrl-Alt-Del", 36 | "Shutdown/Reboot": "Arrêter/Redémarrer", 37 | "Shutdown/Reboot...": "Arrêter/Redémarrer...", 38 | "Power": "Alimentation", 39 | "Shutdown": "Arrêter", 40 | "Reboot": "Redémarrer", 41 | "Reset": "Réinitialiser", 42 | "Clipboard": "Presse-papiers", 43 | "Edit clipboard content in the textarea below.": "Editer le contenu du presse-papier dans la zone ci-dessous.", 44 | "Full screen": "Plein écran", 45 | "Settings": "Paramètres", 46 | "Shared mode": "Mode partagé", 47 | "View only": "Afficher uniquement", 48 | "Clip to window": "Ajuster à la fenêtre", 49 | "Scaling mode:": "Mode mise à l'échelle :", 50 | "None": "Aucun", 51 | "Local scaling": "Mise à l'échelle locale", 52 | "Remote resizing": "Redimensionnement à distance", 53 | "Advanced": "Avancé", 54 | "Quality:": "Qualité :", 55 | "Compression level:": "Niveau de compression :", 56 | "Repeater ID:": "ID Répéteur :", 57 | "WebSocket": "WebSocket", 58 | "Encrypt": "Chiffrer", 59 | "Host:": "Hôte :", 60 | "Port:": "Port :", 61 | "Path:": "Chemin :", 62 | "Automatic reconnect": "Reconnecter automatiquement", 63 | "Reconnect delay (ms):": "Délai de reconnexion (ms) :", 64 | "Show dot when no cursor": "Afficher le point lorsqu'il n'y a pas de curseur", 65 | "Logging:": "Se connecter :", 66 | "Version:": "Version :", 67 | "Disconnect": "Déconnecter", 68 | "Connect": "Connecter", 69 | "Server identity": "Identité du serveur", 70 | "The server has provided the following identifying information:": "Le serveur a fourni l'identification suivante :", 71 | "Fingerprint:": "Empreinte digitale :", 72 | "Please verify that the information is correct and press \"Approve\". Otherwise press \"Reject\".": "SVP, verifiez que l'information est correcte et pressez \"Accepter\". Sinon pressez \"Refuser\".", 73 | "Approve": "Accepter", 74 | "Reject": "Refuser", 75 | "Credentials": "Envoyer les identifiants", 76 | "Username:": "Nom d'utilisateur :", 77 | "Password:": "Mot de passe :", 78 | "Send credentials": "Envoyer les identifiants", 79 | "Cancel": "Annuler", 80 | "Must set host": "Doit définir l'hôte", 81 | "Clear": "Effacer" 82 | } -------------------------------------------------------------------------------- /app/images/warning.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 54 | 57 | 58 | 60 | 61 | 63 | image/svg+xml 64 | 66 | 67 | 68 | 69 | 70 | 75 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /tests/test.clipboard.js: -------------------------------------------------------------------------------- 1 | import AsyncClipboard from '../core/clipboard.js'; 2 | 3 | describe('Async Clipboard', function () { 4 | "use strict"; 5 | 6 | let targetMock; 7 | let clipboard; 8 | 9 | beforeEach(function () { 10 | sinon.stub(navigator, "clipboard").value({ 11 | writeText: sinon.stub().resolves(), 12 | readText: sinon.stub().resolves(), 13 | }); 14 | 15 | sinon.stub(navigator, "permissions").value({ 16 | query: sinon.stub(), 17 | }); 18 | 19 | targetMock = document.createElement("canvas"); 20 | clipboard = new AsyncClipboard(targetMock); 21 | }); 22 | 23 | afterEach(function () { 24 | sinon.restore(); 25 | targetMock = null; 26 | clipboard = null; 27 | }); 28 | 29 | function stubClipboardPermissions(state) { 30 | navigator.permissions.query 31 | .withArgs({ name: 'clipboard-write', allowWithoutGesture: true }) 32 | .resolves({ state: state }); 33 | navigator.permissions.query 34 | .withArgs({ name: 'clipboard-read', allowWithoutGesture: false }) 35 | .resolves({ state: state }); 36 | } 37 | 38 | function nextTick() { 39 | return new Promise(resolve => setTimeout(resolve, 0)); 40 | } 41 | 42 | it('grab() adds listener if permissions granted', async function () { 43 | stubClipboardPermissions('granted'); 44 | 45 | const addListenerSpy = sinon.spy(targetMock, 'addEventListener'); 46 | clipboard.grab(); 47 | 48 | await nextTick(); 49 | 50 | expect(addListenerSpy.calledWith('focus')).to.be.true; 51 | }); 52 | 53 | it('grab() does not add listener if permissions denied', async function () { 54 | stubClipboardPermissions('denied'); 55 | 56 | const addListenerSpy = sinon.spy(targetMock, 'addEventListener'); 57 | clipboard.grab(); 58 | 59 | await nextTick(); 60 | 61 | expect(addListenerSpy.calledWith('focus')).to.be.false; 62 | }); 63 | 64 | it('focus event triggers onpaste() if permissions granted', async function () { 65 | stubClipboardPermissions('granted'); 66 | 67 | const text = 'hello clipboard world'; 68 | navigator.clipboard.readText.resolves(text); 69 | 70 | const spyPromise = new Promise(resolve => clipboard.onpaste = resolve); 71 | 72 | clipboard.grab(); 73 | 74 | await nextTick(); 75 | 76 | targetMock.dispatchEvent(new Event('focus')); 77 | 78 | const res = await spyPromise; 79 | expect(res).to.equal(text); 80 | }); 81 | 82 | it('focus event does not trigger onpaste() if permissions denied', async function () { 83 | stubClipboardPermissions('denied'); 84 | 85 | const text = 'should not read'; 86 | navigator.clipboard.readText.resolves(text); 87 | 88 | clipboard.onpaste = sinon.spy(); 89 | 90 | clipboard.grab(); 91 | 92 | await nextTick(); 93 | 94 | targetMock.dispatchEvent(new Event('focus')); 95 | 96 | expect(clipboard.onpaste.called).to.be.false; 97 | }); 98 | 99 | it('writeClipboard() calls navigator.clipboard.writeText() if permissions granted', async function () { 100 | stubClipboardPermissions('granted'); 101 | clipboard._isAvailable = true; 102 | 103 | const text = 'writing to clipboard'; 104 | const result = clipboard.writeClipboard(text); 105 | 106 | expect(navigator.clipboard.writeText.calledWith(text)).to.be.true; 107 | expect(result).to.be.true; 108 | }); 109 | 110 | it('writeClipboard() does not call navigator.clipboard.writeText() if permissions denied', async function () { 111 | stubClipboardPermissions('denied'); 112 | clipboard._isAvailable = false; 113 | 114 | const text = 'should not write'; 115 | const result = clipboard.writeClipboard(text); 116 | 117 | expect(navigator.clipboard.writeText.called).to.be.false; 118 | expect(result).to.be.false; 119 | }); 120 | 121 | }); 122 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import globals from "globals"; 2 | import js from "@eslint/js"; 3 | 4 | export default [ 5 | js.configs.recommended, 6 | { 7 | languageOptions: { 8 | ecmaVersion: 2022, 9 | sourceType: "module", 10 | globals: { 11 | ...globals.browser, 12 | ...globals.es2022, 13 | } 14 | }, 15 | ignores: ["**/xtscancodes.js"], 16 | rules: { 17 | // Unsafe or confusing stuff that we forbid 18 | 19 | "no-unused-vars": ["error", { "vars": "all", 20 | "args": "none", 21 | "ignoreRestSiblings": true, 22 | "caughtErrors": "none" }], 23 | "no-constant-condition": ["error", { "checkLoops": false }], 24 | "no-var": "error", 25 | "no-useless-constructor": "error", 26 | "object-shorthand": ["error", "methods", { "avoidQuotes": true }], 27 | "prefer-arrow-callback": "error", 28 | "arrow-body-style": ["error", "as-needed", { "requireReturnForObjectLiteral": false } ], 29 | "arrow-parens": ["error", "as-needed", { "requireForBlockBody": true }], 30 | "arrow-spacing": ["error"], 31 | "no-confusing-arrow": ["error", { "allowParens": true }], 32 | 33 | // Enforced coding style 34 | 35 | "brace-style": ["error", "1tbs", { "allowSingleLine": true }], 36 | "indent": ["error", 4, { "SwitchCase": 1, 37 | "VariableDeclarator": "first", 38 | "FunctionDeclaration": { "parameters": "first" }, 39 | "FunctionExpression": { "parameters": "first" }, 40 | "CallExpression": { "arguments": "first" }, 41 | "ArrayExpression": "first", 42 | "ObjectExpression": "first", 43 | "ImportDeclaration": "first", 44 | "ignoreComments": true }], 45 | "comma-spacing": ["error"], 46 | "comma-style": ["error"], 47 | "curly": ["error", "multi-line"], 48 | "func-call-spacing": ["error"], 49 | "func-names": ["error"], 50 | "func-style": ["error", "declaration", { "allowArrowFunctions": true }], 51 | "key-spacing": ["error"], 52 | "keyword-spacing": ["error"], 53 | "no-trailing-spaces": ["error"], 54 | "semi": ["error"], 55 | "space-before-blocks": ["error"], 56 | "space-before-function-paren": ["error", { "anonymous": "always", 57 | "named": "never", 58 | "asyncArrow": "always" }], 59 | "switch-colon-spacing": ["error"], 60 | "camelcase": ["error", { "allow": ["^XK_", "^XF86XK_"] }], 61 | "no-console": ["error"], 62 | } 63 | }, 64 | { 65 | files: ["po/po2js", "po/xgettext-html"], 66 | languageOptions: { 67 | globals: { 68 | ...globals.node, 69 | } 70 | }, 71 | rules: { 72 | "no-console": 0, 73 | }, 74 | }, 75 | { 76 | files: ["tests/*"], 77 | languageOptions: { 78 | globals: { 79 | ...globals.node, 80 | ...globals.mocha, 81 | sinon: false, 82 | expect: false, 83 | } 84 | }, 85 | rules: { 86 | "prefer-arrow-callback": 0, 87 | // Too many anonymous callbacks 88 | "func-names": "off", 89 | }, 90 | }, 91 | { 92 | files: ["utils/*"], 93 | languageOptions: { 94 | globals: { 95 | ...globals.node, 96 | } 97 | }, 98 | rules: { 99 | "no-console": 0, 100 | }, 101 | }, 102 | ]; 103 | -------------------------------------------------------------------------------- /docs/rfb_notes: -------------------------------------------------------------------------------- 1 | 5.1.1 ProtocolVersion: 12, 12 bytes 2 | 3 | - Sent by server, max supported 4 | 12 ascii - "RFB 003.008\n" 5 | - Response by client, version to use 6 | 12 ascii - "RFB 003.003\n" 7 | 8 | 5.1.2 Authentication: >=4, [16, 4] bytes 9 | 10 | - Sent by server 11 | CARD32 - authentication-scheme 12 | 0 - connection failed 13 | CARD32 - length 14 | length - reason 15 | 1 - no authentication 16 | 17 | 2 - VNC authentication 18 | 16 CARD8 - challenge (random bytes) 19 | 20 | - Response by client (if VNC authentication) 21 | 16 CARD8 - client encrypts the challenge with DES, using user 22 | password as key, sends resulting 16 byte response 23 | 24 | - Response by server (if VNC authentication) 25 | CARD32 - 0 - OK 26 | 1 - failed 27 | 2 - too-many 28 | 29 | 5.1.3 ClientInitialisation: 1 byte 30 | - Sent by client 31 | CARD8 - shared-flag, 0 exclusive, non-zero shared 32 | 33 | 5.1.4 ServerInitialisation: >=24 bytes 34 | - Sent by server 35 | CARD16 - framebuffer-width 36 | CARD16 - framebuffer-height 37 | 16 byte PIXEL_FORMAT - server-pixel-format 38 | CARD8 - bits-per-pixel 39 | CARD8 - depth 40 | CARD8 - big-endian-flag, non-zero is big endian 41 | CARD8 - true-color-flag, non-zero then next 6 apply 42 | CARD16 - red-max 43 | CARD16 - green-max 44 | CARD16 - blue-max 45 | CARD8 - red-shift 46 | CARD8 - green-shift 47 | CARD8 - blue-shift 48 | 3 bytes - padding 49 | CARD32 - name-length 50 | 51 | CARD8[length] - name-string 52 | 53 | 54 | 55 | Client to Server Messages: 56 | 57 | 5.2.1 SetPixelFormat: 20 bytes 58 | CARD8: 0 - message-type 59 | ... 60 | 61 | 5.2.2 FixColourMapEntries: >=6 bytes 62 | CARD8: 1 - message-type 63 | ... 64 | 65 | 5.2.3 SetEncodings: >=8 bytes 66 | CARD8: 2 - message-type 67 | CARD8 - padding 68 | CARD16 - numer-of-encodings 69 | 70 | CARD32 - encoding-type in preference order 71 | 0 - raw 72 | 1 - copy-rectangle 73 | 2 - RRE 74 | 4 - CoRRE 75 | 5 - hextile 76 | 77 | 5.2.4 FramebufferUpdateRequest (10 bytes) 78 | CARD8: 3 - message-type 79 | CARD8 - incremental (0 for full-update, non-zero for incremental) 80 | CARD16 - x-position 81 | CARD16 - y-position 82 | CARD16 - width 83 | CARD16 - height 84 | 85 | 86 | 5.2.5 KeyEvent: 8 bytes 87 | CARD8: 4 - message-type 88 | CARD8 - down-flag 89 | 2 bytes - padding 90 | CARD32 - key (X-Windows keysym values) 91 | 92 | 5.2.6 PointerEvent: 6 bytes 93 | CARD8: 5 - message-type 94 | CARD8 - button-mask 95 | CARD16 - x-position 96 | CARD16 - y-position 97 | 98 | 5.2.7 ClientCutText: >=9 bytes 99 | CARD8: 6 - message-type 100 | ... 101 | 102 | 103 | Server to Client Messages: 104 | 105 | 5.3.1 FramebufferUpdate 106 | CARD8: 0 - message-type 107 | 1 byte - padding 108 | CARD16 - number-of-rectangles 109 | 110 | CARD16 - x-position 111 | CARD16 - y-position 112 | CARD16 - width 113 | CARD16 - height 114 | CARD16 - encoding-type: 115 | 0 - raw 116 | 1 - copy rectangle 117 | 2 - RRE 118 | 4 - CoRRE 119 | 5 - hextile 120 | 121 | raw: 122 | - width x height pixel values 123 | 124 | copy rectangle: 125 | CARD16 - src-x-position 126 | CARD16 - src-y-position 127 | 128 | RRE: 129 | CARD32 - N number-of-subrectangles 130 | Nxd bytes - background-pixel-value (d bits-per-pixel) 131 | 132 | ... 133 | 134 | 5.3.2 SetColourMapEntries (no support) 135 | CARD8: 1 - message-type 136 | ... 137 | 138 | 5.3.3 Bell 139 | CARD8: 2 - message-type 140 | 141 | 5.3.4 ServerCutText 142 | CARD8: 3 - message-type 143 | 144 | 145 | 146 | 147 | 148 | -------------------------------------------------------------------------------- /po/xgettext-html: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* 3 | * xgettext-html: HTML gettext parser 4 | * Copyright (C) 2018 The noVNC authors 5 | * Licensed under MPL 2.0 (see LICENSE.txt) 6 | */ 7 | 8 | import { program } from 'commander'; 9 | import jsdom from 'jsdom'; 10 | import fs from 'fs'; 11 | 12 | program 13 | .argument('') 14 | .requiredOption('-o, --output ', 'write output to specified file') 15 | .parse(process.argv); 16 | 17 | const strings = {}; 18 | 19 | function addString(str, location) { 20 | // We assume surrounding whitespace, and whitespace around line 21 | // breaks, is just for source formatting 22 | str = str.split("\n").map(s => s.trim()).join(" ").trim(); 23 | 24 | if (str.length == 0) { 25 | return; 26 | } 27 | 28 | if (strings[str] === undefined) { 29 | strings[str] = {}; 30 | } 31 | strings[str][location] = null; 32 | } 33 | 34 | // See https://html.spec.whatwg.org/multipage/dom.html#attr-translate 35 | function process(elem, locator, enabled) { 36 | function isAnyOf(searchElement, items) { 37 | return items.indexOf(searchElement) !== -1; 38 | } 39 | 40 | if (elem.hasAttribute("translate")) { 41 | if (isAnyOf(elem.getAttribute("translate"), ["", "yes"])) { 42 | enabled = true; 43 | } else if (isAnyOf(elem.getAttribute("translate"), ["no"])) { 44 | enabled = false; 45 | } 46 | } 47 | 48 | if (enabled) { 49 | if (elem.hasAttribute("abbr") && 50 | elem.tagName === "TH") { 51 | addString(elem.getAttribute("abbr"), locator(elem)); 52 | } 53 | if (elem.hasAttribute("alt") && 54 | isAnyOf(elem.tagName, ["AREA", "IMG", "INPUT"])) { 55 | addString(elem.getAttribute("alt"), locator(elem)); 56 | } 57 | if (elem.hasAttribute("download") && 58 | isAnyOf(elem.tagName, ["A", "AREA"])) { 59 | addString(elem.getAttribute("download"), locator(elem)); 60 | } 61 | if (elem.hasAttribute("label") && 62 | isAnyOf(elem.tagName, ["MENUITEM", "MENU", "OPTGROUP", 63 | "OPTION", "TRACK"])) { 64 | addString(elem.getAttribute("label"), locator(elem)); 65 | } 66 | if (elem.hasAttribute("placeholder") && 67 | isAnyOf(elem.tagName in ["INPUT", "TEXTAREA"])) { 68 | addString(elem.getAttribute("placeholder"), locator(elem)); 69 | } 70 | if (elem.hasAttribute("title")) { 71 | addString(elem.getAttribute("title"), locator(elem)); 72 | } 73 | if (elem.hasAttribute("value") && 74 | elem.tagName === "INPUT" && 75 | isAnyOf(elem.getAttribute("type"), ["reset", "button", "submit"])) { 76 | addString(elem.getAttribute("value"), locator(elem)); 77 | } 78 | } 79 | 80 | for (let i = 0; i < elem.childNodes.length; i++) { 81 | let node = elem.childNodes[i]; 82 | if (node.nodeType === node.ELEMENT_NODE) { 83 | process(node, locator, enabled); 84 | } else if (node.nodeType === node.TEXT_NODE && enabled) { 85 | addString(node.data, locator(node)); 86 | } 87 | } 88 | } 89 | 90 | for (let i = 0; i < program.args.length; i++) { 91 | const fn = program.args[i]; 92 | const file = fs.readFileSync(fn, "utf8"); 93 | const dom = new jsdom.JSDOM(file, { includeNodeLocations: true }); 94 | const body = dom.window.document.body; 95 | 96 | let locator = (elem) => { 97 | const offset = dom.nodeLocation(elem).startOffset; 98 | const line = file.slice(0, offset).split("\n").length; 99 | return fn + ":" + line; 100 | }; 101 | 102 | process(body, locator, true); 103 | } 104 | 105 | let output = ""; 106 | 107 | for (let str in strings) { 108 | output += "#:"; 109 | for (let location in strings[str]) { 110 | output += " " + location; 111 | } 112 | output += "\n"; 113 | 114 | output += "msgid " + JSON.stringify(str) + "\n"; 115 | output += "msgstr \"\"\n"; 116 | output += "\n"; 117 | } 118 | 119 | fs.writeFileSync(program.opts().output, output); 120 | -------------------------------------------------------------------------------- /app/images/power.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 54 | 57 | 58 | 60 | 61 | 63 | image/svg+xml 64 | 66 | 67 | 68 | 69 | 70 | 75 | 80 | 86 | 87 | 88 | --------------------------------------------------------------------------------