├── .gitmodules ├── po ├── .eslintrc ├── Makefile ├── po2js └── xgettext-html ├── snap ├── hooks │ └── configure ├── local │ └── svc_wrapper.sh └── snapcraft.yaml ├── .envrc ├── app ├── sounds │ ├── bell.mp3 │ ├── bell.oga │ └── CREDITS ├── styles │ ├── Orbitron700.ttf │ └── Orbitron700.woff ├── locale │ ├── README │ ├── zh_CN.json │ ├── zh_TW.json │ ├── ko.json │ ├── ja.json │ ├── es.json │ ├── tr.json │ ├── sv.json │ ├── pl.json │ ├── cs.json │ ├── de.json │ ├── pt_BR.json │ ├── el.json │ ├── ru.json │ └── nl.json ├── images │ ├── icons │ │ ├── novnc-120x120.png │ │ ├── novnc-144x144.png │ │ ├── novnc-152x152.png │ │ ├── novnc-16x16.png │ │ ├── novnc-192x192.png │ │ ├── novnc-24x24.png │ │ ├── novnc-32x32.png │ │ ├── novnc-48x48.png │ │ ├── novnc-60x60.png │ │ ├── novnc-64x64.png │ │ ├── novnc-72x72.png │ │ ├── novnc-76x76.png │ │ ├── novnc-96x96.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 │ └── clipboard.svg └── 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 ├── links ├── EMBEDDING.md ├── rfb_notes └── API-internal.md ├── utils ├── .eslintrc ├── b64-to-binary.pl ├── README.md ├── validate ├── u2x11 └── genkeysymdef.js ├── .gitignore ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── feature_request.md │ └── bug_report.md └── workflows │ ├── 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 ├── tests ├── .eslintrc ├── test.int.js ├── vnc_playback.html ├── test.base64.js ├── fake.websocket.js ├── test.deflator.js ├── test.localization.js ├── test.copyrect.js ├── test.util.js ├── test.rre.js └── assertions.js ├── core ├── util │ ├── int.js │ ├── strings.js │ ├── element.js │ ├── eventtarget.js │ ├── logging.js │ └── browser.js ├── decoders │ ├── copyrect.js │ ├── tightpng.js │ ├── rre.js │ └── raw.js ├── encodings.js ├── inflator.js ├── deflator.js └── input │ ├── vkeys.js │ └── fixedkeys.js ├── AUTHORS ├── flake.nix ├── flake.lock ├── LICENSE.txt ├── package.json ├── karma.conf.js └── eslint.config.mjs /.gitmodules: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /po/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "node": true, 4 | }, 5 | } 6 | -------------------------------------------------------------------------------- /snap/hooks/configure: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | snapctl restart novnc.novncsvc 4 | -------------------------------------------------------------------------------- /.envrc: -------------------------------------------------------------------------------- 1 | use flake 2 | dotenv_if_exists 3 | source_env_if_exists .local.envrc 4 | -------------------------------------------------------------------------------- /app/sounds/bell.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/replit/noVNC/master/app/sounds/bell.mp3 -------------------------------------------------------------------------------- /app/sounds/bell.oga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/replit/noVNC/master/app/sounds/bell.oga -------------------------------------------------------------------------------- /docs/rfbproto-3.3.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/replit/noVNC/master/docs/rfbproto-3.3.pdf -------------------------------------------------------------------------------- /docs/rfbproto-3.7.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/replit/noVNC/master/docs/rfbproto-3.7.pdf -------------------------------------------------------------------------------- /docs/rfbproto-3.8.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/replit/noVNC/master/docs/rfbproto-3.8.pdf -------------------------------------------------------------------------------- /app/styles/Orbitron700.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/replit/noVNC/master/app/styles/Orbitron700.ttf -------------------------------------------------------------------------------- /app/styles/Orbitron700.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/replit/noVNC/master/app/styles/Orbitron700.woff -------------------------------------------------------------------------------- /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-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/replit/noVNC/master/app/images/icons/novnc-120x120.png -------------------------------------------------------------------------------- /app/images/icons/novnc-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/replit/noVNC/master/app/images/icons/novnc-144x144.png -------------------------------------------------------------------------------- /app/images/icons/novnc-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/replit/noVNC/master/app/images/icons/novnc-152x152.png -------------------------------------------------------------------------------- /app/images/icons/novnc-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/replit/noVNC/master/app/images/icons/novnc-16x16.png -------------------------------------------------------------------------------- /app/images/icons/novnc-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/replit/noVNC/master/app/images/icons/novnc-192x192.png -------------------------------------------------------------------------------- /app/images/icons/novnc-24x24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/replit/noVNC/master/app/images/icons/novnc-24x24.png -------------------------------------------------------------------------------- /app/images/icons/novnc-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/replit/noVNC/master/app/images/icons/novnc-32x32.png -------------------------------------------------------------------------------- /app/images/icons/novnc-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/replit/noVNC/master/app/images/icons/novnc-48x48.png -------------------------------------------------------------------------------- /app/images/icons/novnc-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/replit/noVNC/master/app/images/icons/novnc-60x60.png -------------------------------------------------------------------------------- /app/images/icons/novnc-64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/replit/noVNC/master/app/images/icons/novnc-64x64.png -------------------------------------------------------------------------------- /app/images/icons/novnc-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/replit/noVNC/master/app/images/icons/novnc-72x72.png -------------------------------------------------------------------------------- /app/images/icons/novnc-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/replit/noVNC/master/app/images/icons/novnc-76x76.png -------------------------------------------------------------------------------- /app/images/icons/novnc-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/replit/noVNC/master/app/images/icons/novnc-96x96.png -------------------------------------------------------------------------------- /utils/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "node": true 4 | }, 5 | "rules": { 6 | "no-console": 0 7 | } 8 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.o 3 | tests/data_*.js 4 | utils/rebind.so 5 | utils/websockify 6 | /node_modules 7 | /build 8 | /lib 9 | /.direnv/ 10 | recordings 11 | *.swp 12 | *~ 13 | noVNC-*.tgz 14 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /tests/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "node": true, 4 | "mocha": true 5 | }, 6 | "globals": { 7 | "chai": false, 8 | "sinon": false 9 | }, 10 | "rules": { 11 | "prefer-arrow-callback": 0, 12 | // Too many anonymous callbacks 13 | "func-names": "off", 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /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 | - Joel Martin (@kanaka) 3 | - Solly Ross (@directxman12) 4 | - Samuel Mannehed for Cendio AB (@samhed) 5 | - Pierre Ossman for Cendio AB (@CendioOssman) 6 | maintainersEmeritus: 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.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 install 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 install 19 | - run: git ls-tree --name-only -r HEAD | grep -E "[.](html|css)$" | xargs ./utils/validate 20 | -------------------------------------------------------------------------------- /tests/test.int.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | const expect = chai.expect; 3 | 4 | import { toUnsigned32bit, toSigned32bit } from '../core/util/int.js'; 5 | 6 | describe('Integer casting', function () { 7 | it('should cast unsigned to signed', function () { 8 | let expected = 4294967286; 9 | expect(toUnsigned32bit(-10)).to.equal(expected); 10 | }); 11 | 12 | it('should cast signed to unsigned', function () { 13 | let expected = -10; 14 | expect(toSigned32bit(4294967286)).to.equal(expected); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /utils/README.md: -------------------------------------------------------------------------------- 1 | ## WebSockets Proxy/Bridge 2 | 3 | Websockify has been forked out into its own project. `launch.sh` wil 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 install 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 | -------------------------------------------------------------------------------- /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=text > $OUT 32 | cat $OUT 33 | echo 34 | 35 | # We don't fail the check for warnings as some warnings are 36 | # not relevant for us, and we don't currently have a way to 37 | # ignore just those 38 | if grep -q -s -E "^Error:" $OUT; then 39 | RET=1 40 | fi 41 | done 42 | 43 | rm $OUT 44 | 45 | exit $RET 46 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "noVNC is a web-based VNC client"; 3 | 4 | inputs = { 5 | nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11"; 6 | }; 7 | 8 | outputs = { self, nixpkgs, flake-utils }: 9 | let 10 | systems = [ 11 | "x86_64-linux" 12 | "aarch64-darwin" 13 | ]; 14 | 15 | eachSystem = nixpkgs.lib.genAttrs systems; 16 | 17 | mkPkgs = system: import nixpkgs { 18 | inherit system; 19 | }; 20 | in 21 | { 22 | packages = eachSystem (system: 23 | let 24 | pkgs = mkPkgs system; 25 | in rec { 26 | devShell = pkgs.mkShell { 27 | packages = [ 28 | pkgs.nodejs_20 29 | pkgs.chromedriver 30 | pkgs.chromium 31 | ]; 32 | }; 33 | }); 34 | 35 | devShells = eachSystem (system: { 36 | default = self.packages.${system}.devShell; 37 | }); 38 | }; 39 | } 40 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /app/images/icons/Makefile: -------------------------------------------------------------------------------- 1 | ICONS := \ 2 | novnc-16x16.png \ 3 | novnc-24x24.png \ 4 | novnc-32x32.png \ 5 | novnc-48x48.png \ 6 | novnc-64x64.png 7 | 8 | ANDROID_LAUNCHER := \ 9 | novnc-48x48.png \ 10 | novnc-72x72.png \ 11 | novnc-96x96.png \ 12 | novnc-144x144.png \ 13 | novnc-192x192.png 14 | 15 | IPHONE_LAUNCHER := \ 16 | novnc-60x60.png \ 17 | novnc-120x120.png 18 | 19 | IPAD_LAUNCHER := \ 20 | novnc-76x76.png \ 21 | novnc-152x152.png 22 | 23 | ALL_ICONS := $(ICONS) $(ANDROID_LAUNCHER) $(IPHONE_LAUNCHER) $(IPAD_LAUNCHER) 24 | 25 | all: $(ALL_ICONS) 26 | 27 | novnc-16x16.png: novnc-icon-sm.svg 28 | convert -density 90 \ 29 | -background transparent "$<" "$@" 30 | novnc-24x24.png: novnc-icon-sm.svg 31 | convert -density 135 \ 32 | -background transparent "$<" "$@" 33 | novnc-32x32.png: novnc-icon-sm.svg 34 | convert -density 180 \ 35 | -background transparent "$<" "$@" 36 | 37 | novnc-%.png: novnc-icon.svg 38 | convert -density $$[`echo $* | cut -d x -f 1` * 90 / 48] \ 39 | -background transparent "$<" "$@" 40 | 41 | clean: 42 | rm -f *.png 43 | -------------------------------------------------------------------------------- /po/Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | .PHONY: update-po update-js update-pot 3 | 4 | LINGUAS := cs de el es ja ko nl pl pt_BR ru sv tr zh_CN zh_TW 5 | 6 | VERSION := $(shell grep '"version"' ../package.json | cut -d '"' -f 4) 7 | 8 | POFILES := $(addsuffix .po,$(LINGUAS)) 9 | JSONFILES := $(addprefix ../app/locale/,$(addsuffix .json,$(LINGUAS))) 10 | 11 | update-po: $(POFILES) 12 | update-js: $(JSONFILES) 13 | 14 | %.po: noVNC.pot 15 | msgmerge --update --lang=$* $@ $< 16 | ../app/locale/%.json: %.po 17 | ./po2js $< $@ 18 | 19 | update-pot: 20 | xgettext --output=noVNC.js.pot \ 21 | --copyright-holder="The noVNC Authors" \ 22 | --package-name="noVNC" \ 23 | --package-version="$(VERSION)" \ 24 | --msgid-bugs-address="novnc@googlegroups.com" \ 25 | --add-comments=TRANSLATORS: \ 26 | --from-code=UTF-8 \ 27 | --sort-by-file \ 28 | ../app/*.js \ 29 | ../core/*.js \ 30 | ../core/input/*.js 31 | ./xgettext-html --output=noVNC.html.pot \ 32 | ../vnc.html 33 | msgcat --output-file=noVNC.pot \ 34 | --sort-by-file noVNC.js.pot noVNC.html.pot 35 | rm -f noVNC.js.pot noVNC.html.pot 36 | -------------------------------------------------------------------------------- /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 sepcified.. 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/launch.sh --listen $listen_port --vnc $vnc_host_port & 28 | fi 29 | done 30 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /tests/test.base64.js: -------------------------------------------------------------------------------- 1 | const expect = chai.expect; 2 | 3 | import Base64 from '../core/base64.js'; 4 | 5 | describe('Base64 Tools', function () { 6 | "use strict"; 7 | 8 | const BIN_ARR = new Array(256); 9 | for (let i = 0; i < 256; i++) { 10 | BIN_ARR[i] = i; 11 | } 12 | 13 | const B64_STR = "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w=="; 14 | 15 | 16 | describe('encode', function () { 17 | it('should encode a binary string into Base64', function () { 18 | const encoded = Base64.encode(BIN_ARR); 19 | expect(encoded).to.equal(B64_STR); 20 | }); 21 | }); 22 | 23 | describe('decode', function () { 24 | it('should decode a Base64 string into a normal string', function () { 25 | const decoded = Base64.decode(B64_STR); 26 | expect(decoded).to.deep.equal(BIN_ARR); 27 | }); 28 | 29 | it('should throw an error if we have extra characters at the end of the string', function () { 30 | expect(() => Base64.decode(B64_STR+'abcdef')).to.throw(Error); 31 | }); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /snap/snapcraft.yaml: -------------------------------------------------------------------------------- 1 | name: novnc 2 | base: core18 # the base snap is the execution environment for this snap 3 | version: '@VERSION@' 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/launch.sh: / 20 | stage: 21 | - vnc.html 22 | - app 23 | - core/**/*.js 24 | - vendor/**/*.js 25 | - launch.sh 26 | stage-packages: 27 | - bash 28 | 29 | svc-script: 30 | source: snap/local 31 | plugin: dump 32 | stage: 33 | - svc_wrapper.sh 34 | stage-packages: 35 | - bash 36 | - jq 37 | 38 | websockify: 39 | source: https://github.com/novnc/websockify/archive/v0.9.0.tar.gz 40 | plugin: python 41 | stage-packages: 42 | - python3-numpy 43 | 44 | hooks: 45 | configure: 46 | plugs: [network, network-bind] 47 | 48 | apps: 49 | novnc: 50 | command: ./launch.sh 51 | plugs: [network, network-bind] 52 | novncsvc: 53 | command: ./svc_wrapper.sh 54 | daemon: forking 55 | plugs: [network, network-bind] 56 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | const getopt = require('node-getopt'); 21 | const fs = require('fs'); 22 | const po2json = require("po2json"); 23 | 24 | const opt = getopt.create([ 25 | ['h', 'help', 'display this help'], 26 | ]).bindHelp().parseSystem(); 27 | 28 | if (opt.argv.length != 2) { 29 | console.error("Incorrect number of arguments given"); 30 | process.exit(1); 31 | } 32 | 33 | const data = po2json.parseFileSync(opt.argv[0]); 34 | 35 | const bodyPart = Object.keys(data).filter(msgid => msgid !== "").map((msgid) => { 36 | if (msgid === "") return; 37 | const msgstr = data[msgid][1]; 38 | return " " + JSON.stringify(msgid) + ": " + JSON.stringify(msgstr); 39 | }).join(",\n"); 40 | 41 | const output = "{\n" + bodyPart + "\n}"; 42 | 43 | fs.writeFileSync(opt.argv[1], output); 44 | -------------------------------------------------------------------------------- /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 | encodingTight: 7, 15 | encodingTightPNG: -260, 16 | 17 | pseudoEncodingQualityLevel9: -23, 18 | pseudoEncodingQualityLevel0: -32, 19 | pseudoEncodingDesktopSize: -223, 20 | pseudoEncodingLastRect: -224, 21 | pseudoEncodingCursor: -239, 22 | pseudoEncodingQEMUExtendedKeyEvent: -258, 23 | pseudoEncodingDesktopName: -307, 24 | pseudoEncodingExtendedDesktopSize: -308, 25 | pseudoEncodingXvp: -309, 26 | pseudoEncodingFence: -312, 27 | pseudoEncodingContinuousUpdates: -313, 28 | pseudoEncodingCompressLevel9: -247, 29 | pseudoEncodingCompressLevel0: -256, 30 | pseudoEncodingReplitAudio: 0x52706c41, 31 | pseudoEncodingVMwareCursor: 0x574d5664, 32 | pseudoEncodingExtendedClipboard: 0xc0a1e5ce 33 | }; 34 | 35 | export function encodingName(num) { 36 | switch (num) { 37 | case encodings.encodingRaw: return "Raw"; 38 | case encodings.encodingCopyRect: return "CopyRect"; 39 | case encodings.encodingRRE: return "RRE"; 40 | case encodings.encodingHextile: return "Hextile"; 41 | case encodings.encodingTight: return "Tight"; 42 | case encodings.encodingTightPNG: return "TightPNG"; 43 | default: return "[unknown encoding " + num + "]"; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-utils": { 4 | "inputs": { 5 | "systems": "systems" 6 | }, 7 | "locked": { 8 | "lastModified": 1731533236, 9 | "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", 10 | "owner": "numtide", 11 | "repo": "flake-utils", 12 | "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", 13 | "type": "github" 14 | }, 15 | "original": { 16 | "id": "flake-utils", 17 | "type": "indirect" 18 | } 19 | }, 20 | "nixpkgs": { 21 | "locked": { 22 | "lastModified": 1739758141, 23 | "narHash": "sha256-uq6A2L7o1/tR6VfmYhZWoVAwb3gTy7j4Jx30MIrH0rE=", 24 | "owner": "NixOS", 25 | "repo": "nixpkgs", 26 | "rev": "c618e28f70257593de75a7044438efc1c1fc0791", 27 | "type": "github" 28 | }, 29 | "original": { 30 | "owner": "NixOS", 31 | "ref": "nixos-24.11", 32 | "repo": "nixpkgs", 33 | "type": "github" 34 | } 35 | }, 36 | "root": { 37 | "inputs": { 38 | "flake-utils": "flake-utils", 39 | "nixpkgs": "nixpkgs" 40 | } 41 | }, 42 | "systems": { 43 | "locked": { 44 | "lastModified": 1681028828, 45 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 46 | "owner": "nix-systems", 47 | "repo": "default", 48 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 49 | "type": "github" 50 | }, 51 | "original": { 52 | "owner": "nix-systems", 53 | "repo": "default", 54 | "type": "github" 55 | } 56 | } 57 | }, 58 | "root": "root", 59 | "version": 7 60 | } 61 | -------------------------------------------------------------------------------- /.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 | steps: 13 | - uses: actions/checkout@v4 14 | - uses: actions/setup-node@v4 15 | with: 16 | # Needs to be explicitly specified for auth to work 17 | registry-url: 'https://registry.npmjs.org' 18 | - run: npm install 19 | - uses: actions/upload-artifact@v2 20 | with: 21 | name: npm 22 | path: lib 23 | - run: npm publish --access public 24 | env: 25 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 26 | if: ${{ github.event_name == 'release' && !github.event.release.prerelease }} 27 | - run: npm publish --access public --tag beta 28 | env: 29 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 30 | if: ${{ github.event_name == 'release' && github.event.release.prerelease }} 31 | snap: 32 | runs-on: ubuntu-latest 33 | steps: 34 | - uses: actions/checkout@v4 35 | - run: | 36 | VERSION=$(grep '"version"' package.json | cut -d '"' -f 4) 37 | echo $VERSION 38 | sed -i "s/@VERSION@/$VERSION/g" snap/snapcraft.yaml 39 | - uses: snapcore/action-build@v1 40 | id: snapcraft 41 | - uses: actions/upload-artifact@v2 42 | with: 43 | name: snap 44 | path: ${{ steps.snapcraft.outputs.snap }} 45 | - uses: snapcore/action-publish@v1 46 | with: 47 | store_login: ${{ secrets.SNAPCRAFT_LOGIN }} 48 | snap: ${{ steps.build.outputs.snap }} 49 | release: stable 50 | if: ${{ github.event_name == 'release' && !github.event.release.prerelease }} 51 | - uses: snapcore/action-publish@v1 52 | with: 53 | store_login: ${{ secrets.SNAPCRAFT_LOGIN }} 54 | snap: ${{ steps.build.outputs.snap }} 55 | release: beta 56 | if: ${{ github.event_name == 'release' && github.event.release.prerelease }} 57 | -------------------------------------------------------------------------------- /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 | if (sock.rQwait("RAW", bytesPerLine)) { 28 | return false; 29 | } 30 | 31 | const curY = y + (height - this._lines); 32 | const currHeight = Math.min(this._lines, 33 | Math.floor(sock.rQlen / bytesPerLine)); 34 | const pixels = width * currHeight; 35 | 36 | let data = sock.rQ; 37 | let index = sock.rQi; 38 | 39 | // Convert data if needed 40 | if (depth == 8) { 41 | const newdata = new Uint8Array(pixels * 4); 42 | for (let i = 0; i < pixels; i++) { 43 | newdata[i * 4 + 0] = ((data[index + i] >> 0) & 0x3) * 255 / 3; 44 | newdata[i * 4 + 1] = ((data[index + i] >> 2) & 0x3) * 255 / 3; 45 | newdata[i * 4 + 2] = ((data[index + i] >> 4) & 0x3) * 255 / 3; 46 | newdata[i * 4 + 3] = 255; 47 | } 48 | data = newdata; 49 | index = 0; 50 | } 51 | 52 | // Max sure the image is fully opaque 53 | for (let i = 0; i < pixels; i++) { 54 | data[i * 4 + 3] = 255; 55 | } 56 | 57 | display.blitImage(x, curY, width, currHeight, data, index); 58 | sock.rQskipBytes(currHeight * bytesPerLine); 59 | this._lines -= currHeight; 60 | if (this._lines > 0) { 61 | return false; 62 | } 63 | 64 | return true; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /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 | this.windowBits = 5; 18 | 19 | inflateInit(this.strm, this.windowBits); 20 | } 21 | 22 | setInput(data) { 23 | if (!data) { 24 | //FIXME: flush remaining data. 25 | /* eslint-disable camelcase */ 26 | this.strm.input = null; 27 | this.strm.avail_in = 0; 28 | this.strm.next_in = 0; 29 | } else { 30 | this.strm.input = data; 31 | this.strm.avail_in = this.strm.input.length; 32 | this.strm.next_in = 0; 33 | /* eslint-enable camelcase */ 34 | } 35 | } 36 | 37 | inflate(expected) { 38 | // resize our output buffer if it's too small 39 | // (we could just use multiple chunks, but that would cause an extra 40 | // allocation each time to flatten the chunks) 41 | if (expected > this.chunkSize) { 42 | this.chunkSize = expected; 43 | this.strm.output = new Uint8Array(this.chunkSize); 44 | } 45 | 46 | /* eslint-disable camelcase */ 47 | this.strm.next_out = 0; 48 | this.strm.avail_out = expected; 49 | /* eslint-enable camelcase */ 50 | 51 | let ret = inflate(this.strm, 0); // Flush argument not used. 52 | if (ret < 0) { 53 | throw new Error("zlib inflate failed"); 54 | } 55 | 56 | if (this.strm.next_out != expected) { 57 | throw new Error("Incomplete zlib block"); 58 | } 59 | 60 | return new Uint8Array(this.strm.output.buffer, 0, this.strm.next_out); 61 | } 62 | 63 | reset() { 64 | inflateReset(this.strm); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /app/locale/zh_CN.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 | } -------------------------------------------------------------------------------- /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) 2019 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 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@replit/novnc", 3 | "version": "2.2.0", 4 | "description": "An HTML5 VNC client", 5 | "browser": "lib/rfb", 6 | "directories": { 7 | "lib": "lib", 8 | "doc": "docs", 9 | "test": "tests" 10 | }, 11 | "files": [ 12 | "lib", 13 | "AUTHORS", 14 | "VERSION", 15 | "docs/API.md", 16 | "docs/LIBRARY.md", 17 | "docs/LICENSE*", 18 | "core", 19 | "vendor/pako" 20 | ], 21 | "scripts": { 22 | "lint": "eslint app core po/po2js po/xgettext-html tests utils", 23 | "test": "karma start karma.conf.js", 24 | "prepublish": "node ./utils/use_require.js --clean" 25 | }, 26 | "repository": { 27 | "type": "git", 28 | "url": "git+https://github.com/novnc/noVNC.git" 29 | }, 30 | "author": "Joel Martin (https://github.com/kanaka)", 31 | "contributors": [ 32 | "Solly Ross (https://github.com/directxman12)", 33 | "Peter Åstrand (https://github.com/astrand)", 34 | "Samuel Mannehed (https://github.com/samhed)", 35 | "Pierre Ossman (https://github.com/CendioOssman)" 36 | ], 37 | "license": "MPL-2.0", 38 | "bugs": { 39 | "url": "https://github.com/novnc/noVNC/issues" 40 | }, 41 | "homepage": "https://github.com/novnc/noVNC", 42 | "devDependencies": { 43 | "@babel/core": "*", 44 | "@babel/plugin-syntax-dynamic-import": "*", 45 | "@babel/plugin-transform-modules-commonjs": "*", 46 | "@babel/preset-env": "*", 47 | "@babel/cli": "*", 48 | "babel-plugin-import-redirect": "*", 49 | "browserify": "*", 50 | "babelify": "*", 51 | "core-js": "*", 52 | "chai": "*", 53 | "commander": "*", 54 | "es-module-loader": "*", 55 | "eslint": "*", 56 | "fs-extra": "*", 57 | "jsdom": "*", 58 | "karma": "*", 59 | "karma-mocha": "*", 60 | "karma-chrome-launcher": "*", 61 | "@chiragrupani/karma-chromium-edge-launcher": "*", 62 | "karma-firefox-launcher": "*", 63 | "karma-ie-launcher": "*", 64 | "karma-mocha-reporter": "*", 65 | "karma-safari-launcher": "*", 66 | "karma-script-launcher": "*", 67 | "karma-sinon-chai": "*", 68 | "mocha": "*", 69 | "node-getopt": "*", 70 | "po2json": "*", 71 | "requirejs": "*", 72 | "rollup": "*", 73 | "rollup-plugin-node-resolve": "*", 74 | "sinon": "*", 75 | "sinon-chai": "*" 76 | }, 77 | "dependencies": {}, 78 | "keywords": [ 79 | "vnc", 80 | "rfb", 81 | "novnc", 82 | "websockify" 83 | ] 84 | } 85 | -------------------------------------------------------------------------------- /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/locale/ja.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 | "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": "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 | } -------------------------------------------------------------------------------- /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 | // NB: this should *not* be included as a module until we have 10 | // native support in the browsers, so that our error handler 11 | // can catch script-loading errors. 12 | 13 | // No ES6 can be used in this file since it's used for the translation 14 | /* eslint-disable prefer-arrow-callback */ 15 | 16 | (function _scope() { 17 | "use strict"; 18 | 19 | // Fallback for all uncought errors 20 | function handleError(event, err) { 21 | try { 22 | const msg = document.getElementById('noVNC_fallback_errormsg'); 23 | 24 | // Only show the initial error 25 | if (msg.hasChildNodes()) { 26 | return false; 27 | } 28 | 29 | let div = document.createElement("div"); 30 | div.classList.add('noVNC_message'); 31 | div.appendChild(document.createTextNode(event.message)); 32 | msg.appendChild(div); 33 | 34 | if (event.filename) { 35 | div = document.createElement("div"); 36 | div.className = 'noVNC_location'; 37 | let text = event.filename; 38 | if (event.lineno !== undefined) { 39 | text += ":" + event.lineno; 40 | if (event.colno !== undefined) { 41 | text += ":" + event.colno; 42 | } 43 | } 44 | div.appendChild(document.createTextNode(text)); 45 | msg.appendChild(div); 46 | } 47 | 48 | if (err && err.stack) { 49 | div = document.createElement("div"); 50 | div.className = 'noVNC_stack'; 51 | div.appendChild(document.createTextNode(err.stack)); 52 | msg.appendChild(div); 53 | } 54 | 55 | document.getElementById('noVNC_fallback_error') 56 | .classList.add("noVNC_open"); 57 | } catch (exc) { 58 | document.write("noVNC encountered an error."); 59 | } 60 | // Don't return true since this would prevent the error 61 | // from being printed to the browser console. 62 | return false; 63 | } 64 | window.addEventListener('error', function onerror(evt) { handleError(evt, evt.error); }); 65 | window.addEventListener('unhandledrejection', function onreject(evt) { handleError(evt.reason, evt.reason); }); 66 | })(); 67 | -------------------------------------------------------------------------------- /app/images/windows.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image/svg+xml 51 | 56 | 57 | -------------------------------------------------------------------------------- /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 | this._sendQueue.set(data, this.bufferedAmount); 41 | this.bufferedAmount += data.length; 42 | } 43 | 44 | _getSentData() { 45 | const res = new Uint8Array(this._sendQueue.buffer, 0, this.bufferedAmount); 46 | this.bufferedAmount = 0; 47 | return res; 48 | } 49 | 50 | _open() { 51 | this.readyState = FakeWebSocket.OPEN; 52 | if (this.onopen) { 53 | this.onopen(new Event('open')); 54 | } 55 | } 56 | 57 | _receiveData(data) { 58 | // Break apart the data to expose bugs where we assume data is 59 | // neatly packaged 60 | for (let i = 0;i < data.length;i++) { 61 | let buf = data.subarray(i, i+1); 62 | this.onmessage(new MessageEvent("message", { 'data': buf })); 63 | } 64 | } 65 | } 66 | 67 | FakeWebSocket.OPEN = WebSocket.OPEN; 68 | FakeWebSocket.CONNECTING = WebSocket.CONNECTING; 69 | FakeWebSocket.CLOSING = WebSocket.CLOSING; 70 | FakeWebSocket.CLOSED = WebSocket.CLOSED; 71 | 72 | FakeWebSocket._isFake = true; 73 | 74 | FakeWebSocket.replace = () => { 75 | if (!WebSocket._isFake) { 76 | const realVersion = WebSocket; 77 | // eslint-disable-next-line no-global-assign 78 | WebSocket = FakeWebSocket; 79 | FakeWebSocket._realVersion = realVersion; 80 | } 81 | }; 82 | 83 | FakeWebSocket.restore = () => { 84 | if (WebSocket._isFake) { 85 | // eslint-disable-next-line no-global-assign 86 | WebSocket = WebSocket._realVersion; 87 | } 88 | }; 89 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 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', 'sinon-chai'], 31 | 32 | // list of files / patterns to load in the browser (loaded in order) 33 | files: [ 34 | { pattern: 'app/localization.js', included: false, type: 'module' }, 35 | { pattern: 'app/webutil.js', included: false, type: 'module' }, 36 | { pattern: 'core/**/*.js', included: false, type: 'module' }, 37 | { pattern: 'vendor/pako/**/*.js', included: false, type: 'module' }, 38 | { pattern: 'tests/test.*.js', type: 'module' }, 39 | { pattern: 'tests/fake.*.js', included: false, type: 'module' }, 40 | { pattern: 'tests/assertions.js', type: 'module' }, 41 | ], 42 | 43 | client: { 44 | mocha: { 45 | // replace Karma debug page with mocha display 46 | 'reporter': 'html', 47 | 'ui': 'bdd' 48 | } 49 | }, 50 | 51 | // list of files to exclude 52 | exclude: [ 53 | ], 54 | 55 | plugins: [ 56 | 'karma-*', 57 | '@chiragrupani/karma-chromium-edge-launcher', 58 | { 'launcher:Safari': [ 'type', SafariBrowser ] }, 59 | ], 60 | 61 | // start these browsers 62 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 63 | browsers: browsers, 64 | 65 | // test results reporter to use 66 | // possible values: 'dots', 'progress' 67 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 68 | reporters: ['mocha'], 69 | 70 | 71 | // level of logging 72 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 73 | logLevel: config.LOG_INFO, 74 | 75 | 76 | // enable / disable watching file and executing tests whenever any file changes 77 | autoWatch: false, 78 | 79 | // Continuous Integration mode 80 | // if true, Karma captures browsers, runs the tests and exits 81 | singleRun: true, 82 | }; 83 | 84 | config.set(my_conf); 85 | }; 86 | -------------------------------------------------------------------------------- /tests/test.deflator.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | const expect = chai.expect; 3 | 4 | import { inflateInit, inflate } from "../vendor/pako/lib/zlib/inflate.js"; 5 | import ZStream from "../vendor/pako/lib/zlib/zstream.js"; 6 | import Deflator from "../core/deflator.js"; 7 | 8 | function _inflator(compText, expected) { 9 | let strm = new ZStream(); 10 | let chunkSize = 1024 * 10 * 10; 11 | strm.output = new Uint8Array(chunkSize); 12 | 13 | inflateInit(strm, 5); 14 | 15 | if (expected > chunkSize) { 16 | chunkSize = expected; 17 | strm.output = new Uint8Array(chunkSize); 18 | } 19 | 20 | /* eslint-disable camelcase */ 21 | strm.input = compText; 22 | strm.avail_in = strm.input.length; 23 | strm.next_in = 0; 24 | 25 | strm.next_out = 0; 26 | strm.avail_out = expected.length; 27 | /* eslint-enable camelcase */ 28 | 29 | let ret = inflate(strm, 0); 30 | 31 | // Check that return code is not an error 32 | expect(ret).to.be.greaterThan(-1); 33 | 34 | return new Uint8Array(strm.output.buffer, 0, strm.next_out); 35 | } 36 | 37 | describe('Deflate data', function () { 38 | 39 | it('should be able to deflate messages', function () { 40 | let deflator = new Deflator(); 41 | 42 | let text = "123asdf"; 43 | let preText = new Uint8Array(text.length); 44 | for (let i = 0; i < preText.length; i++) { 45 | preText[i] = text.charCodeAt(i); 46 | } 47 | 48 | let compText = deflator.deflate(preText); 49 | 50 | let inflatedText = _inflator(compText, text.length); 51 | expect(inflatedText).to.array.equal(preText); 52 | 53 | }); 54 | 55 | it('should be able to deflate large messages', function () { 56 | let deflator = new Deflator(); 57 | 58 | /* Generate a big string with random characters. Used because 59 | repetition of letters might be deflated more effectively than 60 | random ones. */ 61 | let text = ""; 62 | let characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; 63 | for (let i = 0; i < 300000; i++) { 64 | text += characters.charAt(Math.floor(Math.random() * characters.length)); 65 | } 66 | 67 | let preText = new Uint8Array(text.length); 68 | for (let i = 0; i < preText.length; i++) { 69 | preText[i] = text.charCodeAt(i); 70 | } 71 | 72 | let compText = deflator.deflate(preText); 73 | 74 | //Check that the compressed size is expected size 75 | expect(compText.length).to.be.greaterThan((1024 * 10 * 10) * 2); 76 | 77 | let inflatedText = _inflator(compText, text.length); 78 | 79 | expect(inflatedText).to.array.equal(preText); 80 | 81 | }); 82 | }); 83 | -------------------------------------------------------------------------------- /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": "Debes configurar el host", 8 | "Reconnecting...": "Reconectando...", 9 | "Password is required": "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 | "Shared Mode": "Modo Compartido", 45 | "View Only": "Solo visualización", 46 | "Clip to Window": "Recortar al tamaño de la ventana", 47 | "Scaling Mode:": "Modo de escalado:", 48 | "None": "Ninguno", 49 | "Local Scaling": "Escalado Local", 50 | "Local Downscaling": "Reducción de escala local", 51 | "Remote Resizing": "Cambio de tamaño remoto", 52 | "Advanced": "Avanzado", 53 | "Local Cursor": "Cursor Local", 54 | "Repeater ID:": "ID del Repetidor", 55 | "WebSocket": "WebSocket", 56 | "Encrypt": "", 57 | "Host:": "Host", 58 | "Port:": "Puesto", 59 | "Path:": "Ruta", 60 | "Automatic Reconnect": "Reconexión automática", 61 | "Reconnect Delay (ms):": "Retraso en la reconexión (ms)", 62 | "Logging:": "Logging", 63 | "Disconnect": "Desconectar", 64 | "Connect": "Conectar", 65 | "Password:": "Contraseña", 66 | "Cancel": "Cancelar", 67 | "Canvas not supported.": "Canvas no está 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 } 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 | this.windowBits = 5; 19 | 20 | deflateInit(this.strm, this.windowBits); 21 | } 22 | 23 | deflate(inData) { 24 | /* eslint-disable camelcase */ 25 | this.strm.input = inData; 26 | this.strm.avail_in = this.strm.input.length; 27 | this.strm.next_in = 0; 28 | this.strm.output = this.outputBuffer; 29 | this.strm.avail_out = this.chunkSize; 30 | this.strm.next_out = 0; 31 | /* eslint-enable camelcase */ 32 | 33 | let lastRet = deflate(this.strm, Z_FULL_FLUSH); 34 | let outData = new Uint8Array(this.strm.output.buffer, 0, this.strm.next_out); 35 | 36 | if (lastRet < 0) { 37 | throw new Error("zlib deflate failed"); 38 | } 39 | 40 | if (this.strm.avail_in > 0) { 41 | // Read chunks until done 42 | 43 | let chunks = [outData]; 44 | let totalLen = outData.length; 45 | do { 46 | /* eslint-disable camelcase */ 47 | this.strm.output = new Uint8Array(this.chunkSize); 48 | this.strm.next_out = 0; 49 | this.strm.avail_out = this.chunkSize; 50 | /* eslint-enable camelcase */ 51 | 52 | lastRet = deflate(this.strm, Z_FULL_FLUSH); 53 | 54 | if (lastRet < 0) { 55 | throw new Error("zlib deflate failed"); 56 | } 57 | 58 | let chunk = new Uint8Array(this.strm.output.buffer, 0, this.strm.next_out); 59 | totalLen += chunk.length; 60 | chunks.push(chunk); 61 | } while (this.strm.avail_in > 0); 62 | 63 | // Combine chunks into a single data 64 | 65 | let newData = new Uint8Array(totalLen); 66 | let offset = 0; 67 | 68 | for (let i = 0; i < chunks.length; i++) { 69 | newData.set(chunks[i], offset); 70 | offset += chunks[i].length; 71 | } 72 | 73 | outData = newData; 74 | } 75 | 76 | /* eslint-disable camelcase */ 77 | this.strm.input = null; 78 | this.strm.avail_in = 0; 79 | this.strm.next_in = 0; 80 | /* eslint-enable camelcase */ 81 | 82 | return outData; 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /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/locale/sv.json: -------------------------------------------------------------------------------- 1 | { 2 | "Connecting...": "Ansluter...", 3 | "Disconnecting...": "Kopplar ner...", 4 | "Reconnecting...": "Återansluter...", 5 | "Internal error": "Internt fel", 6 | "Must set host": "Du måste specifiera en värd", 7 | "Connected (encrypted) to ": "Ansluten (krypterat) till ", 8 | "Connected (unencrypted) to ": "Ansluten (okrypterat) till ", 9 | "Something went wrong, connection is closed": "Något gick fel, anslutningen avslutades", 10 | "Failed to connect to server": "Misslyckades att ansluta till servern", 11 | "Disconnected": "Frånkopplad", 12 | "New connection has been rejected with reason: ": "Ny anslutning har blivit nekad med följande skäl: ", 13 | "New connection has been rejected": "Ny anslutning har blivit nekad", 14 | "Credentials are required": "Användaruppgifter krävs", 15 | "noVNC encountered an error:": "noVNC stötte på ett problem:", 16 | "Hide/Show the control bar": "Göm/Visa kontrollbaren", 17 | "Drag": "Dra", 18 | "Move/Drag Viewport": "Flytta/Dra Vyn", 19 | "Keyboard": "Tangentbord", 20 | "Show Keyboard": "Visa Tangentbord", 21 | "Extra keys": "Extraknappar", 22 | "Show Extra Keys": "Visa Extraknappar", 23 | "Ctrl": "Ctrl", 24 | "Toggle Ctrl": "Växla Ctrl", 25 | "Alt": "Alt", 26 | "Toggle Alt": "Växla Alt", 27 | "Toggle Windows": "Växla Windows", 28 | "Windows": "Windows", 29 | "Send Tab": "Skicka Tab", 30 | "Tab": "Tab", 31 | "Esc": "Esc", 32 | "Send Escape": "Skicka Escape", 33 | "Ctrl+Alt+Del": "Ctrl+Alt+Del", 34 | "Send Ctrl-Alt-Del": "Skicka Ctrl-Alt-Del", 35 | "Shutdown/Reboot": "Stäng av/Boota om", 36 | "Shutdown/Reboot...": "Stäng av/Boota om...", 37 | "Power": "Ström", 38 | "Shutdown": "Stäng av", 39 | "Reboot": "Boota om", 40 | "Reset": "Återställ", 41 | "Clipboard": "Urklipp", 42 | "Clear": "Rensa", 43 | "Fullscreen": "Fullskärm", 44 | "Settings": "Inställningar", 45 | "Shared Mode": "Delat Läge", 46 | "View Only": "Endast Visning", 47 | "Clip to Window": "Begränsa till Fönster", 48 | "Scaling Mode:": "Skalningsläge:", 49 | "None": "Ingen", 50 | "Local Scaling": "Lokal Skalning", 51 | "Remote Resizing": "Ändra Storlek", 52 | "Advanced": "Avancerat", 53 | "Quality:": "Kvalitet:", 54 | "Compression level:": "Kompressionsnivå:", 55 | "Repeater ID:": "Repeater-ID:", 56 | "WebSocket": "WebSocket", 57 | "Encrypt": "Kryptera", 58 | "Host:": "Värd:", 59 | "Port:": "Port:", 60 | "Path:": "Sökväg:", 61 | "Automatic Reconnect": "Automatisk Återanslutning", 62 | "Reconnect Delay (ms):": "Fördröjning (ms):", 63 | "Show Dot when No Cursor": "Visa prick när ingen muspekare finns", 64 | "Logging:": "Loggning:", 65 | "Version:": "Version:", 66 | "Disconnect": "Koppla från", 67 | "Connect": "Anslut", 68 | "Username:": "Användarnamn:", 69 | "Password:": "Lösenord:", 70 | "Send Credentials": "Skicka Användaruppgifter", 71 | "Cancel": "Avbryt" 72 | } -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /tests/test.localization.js: -------------------------------------------------------------------------------- 1 | const expect = chai.expect; 2 | import { l10n } from '../app/localization.js'; 3 | 4 | describe('Localization', function () { 5 | "use strict"; 6 | 7 | describe('language selection', function () { 8 | let origNavigator; 9 | beforeEach(function () { 10 | // window.navigator is a protected read-only property in many 11 | // environments, so we need to redefine it whilst running these 12 | // tests. 13 | origNavigator = Object.getOwnPropertyDescriptor(window, "navigator"); 14 | 15 | Object.defineProperty(window, "navigator", {value: {}}); 16 | if (window.navigator.languages !== undefined) { 17 | // Object.defineProperty() doesn't work properly in old 18 | // versions of Chrome 19 | this.skip(); 20 | } 21 | 22 | window.navigator.languages = []; 23 | }); 24 | afterEach(function () { 25 | if (origNavigator !== undefined) { 26 | Object.defineProperty(window, "navigator", origNavigator); 27 | } 28 | }); 29 | 30 | it('should use English by default', function () { 31 | expect(l10n.language).to.equal('en'); 32 | }); 33 | it('should use English if no user language matches', function () { 34 | window.navigator.languages = ["nl", "de"]; 35 | l10n.setup(["es", "fr"]); 36 | expect(l10n.language).to.equal('en'); 37 | }); 38 | it('should use the most preferred user language', function () { 39 | window.navigator.languages = ["nl", "de", "fr"]; 40 | l10n.setup(["es", "fr", "de"]); 41 | expect(l10n.language).to.equal('de'); 42 | }); 43 | it('should prefer sub-languages languages', function () { 44 | window.navigator.languages = ["pt-BR"]; 45 | l10n.setup(["pt", "pt-BR"]); 46 | expect(l10n.language).to.equal('pt-BR'); 47 | }); 48 | it('should fall back to language "parents"', function () { 49 | window.navigator.languages = ["pt-BR"]; 50 | l10n.setup(["fr", "pt", "de"]); 51 | expect(l10n.language).to.equal('pt'); 52 | }); 53 | it('should not use specific language when user asks for a generic language', function () { 54 | window.navigator.languages = ["pt", "de"]; 55 | l10n.setup(["fr", "pt-BR", "de"]); 56 | expect(l10n.language).to.equal('de'); 57 | }); 58 | it('should handle underscore as a separator', function () { 59 | window.navigator.languages = ["pt-BR"]; 60 | l10n.setup(["pt_BR"]); 61 | expect(l10n.language).to.equal('pt_BR'); 62 | }); 63 | it('should handle difference in case', function () { 64 | window.navigator.languages = ["pt-br"]; 65 | l10n.setup(["pt-BR"]); 66 | expect(l10n.language).to.equal('pt-BR'); 67 | }); 68 | }); 69 | }); 70 | -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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": "Mostar 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/el.json: -------------------------------------------------------------------------------- 1 | { 2 | "Connecting...": "Συνδέεται...", 3 | "Disconnecting...": "Aποσυνδέεται...", 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 | "Disconnected": "Αποσυνδέθηκε", 11 | "New connection has been rejected with reason: ": "Η νέα σύνδεση απορρίφθηκε διότι: ", 12 | "New connection has been rejected": "Η νέα σύνδεση απορρίφθηκε ", 13 | "Password is required": "Απαιτείται ο κωδικός πρόσβασης", 14 | "noVNC encountered an error:": "το noVNC αντιμετώπισε ένα σφάλμα:", 15 | "Hide/Show the control bar": "Απόκρυψη/Εμφάνιση γραμμής ελέγχου", 16 | "Move/Drag Viewport": "Μετακίνηση/Σύρσιμο Θεατού πεδίου", 17 | "viewport drag": "σύρσιμο θεατού πεδίου", 18 | "Active Mouse Button": "Ενεργό Πλήκτρο Ποντικιού", 19 | "No mousebutton": "Χωρίς Πλήκτρο Ποντικιού", 20 | "Left mousebutton": "Αριστερό Πλήκτρο Ποντικιού", 21 | "Middle mousebutton": "Μεσαίο Πλήκτρο Ποντικιού", 22 | "Right mousebutton": "Δεξί Πλήκτρο Ποντικιού", 23 | "Keyboard": "Πληκτρολόγιο", 24 | "Show Keyboard": "Εμφάνιση Πληκτρολογίου", 25 | "Extra keys": "Επιπλέον πλήκτρα", 26 | "Show Extra Keys": "Εμφάνιση Επιπλέον Πλήκτρων", 27 | "Ctrl": "Ctrl", 28 | "Toggle Ctrl": "Εναλλαγή Ctrl", 29 | "Alt": "Alt", 30 | "Toggle Alt": "Εναλλαγή Alt", 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 | "Clear": "Καθάρισμα", 45 | "Fullscreen": "Πλήρης Οθόνη", 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 | "Repeater ID:": "Repeater ID:", 56 | "WebSocket": "WebSocket", 57 | "Encrypt": "Κρυπτογράφηση", 58 | "Host:": "Όνομα διακομιστή:", 59 | "Port:": "Πόρτα διακομιστή:", 60 | "Path:": "Διαδρομή:", 61 | "Automatic Reconnect": "Αυτόματη επανασύνδεση", 62 | "Reconnect Delay (ms):": "Καθυστέρηση επανασύνδεσης (ms):", 63 | "Logging:": "Καταγραφή:", 64 | "Disconnect": "Αποσύνδεση", 65 | "Connect": "Σύνδεση", 66 | "Password:": "Κωδικός Πρόσβασης:", 67 | "Cancel": "Ακύρωση", 68 | "Canvas not supported.": "Δεν υποστηρίζεται το στοιχείο Canvas" 69 | } -------------------------------------------------------------------------------- /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 | "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 | "Toggle Windows": "Переключение вкладок", 33 | "Windows": "Вкладка", 34 | "Send Tab": "Передать нажатие Tab", 35 | "Tab": "Tab", 36 | "Esc": "Esc", 37 | "Send Escape": "Передать нажатие Escape", 38 | "Ctrl+Alt+Del": "Ctrl+Alt+Del", 39 | "Send Ctrl-Alt-Del": "Передать нажатие Ctrl-Alt-Del", 40 | "Shutdown/Reboot": "Выключить/Перезагрузить", 41 | "Shutdown/Reboot...": "Выключить/Перезагрузить...", 42 | "Power": "Питание", 43 | "Shutdown": "Выключить", 44 | "Reboot": "Перезагрузить", 45 | "Reset": "Сброс", 46 | "Clipboard": "Буфер обмена", 47 | "Clear": "Очистить", 48 | "Fullscreen": "Во весь экран", 49 | "Settings": "Настройки", 50 | "Shared Mode": "Общий режим", 51 | "View Only": "Просмотр", 52 | "Clip to Window": "В окно", 53 | "Scaling Mode:": "Масштаб:", 54 | "None": "Нет", 55 | "Local Scaling": "Локльный масштаб", 56 | "Remote Resizing": "Удаленный масштаб", 57 | "Advanced": "Дополнительно", 58 | "Repeater ID:": "Идентификатор ID:", 59 | "WebSocket": "WebSocket", 60 | "Encrypt": "Шифрование", 61 | "Host:": "Сервер:", 62 | "Port:": "Порт:", 63 | "Path:": "Путь:", 64 | "Automatic Reconnect": "Автоматическое переподключение", 65 | "Reconnect Delay (ms):": "Задержка переподключения (мс):", 66 | "Show Dot when No Cursor": "Показать точку вместо курсора", 67 | "Logging:": "Лог:", 68 | "Disconnect": "Отключение", 69 | "Connect": "Подключение", 70 | "Password:": "Пароль:", 71 | "Send Password": "Пароль: ", 72 | "Cancel": "Выход" 73 | } -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import globals from "globals"; 2 | import path from "node:path"; 3 | import { fileURLToPath } from "node:url"; 4 | import js from "@eslint/js"; 5 | import { FlatCompat } from "@eslint/eslintrc"; 6 | 7 | const __filename = fileURLToPath(import.meta.url); 8 | const __dirname = path.dirname(__filename); 9 | const compat = new FlatCompat({ 10 | baseDirectory: __dirname, 11 | recommendedConfig: js.configs.recommended, 12 | allConfig: js.configs.all 13 | }); 14 | 15 | export default [{ 16 | ignores: ["**/xtscancodes.js"], 17 | }, ...compat.extends("eslint:recommended"), { 18 | languageOptions: { 19 | globals: { 20 | ...globals.browser, 21 | }, 22 | 23 | ecmaVersion: 5, 24 | sourceType: "module", 25 | }, 26 | 27 | rules: { 28 | "no-unused-vars": ["error", { 29 | vars: "all", 30 | args: "none", 31 | ignoreRestSiblings: true, 32 | }], 33 | 34 | "no-constant-condition": ["error", { 35 | checkLoops: false, 36 | }], 37 | 38 | "no-var": "error", 39 | "no-useless-constructor": "error", 40 | 41 | "object-shorthand": ["error", "methods", { 42 | avoidQuotes: true, 43 | }], 44 | 45 | "prefer-arrow-callback": "error", 46 | 47 | "arrow-body-style": ["error", "as-needed", { 48 | requireReturnForObjectLiteral: false, 49 | }], 50 | 51 | "arrow-parens": ["error", "as-needed", { 52 | requireForBlockBody: true, 53 | }], 54 | 55 | "arrow-spacing": ["error"], 56 | 57 | "no-confusing-arrow": ["error", { 58 | allowParens: true, 59 | }], 60 | 61 | "brace-style": ["error", "1tbs", { 62 | allowSingleLine: true, 63 | }], 64 | 65 | indent: ["error", 4, { 66 | SwitchCase: 1, 67 | 68 | FunctionDeclaration: { 69 | parameters: "first", 70 | }, 71 | 72 | CallExpression: { 73 | arguments: "first", 74 | }, 75 | 76 | ArrayExpression: "first", 77 | ObjectExpression: "first", 78 | ignoreComments: true, 79 | }], 80 | 81 | "comma-spacing": ["error"], 82 | "comma-style": ["error"], 83 | curly: ["error", "multi-line"], 84 | "func-call-spacing": ["error"], 85 | "func-names": ["error"], 86 | 87 | "func-style": ["error", "declaration", { 88 | allowArrowFunctions: true, 89 | }], 90 | 91 | "key-spacing": ["error"], 92 | "keyword-spacing": ["error"], 93 | "no-trailing-spaces": ["error"], 94 | semi: ["error"], 95 | "space-before-blocks": ["error"], 96 | 97 | "space-before-function-paren": ["error", { 98 | anonymous: "always", 99 | named: "never", 100 | asyncArrow: "always", 101 | }], 102 | 103 | "switch-colon-spacing": ["error"], 104 | 105 | camelcase: ["error", { 106 | allow: ["^XK_", "^XF86XK_"], 107 | }], 108 | }, 109 | }]; -------------------------------------------------------------------------------- /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/locale/nl.json: -------------------------------------------------------------------------------- 1 | { 2 | "Connecting...": "Verbinden...", 3 | "Disconnecting...": "Verbinding verbreken...", 4 | "Reconnecting...": "Opnieuw verbinding maken...", 5 | "Internal error": "Interne fout", 6 | "Must set host": "Host moeten worden ingesteld", 7 | "Connected (encrypted) to ": "Verbonden (versleuteld) met ", 8 | "Connected (unencrypted) to ": "Verbonden (onversleuteld) met ", 9 | "Something went wrong, connection is closed": "Er iets fout gelopen, verbinding werd verbroken", 10 | "Failed to connect to server": "Verbinding maken met server is mislukt", 11 | "Disconnected": "Verbinding verbroken", 12 | "New connection has been rejected with reason: ": "Nieuwe verbinding is geweigerd omwille van de volgende reden: ", 13 | "New connection has been rejected": "Nieuwe verbinding is geweigerd", 14 | "Password is required": "Wachtwoord is vereist", 15 | "noVNC encountered an error:": "noVNC heeft een fout bemerkt:", 16 | "Hide/Show the control bar": "Verberg/Toon de bedieningsbalk", 17 | "Move/Drag Viewport": "Verplaats/Versleep Kijkvenster", 18 | "viewport drag": "kijkvenster slepen", 19 | "Active Mouse Button": "Actieve Muisknop", 20 | "No mousebutton": "Geen muisknop", 21 | "Left mousebutton": "Linker muisknop", 22 | "Middle mousebutton": "Middelste muisknop", 23 | "Right mousebutton": "Rechter muisknop", 24 | "Keyboard": "Toetsenbord", 25 | "Show Keyboard": "Toon Toetsenbord", 26 | "Extra keys": "Extra toetsen", 27 | "Show Extra Keys": "Toon Extra Toetsen", 28 | "Ctrl": "Ctrl", 29 | "Toggle Ctrl": "Ctrl omschakelen", 30 | "Alt": "Alt", 31 | "Toggle Alt": "Alt omschakelen", 32 | "Toggle Windows": "Windows omschakelen", 33 | "Windows": "Windows", 34 | "Send Tab": "Tab Sturen", 35 | "Tab": "Tab", 36 | "Esc": "Esc", 37 | "Send Escape": "Escape Sturen", 38 | "Ctrl+Alt+Del": "Ctrl-Alt-Del", 39 | "Send Ctrl-Alt-Del": "Ctrl-Alt-Del Sturen", 40 | "Shutdown/Reboot": "Uitschakelen/Herstarten", 41 | "Shutdown/Reboot...": "Uitschakelen/Herstarten...", 42 | "Power": "Systeem", 43 | "Shutdown": "Uitschakelen", 44 | "Reboot": "Herstarten", 45 | "Reset": "Resetten", 46 | "Clipboard": "Klembord", 47 | "Clear": "Wissen", 48 | "Fullscreen": "Volledig Scherm", 49 | "Settings": "Instellingen", 50 | "Shared Mode": "Gedeelde Modus", 51 | "View Only": "Alleen Kijken", 52 | "Clip to Window": "Randen buiten venster afsnijden", 53 | "Scaling Mode:": "Schaalmodus:", 54 | "None": "Geen", 55 | "Local Scaling": "Lokaal Schalen", 56 | "Remote Resizing": "Op Afstand Formaat Wijzigen", 57 | "Advanced": "Geavanceerd", 58 | "Repeater ID:": "Repeater ID:", 59 | "WebSocket": "WebSocket", 60 | "Encrypt": "Versleutelen", 61 | "Host:": "Host:", 62 | "Port:": "Poort:", 63 | "Path:": "Pad:", 64 | "Automatic Reconnect": "Automatisch Opnieuw Verbinden", 65 | "Reconnect Delay (ms):": "Vertraging voor Opnieuw Verbinden (ms):", 66 | "Show Dot when No Cursor": "Geef stip weer indien geen cursor", 67 | "Logging:": "Logmeldingen:", 68 | "Disconnect": "Verbinding verbreken", 69 | "Connect": "Verbinden", 70 | "Password:": "Wachtwoord:", 71 | "Send Password": "Verzend Wachtwoord:", 72 | "Cancel": "Annuleren" 73 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /tests/test.copyrect.js: -------------------------------------------------------------------------------- 1 | const expect = chai.expect; 2 | 3 | import Websock from '../core/websock.js'; 4 | import Display from '../core/display.js'; 5 | 6 | import CopyRectDecoder from '../core/decoders/copyrect.js'; 7 | 8 | import FakeWebSocket from './fake.websocket.js'; 9 | 10 | function testDecodeRect(decoder, x, y, width, height, data, display, depth) { 11 | let sock; 12 | 13 | sock = new Websock; 14 | sock.open("ws://example.com"); 15 | 16 | sock.on('message', () => { 17 | decoder.decodeRect(x, y, width, height, sock, display, depth); 18 | }); 19 | 20 | // Empty messages are filtered at multiple layers, so we need to 21 | // do a direct call 22 | if (data.length === 0) { 23 | decoder.decodeRect(x, y, width, height, sock, display, depth); 24 | } else { 25 | sock._websocket._receiveData(new Uint8Array(data)); 26 | } 27 | 28 | display.flip(); 29 | } 30 | 31 | describe('CopyRect Decoder', function () { 32 | let decoder; 33 | let display; 34 | 35 | before(FakeWebSocket.replace); 36 | after(FakeWebSocket.restore); 37 | 38 | beforeEach(function () { 39 | decoder = new CopyRectDecoder(); 40 | display = new Display(document.createElement('canvas')); 41 | display.resize(4, 4); 42 | }); 43 | 44 | it('should handle the CopyRect encoding', function () { 45 | // seed some initial data to copy 46 | display.fillRect(0, 0, 4, 4, [ 0x11, 0x22, 0x33 ]); 47 | display.fillRect(0, 0, 2, 2, [ 0x00, 0x00, 0xff ]); 48 | display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]); 49 | 50 | testDecodeRect(decoder, 0, 2, 2, 2, 51 | [0x00, 0x02, 0x00, 0x00], 52 | display, 24); 53 | testDecodeRect(decoder, 2, 2, 2, 2, 54 | [0x00, 0x00, 0x00, 0x00], 55 | display, 24); 56 | 57 | let targetData = new Uint8Array([ 58 | 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 59 | 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 60 | 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 61 | 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 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 | testDecodeRect(decoder, 1, 2, 0, 0, [0x00, 0x00, 0x00, 0x00], 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(display).to.have.displayed(targetData); 82 | }); 83 | }); 84 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /tests/test.util.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | const expect = chai.expect; 3 | 4 | import * as Log from '../core/util/logging.js'; 5 | import { encodeUTF8, decodeUTF8 } from '../core/util/strings.js'; 6 | 7 | describe('Utils', function () { 8 | "use strict"; 9 | 10 | describe('logging functions', function () { 11 | beforeEach(function () { 12 | sinon.spy(console, 'log'); 13 | sinon.spy(console, 'debug'); 14 | sinon.spy(console, 'warn'); 15 | sinon.spy(console, 'error'); 16 | sinon.spy(console, 'info'); 17 | }); 18 | 19 | afterEach(function () { 20 | console.log.restore(); 21 | console.debug.restore(); 22 | console.warn.restore(); 23 | console.error.restore(); 24 | console.info.restore(); 25 | Log.initLogging(); 26 | }); 27 | 28 | it('should use noop for levels lower than the min level', function () { 29 | Log.initLogging('warn'); 30 | Log.Debug('hi'); 31 | Log.Info('hello'); 32 | expect(console.log).to.not.have.been.called; 33 | }); 34 | 35 | it('should use console.debug for Debug', function () { 36 | Log.initLogging('debug'); 37 | Log.Debug('dbg'); 38 | expect(console.debug).to.have.been.calledWith('dbg'); 39 | }); 40 | 41 | it('should use console.info for Info', function () { 42 | Log.initLogging('debug'); 43 | Log.Info('inf'); 44 | expect(console.info).to.have.been.calledWith('inf'); 45 | }); 46 | 47 | it('should use console.warn for Warn', function () { 48 | Log.initLogging('warn'); 49 | Log.Warn('wrn'); 50 | expect(console.warn).to.have.been.called; 51 | expect(console.warn).to.have.been.calledWith('wrn'); 52 | }); 53 | 54 | it('should use console.error for Error', function () { 55 | Log.initLogging('error'); 56 | Log.Error('err'); 57 | expect(console.error).to.have.been.called; 58 | expect(console.error).to.have.been.calledWith('err'); 59 | }); 60 | }); 61 | 62 | describe('string functions', function () { 63 | it('should decode UTF-8 to DOMString correctly', function () { 64 | const utf8string = '\xd0\x9f'; 65 | const domstring = decodeUTF8(utf8string); 66 | expect(domstring).to.equal("П"); 67 | }); 68 | 69 | it('should encode DOMString to UTF-8 correctly', function () { 70 | const domstring = "åäöa"; 71 | const utf8string = encodeUTF8(domstring); 72 | expect(utf8string).to.equal('\xc3\xa5\xc3\xa4\xc3\xb6\x61'); 73 | }); 74 | 75 | it('should allow Latin-1 strings if allowLatin1 is set when decoding', function () { 76 | const latin1string = '\xe5\xe4\xf6'; 77 | expect(() => decodeUTF8(latin1string)).to.throw(Error); 78 | expect(decodeUTF8(latin1string, true)).to.equal('åäö'); 79 | }); 80 | }); 81 | 82 | // TODO(directxman12): test the conf_default and conf_defaults methods 83 | // TODO(directxman12): test the event methods (addEvent, removeEvent, stopEvent) 84 | // TODO(directxman12): figure out a good way to test getPosition and getEventPosition 85 | // TODO(directxman12): figure out how to test the browser detection functions properly 86 | // (we can't really test them against the browsers, except for Gecko 87 | // via PhantomJS, the default test driver) 88 | }); 89 | /* eslint-enable no-console */ 90 | -------------------------------------------------------------------------------- /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 | const fs = require('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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | const getopt = require('node-getopt'); 9 | const jsdom = require("jsdom"); 10 | const fs = require("fs"); 11 | 12 | const opt = getopt.create([ 13 | ['o', 'output=FILE', 'write output to specified file'], 14 | ['h', 'help', 'display this help'], 15 | ]).bindHelp().parseSystem(); 16 | 17 | const strings = {}; 18 | 19 | function addString(str, location) { 20 | if (str.length == 0) { 21 | return; 22 | } 23 | 24 | if (strings[str] === undefined) { 25 | strings[str] = {}; 26 | } 27 | strings[str][location] = null; 28 | } 29 | 30 | // See https://html.spec.whatwg.org/multipage/dom.html#attr-translate 31 | function process(elem, locator, enabled) { 32 | function isAnyOf(searchElement, items) { 33 | return items.indexOf(searchElement) !== -1; 34 | } 35 | 36 | if (elem.hasAttribute("translate")) { 37 | if (isAnyOf(elem.getAttribute("translate"), ["", "yes"])) { 38 | enabled = true; 39 | } else if (isAnyOf(elem.getAttribute("translate"), ["no"])) { 40 | enabled = false; 41 | } 42 | } 43 | 44 | if (enabled) { 45 | if (elem.hasAttribute("abbr") && 46 | elem.tagName === "TH") { 47 | addString(elem.getAttribute("abbr"), locator(elem)); 48 | } 49 | if (elem.hasAttribute("alt") && 50 | isAnyOf(elem.tagName, ["AREA", "IMG", "INPUT"])) { 51 | addString(elem.getAttribute("alt"), locator(elem)); 52 | } 53 | if (elem.hasAttribute("download") && 54 | isAnyOf(elem.tagName, ["A", "AREA"])) { 55 | addString(elem.getAttribute("download"), locator(elem)); 56 | } 57 | if (elem.hasAttribute("label") && 58 | isAnyOf(elem.tagName, ["MENUITEM", "MENU", "OPTGROUP", 59 | "OPTION", "TRACK"])) { 60 | addString(elem.getAttribute("label"), locator(elem)); 61 | } 62 | if (elem.hasAttribute("placeholder") && 63 | isAnyOf(elem.tagName in ["INPUT", "TEXTAREA"])) { 64 | addString(elem.getAttribute("placeholder"), locator(elem)); 65 | } 66 | if (elem.hasAttribute("title")) { 67 | addString(elem.getAttribute("title"), locator(elem)); 68 | } 69 | if (elem.hasAttribute("value") && 70 | elem.tagName === "INPUT" && 71 | isAnyOf(elem.getAttribute("type"), ["reset", "button", "submit"])) { 72 | addString(elem.getAttribute("value"), locator(elem)); 73 | } 74 | } 75 | 76 | for (let i = 0; i < elem.childNodes.length; i++) { 77 | let node = elem.childNodes[i]; 78 | if (node.nodeType === node.ELEMENT_NODE) { 79 | process(node, locator, enabled); 80 | } else if (node.nodeType === node.TEXT_NODE && enabled) { 81 | addString(node.data.trim(), locator(node)); 82 | } 83 | } 84 | } 85 | 86 | for (let i = 0; i < opt.argv.length; i++) { 87 | const fn = opt.argv[i]; 88 | const file = fs.readFileSync(fn, "utf8"); 89 | const dom = new jsdom.JSDOM(file, { includeNodeLocations: true }); 90 | const body = dom.window.document.body; 91 | 92 | let locator = (elem) => { 93 | const offset = dom.nodeLocation(elem).startOffset; 94 | const line = file.slice(0, offset).split("\n").length; 95 | return fn + ":" + line; 96 | }; 97 | 98 | process(body, locator, true); 99 | } 100 | 101 | let output = ""; 102 | 103 | for (let str in strings) { 104 | output += "#:"; 105 | for (location in strings[str]) { 106 | output += " " + location; 107 | } 108 | output += "\n"; 109 | 110 | output += "msgid " + JSON.stringify(str) + "\n"; 111 | output += "msgstr \"\"\n"; 112 | output += "\n"; 113 | } 114 | 115 | fs.writeFileSync(opt.options.output, output); 116 | -------------------------------------------------------------------------------- /tests/test.rre.js: -------------------------------------------------------------------------------- 1 | const expect = chai.expect; 2 | 3 | import Websock from '../core/websock.js'; 4 | import Display from '../core/display.js'; 5 | 6 | import RREDecoder from '../core/decoders/rre.js'; 7 | 8 | import FakeWebSocket from './fake.websocket.js'; 9 | 10 | function testDecodeRect(decoder, x, y, width, height, data, display, depth) { 11 | let sock; 12 | 13 | sock = new Websock; 14 | sock.open("ws://example.com"); 15 | 16 | sock.on('message', () => { 17 | decoder.decodeRect(x, y, width, height, sock, display, depth); 18 | }); 19 | 20 | // Empty messages are filtered at multiple layers, so we need to 21 | // do a direct call 22 | if (data.length === 0) { 23 | decoder.decodeRect(x, y, width, height, sock, display, depth); 24 | } else { 25 | sock._websocket._receiveData(new Uint8Array(data)); 26 | } 27 | 28 | display.flip(); 29 | } 30 | 31 | function push16(arr, num) { 32 | arr.push((num >> 8) & 0xFF, 33 | num & 0xFF); 34 | } 35 | 36 | function push32(arr, num) { 37 | arr.push((num >> 24) & 0xFF, 38 | (num >> 16) & 0xFF, 39 | (num >> 8) & 0xFF, 40 | num & 0xFF); 41 | } 42 | 43 | describe('RRE Decoder', function () { 44 | let decoder; 45 | let display; 46 | 47 | before(FakeWebSocket.replace); 48 | after(FakeWebSocket.restore); 49 | 50 | beforeEach(function () { 51 | decoder = new RREDecoder(); 52 | display = new Display(document.createElement('canvas')); 53 | display.resize(4, 4); 54 | }); 55 | 56 | // TODO(directxman12): test rre_chunk_sz? 57 | 58 | it('should handle the RRE encoding', function () { 59 | let data = []; 60 | push32(data, 2); // 2 subrects 61 | push32(data, 0x00ff0000); // becomes 00ff0000 --> #00FF00 bg color 62 | data.push(0x00); // becomes 0000ff00 --> #0000FF fg color 63 | data.push(0x00); 64 | data.push(0xff); 65 | data.push(0x00); 66 | push16(data, 0); // x: 0 67 | push16(data, 0); // y: 0 68 | push16(data, 2); // width: 2 69 | push16(data, 2); // height: 2 70 | data.push(0x00); // becomes 0000ff00 --> #0000FF fg color 71 | data.push(0x00); 72 | data.push(0xff); 73 | data.push(0x00); 74 | push16(data, 2); // x: 2 75 | push16(data, 2); // y: 2 76 | push16(data, 2); // width: 2 77 | push16(data, 2); // height: 2 78 | 79 | testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24); 80 | 81 | let targetData = new Uint8Array([ 82 | 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 83 | 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 84 | 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 85 | 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255 86 | ]); 87 | 88 | expect(display).to.have.displayed(targetData); 89 | }); 90 | 91 | it('should handle empty rects', function () { 92 | display.fillRect(0, 0, 4, 4, [ 0x00, 0x00, 0xff ]); 93 | display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]); 94 | display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]); 95 | 96 | testDecodeRect(decoder, 1, 2, 0, 0, [ 0x00, 0xff, 0xff, 0xff, 0xff ], display, 24); 97 | 98 | let targetData = new Uint8Array([ 99 | 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 100 | 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 101 | 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 102 | 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255 103 | ]); 104 | 105 | expect(display).to.have.displayed(targetData); 106 | }); 107 | }); 108 | -------------------------------------------------------------------------------- /docs/EMBEDDING.md: -------------------------------------------------------------------------------- 1 | # Embedding and Deploying noVNC Application 2 | 3 | This document describes how to embed and deploy the noVNC application, which 4 | includes settings and a full user interface. If you are looking for 5 | documentation on how to use the core noVNC library in your own application, 6 | then please see our [library documentation](LIBRARY.md). 7 | 8 | ## Files 9 | 10 | The noVNC application consists of the following files and directories: 11 | 12 | * `vnc.html` - The main page for the application and where users should go. It 13 | is possible to rename this file. 14 | 15 | * `app/` - Support files for the application. Contains code, images, styles and 16 | translations. 17 | 18 | * `core/` - The core noVNC library. 19 | 20 | * `vendor/` - Third party support libraries used by the application and the 21 | core library. 22 | 23 | The most basic deployment consists of simply serving these files from a web 24 | server and setting up a WebSocket proxy to the VNC server. 25 | 26 | ## Parameters 27 | 28 | The noVNC application can be controlled by including certain settings in the 29 | query string. Currently the following options are available: 30 | 31 | * `autoconnect` - Automatically connect as soon as the page has finished 32 | loading. 33 | 34 | * `reconnect` - If noVNC should automatically reconnect if the connection is 35 | dropped. 36 | 37 | * `reconnect_delay` - How long to wait in milliseconds before attempting to 38 | reconnect. 39 | 40 | * `host` - The WebSocket host to connect to. 41 | 42 | * `port` - The WebSocket port to connect to. 43 | 44 | * `encrypt` - If TLS should be used for the WebSocket connection. 45 | 46 | * `path` - The WebSocket path to use. 47 | 48 | * `password` - The password sent to the server, if required. 49 | 50 | * `repeaterID` - The repeater ID to use if a VNC repeater is detected. 51 | 52 | * `shared` - If other VNC clients should be disconnected when noVNC connects. 53 | 54 | * `bell` - If the keyboard bell should be enabled or not. 55 | 56 | * `view_only` - If the remote session should be in non-interactive mode. 57 | 58 | * `view_clip` - If the remote session should be clipped or use scrollbars if 59 | it cannot fit in the browser. 60 | 61 | * `resize` - How to resize the remote session if it is not the same size as 62 | the browser window. Can be one of `off`, `scale` and `remote`. 63 | 64 | * `quality` - The session JPEG quality level. Can be `0` to `9`. 65 | 66 | * `compression` - The session compression level. Can be `0` to `9`. 67 | 68 | * `show_dot` - If a dot cursor should be shown when the remote server provides 69 | no local cursor, or provides a fully-transparent (invisible) cursor. 70 | 71 | * `logging` - The console log level. Can be one of `error`, `warn`, `info` or 72 | `debug`. 73 | 74 | ## HTTP Serving Considerations 75 | ### Browser Cache Issue 76 | 77 | If you serve noVNC files using a web server that provides an ETag header, and 78 | include any options in the query string, a nasty browser cache issue can bite 79 | you on upgrade, resulting in a red error box. The issue is caused by a mismatch 80 | between the new vnc.html (which is reloaded because the user has used it with 81 | new query string after the upgrade) and the old javascript files (that the 82 | browser reuses from its cache). To avoid this issue, the browser must be told 83 | to always revalidate cached files using conditional requests. The correct 84 | semantics are achieved via the (confusingly named) `Cache-Control: no-cache` 85 | header that needs to be provided in the web server responses. 86 | 87 | ### Example Server Configurations 88 | 89 | Apache: 90 | 91 | ``` 92 | # In the main configuration file 93 | # (Debian/Ubuntu users: use "a2enmod headers" instead) 94 | LoadModule headers_module modules/mod_headers.so 95 | 96 | # In the or block related to noVNC 97 | Header set Cache-Control "no-cache" 98 | ``` 99 | 100 | Nginx: 101 | 102 | ``` 103 | # In the location block related to noVNC 104 | add_header Cache-Control no-cache; 105 | ``` 106 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /core/util/browser.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 | * Browser feature support detection 9 | */ 10 | 11 | import * as Log from './logging.js'; 12 | 13 | // Touch detection 14 | export let isTouchDevice = ('ontouchstart' in document.documentElement) || 15 | // requried for Chrome debugger 16 | (document.ontouchstart !== undefined) || 17 | // required for MS Surface 18 | (navigator.maxTouchPoints > 0) || 19 | (navigator.msMaxTouchPoints > 0); 20 | window.addEventListener('touchstart', function onFirstTouch() { 21 | isTouchDevice = true; 22 | window.removeEventListener('touchstart', onFirstTouch, false); 23 | }, false); 24 | 25 | 26 | // The goal is to find a certain physical width, the devicePixelRatio 27 | // brings us a bit closer but is not optimal. 28 | export let dragThreshold = 10 * (window.devicePixelRatio || 1); 29 | 30 | let _supportsCursorURIs = false; 31 | 32 | try { 33 | const target = document.createElement('canvas'); 34 | target.style.cursor = 'url("data:image/x-icon;base64,AAACAAEACAgAAAIAAgA4AQAAFgAAACgAAAAIAAAAEAAAAAEAIAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAAAAAAAAAAAAAAAA==") 2 2, default'; 35 | 36 | if (target.style.cursor.indexOf("url") === 0) { 37 | Log.Info("Data URI scheme cursor supported"); 38 | _supportsCursorURIs = true; 39 | } else { 40 | Log.Warn("Data URI scheme cursor not supported"); 41 | } 42 | } catch (exc) { 43 | Log.Error("Data URI scheme cursor test exception: " + exc); 44 | } 45 | 46 | export const supportsCursorURIs = _supportsCursorURIs; 47 | 48 | let _hasScrollbarGutter = true; 49 | try { 50 | // Create invisible container 51 | const container = document.createElement('div'); 52 | container.style.visibility = 'hidden'; 53 | container.style.overflow = 'scroll'; // forcing scrollbars 54 | document.body.appendChild(container); 55 | 56 | // Create a div and place it in the container 57 | const child = document.createElement('div'); 58 | container.appendChild(child); 59 | 60 | // Calculate the difference between the container's full width 61 | // and the child's width - the difference is the scrollbars 62 | const scrollbarWidth = (container.offsetWidth - child.offsetWidth); 63 | 64 | // Clean up 65 | container.parentNode.removeChild(container); 66 | 67 | _hasScrollbarGutter = scrollbarWidth != 0; 68 | } catch (exc) { 69 | Log.Error("Scrollbar test exception: " + exc); 70 | } 71 | export const hasScrollbarGutter = _hasScrollbarGutter; 72 | 73 | /* 74 | * The functions for detection of platforms and browsers below are exported 75 | * but the use of these should be minimized as much as possible. 76 | * 77 | * It's better to use feature detection than platform detection. 78 | */ 79 | 80 | export function isMac() { 81 | return navigator && !!(/mac/i).exec(navigator.platform); 82 | } 83 | 84 | export function isWindows() { 85 | return navigator && !!(/win/i).exec(navigator.platform); 86 | } 87 | 88 | export function isIOS() { 89 | return navigator && 90 | (!!(/ipad/i).exec(navigator.platform) || 91 | !!(/iphone/i).exec(navigator.platform) || 92 | !!(/ipod/i).exec(navigator.platform)); 93 | } 94 | 95 | export function isSafari() { 96 | return navigator && (navigator.userAgent.indexOf('Safari') !== -1 && 97 | navigator.userAgent.indexOf('Chrome') === -1); 98 | } 99 | 100 | export function isFirefox() { 101 | return navigator && !!(/firefox/i).exec(navigator.userAgent); 102 | } 103 | 104 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /docs/API-internal.md: -------------------------------------------------------------------------------- 1 | # 1. Internal Modules 2 | 3 | The noVNC client is composed of several internal modules that handle 4 | rendering, input, networking, etc. Each of the modules is designed to 5 | be cross-browser and independent from each other. 6 | 7 | Note however that the API of these modules is not guaranteed to be 8 | stable, and this documentation is not maintained as well as the 9 | official external API. 10 | 11 | 12 | ## 1.1 Module List 13 | 14 | * __Keyboard__ (core/input/keyboard.js): Keyboard input event handler with 15 | non-US keyboard support. Translates keyDown and keyUp events to X11 16 | keysym values. 17 | 18 | * __Display__ (core/display.js): Efficient 2D rendering abstraction 19 | layered on the HTML5 canvas element. 20 | 21 | * __Websock__ (core/websock.js): Websock client from websockify 22 | with transparent binary data support. 23 | [Websock API](https://github.com/novnc/websockify-js/wiki/websock.js) wiki page. 24 | 25 | 26 | ## 1.2 Callbacks 27 | 28 | For the Mouse, Keyboard and Display objects the callback functions are 29 | assigned to configuration attributes, just as for the RFB object. The 30 | WebSock module has a method named 'on' that takes two parameters: the 31 | callback event name, and the callback function. 32 | 33 | ## 2. Modules 34 | 35 | ## 2.1 Keyboard Module 36 | 37 | ### 2.1.1 Configuration Attributes 38 | 39 | None 40 | 41 | ### 2.1.2 Methods 42 | 43 | | name | parameters | description 44 | | ------ | ---------- | ------------ 45 | | grab | () | Begin capturing keyboard events 46 | | ungrab | () | Stop capturing keyboard events 47 | 48 | ### 2.1.3 Callbacks 49 | 50 | | name | parameters | description 51 | | ---------- | -------------------- | ------------ 52 | | onkeypress | (keysym, code, down) | Handler for key press/release 53 | 54 | 55 | ## 2.2 Display Module 56 | 57 | ### 2.2.1 Configuration Attributes 58 | 59 | | name | type | mode | default | description 60 | | ------------ | ----- | ---- | ------- | ------------ 61 | | scale | float | RW | 1.0 | Display area scale factor 0.0 - 1.0 62 | | clipViewport | bool | RW | false | Use viewport clipping 63 | | width | int | RO | | Display area width 64 | | height | int | RO | | Display area height 65 | 66 | ### 2.2.2 Methods 67 | 68 | | name | parameters | description 69 | | ------------------ | ------------------------------------------------------- | ------------ 70 | | viewportChangePos | (deltaX, deltaY) | Move the viewport relative to the current location 71 | | viewportChangeSize | (width, height) | Change size of the viewport 72 | | absX | (x) | Return X relative to the remote display 73 | | absY | (y) | Return Y relative to the remote display 74 | | resize | (width, height) | Set width and height 75 | | flip | (from_queue) | Update the visible canvas with the contents of the rendering canvas 76 | | pending | () | Check if there are waiting items in the render queue 77 | | flush | () | Resume processing the render queue unless it's empty 78 | | fillRect | (x, y, width, height, color, from_queue) | Draw a filled in rectangle 79 | | copyImage | (old_x, old_y, new_x, new_y, width, height, from_queue) | Copy a rectangular area 80 | | imageRect | (x, y, width, height, mime, arr) | Draw a rectangle with an image 81 | | blitImage | (x, y, width, height, arr, offset, from_queue) | Blit pixels (of R,G,B,A) to the display 82 | | drawImage | (img, x, y) | Draw image and track damage 83 | | autoscale | (containerWidth, containerHeight) | Scale the display 84 | 85 | ### 2.2.3 Callbacks 86 | 87 | | name | parameters | description 88 | | ------- | ---------- | ------------ 89 | | onflush | () | A display flush has been requested and we are now ready to resume FBU processing 90 | -------------------------------------------------------------------------------- /core/input/fixedkeys.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 | * Fallback mapping between HTML key codes (physical keys) and 9 | * HTML key values. This only works for keys that don't vary 10 | * between layouts. We also omit those who manage fine by mapping the 11 | * Unicode representation. 12 | * 13 | * See https://www.w3.org/TR/uievents-code/ for possible codes. 14 | * See https://www.w3.org/TR/uievents-key/ for possible values. 15 | */ 16 | 17 | /* eslint-disable key-spacing */ 18 | 19 | export default { 20 | 21 | // 3.1.1.1. Writing System Keys 22 | 23 | 'Backspace': 'Backspace', 24 | 25 | // 3.1.1.2. Functional Keys 26 | 27 | 'AltLeft': 'Alt', 28 | 'AltRight': 'Alt', // This could also be 'AltGraph' 29 | 'CapsLock': 'CapsLock', 30 | 'ContextMenu': 'ContextMenu', 31 | 'ControlLeft': 'Control', 32 | 'ControlRight': 'Control', 33 | 'Enter': 'Enter', 34 | 'MetaLeft': 'Meta', 35 | 'MetaRight': 'Meta', 36 | 'ShiftLeft': 'Shift', 37 | 'ShiftRight': 'Shift', 38 | 'Tab': 'Tab', 39 | // FIXME: Japanese/Korean keys 40 | 41 | // 3.1.2. Control Pad Section 42 | 43 | 'Delete': 'Delete', 44 | 'End': 'End', 45 | 'Help': 'Help', 46 | 'Home': 'Home', 47 | 'Insert': 'Insert', 48 | 'PageDown': 'PageDown', 49 | 'PageUp': 'PageUp', 50 | 51 | // 3.1.3. Arrow Pad Section 52 | 53 | 'ArrowDown': 'ArrowDown', 54 | 'ArrowLeft': 'ArrowLeft', 55 | 'ArrowRight': 'ArrowRight', 56 | 'ArrowUp': 'ArrowUp', 57 | 58 | // 3.1.4. Numpad Section 59 | 60 | 'NumLock': 'NumLock', 61 | 'NumpadBackspace': 'Backspace', 62 | 'NumpadClear': 'Clear', 63 | 64 | // 3.1.5. Function Section 65 | 66 | 'Escape': 'Escape', 67 | 'F1': 'F1', 68 | 'F2': 'F2', 69 | 'F3': 'F3', 70 | 'F4': 'F4', 71 | 'F5': 'F5', 72 | 'F6': 'F6', 73 | 'F7': 'F7', 74 | 'F8': 'F8', 75 | 'F9': 'F9', 76 | 'F10': 'F10', 77 | 'F11': 'F11', 78 | 'F12': 'F12', 79 | 'F13': 'F13', 80 | 'F14': 'F14', 81 | 'F15': 'F15', 82 | 'F16': 'F16', 83 | 'F17': 'F17', 84 | 'F18': 'F18', 85 | 'F19': 'F19', 86 | 'F20': 'F20', 87 | 'F21': 'F21', 88 | 'F22': 'F22', 89 | 'F23': 'F23', 90 | 'F24': 'F24', 91 | 'F25': 'F25', 92 | 'F26': 'F26', 93 | 'F27': 'F27', 94 | 'F28': 'F28', 95 | 'F29': 'F29', 96 | 'F30': 'F30', 97 | 'F31': 'F31', 98 | 'F32': 'F32', 99 | 'F33': 'F33', 100 | 'F34': 'F34', 101 | 'F35': 'F35', 102 | 'PrintScreen': 'PrintScreen', 103 | 'ScrollLock': 'ScrollLock', 104 | 'Pause': 'Pause', 105 | 106 | // 3.1.6. Media Keys 107 | 108 | 'BrowserBack': 'BrowserBack', 109 | 'BrowserFavorites': 'BrowserFavorites', 110 | 'BrowserForward': 'BrowserForward', 111 | 'BrowserHome': 'BrowserHome', 112 | 'BrowserRefresh': 'BrowserRefresh', 113 | 'BrowserSearch': 'BrowserSearch', 114 | 'BrowserStop': 'BrowserStop', 115 | 'Eject': 'Eject', 116 | 'LaunchApp1': 'LaunchMyComputer', 117 | 'LaunchApp2': 'LaunchCalendar', 118 | 'LaunchMail': 'LaunchMail', 119 | 'MediaPlayPause': 'MediaPlay', 120 | 'MediaStop': 'MediaStop', 121 | 'MediaTrackNext': 'MediaTrackNext', 122 | 'MediaTrackPrevious': 'MediaTrackPrevious', 123 | 'Power': 'Power', 124 | 'Sleep': 'Sleep', 125 | 'AudioVolumeDown': 'AudioVolumeDown', 126 | 'AudioVolumeMute': 'AudioVolumeMute', 127 | 'AudioVolumeUp': 'AudioVolumeUp', 128 | 'WakeUp': 'WakeUp', 129 | }; 130 | -------------------------------------------------------------------------------- /tests/assertions.js: -------------------------------------------------------------------------------- 1 | // noVNC specific assertions 2 | chai.use(function (_chai, utils) { 3 | function _equal(a, b) { 4 | return a === b; 5 | } 6 | _chai.Assertion.addMethod('displayed', function (targetData, cmp=_equal) { 7 | const obj = this._obj; 8 | const ctx = obj._target.getContext('2d'); 9 | const data = ctx.getImageData(0, 0, obj._target.width, obj._target.height).data; 10 | const len = data.length; 11 | new chai.Assertion(len).to.be.equal(targetData.length, "unexpected display size"); 12 | let same = true; 13 | for (let i = 0; i < len; i++) { 14 | if (!cmp(data[i], targetData[i])) { 15 | same = false; 16 | break; 17 | } 18 | } 19 | if (!same) { 20 | // eslint-disable-next-line no-console 21 | console.log("expected data: %o, actual data: %o", targetData, data); 22 | } 23 | this.assert(same, 24 | "expected #{this} to have displayed the image #{exp}, but instead it displayed #{act}", 25 | "expected #{this} not to have displayed the image #{act}", 26 | targetData, 27 | data); 28 | }); 29 | 30 | _chai.Assertion.addMethod('sent', function (targetData) { 31 | const obj = this._obj; 32 | obj.inspect = () => { 33 | const res = { _websocket: obj._websocket, rQi: obj._rQi, _rQ: new Uint8Array(obj._rQ.buffer, 0, obj._rQlen), 34 | _sQ: new Uint8Array(obj._sQ.buffer, 0, obj._sQlen) }; 35 | res.prototype = obj; 36 | return res; 37 | }; 38 | const data = obj._websocket._getSentData(); 39 | let same = true; 40 | if (data.length != targetData.length) { 41 | same = false; 42 | } else { 43 | for (let i = 0; i < data.length; i++) { 44 | if (data[i] != targetData[i]) { 45 | same = false; 46 | break; 47 | } 48 | } 49 | } 50 | if (!same) { 51 | // eslint-disable-next-line no-console 52 | console.log("expected data: %o, actual data: %o", targetData, data); 53 | } 54 | this.assert(same, 55 | "expected #{this} to have sent the data #{exp}, but it actually sent #{act}", 56 | "expected #{this} not to have sent the data #{act}", 57 | Array.prototype.slice.call(targetData), 58 | Array.prototype.slice.call(data)); 59 | }); 60 | 61 | _chai.Assertion.addProperty('array', function () { 62 | utils.flag(this, 'array', true); 63 | }); 64 | 65 | _chai.Assertion.overwriteMethod('equal', function (_super) { 66 | return function assertArrayEqual(target) { 67 | if (utils.flag(this, 'array')) { 68 | const obj = this._obj; 69 | 70 | let same = true; 71 | 72 | if (utils.flag(this, 'deep')) { 73 | for (let i = 0; i < obj.length; i++) { 74 | if (!utils.eql(obj[i], target[i])) { 75 | same = false; 76 | break; 77 | } 78 | } 79 | 80 | this.assert(same, 81 | "expected #{this} to have elements deeply equal to #{exp}", 82 | "expected #{this} not to have elements deeply equal to #{exp}", 83 | Array.prototype.slice.call(target)); 84 | } else { 85 | for (let i = 0; i < obj.length; i++) { 86 | if (obj[i] != target[i]) { 87 | same = false; 88 | break; 89 | } 90 | } 91 | 92 | this.assert(same, 93 | "expected #{this} to have elements equal to #{exp}", 94 | "expected #{this} not to have elements equal to #{exp}", 95 | Array.prototype.slice.call(target)); 96 | } 97 | } else { 98 | _super.apply(this, arguments); 99 | } 100 | }; 101 | }); 102 | }); 103 | -------------------------------------------------------------------------------- /app/images/clipboard.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 54 | 57 | 58 | 60 | 61 | 63 | image/svg+xml 64 | 66 | 67 | 68 | 69 | 70 | 75 | 82 | 90 | 95 | 100 | 105 | 106 | 107 | --------------------------------------------------------------------------------