diff --git a/server/src/main/java/com/cloud/servlet/ConsoleProxyServlet.java b/server/src/main/java/com/cloud/servlet/ConsoleProxyServlet.java index ed73625d7e9..b735be811e2 100644 --- a/server/src/main/java/com/cloud/servlet/ConsoleProxyServlet.java +++ b/server/src/main/java/com/cloud/servlet/ConsoleProxyServlet.java @@ -484,7 +484,7 @@ public class ConsoleProxyServlet extends HttpServlet { if (param.getHypervHost() != null || !ConsoleProxyManager.NoVncConsoleDefault.value()) { sb.append("/ajax?token=" + encryptor.encryptObject(ConsoleProxyClientParam.class, param)); } else { - sb.append("/resource/noVNC/vnc_lite.html?port=" + ConsoleProxyManager.DEFAULT_NOVNC_PORT + "&token=" + sb.append("/resource/noVNC/vnc.html?port=" + ConsoleProxyManager.DEFAULT_NOVNC_PORT + "&token=" + encryptor.encryptObject(ConsoleProxyClientParam.class, param)); } diff --git a/systemvm/agent/noVNC/.eslintignore b/systemvm/agent/noVNC/.eslintignore deleted file mode 100644 index d38162800ad..00000000000 --- a/systemvm/agent/noVNC/.eslintignore +++ /dev/null @@ -1 +0,0 @@ -**/xtscancodes.js diff --git a/systemvm/agent/noVNC/.eslintrc b/systemvm/agent/noVNC/.eslintrc deleted file mode 100644 index 900a7186efc..00000000000 --- a/systemvm/agent/noVNC/.eslintrc +++ /dev/null @@ -1,48 +0,0 @@ -{ - "env": { - "browser": true, - "es6": true - }, - "parserOptions": { - "sourceType": "module" - }, - "extends": "eslint:recommended", - "rules": { - // Unsafe or confusing stuff that we forbid - - "no-unused-vars": ["error", { "vars": "all", "args": "none", "ignoreRestSiblings": true }], - "no-constant-condition": ["error", { "checkLoops": false }], - "no-var": "error", - "no-useless-constructor": "error", - "object-shorthand": ["error", "methods", { "avoidQuotes": true }], - "prefer-arrow-callback": "error", - "arrow-body-style": ["error", "as-needed", { "requireReturnForObjectLiteral": false } ], - "arrow-parens": ["error", "as-needed", { "requireForBlockBody": true }], - "arrow-spacing": ["error"], - "no-confusing-arrow": ["error", { "allowParens": true }], - - // Enforced coding style - - "brace-style": ["error", "1tbs", { "allowSingleLine": true }], - "indent": ["error", 4, { "SwitchCase": 1, - "CallExpression": { "arguments": "first" }, - "ArrayExpression": "first", - "ObjectExpression": "first", - "ignoreComments": true }], - "comma-spacing": ["error"], - "comma-style": ["error"], - "curly": ["error", "multi-line"], - "func-call-spacing": ["error"], - "func-names": ["error"], - "func-style": ["error", "declaration", { "allowArrowFunctions": true }], - "key-spacing": ["error"], - "keyword-spacing": ["error"], - "no-trailing-spaces": ["error"], - "semi": ["error"], - "space-before-blocks": ["error"], - "space-before-function-paren": ["error", { "anonymous": "always", - "named": "never", - "asyncArrow": "always" }], - "switch-colon-spacing": ["error"], - } -} diff --git a/systemvm/agent/noVNC/.github/ISSUE_TEMPLATE/bug_report.md b/systemvm/agent/noVNC/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 94ac6f8dc6e..00000000000 --- a/systemvm/agent/noVNC/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve - ---- - -**Describe the bug** -A clear and concise description of what the bug is. - -**To Reproduce** -Steps to reproduce the behavior: -1. Go to '...' -2. Click on '....' -3. Scroll down to '....' -4. See error - -**Expected behavior** -A clear and concise description of what you expected to happen. - -**Screenshots** -If applicable, add screenshots to help explain your problem. - -**Client (please complete the following information):** - - OS: [e.g. iOS] - - Browser: [e.g. chrome, safari] - - Browser version: [e.g. 22] - -**Server (please complete the following information):** - - noVNC version: [e.g. 1.0.0 or git commit id] - - VNC server: [e.g. QEMU, TigerVNC] - - WebSocket proxy: [e.g. websockify] - -**Additional context** -Add any other context about the problem here. diff --git a/systemvm/agent/noVNC/.github/ISSUE_TEMPLATE/feature_request.md b/systemvm/agent/noVNC/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 066b2d920a2..00000000000 --- a/systemvm/agent/noVNC/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for this project - ---- - -**Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] - -**Describe the solution you'd like** -A clear and concise description of what you want to happen. - -**Describe alternatives you've considered** -A clear and concise description of any alternative solutions or features you've considered. - -**Additional context** -Add any other context or screenshots about the feature request here. diff --git a/systemvm/agent/noVNC/.gitignore b/systemvm/agent/noVNC/.gitignore deleted file mode 100644 index c178dbab43d..00000000000 --- a/systemvm/agent/noVNC/.gitignore +++ /dev/null @@ -1,12 +0,0 @@ -*.pyc -*.o -tests/data_*.js -utils/rebind.so -utils/websockify -/node_modules -/build -/lib -recordings -*.swp -*~ -noVNC-*.tgz diff --git a/systemvm/agent/noVNC/.gitmodules b/systemvm/agent/noVNC/.gitmodules deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/systemvm/agent/noVNC/.travis.yml b/systemvm/agent/noVNC/.travis.yml deleted file mode 100644 index 78b521a80ba..00000000000 --- a/systemvm/agent/noVNC/.travis.yml +++ /dev/null @@ -1,58 +0,0 @@ -language: node_js -sudo: false -cache: - directories: - - node_modules -node_js: - - 6 -env: - matrix: - - TEST_BROWSER_NAME=chrome TEST_BROWSER_OS='Windows 10' -# FIXME Skip tests in Linux since Sauce Labs browser versions are ancient. -# - TEST_BROWSER_NAME=chrome TEST_BROWSER_OS='Linux' - - TEST_BROWSER_NAME=chrome TEST_BROWSER_OS='OS X 10.11' - - TEST_BROWSER_NAME=firefox TEST_BROWSER_OS='Windows 10' -# - TEST_BROWSER_NAME=firefox TEST_BROWSER_OS='Linux' - - TEST_BROWSER_NAME=firefox TEST_BROWSER_OS='OS X 10.11' - - TEST_BROWSER_NAME='internet explorer' TEST_BROWSER_OS='Windows 10' - - TEST_BROWSER_NAME='internet explorer' TEST_BROWSER_OS='Windows 7' - - TEST_BROWSER_NAME=microsoftedge TEST_BROWSER_OS='Windows 10' - - TEST_BROWSER_NAME=safari TEST_BROWSER_OS='OS X 10.13' -before_script: npm install -g karma-cli -addons: - sauce_connect: - username: "directxman12" - jwt: - secure: "d3ekMYslpn6R4f0ajtRMt9SUFmNGDiItHpqaXC5T4KI0KMEsxgvEOfJot5PiFFJWg1DSpJZH6oaW2UxGZ3duJLZrXIEd/JePY8a6NtT35BNgiDPgcp+eu2Bu3rhrSNg7/HEsD1ma+JeUTnv18Ai5oMFfCCQJx2J6osIxyl/ZVxA=" -stages: -- lint -- test -- name: deploy - if: tag is PRESENT -jobs: - include: - - stage: lint - env: - addons: - before_script: - script: npm run lint - - - env: - addons: - before_script: - script: git ls-tree --name-only -r HEAD | grep -E "[.](html|css)$" | xargs ./utils/validate - - stage: deploy - env: - addons: - script: skip - before_script: skip - deploy: - provider: npm - email: ossman@cendio.se - api_key: - secure: "Qq2Mi9xQawO2zlAigzshzMu2QMHvu1IaN9l0ZIivE99wHJj7eS5f4miJ9wB+/mWRRgb3E8uj9ZRV24+Oc36drlBTU9sz+lHhH0uFMfAIseceK64wZV9sLAZm472fmPp2xdUeTCCqPaRy7g1XBqiJ0LyZvEFLsRijqcLjPBF+b8w=" - on: - tags: true - repo: novnc/noVNC - - diff --git a/systemvm/agent/noVNC/AUTHORS b/systemvm/agent/noVNC/AUTHORS deleted file mode 100644 index dec0e89329a..00000000000 --- a/systemvm/agent/noVNC/AUTHORS +++ /dev/null @@ -1,13 +0,0 @@ -maintainers: -- Joel Martin (@kanaka) -- Solly Ross (@directxman12) -- Samuel Mannehed for Cendio AB (@samhed) -- Pierre Ossman for Cendio AB (@CendioOssman) -maintainersEmeritus: -- @astrand -contributors: -# There are a bunch of people that should be here. -# If you want to be on this list, feel free send a PR -# to add yourself. -- jalf -- NTT corp. diff --git a/systemvm/agent/noVNC/LICENSE.txt b/systemvm/agent/noVNC/LICENSE.txt deleted file mode 100644 index 20f3eb025fe..00000000000 --- a/systemvm/agent/noVNC/LICENSE.txt +++ /dev/null @@ -1,68 +0,0 @@ -noVNC is Copyright (C) 2018 The noVNC Authors -(./AUTHORS) - -The noVNC core library files are licensed under the MPL 2.0 (Mozilla -Public License 2.0). The noVNC core library is composed of the -Javascript code necessary for full noVNC operation. This includes (but -is not limited to): - - core/**/*.js - app/*.js - test/playback.js - -The HTML, CSS, font and images files that included with the noVNC -source distibution (or repository) are not considered part of the -noVNC core library and are licensed under more permissive licenses. -The intent is to allow easy integration of noVNC into existing web -sites and web applications. - -The HTML, CSS, font and image files are licensed as follows: - - *.html : 2-Clause BSD license - - app/styles/*.css : 2-Clause BSD license - - app/styles/Orbitron* : SIL Open Font License 1.1 - (Copyright 2009 Matt McInerney) - - app/images/ : Creative Commons Attribution-ShareAlike - http://creativecommons.org/licenses/by-sa/3.0/ - -Some portions of noVNC are copyright to their individual authors. -Please refer to the individual source files and/or to the noVNC commit -history: https://github.com/novnc/noVNC/commits/master - -The are several files and projects that have been incorporated into -the noVNC core library. Here is a list of those files and the original -licenses (all MPL 2.0 compatible): - - core/base64.js : MPL 2.0 - - core/des.js : Various BSD style licenses - - vendor/pako/ : MIT - - vendor/browser-es-module-loader/src/ : MIT - - vendor/browser-es-module-loader/dist/ : Various BSD style licenses - - vendor/promise.js : MIT - -Any other files not mentioned above are typically marked with -a copyright/license header at the top of the file. The default noVNC -license is MPL-2.0. - -The following license texts are included: - - docs/LICENSE.MPL-2.0 - docs/LICENSE.OFL-1.1 - docs/LICENSE.BSD-3-Clause (New BSD) - docs/LICENSE.BSD-2-Clause (Simplified BSD / FreeBSD) - vendor/pako/LICENSE (MIT) - -Or alternatively the license texts may be found here: - - http://www.mozilla.org/MPL/2.0/ - http://scripts.sil.org/OFL - http://en.wikipedia.org/wiki/BSD_licenses - https://opensource.org/licenses/MIT diff --git a/systemvm/agent/noVNC/README.md b/systemvm/agent/noVNC/README.md deleted file mode 100644 index 566b8e4f5af..00000000000 --- a/systemvm/agent/noVNC/README.md +++ /dev/null @@ -1,152 +0,0 @@ -## noVNC: HTML VNC Client Library and Application - -[![Build Status](https://travis-ci.org/novnc/noVNC.svg?branch=master)](https://travis-ci.org/novnc/noVNC) - -### Description - -noVNC is both a HTML VNC client JavaScript library and an application built on -top of that library. noVNC runs well in any modern browser including mobile -browsers (iOS and Android). - -Many companies, projects and products have integrated noVNC including -[OpenStack](http://www.openstack.org), -[OpenNebula](http://opennebula.org/), -[LibVNCServer](http://libvncserver.sourceforge.net), and -[ThinLinc](https://cendio.com/thinlinc). See -[the Projects and Companies wiki page](https://github.com/novnc/noVNC/wiki/Projects-and-companies-using-noVNC) -for a more complete list with additional info and links. - -### Table of Contents - -- [News/help/contact](#newshelpcontact) -- [Features](#features) -- [Screenshots](#screenshots) -- [Browser Requirements](#browser-requirements) -- [Server Requirements](#server-requirements) -- [Quick Start](#quick-start) -- [Integration and Deployment](#integration-and-deployment) -- [Authors/Contributors](#authorscontributors) - -### News/help/contact - -The project website is found at [novnc.com](http://novnc.com). -Notable commits, announcements and news are posted to -[@noVNC](http://www.twitter.com/noVNC). - -If you are a noVNC developer/integrator/user (or want to be) please join the -[noVNC discussion group](https://groups.google.com/forum/?fromgroups#!forum/novnc). - -Bugs and feature requests can be submitted via -[github issues](https://github.com/novnc/noVNC/issues). If you have questions -about using noVNC then please first use the -[discussion group](https://groups.google.com/forum/?fromgroups#!forum/novnc). -We also have a [wiki](https://github.com/novnc/noVNC/wiki/) with lots of -helpful information. - -If you are looking for a place to start contributing to noVNC, a good place to -start would be the issues that are marked as -["patchwelcome"](https://github.com/novnc/noVNC/issues?labels=patchwelcome). -Please check our -[contribution guide](https://github.com/novnc/noVNC/wiki/Contributing) though. - -If you want to show appreciation for noVNC you could donate to a great non- -profits such as: -[Compassion International](http://www.compassion.com/), -[SIL](http://www.sil.org), -[Habitat for Humanity](http://www.habitat.org), -[Electronic Frontier Foundation](https://www.eff.org/), -[Against Malaria Foundation](http://www.againstmalaria.com/), -[Nothing But Nets](http://www.nothingbutnets.net/), etc. -Please tweet [@noVNC](http://www.twitter.com/noVNC) if you do. - - -### Features - -* Supports all modern browsers including mobile (iOS, Android) -* Supported VNC encodings: raw, copyrect, rre, hextile, tight, tightPNG -* Supports scaling, clipping and resizing the desktop -* Local cursor rendering -* Clipboard copy/paste -* Translations -* Licensed mainly under the [MPL 2.0](http://www.mozilla.org/MPL/2.0/), see - [the license document](LICENSE.txt) for details - -### Screenshots - -Running in Firefox before and after connecting: - -  - - -See more screenshots -[here](http://novnc.com/screenshots.html). - - -### Browser Requirements - -noVNC uses many modern web technologies so a formal requirement list is -not available. However these are the minimum versions we are currently -aware of: - -* Chrome 49, Firefox 44, Safari 10, Opera 36, IE 11, Edge 12 - - -### Server Requirements - -noVNC follows the standard VNC protocol, but unlike other VNC clients it does -require WebSockets support. Many servers include support (e.g. -[x11vnc/libvncserver](http://libvncserver.sourceforge.net/), -[QEMU](http://www.qemu.org/), and -[MobileVNC](http://www.smartlab.at/mobilevnc/)), but for the others you need to -use a WebSockets to TCP socket proxy. noVNC has a sister project -[websockify](https://github.com/novnc/websockify) that provides a simple such -proxy. - - -### Quick Start - -* Use the launch script to automatically download and start websockify, which - includes a mini-webserver and the WebSockets proxy. The `--vnc` option is - used to specify the location of a running VNC server: - - `./utils/launch.sh --vnc localhost:5901` - -* Point your browser to the cut-and-paste URL that is output by the launch - script. Hit the Connect button, enter a password if the VNC server has one - configured, and enjoy! - - -### Integration and Deployment - -Please see our other documents for how to integrate noVNC in your own software, -or deploying the noVNC application in production environments: - -* [Embedding](docs/EMBEDDING.md) - For the noVNC application -* [Library](docs/LIBRARY.md) - For the noVNC JavaScript library - - -### Authors/Contributors - -See [AUTHORS](AUTHORS) for a (full-ish) list of authors. If you're not on -that list and you think you should be, feel free to send a PR to fix that. - -* Core team: - * [Joel Martin](https://github.com/kanaka) - * [Samuel Mannehed](https://github.com/samhed) (Cendio) - * [Peter Åstrand](https://github.com/astrand) (Cendio) - * [Solly Ross](https://github.com/DirectXMan12) (Red Hat / OpenStack) - * [Pierre Ossman](https://github.com/CendioOssman) (Cendio) - -* Notable contributions: - * UI and Icons : Pierre Ossman, Chris Gordon - * Original Logo : Michael Sersen - * tight encoding : Michael Tinglof (Mercuri.ca) - -* Included libraries: - * base64 : Martijn Pieters (Digital Creations 2), Samuel Sieb (sieb.net) - * DES : Dave Zimmerman (Widget Workshop), Jef Poskanzer (ACME Labs) - * Pako : Vitaly Puzrin (https://github.com/nodeca/pako) - -Do you want to be on this list? Check out our -[contribution guide](https://github.com/novnc/noVNC/wiki/Contributing) and -start hacking! diff --git a/systemvm/agent/noVNC/VERSION b/systemvm/agent/noVNC/VERSION deleted file mode 100644 index 9084fa2f716..00000000000 --- a/systemvm/agent/noVNC/VERSION +++ /dev/null @@ -1 +0,0 @@ -1.1.0 diff --git a/systemvm/agent/noVNC/app/error-handler.js b/systemvm/agent/noVNC/app/error-handler.js index 8e294166fc6..81a6cba8e6e 100644 --- a/systemvm/agent/noVNC/app/error-handler.js +++ b/systemvm/agent/noVNC/app/error-handler.js @@ -1,3 +1,11 @@ +/* + * noVNC: HTML5 VNC client + * Copyright (C) 2019 The noVNC Authors + * Licensed under MPL 2.0 (see LICENSE.txt) + * + * See README.md for usage and integration instructions. + */ + // NB: this should *not* be included as a module until we have // native support in the browsers, so that our error handler // can catch script-loading errors. diff --git a/systemvm/agent/noVNC/app/images/alt.png b/systemvm/agent/noVNC/app/images/alt.png new file mode 100644 index 00000000000..2d5e35e7fb8 Binary files /dev/null and b/systemvm/agent/noVNC/app/images/alt.png differ diff --git a/systemvm/agent/noVNC/app/images/alt.svg b/systemvm/agent/noVNC/app/images/alt.svg deleted file mode 100644 index e5bb4612ece..00000000000 --- a/systemvm/agent/noVNC/app/images/alt.svg +++ /dev/null @@ -1,92 +0,0 @@ - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - diff --git a/systemvm/agent/noVNC/app/images/clipboard.png b/systemvm/agent/noVNC/app/images/clipboard.png new file mode 100644 index 00000000000..d7fe507eb79 Binary files /dev/null and b/systemvm/agent/noVNC/app/images/clipboard.png differ diff --git a/systemvm/agent/noVNC/app/images/clipboard.svg b/systemvm/agent/noVNC/app/images/clipboard.svg deleted file mode 100644 index 79af2752504..00000000000 --- a/systemvm/agent/noVNC/app/images/clipboard.svg +++ /dev/null @@ -1,106 +0,0 @@ - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - diff --git a/systemvm/agent/noVNC/app/images/connect.png b/systemvm/agent/noVNC/app/images/connect.png new file mode 100644 index 00000000000..abdbe420d5c Binary files /dev/null and b/systemvm/agent/noVNC/app/images/connect.png differ diff --git a/systemvm/agent/noVNC/app/images/connect.svg b/systemvm/agent/noVNC/app/images/connect.svg deleted file mode 100644 index 56cde414b46..00000000000 --- a/systemvm/agent/noVNC/app/images/connect.svg +++ /dev/null @@ -1,96 +0,0 @@ - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - diff --git a/systemvm/agent/noVNC/app/images/ctrl.png b/systemvm/agent/noVNC/app/images/ctrl.png new file mode 100644 index 00000000000..fbc9e1314ec Binary files /dev/null and b/systemvm/agent/noVNC/app/images/ctrl.png differ diff --git a/systemvm/agent/noVNC/app/images/ctrl.svg b/systemvm/agent/noVNC/app/images/ctrl.svg deleted file mode 100644 index 856e9395829..00000000000 --- a/systemvm/agent/noVNC/app/images/ctrl.svg +++ /dev/null @@ -1,96 +0,0 @@ - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - diff --git a/systemvm/agent/noVNC/app/images/ctrlaltdel.png b/systemvm/agent/noVNC/app/images/ctrlaltdel.png new file mode 100644 index 00000000000..dd0497819d4 Binary files /dev/null and b/systemvm/agent/noVNC/app/images/ctrlaltdel.png differ diff --git a/systemvm/agent/noVNC/app/images/ctrlaltdel.svg b/systemvm/agent/noVNC/app/images/ctrlaltdel.svg deleted file mode 100644 index d7744ea31da..00000000000 --- a/systemvm/agent/noVNC/app/images/ctrlaltdel.svg +++ /dev/null @@ -1,100 +0,0 @@ - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - diff --git a/systemvm/agent/noVNC/app/images/disconnect.png b/systemvm/agent/noVNC/app/images/disconnect.png new file mode 100644 index 00000000000..97eb1a98821 Binary files /dev/null and b/systemvm/agent/noVNC/app/images/disconnect.png differ diff --git a/systemvm/agent/noVNC/app/images/disconnect.svg b/systemvm/agent/noVNC/app/images/disconnect.svg deleted file mode 100644 index 6be7d187657..00000000000 --- a/systemvm/agent/noVNC/app/images/disconnect.svg +++ /dev/null @@ -1,94 +0,0 @@ - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - diff --git a/systemvm/agent/noVNC/app/images/drag.png b/systemvm/agent/noVNC/app/images/drag.png new file mode 100644 index 00000000000..f006202a793 Binary files /dev/null and b/systemvm/agent/noVNC/app/images/drag.png differ diff --git a/systemvm/agent/noVNC/app/images/drag.svg b/systemvm/agent/noVNC/app/images/drag.svg deleted file mode 100644 index 139caf947cd..00000000000 --- a/systemvm/agent/noVNC/app/images/drag.svg +++ /dev/null @@ -1,76 +0,0 @@ - - - - - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/systemvm/agent/noVNC/app/images/error.png b/systemvm/agent/noVNC/app/images/error.png new file mode 100644 index 00000000000..04e78e1a4ee Binary files /dev/null and b/systemvm/agent/noVNC/app/images/error.png differ diff --git a/systemvm/agent/noVNC/app/images/error.svg b/systemvm/agent/noVNC/app/images/error.svg deleted file mode 100644 index 8356d3f1374..00000000000 --- a/systemvm/agent/noVNC/app/images/error.svg +++ /dev/null @@ -1,81 +0,0 @@ - - - - - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/systemvm/agent/noVNC/app/images/esc.png b/systemvm/agent/noVNC/app/images/esc.png new file mode 100644 index 00000000000..e1b1bfa2a9c Binary files /dev/null and b/systemvm/agent/noVNC/app/images/esc.png differ diff --git a/systemvm/agent/noVNC/app/images/esc.svg b/systemvm/agent/noVNC/app/images/esc.svg deleted file mode 100644 index 830152b5f93..00000000000 --- a/systemvm/agent/noVNC/app/images/esc.svg +++ /dev/null @@ -1,92 +0,0 @@ - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - diff --git a/systemvm/agent/noVNC/app/images/expander.png b/systemvm/agent/noVNC/app/images/expander.png new file mode 100644 index 00000000000..27937219e9b Binary files /dev/null and b/systemvm/agent/noVNC/app/images/expander.png differ diff --git a/systemvm/agent/noVNC/app/images/expander.svg b/systemvm/agent/noVNC/app/images/expander.svg deleted file mode 100644 index e1635358be9..00000000000 --- a/systemvm/agent/noVNC/app/images/expander.svg +++ /dev/null @@ -1,69 +0,0 @@ - - - - - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/systemvm/agent/noVNC/app/images/fullscreen.png b/systemvm/agent/noVNC/app/images/fullscreen.png new file mode 100644 index 00000000000..a7f26344292 Binary files /dev/null and b/systemvm/agent/noVNC/app/images/fullscreen.png differ diff --git a/systemvm/agent/noVNC/app/images/fullscreen.svg b/systemvm/agent/noVNC/app/images/fullscreen.svg deleted file mode 100644 index 29bd05da14d..00000000000 --- a/systemvm/agent/noVNC/app/images/fullscreen.svg +++ /dev/null @@ -1,93 +0,0 @@ - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - diff --git a/systemvm/agent/noVNC/app/images/handle.png b/systemvm/agent/noVNC/app/images/handle.png new file mode 100644 index 00000000000..cf0e5d55f1c Binary files /dev/null and b/systemvm/agent/noVNC/app/images/handle.png differ diff --git a/systemvm/agent/noVNC/app/images/handle.svg b/systemvm/agent/noVNC/app/images/handle.svg deleted file mode 100644 index 4a7a126f9d3..00000000000 --- a/systemvm/agent/noVNC/app/images/handle.svg +++ /dev/null @@ -1,82 +0,0 @@ - - - - - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/systemvm/agent/noVNC/app/images/handle_bg.png b/systemvm/agent/noVNC/app/images/handle_bg.png new file mode 100644 index 00000000000..efb0357ac82 Binary files /dev/null and b/systemvm/agent/noVNC/app/images/handle_bg.png differ diff --git a/systemvm/agent/noVNC/app/images/handle_bg.svg b/systemvm/agent/noVNC/app/images/handle_bg.svg deleted file mode 100644 index 7579c42cb78..00000000000 --- a/systemvm/agent/noVNC/app/images/handle_bg.svg +++ /dev/null @@ -1,172 +0,0 @@ - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - diff --git a/systemvm/agent/noVNC/app/images/info.png b/systemvm/agent/noVNC/app/images/info.png new file mode 100644 index 00000000000..21e358981af Binary files /dev/null and b/systemvm/agent/noVNC/app/images/info.png differ diff --git a/systemvm/agent/noVNC/app/images/info.svg b/systemvm/agent/noVNC/app/images/info.svg deleted file mode 100644 index 557b772ff72..00000000000 --- a/systemvm/agent/noVNC/app/images/info.svg +++ /dev/null @@ -1,81 +0,0 @@ - - - - - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/systemvm/agent/noVNC/app/images/keyboard.png b/systemvm/agent/noVNC/app/images/keyboard.png new file mode 100644 index 00000000000..fe8056bda1e Binary files /dev/null and b/systemvm/agent/noVNC/app/images/keyboard.png differ diff --git a/systemvm/agent/noVNC/app/images/keyboard.svg b/systemvm/agent/noVNC/app/images/keyboard.svg deleted file mode 100644 index 137b350ab5d..00000000000 --- a/systemvm/agent/noVNC/app/images/keyboard.svg +++ /dev/null @@ -1,88 +0,0 @@ - - - - - - - - - - - - image/svg+xml - - - - - - - - - - diff --git a/systemvm/agent/noVNC/app/images/mouse_left.svg b/systemvm/agent/noVNC/app/images/mouse_left.svg deleted file mode 100644 index ce4cca41c79..00000000000 --- a/systemvm/agent/noVNC/app/images/mouse_left.svg +++ /dev/null @@ -1,92 +0,0 @@ - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - diff --git a/systemvm/agent/noVNC/app/images/mouse_middle.svg b/systemvm/agent/noVNC/app/images/mouse_middle.svg deleted file mode 100644 index 6603425cb3e..00000000000 --- a/systemvm/agent/noVNC/app/images/mouse_middle.svg +++ /dev/null @@ -1,92 +0,0 @@ - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - diff --git a/systemvm/agent/noVNC/app/images/mouse_none.svg b/systemvm/agent/noVNC/app/images/mouse_none.svg deleted file mode 100644 index 3e0f838a77a..00000000000 --- a/systemvm/agent/noVNC/app/images/mouse_none.svg +++ /dev/null @@ -1,92 +0,0 @@ - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - diff --git a/systemvm/agent/noVNC/app/images/mouse_right.svg b/systemvm/agent/noVNC/app/images/mouse_right.svg deleted file mode 100644 index f4bad76797c..00000000000 --- a/systemvm/agent/noVNC/app/images/mouse_right.svg +++ /dev/null @@ -1,92 +0,0 @@ - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - diff --git a/systemvm/agent/noVNC/app/images/power.png b/systemvm/agent/noVNC/app/images/power.png new file mode 100644 index 00000000000..7f871358d1f Binary files /dev/null and b/systemvm/agent/noVNC/app/images/power.png differ diff --git a/systemvm/agent/noVNC/app/images/power.svg b/systemvm/agent/noVNC/app/images/power.svg deleted file mode 100644 index 4925d3e8eba..00000000000 --- a/systemvm/agent/noVNC/app/images/power.svg +++ /dev/null @@ -1,87 +0,0 @@ - - - - - - - - - - - - image/svg+xml - - - - - - - - - - diff --git a/systemvm/agent/noVNC/app/images/settings.png b/systemvm/agent/noVNC/app/images/settings.png new file mode 100644 index 00000000000..8d0c0d4e865 Binary files /dev/null and b/systemvm/agent/noVNC/app/images/settings.png differ diff --git a/systemvm/agent/noVNC/app/images/settings.svg b/systemvm/agent/noVNC/app/images/settings.svg deleted file mode 100644 index dbb2e80a5b3..00000000000 --- a/systemvm/agent/noVNC/app/images/settings.svg +++ /dev/null @@ -1,76 +0,0 @@ - - - - - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/systemvm/agent/noVNC/app/images/tab.png b/systemvm/agent/noVNC/app/images/tab.png new file mode 100644 index 00000000000..6adb7819d1e Binary files /dev/null and b/systemvm/agent/noVNC/app/images/tab.png differ diff --git a/systemvm/agent/noVNC/app/images/tab.svg b/systemvm/agent/noVNC/app/images/tab.svg deleted file mode 100644 index 1ccb3229cdd..00000000000 --- a/systemvm/agent/noVNC/app/images/tab.svg +++ /dev/null @@ -1,86 +0,0 @@ - - - - - - - - - - - - image/svg+xml - - - - - - - - - - diff --git a/systemvm/agent/noVNC/app/images/toggleextrakeys.png b/systemvm/agent/noVNC/app/images/toggleextrakeys.png new file mode 100644 index 00000000000..0f80f6d5ba3 Binary files /dev/null and b/systemvm/agent/noVNC/app/images/toggleextrakeys.png differ diff --git a/systemvm/agent/noVNC/app/images/toggleextrakeys.svg b/systemvm/agent/noVNC/app/images/toggleextrakeys.svg deleted file mode 100644 index b578c0d4062..00000000000 --- a/systemvm/agent/noVNC/app/images/toggleextrakeys.svg +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - diff --git a/systemvm/agent/noVNC/app/images/warning.png b/systemvm/agent/noVNC/app/images/warning.png new file mode 100644 index 00000000000..e9dd550c132 Binary files /dev/null and b/systemvm/agent/noVNC/app/images/warning.png differ diff --git a/systemvm/agent/noVNC/app/images/warning.svg b/systemvm/agent/noVNC/app/images/warning.svg deleted file mode 100644 index 7114f9b1235..00000000000 --- a/systemvm/agent/noVNC/app/images/warning.svg +++ /dev/null @@ -1,81 +0,0 @@ - - - - - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/systemvm/agent/noVNC/app/images/windows.png b/systemvm/agent/noVNC/app/images/windows.png new file mode 100644 index 00000000000..036e1ae1130 Binary files /dev/null and b/systemvm/agent/noVNC/app/images/windows.png differ diff --git a/systemvm/agent/noVNC/app/images/windows.svg b/systemvm/agent/noVNC/app/images/windows.svg deleted file mode 100644 index 270405c7ff2..00000000000 --- a/systemvm/agent/noVNC/app/images/windows.svg +++ /dev/null @@ -1,85 +0,0 @@ - - - -image/svg+xml - - - - - - - - - - \ No newline at end of file diff --git a/systemvm/agent/noVNC/app/locale/README b/systemvm/agent/noVNC/app/locale/README new file mode 100644 index 00000000000..ca4f548bcbe --- /dev/null +++ b/systemvm/agent/noVNC/app/locale/README @@ -0,0 +1 @@ +DO NOT MODIFY THE FILES IN THIS FOLDER, THEY ARE AUTOMATICALLY GENERATED FROM THE PO-FILES. diff --git a/systemvm/agent/noVNC/app/locale/ja.json b/systemvm/agent/noVNC/app/locale/ja.json new file mode 100644 index 00000000000..e5fe3401fcb --- /dev/null +++ b/systemvm/agent/noVNC/app/locale/ja.json @@ -0,0 +1,73 @@ +{ + "Connecting...": "接続しています...", + "Disconnecting...": "切断しています...", + "Reconnecting...": "再接続しています...", + "Internal error": "内部エラー", + "Must set host": "ホストを設定する必要があります", + "Connected (encrypted) to ": "接続しました (暗号化済み): ", + "Connected (unencrypted) to ": "接続しました (暗号化されていません): ", + "Something went wrong, connection is closed": "何かが問題で、接続が閉じられました", + "Failed to connect to server": "サーバーへの接続に失敗しました", + "Disconnected": "切断しました", + "New connection has been rejected with reason: ": "新規接続は次の理由で拒否されました: ", + "New connection has been rejected": "新規接続は拒否されました", + "Password is required": "パスワードが必要です", + "noVNC encountered an error:": "noVNC でエラーが発生しました:", + "Hide/Show the control bar": "コントロールバーを隠す/表示する", + "Move/Drag Viewport": "ビューポートを移動/ドラッグ", + "viewport drag": "ビューポートをドラッグ", + "Active Mouse Button": "アクティブなマウスボタン", + "No mousebutton": "マウスボタンなし", + "Left mousebutton": "左マウスボタン", + "Middle mousebutton": "中マウスボタン", + "Right mousebutton": "右マウスボタン", + "Keyboard": "キーボード", + "Show Keyboard": "キーボードを表示", + "Extra keys": "追加キー", + "Show Extra Keys": "追加キーを表示", + "Ctrl": "Ctrl", + "Toggle Ctrl": "Ctrl キーを切り替え", + "Alt": "Alt", + "Toggle Alt": "Alt キーを切り替え", + "Toggle Windows": "Windows キーを切り替え", + "Windows": "Windows", + "Send Tab": "Tab キーを送信", + "Tab": "Tab", + "Esc": "Esc", + "Send Escape": "Escape キーを送信", + "Ctrl+Alt+Del": "Ctrl+Alt+Del", + "Send Ctrl-Alt-Del": "Ctrl-Alt-Del を送信", + "Shutdown/Reboot": "シャットダウン/再起動", + "Shutdown/Reboot...": "シャットダウン/再起動...", + "Power": "電源", + "Shutdown": "シャットダウン", + "Reboot": "再起動", + "Reset": "リセット", + "Clipboard": "クリップボード", + "Clear": "クリア", + "Fullscreen": "全画面表示", + "Settings": "設定", + "Shared Mode": "共有モード", + "View Only": "表示のみ", + "Clip to Window": "ウィンドウにクリップ", + "Scaling Mode:": "スケーリングモード:", + "None": "なし", + "Local Scaling": "ローカルスケーリング", + "Remote Resizing": "リモートでリサイズ", + "Advanced": "高度", + "Repeater ID:": "リピーター ID:", + "WebSocket": "WebSocket", + "Encrypt": "暗号化", + "Host:": "ホスト:", + "Port:": "ポート:", + "Path:": "パス:", + "Automatic Reconnect": "自動再接続", + "Reconnect Delay (ms):": "再接続する遅延 (ミリ秒):", + "Show Dot when No Cursor": "カーソルがないときにドットを表示", + "Logging:": "ロギング:", + "Disconnect": "切断", + "Connect": "接続", + "Password:": "パスワード:", + "Send Password": "パスワードを送信", + "Cancel": "キャンセル" +} \ No newline at end of file diff --git a/systemvm/agent/noVNC/app/locale/sv.json b/systemvm/agent/noVNC/app/locale/sv.json index d49ea540d93..e46df45b585 100644 --- a/systemvm/agent/noVNC/app/locale/sv.json +++ b/systemvm/agent/noVNC/app/locale/sv.json @@ -11,16 +11,11 @@ "Disconnected": "Frånkopplad", "New connection has been rejected with reason: ": "Ny anslutning har blivit nekad med följande skäl: ", "New connection has been rejected": "Ny anslutning har blivit nekad", - "Password is required": "Lösenord krävs", + "Credentials are required": "Användaruppgifter krävs", "noVNC encountered an error:": "noVNC stötte på ett problem:", "Hide/Show the control bar": "Göm/Visa kontrollbaren", + "Drag": "Dra", "Move/Drag Viewport": "Flytta/Dra Vyn", - "viewport drag": "dra vy", - "Active Mouse Button": "Aktiv musknapp", - "No mousebutton": "Ingen musknapp", - "Left mousebutton": "Vänster musknapp", - "Middle mousebutton": "Mitten-musknapp", - "Right mousebutton": "Höger musknapp", "Keyboard": "Tangentbord", "Show Keyboard": "Visa Tangentbord", "Extra keys": "Extraknappar", @@ -55,6 +50,8 @@ "Local Scaling": "Lokal Skalning", "Remote Resizing": "Ändra Storlek", "Advanced": "Avancerat", + "Quality:": "Kvalitet:", + "Compression level:": "Kompressionsnivå:", "Repeater ID:": "Repeater-ID:", "WebSocket": "WebSocket", "Encrypt": "Kryptera", @@ -65,9 +62,11 @@ "Reconnect Delay (ms):": "Fördröjning (ms):", "Show Dot when No Cursor": "Visa prick när ingen muspekare finns", "Logging:": "Loggning:", + "Version:": "Version:", "Disconnect": "Koppla från", "Connect": "Anslut", + "Username:": "Användarnamn:", "Password:": "Lösenord:", - "Send Password": "Skicka lösenord", + "Send Credentials": "Skicka Användaruppgifter", "Cancel": "Avbryt" } \ No newline at end of file diff --git a/systemvm/agent/noVNC/app/locale/zh_CN.json b/systemvm/agent/noVNC/app/locale/zh_CN.json index b66995620ff..f0aea9af3e1 100644 --- a/systemvm/agent/noVNC/app/locale/zh_CN.json +++ b/systemvm/agent/noVNC/app/locale/zh_CN.json @@ -1,19 +1,19 @@ { - "Connecting...": "链接中...", - "Disconnecting...": "正在中断连接...", - "Reconnecting...": "重新链接中...", + "Connecting...": "连接中...", + "Disconnecting...": "正在断开连接...", + "Reconnecting...": "重新连接中...", "Internal error": "内部错误", "Must set host": "请提供主机名", - "Connected (encrypted) to ": "已加密链接到", - "Connected (unencrypted) to ": "未加密链接到", - "Something went wrong, connection is closed": "发生错误,链接已关闭", - "Failed to connect to server": "无法链接到服务器", - "Disconnected": "链接已中断", - "New connection has been rejected with reason: ": "链接被拒绝,原因:", - "New connection has been rejected": "链接被拒绝", + "Connected (encrypted) to ": "已连接到(加密)", + "Connected (unencrypted) to ": "已连接到(未加密)", + "Something went wrong, connection is closed": "发生错误,连接已关闭", + "Failed to connect to server": "无法连接到服务器", + "Disconnected": "已断开连接", + "New connection has been rejected with reason: ": "连接被拒绝,原因:", + "New connection has been rejected": "连接被拒绝", "Password is required": "请提供密码", "noVNC encountered an error:": "noVNC 遇到一个错误:", - "Hide/Show the control bar": "显示/隐藏控制列", + "Hide/Show the control bar": "显示/隐藏控制栏", "Move/Drag Viewport": "拖放显示范围", "viewport drag": "显示范围拖放", "Active Mouse Button": "启动鼠标按鍵", @@ -43,10 +43,10 @@ "Reset": "重置", "Clipboard": "剪贴板", "Clear": "清除", - "Fullscreen": "全屏幕", + "Fullscreen": "全屏", "Settings": "设置", "Shared Mode": "分享模式", - "View Only": "仅检视", + "View Only": "仅查看", "Clip to Window": "限制/裁切窗口大小", "Scaling Mode:": "缩放模式:", "None": "无", @@ -59,11 +59,11 @@ "Host:": "主机:", "Port:": "端口:", "Path:": "路径:", - "Automatic Reconnect": "自动重新链接", - "Reconnect Delay (ms):": "重新链接间隔 (ms):", + "Automatic Reconnect": "自动重新连接", + "Reconnect Delay (ms):": "重新连接间隔 (ms):", "Logging:": "日志级别:", - "Disconnect": "终端链接", - "Connect": "链接", + "Disconnect": "中断连接", + "Connect": "连接", "Password:": "密码:", "Cancel": "取消" } \ No newline at end of file diff --git a/systemvm/agent/noVNC/app/styles/base.css b/systemvm/agent/noVNC/app/styles/base.css index 3ca9894dc74..fd78b79c772 100644 --- a/systemvm/agent/noVNC/app/styles/base.css +++ b/systemvm/agent/noVNC/app/styles/base.css @@ -1,6 +1,6 @@ /* * noVNC base CSS - * Copyright (C) 2018 The noVNC Authors + * Copyright (C) 2019 The noVNC Authors * noVNC is licensed under the MPL 2.0 (see LICENSE.txt) * This file is licensed under the 2-Clause BSD license (see LICENSE.txt). */ @@ -83,8 +83,20 @@ html { * ---------------------------------------- */ -input[type=input], input[type=password], input[type=number], -input:not([type]), textarea { +input:not([type]), +input[type=date], +input[type=datetime-local], +input[type=email], +input[type=month], +input[type=number], +input[type=password], +input[type=search], +input[type=tel], +input[type=text], +input[type=time], +input[type=url], +input[type=week], +textarea { /* Disable default rendering */ -webkit-appearance: none; -moz-appearance: none; @@ -98,7 +110,11 @@ input:not([type]), textarea { background: linear-gradient(to top, rgb(255, 255, 255) 80%, rgb(240, 240, 240)); } -input[type=button], input[type=submit], select { +input[type=button], +input[type=color], +input[type=reset], +input[type=submit], +select { /* Disable default rendering */ -webkit-appearance: none; -moz-appearance: none; @@ -116,7 +132,10 @@ input[type=button], input[type=submit], select { vertical-align: middle; } -input[type=button], input[type=submit] { +input[type=button], +input[type=color], +input[type=reset], +input[type=submit] { padding-left: 20px; padding-right: 20px; } @@ -126,35 +145,72 @@ option { background: white; } -input[type=input]:focus, input[type=password]:focus, -input:not([type]):focus, input[type=button]:focus, +input:not([type]):focus, +input[type=button]:focus, +input[type=color]:focus, +input[type=date]:focus, +input[type=datetime-local]:focus, +input[type=email]:focus, +input[type=month]:focus, +input[type=number]:focus, +input[type=password]:focus, +input[type=reset]:focus, +input[type=search]:focus, input[type=submit]:focus, -textarea:focus, select:focus { +input[type=tel]:focus, +input[type=text]:focus, +input[type=time]:focus, +input[type=url]:focus, +input[type=week]:focus, +select:focus, +textarea:focus { box-shadow: 0px 0px 3px rgba(74, 144, 217, 0.5); border-color: rgb(74, 144, 217); outline: none; } input[type=button]::-moz-focus-inner, +input[type=color]::-moz-focus-inner, +input[type=reset]::-moz-focus-inner, input[type=submit]::-moz-focus-inner { border: none; } -input[type=input]:disabled, input[type=password]:disabled, -input:not([type]):disabled, input[type=button]:disabled, -input[type=submit]:disabled, input[type=number]:disabled, -textarea:disabled, select:disabled { +input:not([type]):disabled, +input[type=button]:disabled, +input[type=color]:disabled, +input[type=date]:disabled, +input[type=datetime-local]:disabled, +input[type=email]:disabled, +input[type=month]:disabled, +input[type=number]:disabled, +input[type=password]:disabled, +input[type=reset]:disabled, +input[type=search]:disabled, +input[type=submit]:disabled, +input[type=tel]:disabled, +input[type=text]:disabled, +input[type=time]:disabled, +input[type=url]:disabled, +input[type=week]:disabled, +select:disabled, +textarea:disabled { color: rgb(128, 128, 128); background: rgb(240, 240, 240); } -input[type=button]:active, input[type=submit]:active, +input[type=button]:active, +input[type=color]:active, +input[type=reset]:active, +input[type=submit]:active, select:active { border-bottom-width: 1px; margin-top: 3px; } :root:not(.noVNC_touch) input[type=button]:hover:not(:disabled), +:root:not(.noVNC_touch) input[type=color]:hover:not(:disabled), +:root:not(.noVNC_touch) input[type=reset]:hover:not(:disabled), :root:not(.noVNC_touch) input[type=submit]:hover:not(:disabled), :root:not(.noVNC_touch) select:hover:not(:disabled) { background: linear-gradient(to top, rgb(255, 255, 255), rgb(250, 250, 250)); @@ -579,7 +635,7 @@ select:active { } /* Extra manual keys */ -:root:not(.noVNC_connected) #noVNC_extra_keys { +:root:not(.noVNC_connected) #noVNC_toggle_extra_keys_button { display: none; } @@ -631,6 +687,16 @@ select:active { width: 100px; } +/* Version */ + +.noVNC_version_wrapper { + font-size: small; +} + +.noVNC_version { + margin-left: 1rem; +} + /* Connection Controls */ :root:not(.noVNC_connected) #noVNC_disconnect_button { display: none; @@ -780,19 +846,23 @@ select:active { * ---------------------------------------- */ -#noVNC_password_dlg { +#noVNC_credentials_dlg { position: relative; transform: translateY(-50px); } -#noVNC_password_dlg.noVNC_open { +#noVNC_credentials_dlg.noVNC_open { transform: translateY(0); } -#noVNC_password_dlg ul { +#noVNC_credentials_dlg ul { list-style: none; margin: 0px; padding: 0px; } +.noVNC_hidden { + display: none; +} + /* ---------------------------------------- * Main Area diff --git a/systemvm/agent/noVNC/app/ui.js b/systemvm/agent/noVNC/app/ui.js index 13d1c015871..9158c33f317 100644 --- a/systemvm/agent/noVNC/app/ui.js +++ b/systemvm/agent/noVNC/app/ui.js @@ -1,6 +1,6 @@ /* * noVNC: HTML5 VNC client - * Copyright (C) 2018 The noVNC Authors + * Copyright (C) 2019 The noVNC Authors * Licensed under MPL 2.0 (see LICENSE.txt) * * See README.md for usage and integration instructions. @@ -8,7 +8,7 @@ import * as Log from '../core/util/logging.js'; import _, { l10n } from './localization.js'; -import { isTouchDevice, isSafari, isIOS, isAndroid, dragThreshold } +import { isTouchDevice, isSafari, hasScrollbarGutter, dragThreshold } from '../core/util/browser.js'; import { setCapture, getPointerEvent } from '../core/util/events.js'; import KeyTable from "../core/input/keysym.js"; @@ -17,6 +17,8 @@ import Keyboard from "../core/input/keyboard.js"; import RFB from "../core/rfb.js"; import * as WebUtil from "./webutil.js"; +const PAGE_TITLE = "noVNC"; + const UI = { connected: false, @@ -35,9 +37,11 @@ const UI = { lastKeyboardinput: null, defaultKeyboardinputLen: 100, - inhibit_reconnect: true, - reconnect_callback: null, - reconnect_password: null, + inhibitReconnect: true, + reconnectCallback: null, + reconnectPassword: null, + + fullScreen: false, prime() { return WebUtil.initSettings().then(() => { @@ -59,6 +63,17 @@ const UI = { // Translate the DOM l10n.translateDOM(); + WebUtil.fetchJSON('./package.json') + .then((packageInfo) => { + Array.from(document.getElementsByClassName('noVNC_version')).forEach(el => el.innerText = packageInfo.version); + }) + .catch((err) => { + Log.Error("Couldn't fetch package.json: " + err); + Array.from(document.getElementsByClassName('noVNC_version_wrapper')) + .concat(Array.from(document.getElementsByClassName('noVNC_version_separator'))) + .forEach(el => el.style.display = 'none'); + }); + // Adapt the interface for touch screen devices if (isTouchDevice) { document.documentElement.classList.add("noVNC_touch"); @@ -145,10 +160,13 @@ const UI = { /* Populate the controls if defaults are provided in the URL */ UI.initSetting('host', window.location.hostname); UI.initSetting('port', port); + UI.initSetting('token', window.location.token); UI.initSetting('encrypt', (window.location.protocol === "https:")); UI.initSetting('view_clip', false); UI.initSetting('resize', 'off'); - UI.initSetting('shared', false); + UI.initSetting('quality', 6); + UI.initSetting('compression', 2); + UI.initSetting('shared', true); UI.initSetting('view_only', false); UI.initSetting('show_dot', false); UI.initSetting('path', 'websockify'); @@ -219,14 +237,6 @@ const UI = { }, addTouchSpecificHandlers() { - document.getElementById("noVNC_mouse_button0") - .addEventListener('click', () => UI.setMouseButton(1)); - document.getElementById("noVNC_mouse_button1") - .addEventListener('click', () => UI.setMouseButton(2)); - document.getElementById("noVNC_mouse_button2") - .addEventListener('click', () => UI.setMouseButton(4)); - document.getElementById("noVNC_mouse_button4") - .addEventListener('click', () => UI.setMouseButton(0)); document.getElementById("noVNC_keyboard_button") .addEventListener('click', UI.toggleVirtualKeyboard); @@ -303,17 +313,17 @@ const UI = { document.getElementById("noVNC_cancel_reconnect_button") .addEventListener('click', UI.cancelReconnect); - document.getElementById("noVNC_password_button") - .addEventListener('click', UI.setPassword); + document.getElementById("noVNC_credentials_button") + .addEventListener('click', UI.setCredentials); }, addClipboardHandlers() { document.getElementById("noVNC_clipboard_button") .addEventListener('click', UI.toggleClipboardPanel); - document.getElementById("noVNC_clipboard_text") - .addEventListener('change', UI.clipboardSend); document.getElementById("noVNC_clipboard_clear_button") .addEventListener('click', UI.clipboardClear); + document.getElementById("noVNC_clipboard_send_button") + .addEventListener('click', UI.clipboardSend); }, // Add a call to save settings when the element changes, @@ -334,6 +344,10 @@ const UI = { UI.addSettingChangeHandler('resize'); UI.addSettingChangeHandler('resize', UI.applyResizeMode); UI.addSettingChangeHandler('resize', UI.updateViewClip); + UI.addSettingChangeHandler('quality'); + UI.addSettingChangeHandler('quality', UI.updateQuality); + UI.addSettingChangeHandler('compression'); + UI.addSettingChangeHandler('compression', UI.updateCompression); UI.addSettingChangeHandler('view_clip'); UI.addSettingChangeHandler('view_clip', UI.updateViewClip); UI.addSettingChangeHandler('shared'); @@ -375,25 +389,25 @@ const UI = { document.documentElement.classList.remove("noVNC_disconnecting"); document.documentElement.classList.remove("noVNC_reconnecting"); - const transition_elem = document.getElementById("noVNC_transition_text"); + const transitionElem = document.getElementById("noVNC_transition_text"); switch (state) { case 'init': break; case 'connecting': - transition_elem.textContent = _("Connecting..."); + transitionElem.textContent = _("Connecting..."); document.documentElement.classList.add("noVNC_connecting"); break; case 'connected': document.documentElement.classList.add("noVNC_connected"); break; case 'disconnecting': - transition_elem.textContent = _("Disconnecting..."); + transitionElem.textContent = _("Disconnecting..."); document.documentElement.classList.add("noVNC_disconnecting"); break; case 'disconnected': break; case 'reconnecting': - transition_elem.textContent = _("Reconnecting..."); + transitionElem.textContent = _("Reconnecting..."); document.documentElement.classList.add("noVNC_reconnecting"); break; default: @@ -411,7 +425,6 @@ const UI = { UI.disableSetting('port'); UI.disableSetting('path'); UI.disableSetting('repeaterID'); - UI.setMouseButton(1); // Hide the controlbar after 2 seconds UI.closeControlbarTimeout = setTimeout(UI.closeControlbar, 2000); @@ -426,38 +439,35 @@ const UI = { UI.keepControlbar(); } - // State change closes the password dialog - document.getElementById('noVNC_password_dlg') + // State change closes dialogs as they may not be relevant + // anymore + UI.closeAllPanels(); + document.getElementById('noVNC_credentials_dlg') .classList.remove('noVNC_open'); }, - showStatus(text, status_type, time) { + showStatus(text, statusType, time) { const statusElem = document.getElementById('noVNC_status'); - clearTimeout(UI.statusTimeout); - - if (typeof status_type === 'undefined') { - status_type = 'normal'; + if (typeof statusType === 'undefined') { + statusType = 'normal'; } // Don't overwrite more severe visible statuses and never // errors. Only shows the first error. - let visible_status_type = 'none'; if (statusElem.classList.contains("noVNC_open")) { if (statusElem.classList.contains("noVNC_status_error")) { - visible_status_type = 'error'; - } else if (statusElem.classList.contains("noVNC_status_warn")) { - visible_status_type = 'warn'; - } else { - visible_status_type = 'normal'; + return; + } + if (statusElem.classList.contains("noVNC_status_warn") && + statusType === 'normal') { + return; } } - if (visible_status_type === 'error' || - (visible_status_type === 'warn' && status_type === 'normal')) { - return; - } - switch (status_type) { + clearTimeout(UI.statusTimeout); + + switch (statusType) { case 'error': statusElem.classList.remove("noVNC_status_warn"); statusElem.classList.remove("noVNC_status_normal"); @@ -487,7 +497,7 @@ const UI = { } // Error messages do not timeout - if (status_type !== 'error') { + if (statusType !== 'error') { UI.statusTimeout = window.setTimeout(UI.hideStatus, time); } }, @@ -507,6 +517,13 @@ const UI = { }, idleControlbar() { + // Don't fade if a child of the control bar has focus + if (document.getElementById('noVNC_control_bar') + .contains(document.activeElement) && document.hasFocus()) { + UI.activateControlbar(); + return; + } + document.getElementById('noVNC_control_bar_anchor') .classList.add("noVNC_idle"); }, @@ -524,6 +541,7 @@ const UI = { UI.closeAllPanels(); document.getElementById('noVNC_control_bar') .classList.remove("noVNC_open"); + UI.rfb.focus(); }, toggleControlbar() { @@ -821,6 +839,8 @@ const UI = { UI.updateSetting('encrypt'); UI.updateSetting('view_clip'); UI.updateSetting('resize'); + UI.updateSetting('quality'); + UI.updateSetting('compression'); UI.updateSetting('shared'); UI.updateSetting('view_only'); UI.updateSetting('path'); @@ -927,6 +947,8 @@ const UI = { UI.closeClipboardPanel(); } else { UI.openClipboardPanel(); + setTimeout(() => document + .getElementById('noVNC_clipboard_text').focus(), 100); } }, @@ -938,14 +960,13 @@ const UI = { clipboardClear() { document.getElementById('noVNC_clipboard_text').value = ""; - UI.rfb.clipboardPasteFrom(""); }, clipboardSend() { const text = document.getElementById('noVNC_clipboard_text').value; - Log.Debug(">> UI.clipboardSend: " + text.substr(0, 40) + "..."); - UI.rfb.clipboardPasteFrom(text); - Log.Debug("<< UI.clipboardSend"); + UI.rfb.sendText(text); + UI.closeClipboardPanel(); + UI.focusOnConsole(); }, /* ------^------- @@ -974,10 +995,11 @@ const UI = { const host = UI.getSetting('host'); const port = UI.getSetting('port'); const path = UI.getSetting('path'); + const token = UI.getSetting('token') if (typeof password === 'undefined') { password = WebUtil.getConfigVar('password'); - UI.reconnect_password = password; + UI.reconnectPassword = password; } if (password === null) { @@ -992,7 +1014,6 @@ const UI = { return; } - UI.closeAllPanels(); UI.closeConnectPanel(); UI.updateVisualState('connecting'); @@ -1006,16 +1027,10 @@ const UI = { url += ':' + port; } url += '/' + path; - - var urlParams = new URLSearchParams(window.location.search); - var param = urlParams.get('token'); - if (param) { - url += "?token=" + param - } + url += '?token=' + token; UI.rfb = new RFB(document.getElementById('noVNC_container'), url, { shared: UI.getSetting('shared'), - showDotCursor: UI.getSetting('show_dot'), repeaterID: UI.getSetting('repeaterID'), credentials: { password: password } }); UI.rfb.addEventListener("connect", UI.connectFinished); @@ -1029,18 +1044,20 @@ const UI = { UI.rfb.clipViewport = UI.getSetting('view_clip'); UI.rfb.scaleViewport = UI.getSetting('resize') === 'scale'; UI.rfb.resizeSession = UI.getSetting('resize') === 'remote'; + UI.rfb.qualityLevel = parseInt(UI.getSetting('quality')); + UI.rfb.compressionLevel = parseInt(UI.getSetting('compression')); + UI.rfb.showDotCursor = UI.getSetting('show_dot'); UI.updateViewOnly(); // requires UI.rfb }, disconnect() { - UI.closeAllPanels(); UI.rfb.disconnect(); UI.connected = false; // Disable automatic reconnecting - UI.inhibit_reconnect = true; + UI.inhibitReconnect = true; UI.updateVisualState('disconnecting'); @@ -1048,20 +1065,20 @@ const UI = { }, reconnect() { - UI.reconnect_callback = null; + UI.reconnectCallback = null; // if reconnect has been disabled in the meantime, do nothing. - if (UI.inhibit_reconnect) { + if (UI.inhibitReconnect) { return; } - UI.connect(null, UI.reconnect_password); + UI.connect(null, UI.reconnectPassword); }, cancelReconnect() { - if (UI.reconnect_callback !== null) { - clearTimeout(UI.reconnect_callback); - UI.reconnect_callback = null; + if (UI.reconnectCallback !== null) { + clearTimeout(UI.reconnectCallback); + UI.reconnectCallback = null; } UI.updateVisualState('disconnected'); @@ -1072,13 +1089,13 @@ const UI = { connectFinished(e) { UI.connected = true; - UI.inhibit_reconnect = false; + UI.inhibitReconnect = false; let msg; if (UI.getSetting('encrypt')) { - msg = _("Connected (encrypted) to ") + UI.desktopName; + msg = _("Connected"); } else { - msg = _("Connected (unencrypted) to ") + UI.desktopName; + msg = _("Connected") } UI.showStatus(msg); UI.updateVisualState('connected'); @@ -1106,17 +1123,19 @@ const UI = { } else { UI.showStatus(_("Failed to connect to server"), 'error'); } - } else if (UI.getSetting('reconnect', false) === true && !UI.inhibit_reconnect) { + } else if (UI.getSetting('reconnect', false) === true && !UI.inhibitReconnect) { UI.updateVisualState('reconnecting'); const delay = parseInt(UI.getSetting('reconnect_delay')); - UI.reconnect_callback = setTimeout(UI.reconnect, delay); + UI.reconnectCallback = setTimeout(UI.reconnect, delay); return; } else { UI.updateVisualState('disconnected'); UI.showStatus(_("Disconnected"), 'normal'); } + document.title = PAGE_TITLE; + UI.openControlbar(); UI.openConnectPanel(); }, @@ -1143,27 +1162,46 @@ const UI = { credentials(e) { // FIXME: handle more types - document.getElementById('noVNC_password_dlg') + + document.getElementById("noVNC_username_block").classList.remove("noVNC_hidden"); + document.getElementById("noVNC_password_block").classList.remove("noVNC_hidden"); + + let inputFocus = "none"; + if (e.detail.types.indexOf("username") === -1) { + document.getElementById("noVNC_username_block").classList.add("noVNC_hidden"); + } else { + inputFocus = inputFocus === "none" ? "noVNC_username_input" : inputFocus; + } + if (e.detail.types.indexOf("password") === -1) { + document.getElementById("noVNC_password_block").classList.add("noVNC_hidden"); + } else { + inputFocus = inputFocus === "none" ? "noVNC_password_input" : inputFocus; + } + document.getElementById('noVNC_credentials_dlg') .classList.add('noVNC_open'); setTimeout(() => document - .getElementById('noVNC_password_input').focus(), 100); + .getElementById(inputFocus).focus(), 100); - Log.Warn("Server asked for a password"); - UI.showStatus(_("Password is required"), "warning"); + Log.Warn("Server asked for credentials"); + UI.showStatus(_("Credentials are required"), "warning"); }, - setPassword(e) { + setCredentials(e) { // Prevent actually submitting the form e.preventDefault(); - const inputElem = document.getElementById('noVNC_password_input'); - const password = inputElem.value; + let inputElemUsername = document.getElementById('noVNC_username_input'); + const username = inputElemUsername.value; + + let inputElemPassword = document.getElementById('noVNC_password_input'); + const password = inputElemPassword.value; // Clear the input after reading the password - inputElem.value = ""; - UI.rfb.sendCredentials({ password: password }); - UI.reconnect_password = password; - document.getElementById('noVNC_password_dlg') + inputElemPassword.value = ""; + + UI.rfb.sendCredentials({ username: username, password: password }); + UI.reconnectPassword = password; + document.getElementById('noVNC_credentials_dlg') .classList.remove('noVNC_open'); }, @@ -1174,38 +1212,14 @@ const UI = { * ------v------*/ toggleFullscreen() { - if (document.fullscreenElement || // alternative standard method - document.mozFullScreenElement || // currently working methods - document.webkitFullscreenElement || - document.msFullscreenElement) { - if (document.exitFullscreen) { - document.exitFullscreen(); - } else if (document.mozCancelFullScreen) { - document.mozCancelFullScreen(); - } else if (document.webkitExitFullscreen) { - document.webkitExitFullscreen(); - } else if (document.msExitFullscreen) { - document.msExitFullscreen(); - } - } else { - if (document.documentElement.requestFullscreen) { - document.documentElement.requestFullscreen(); - } else if (document.documentElement.mozRequestFullScreen) { - document.documentElement.mozRequestFullScreen(); - } else if (document.documentElement.webkitRequestFullscreen) { - document.documentElement.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT); - } else if (document.body.msRequestFullscreen) { - document.body.msRequestFullscreen(); - } - } - UI.updateFullscreenButton(); + this.fullScreen = !this.fullScreen + UI.rfb.scaleViewport = this.fullScreen + UI.updateFullscreenButton(this.fullScreen); + UI.focusOnConsole(); }, - updateFullscreenButton() { - if (document.fullscreenElement || // alternative standard method - document.mozFullScreenElement || // currently working methods - document.webkitFullscreenElement || - document.msFullscreenElement ) { + updateFullscreenButton(fullScreen) { + if (fullScreen) { document.getElementById('noVNC_fullscreen_button') .classList.add("noVNC_selected"); } else { @@ -1246,8 +1260,9 @@ const UI = { // Can't be clipping if viewport is scaled to fit UI.forceSetting('view_clip', false); UI.rfb.clipViewport = false; - } else if (isIOS() || isAndroid()) { - // iOS and Android usually have shit scrollbars + } else if (!hasScrollbarGutter) { + // Some platforms have scrollbars that are difficult + // to use in our case, so we always use our own panning UI.forceSetting('view_clip', true); UI.rfb.clipViewport = true; } else { @@ -1290,30 +1305,40 @@ const UI = { viewDragButton.classList.remove("noVNC_selected"); } - // Different behaviour for touch vs non-touch - // The button is disabled instead of hidden on touch devices - if (isTouchDevice) { + if (UI.rfb.clipViewport) { viewDragButton.classList.remove("noVNC_hidden"); - - if (UI.rfb.clipViewport) { - viewDragButton.disabled = false; - } else { - viewDragButton.disabled = true; - } } else { - viewDragButton.disabled = false; - - if (UI.rfb.clipViewport) { - viewDragButton.classList.remove("noVNC_hidden"); - } else { - viewDragButton.classList.add("noVNC_hidden"); - } + viewDragButton.classList.add("noVNC_hidden"); } }, /* ------^------- * /VIEWDRAG * ============== + * QUALITY + * ------v------*/ + + updateQuality() { + if (!UI.rfb) return; + + UI.rfb.qualityLevel = parseInt(UI.getSetting('quality')); + }, + +/* ------^------- + * /QUALITY + * ============== + * COMPRESSION + * ------v------*/ + + updateCompression() { + if (!UI.rfb) return; + + UI.rfb.compressionLevel = parseInt(UI.getSetting('compression')); + }, + +/* ------^------- + * /COMPRESSION + * ============== * KEYBOARD * ------v------*/ @@ -1508,20 +1533,20 @@ const UI = { }, sendEsc() { - UI.rfb.sendKey(KeyTable.XK_Escape, "Escape"); + UI.sendKey(KeyTable.XK_Escape, "Escape"); }, sendTab() { - UI.rfb.sendKey(KeyTable.XK_Tab); + UI.sendKey(KeyTable.XK_Tab, "Tab"); }, toggleCtrl() { const btn = document.getElementById('noVNC_toggle_ctrl_button'); if (btn.classList.contains("noVNC_selected")) { - UI.rfb.sendKey(KeyTable.XK_Control_L, "ControlLeft", false); + UI.sendKey(KeyTable.XK_Control_L, "ControlLeft", false); btn.classList.remove("noVNC_selected"); } else { - UI.rfb.sendKey(KeyTable.XK_Control_L, "ControlLeft", true); + UI.sendKey(KeyTable.XK_Control_L, "ControlLeft", true); btn.classList.add("noVNC_selected"); } }, @@ -1529,10 +1554,10 @@ const UI = { toggleWindows() { const btn = document.getElementById('noVNC_toggle_windows_button'); if (btn.classList.contains("noVNC_selected")) { - UI.rfb.sendKey(KeyTable.XK_Super_L, "MetaLeft", false); + UI.sendKey(KeyTable.XK_Super_L, "MetaLeft", false); btn.classList.remove("noVNC_selected"); } else { - UI.rfb.sendKey(KeyTable.XK_Super_L, "MetaLeft", true); + UI.sendKey(KeyTable.XK_Super_L, "MetaLeft", true); btn.classList.add("noVNC_selected"); } }, @@ -1540,16 +1565,42 @@ const UI = { toggleAlt() { const btn = document.getElementById('noVNC_toggle_alt_button'); if (btn.classList.contains("noVNC_selected")) { - UI.rfb.sendKey(KeyTable.XK_Alt_L, "AltLeft", false); + UI.sendKey(KeyTable.XK_Alt_L, "AltLeft", false); btn.classList.remove("noVNC_selected"); } else { - UI.rfb.sendKey(KeyTable.XK_Alt_L, "AltLeft", true); + UI.sendKey(KeyTable.XK_Alt_L, "AltLeft", true); btn.classList.add("noVNC_selected"); } }, sendCtrlAltDel() { UI.rfb.sendCtrlAltDel(); + // See below + UI.rfb.focus(); + UI.idleControlbar(); + }, + + // Move focus to the screen in order to be able to use the + // keyboard right after these extra keys. + // The exception is when a virtual keyboard is used, because + // if we focus the screen the virtual keyboard would be closed. + // In this case we focus our special virtual keyboard input + // element instead. + focusOnConsole() { + if (document.getElementById('noVNC_keyboard_button') + .classList.contains("noVNC_selected")) { + document.getElementById('noVNC_keyboardinput').focus(); + } else { + UI.rfb.focus(); + } + }, + + sendKey(keysym, code, down) { + UI.rfb.sendKey(keysym, code, down); + UI.focusOnConsole() + // fade out the controlbar to highlight that + // the focus has been moved to the screen + UI.idleControlbar(); }, /* ------^------- @@ -1558,24 +1609,6 @@ const UI = { * MISC * ------v------*/ - setMouseButton(num) { - const view_only = UI.rfb.viewOnly; - if (UI.rfb && !view_only) { - UI.rfb.touchButton = num; - } - - const blist = [0, 1, 2, 4]; - for (let b = 0; b < blist.length; b++) { - const button = document.getElementById('noVNC_mouse_button' + - blist[b]); - if (blist[b] === num && !view_only) { - button.classList.remove("noVNC_hidden"); - } else { - button.classList.add("noVNC_hidden"); - } - } - }, - updateViewOnly() { if (!UI.rfb) return; UI.rfb.viewOnly = UI.getSetting('view_only'); @@ -1586,14 +1619,14 @@ const UI = { .classList.add('noVNC_hidden'); document.getElementById('noVNC_toggle_extra_keys_button') .classList.add('noVNC_hidden'); - document.getElementById('noVNC_mouse_button' + UI.rfb.touchButton) + document.getElementById('noVNC_clipboard_button') .classList.add('noVNC_hidden'); } else { document.getElementById('noVNC_keyboard_button') .classList.remove('noVNC_hidden'); document.getElementById('noVNC_toggle_extra_keys_button') .classList.remove('noVNC_hidden'); - document.getElementById('noVNC_mouse_button' + UI.rfb.touchButton) + document.getElementById('noVNC_clipboard_button') .classList.remove('noVNC_hidden'); } }, @@ -1604,13 +1637,13 @@ const UI = { }, updateLogging() { - WebUtil.init_logging(UI.getSetting('logging')); + WebUtil.initLogging(UI.getSetting('logging')); }, updateDesktopName(e) { UI.desktopName = e.detail.name; // Display the desktop name in the document title - document.title = e.detail.name + " - noVNC"; + document.title = e.detail.name + " - " + PAGE_TITLE; }, bell(e) { @@ -1646,7 +1679,7 @@ const UI = { }; // Set up translations -const LINGUAS = ["cs", "de", "el", "es", "ko", "nl", "pl", "ru", "sv", "tr", "zh_CN", "zh_TW"]; +const LINGUAS = ["cs", "de", "el", "es", "ja", "ko", "nl", "pl", "ru", "sv", "tr", "zh_CN", "zh_TW"]; l10n.setup(LINGUAS); if (l10n.language === "en" || l10n.dictionary !== undefined) { UI.prime(); diff --git a/systemvm/agent/noVNC/app/webutil.js b/systemvm/agent/noVNC/app/webutil.js index 98e1d9e68da..568f0e24b4d 100644 --- a/systemvm/agent/noVNC/app/webutil.js +++ b/systemvm/agent/noVNC/app/webutil.js @@ -1,21 +1,21 @@ /* * noVNC: HTML5 VNC client - * Copyright (C) 2018 The noVNC Authors + * Copyright (C) 2019 The noVNC Authors * Licensed under MPL 2.0 (see LICENSE.txt) * * See README.md for usage and integration instructions. */ -import { init_logging as main_init_logging } from '../core/util/logging.js'; +import { initLogging as mainInitLogging } from '../core/util/logging.js'; // init log level reading the logging HTTP param -export function init_logging(level) { +export function initLogging(level) { "use strict"; if (typeof level !== "undefined") { - main_init_logging(level); + mainInitLogging(level); } else { const param = document.location.href.match(/logging=([A-Za-z0-9._-]*)/); - main_init_logging(param || undefined); + mainInitLogging(param || undefined); } } @@ -115,13 +115,8 @@ export function eraseCookie(name) { let settings = {}; export function initSettings() { - if (!window.chrome || !window.chrome.storage) { - settings = {}; - return Promise.resolve(); - } - - return new Promise(resolve => window.chrome.storage.sync.get(resolve)) - .then((cfg) => { settings = cfg; }); + settings = {}; + return Promise.resolve(); } // Update the settings cache, but do not write to permanent storage @@ -134,22 +129,13 @@ export function writeSetting(name, value) { "use strict"; if (settings[name] === value) return; settings[name] = value; - if (window.chrome && window.chrome.storage) { - window.chrome.storage.sync.set(settings); - } else { - localStorage.setItem(name, value); - } } export function readSetting(name, defaultValue) { "use strict"; let value; - if ((name in settings) || (window.chrome && window.chrome.storage)) { - value = settings[name]; - } else { - value = localStorage.getItem(name); - settings[name] = value; - } + value = settings[name]; + if (typeof value === "undefined") { value = null; } @@ -169,11 +155,6 @@ export function eraseSetting(name) { // between this delete and the next read, it could lead to an unexpected // value change. delete settings[name]; - if (window.chrome && window.chrome.storage) { - window.chrome.storage.sync.remove(name); - } else { - localStorage.removeItem(name); - } } export function injectParamIfMissing(path, param, value) { @@ -184,7 +165,7 @@ export function injectParamIfMissing(path, param, value) { const elem = document.createElement('a'); elem.href = path; - const param_eq = encodeURIComponent(param) + "="; + const paramEq = encodeURIComponent(param) + "="; let query; if (elem.search) { query = elem.search.slice(1).split('&'); @@ -192,8 +173,8 @@ export function injectParamIfMissing(path, param, value) { query = []; } - if (!query.some(v => v.startsWith(param_eq))) { - query.push(param_eq + encodeURIComponent(value)); + if (!query.some(v => v.startsWith(paramEq))) { + query.push(paramEq + encodeURIComponent(value)); elem.search = "?" + query.join("&"); } diff --git a/systemvm/agent/noVNC/core/base64.js b/systemvm/agent/noVNC/core/base64.js index 88e745466e5..db572c2db4c 100644 --- a/systemvm/agent/noVNC/core/base64.js +++ b/systemvm/agent/noVNC/core/base64.js @@ -57,12 +57,12 @@ export default { /* eslint-enable comma-spacing */ decode(data, offset = 0) { - let data_length = data.indexOf('=') - offset; - if (data_length < 0) { data_length = data.length - offset; } + let dataLength = data.indexOf('=') - offset; + if (dataLength < 0) { dataLength = data.length - offset; } /* Every four characters is 3 resulting numbers */ - const result_length = (data_length >> 2) * 3 + Math.floor((data_length % 4) / 1.5); - const result = new Array(result_length); + const resultLength = (dataLength >> 2) * 3 + Math.floor((dataLength % 4) / 1.5); + const result = new Array(resultLength); // Convert one by one. diff --git a/systemvm/agent/noVNC/core/decoders/copyrect.js b/systemvm/agent/noVNC/core/decoders/copyrect.js index a78ded754f9..9e6391a1733 100644 --- a/systemvm/agent/noVNC/core/decoders/copyrect.js +++ b/systemvm/agent/noVNC/core/decoders/copyrect.js @@ -1,8 +1,6 @@ /* * noVNC: HTML5 VNC client - * Copyright (C) 2012 Joel Martin - * Copyright (C) 2018 Samuel Mannehed for Cendio AB - * Copyright (C) 2018 Pierre Ossman for Cendio AB + * Copyright (C) 2019 The noVNC Authors * Licensed under MPL 2.0 (see LICENSE.txt) * * See README.md for usage and integration instructions. @@ -17,6 +15,11 @@ export default class CopyRectDecoder { let deltaX = sock.rQshift16(); let deltaY = sock.rQshift16(); + + if ((width === 0) || (height === 0)) { + return true; + } + display.copyImage(deltaX, deltaY, x, y, width, height); return true; diff --git a/systemvm/agent/noVNC/core/decoders/hextile.js b/systemvm/agent/noVNC/core/decoders/hextile.js index aa76d2f37b1..ac21eff03db 100644 --- a/systemvm/agent/noVNC/core/decoders/hextile.js +++ b/systemvm/agent/noVNC/core/decoders/hextile.js @@ -1,8 +1,6 @@ /* * noVNC: HTML5 VNC client - * Copyright (C) 2012 Joel Martin - * Copyright (C) 2018 Samuel Mannehed for Cendio AB - * Copyright (C) 2018 Pierre Ossman for Cendio AB + * Copyright (C) 2019 The noVNC Authors * Licensed under MPL 2.0 (see LICENSE.txt) * * See README.md for usage and integration instructions. @@ -15,14 +13,15 @@ export default class HextileDecoder { constructor() { this._tiles = 0; this._lastsubencoding = 0; + this._tileBuffer = new Uint8Array(16 * 16 * 4); } decodeRect(x, y, width, height, sock, display, depth) { if (this._tiles === 0) { - this._tiles_x = Math.ceil(width / 16); - this._tiles_y = Math.ceil(height / 16); - this._total_tiles = this._tiles_x * this._tiles_y; - this._tiles = this._total_tiles; + this._tilesX = Math.ceil(width / 16); + this._tilesY = Math.ceil(height / 16); + this._totalTiles = this._tilesX * this._tilesY; + this._tiles = this._totalTiles; } while (this._tiles > 0) { @@ -41,11 +40,11 @@ export default class HextileDecoder { subencoding + ")"); } - const curr_tile = this._total_tiles - this._tiles; - const tile_x = curr_tile % this._tiles_x; - const tile_y = Math.floor(curr_tile / this._tiles_x); - const tx = x + tile_x * 16; - const ty = y + tile_y * 16; + const currTile = this._totalTiles - this._tiles; + const tileX = currTile % this._tilesX; + const tileY = Math.floor(currTile / this._tilesX); + const tx = x + tileX * 16; + const ty = y + tileY * 16; const tw = Math.min(16, (x + width) - tx); const th = Math.min(16, (y + height) - ty); @@ -89,6 +88,11 @@ export default class HextileDecoder { display.fillRect(tx, ty, tw, th, this._background); } } else if (subencoding & 0x01) { // Raw + let pixels = tw * th; + // Max sure the image is fully opaque + for (let i = 0;i < pixels;i++) { + rQ[rQi + i * 4 + 3] = 255; + } display.blitImage(tx, ty, tw, th, rQ, rQi); rQi += bytes - 1; } else { @@ -101,7 +105,7 @@ export default class HextileDecoder { rQi += 4; } - display.startTile(tx, ty, tw, th, this._background); + this._startTile(tx, ty, tw, th, this._background); if (subencoding & 0x08) { // AnySubrects let subrects = rQ[rQi]; rQi++; @@ -124,10 +128,10 @@ export default class HextileDecoder { const sw = (wh >> 4) + 1; const sh = (wh & 0x0f) + 1; - display.subTile(sx, sy, sw, sh, color); + this._subTile(sx, sy, sw, sh, color); } } - display.finishTile(); + this._finishTile(display); } sock.rQi = rQi; this._lastsubencoding = subencoding; @@ -136,4 +140,52 @@ export default class HextileDecoder { return true; } + + // start updating a tile + _startTile(x, y, width, height, color) { + this._tileX = x; + this._tileY = y; + this._tileW = width; + this._tileH = height; + + const red = color[0]; + const green = color[1]; + const blue = color[2]; + + const data = this._tileBuffer; + for (let i = 0; i < width * height * 4; i += 4) { + data[i] = red; + data[i + 1] = green; + data[i + 2] = blue; + data[i + 3] = 255; + } + } + + // update sub-rectangle of the current tile + _subTile(x, y, w, h, color) { + const red = color[0]; + const green = color[1]; + const blue = color[2]; + const xend = x + w; + const yend = y + h; + + const data = this._tileBuffer; + const width = this._tileW; + for (let j = y; j < yend; j++) { + for (let i = x; i < xend; i++) { + const p = (i + (j * width)) * 4; + data[p] = red; + data[p + 1] = green; + data[p + 2] = blue; + data[p + 3] = 255; + } + } + } + + // draw the current tile to the screen + _finishTile(display) { + display.blitImage(this._tileX, this._tileY, + this._tileW, this._tileH, + this._tileBuffer, 0); + } } diff --git a/systemvm/agent/noVNC/core/decoders/raw.js b/systemvm/agent/noVNC/core/decoders/raw.js index f676e0d941f..e8ea178e8f5 100644 --- a/systemvm/agent/noVNC/core/decoders/raw.js +++ b/systemvm/agent/noVNC/core/decoders/raw.js @@ -1,8 +1,6 @@ /* * noVNC: HTML5 VNC client - * Copyright (C) 2012 Joel Martin - * Copyright (C) 2018 Samuel Mannehed for Cendio AB - * Copyright (C) 2018 Pierre Ossman for Cendio AB + * Copyright (C) 2019 The noVNC Authors * Licensed under MPL 2.0 (see LICENSE.txt) * * See README.md for usage and integration instructions. @@ -15,6 +13,10 @@ export default class RawDecoder { } decodeRect(x, y, width, height, sock, display, depth) { + if ((width === 0) || (height === 0)) { + return true; + } + if (this._lines === 0) { this._lines = height; } @@ -26,29 +28,35 @@ export default class RawDecoder { return false; } - const cur_y = y + (height - this._lines); - const curr_height = Math.min(this._lines, - Math.floor(sock.rQlen / bytesPerLine)); + const curY = y + (height - this._lines); + const currHeight = Math.min(this._lines, + Math.floor(sock.rQlen / bytesPerLine)); + const pixels = width * currHeight; + let data = sock.rQ; let index = sock.rQi; // Convert data if needed if (depth == 8) { - const pixels = width * curr_height; const newdata = new Uint8Array(pixels * 4); for (let i = 0; i < pixels; i++) { newdata[i * 4 + 0] = ((data[index + i] >> 0) & 0x3) * 255 / 3; newdata[i * 4 + 1] = ((data[index + i] >> 2) & 0x3) * 255 / 3; newdata[i * 4 + 2] = ((data[index + i] >> 4) & 0x3) * 255 / 3; - newdata[i * 4 + 4] = 0; + newdata[i * 4 + 3] = 255; } data = newdata; index = 0; } - display.blitImage(x, cur_y, width, curr_height, data, index); - sock.rQskipBytes(curr_height * bytesPerLine); - this._lines -= curr_height; + // Max sure the image is fully opaque + for (let i = 0; i < pixels; i++) { + data[i * 4 + 3] = 255; + } + + display.blitImage(x, curY, width, currHeight, data, index); + sock.rQskipBytes(currHeight * bytesPerLine); + this._lines -= currHeight; if (this._lines > 0) { return false; } diff --git a/systemvm/agent/noVNC/core/decoders/rre.js b/systemvm/agent/noVNC/core/decoders/rre.js index 57414a098ff..6219369d6b8 100644 --- a/systemvm/agent/noVNC/core/decoders/rre.js +++ b/systemvm/agent/noVNC/core/decoders/rre.js @@ -1,8 +1,6 @@ /* * noVNC: HTML5 VNC client - * Copyright (C) 2012 Joel Martin - * Copyright (C) 2018 Samuel Mannehed for Cendio AB - * Copyright (C) 2018 Pierre Ossman for Cendio AB + * Copyright (C) 2019 The noVNC Authors * Licensed under MPL 2.0 (see LICENSE.txt) * * See README.md for usage and integration instructions. diff --git a/systemvm/agent/noVNC/core/decoders/tight.js b/systemvm/agent/noVNC/core/decoders/tight.js index bcda04ce7f8..7952707c5e6 100644 --- a/systemvm/agent/noVNC/core/decoders/tight.js +++ b/systemvm/agent/noVNC/core/decoders/tight.js @@ -1,9 +1,7 @@ /* * noVNC: HTML5 VNC client - * Copyright (C) 2012 Joel Martin + * Copyright (C) 2019 The noVNC Authors * (c) 2012 Michael Tinglof, Joe Balaz, Les Piech (Mercuri.ca) - * Copyright (C) 2018 Samuel Mannehed for Cendio AB - * Copyright (C) 2018 Pierre Ossman for Cendio AB * Licensed under MPL 2.0 (see LICENSE.txt) * * See README.md for usage and integration instructions. @@ -58,7 +56,7 @@ export default class TightDecoder { } else if (this._ctl === 0x0A) { ret = this._pngRect(x, y, width, height, sock, display, depth); - } else if ((this._ctl & 0x80) == 0) { + } else if ((this._ctl & 0x08) == 0) { ret = this._basicRect(this._ctl, x, y, width, height, sock, display, depth); } else { @@ -82,7 +80,7 @@ export default class TightDecoder { const rQ = sock.rQ; display.fillRect(x, y, width, height, - [rQ[rQi + 2], rQ[rQi + 1], rQ[rQi]], false); + [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2]], false); sock.rQskipBytes(3); return true; @@ -94,7 +92,7 @@ export default class TightDecoder { return false; } - display.imageRect(x, y, "image/jpeg", data); + display.imageRect(x, y, width, height, "image/jpeg", data); return true; } @@ -150,6 +148,10 @@ export default class TightDecoder { const uncompressedSize = width * height * 3; let data; + if (uncompressedSize === 0) { + return true; + } + if (uncompressedSize < 12) { if (sock.rQwait("TIGHT", uncompressedSize)) { return false; @@ -162,13 +164,20 @@ export default class TightDecoder { return false; } - data = this._zlibs[streamId].inflate(data, true, uncompressedSize); - if (data.length != uncompressedSize) { - throw new Error("Incomplete zlib block"); - } + this._zlibs[streamId].setInput(data); + data = this._zlibs[streamId].inflate(uncompressedSize); + this._zlibs[streamId].setInput(null); } - display.blitRgbImage(x, y, width, height, data, 0, false); + let rgbx = new Uint8Array(width * height * 4); + for (let i = 0, j = 0; i < width * height * 4; i += 4, j += 3) { + rgbx[i] = data[j]; + rgbx[i + 1] = data[j + 1]; + rgbx[i + 2] = data[j + 2]; + rgbx[i + 3] = 255; // Alpha + } + + display.blitImage(x, y, width, height, rgbx, 0, false); return true; } @@ -198,6 +207,10 @@ export default class TightDecoder { let data; + if (uncompressedSize === 0) { + return true; + } + if (uncompressedSize < 12) { if (sock.rQwait("TIGHT", uncompressedSize)) { return false; @@ -210,10 +223,9 @@ export default class TightDecoder { return false; } - data = this._zlibs[streamId].inflate(data, true, uncompressedSize); - if (data.length != uncompressedSize) { - throw new Error("Incomplete zlib block"); - } + this._zlibs[streamId].setInput(data); + data = this._zlibs[streamId].inflate(uncompressedSize); + this._zlibs[streamId].setInput(null); } // Convert indexed (palette based) image data to RGB @@ -241,7 +253,7 @@ export default class TightDecoder { for (let b = 7; b >= 0; b--) { dp = (y * width + x * 8 + 7 - b) * 4; sp = (data[y * w + x] >> b & 1) * 3; - dest[dp] = palette[sp]; + dest[dp] = palette[sp]; dest[dp + 1] = palette[sp + 1]; dest[dp + 2] = palette[sp + 2]; dest[dp + 3] = 255; @@ -251,14 +263,14 @@ export default class TightDecoder { for (let b = 7; b >= 8 - width % 8; b--) { dp = (y * width + x * 8 + 7 - b) * 4; sp = (data[y * w + x] >> b & 1) * 3; - dest[dp] = palette[sp]; + dest[dp] = palette[sp]; dest[dp + 1] = palette[sp + 1]; dest[dp + 2] = palette[sp + 2]; dest[dp + 3] = 255; } } - display.blitRgbxImage(x, y, width, height, dest, 0, false); + display.blitImage(x, y, width, height, dest, 0, false); } _paletteRect(x, y, width, height, data, palette, display) { @@ -267,13 +279,13 @@ export default class TightDecoder { const total = width * height * 4; for (let i = 0, j = 0; i < total; i += 4, j++) { const sp = data[j] * 3; - dest[i] = palette[sp]; + dest[i] = palette[sp]; dest[i + 1] = palette[sp + 1]; dest[i + 2] = palette[sp + 2]; dest[i + 3] = 255; } - display.blitRgbxImage(x, y, width, height, dest, 0, false); + display.blitImage(x, y, width, height, dest, 0, false); } _gradientFilter(streamId, x, y, width, height, sock, display, depth) { diff --git a/systemvm/agent/noVNC/core/decoders/tightpng.js b/systemvm/agent/noVNC/core/decoders/tightpng.js index 7bbde3a43b5..82f492de8db 100644 --- a/systemvm/agent/noVNC/core/decoders/tightpng.js +++ b/systemvm/agent/noVNC/core/decoders/tightpng.js @@ -1,8 +1,6 @@ /* * noVNC: HTML5 VNC client - * Copyright (C) 2012 Joel Martin - * Copyright (C) 2018 Samuel Mannehed for Cendio AB - * Copyright (C) 2018 Pierre Ossman for Cendio AB + * Copyright (C) 2019 The noVNC Authors * Licensed under MPL 2.0 (see LICENSE.txt) * * See README.md for usage and integration instructions. @@ -18,7 +16,7 @@ export default class TightPNGDecoder extends TightDecoder { return false; } - display.imageRect(x, y, "image/png", data); + display.imageRect(x, y, width, height, "image/png", data); return true; } diff --git a/systemvm/agent/noVNC/core/deflator.js b/systemvm/agent/noVNC/core/deflator.js new file mode 100644 index 00000000000..fe2a8f7036b --- /dev/null +++ b/systemvm/agent/noVNC/core/deflator.js @@ -0,0 +1,85 @@ +/* + * noVNC: HTML5 VNC client + * Copyright (C) 2020 The noVNC Authors + * Licensed under MPL 2.0 (see LICENSE.txt) + * + * See README.md for usage and integration instructions. + */ + +import { deflateInit, deflate } from "../vendor/pako/lib/zlib/deflate.js"; +import { Z_FULL_FLUSH } from "../vendor/pako/lib/zlib/deflate.js"; +import ZStream from "../vendor/pako/lib/zlib/zstream.js"; + +export default class Deflator { + constructor() { + this.strm = new ZStream(); + this.chunkSize = 1024 * 10 * 10; + this.outputBuffer = new Uint8Array(this.chunkSize); + this.windowBits = 5; + + deflateInit(this.strm, this.windowBits); + } + + deflate(inData) { + /* eslint-disable camelcase */ + this.strm.input = inData; + this.strm.avail_in = this.strm.input.length; + this.strm.next_in = 0; + this.strm.output = this.outputBuffer; + this.strm.avail_out = this.chunkSize; + this.strm.next_out = 0; + /* eslint-enable camelcase */ + + let lastRet = deflate(this.strm, Z_FULL_FLUSH); + let outData = new Uint8Array(this.strm.output.buffer, 0, this.strm.next_out); + + if (lastRet < 0) { + throw new Error("zlib deflate failed"); + } + + if (this.strm.avail_in > 0) { + // Read chunks until done + + let chunks = [outData]; + let totalLen = outData.length; + do { + /* eslint-disable camelcase */ + this.strm.output = new Uint8Array(this.chunkSize); + this.strm.next_out = 0; + this.strm.avail_out = this.chunkSize; + /* eslint-enable camelcase */ + + lastRet = deflate(this.strm, Z_FULL_FLUSH); + + if (lastRet < 0) { + throw new Error("zlib deflate failed"); + } + + let chunk = new Uint8Array(this.strm.output.buffer, 0, this.strm.next_out); + totalLen += chunk.length; + chunks.push(chunk); + } while (this.strm.avail_in > 0); + + // Combine chunks into a single data + + let newData = new Uint8Array(totalLen); + let offset = 0; + + for (let i = 0; i < chunks.length; i++) { + newData.set(chunks[i], offset); + offset += chunks[i].length; + } + + outData = newData; + } + + /* eslint-disable camelcase */ + this.strm.input = null; + this.strm.avail_in = 0; + this.strm.next_in = 0; + /* eslint-enable camelcase */ + + return outData; + } + +} diff --git a/systemvm/agent/noVNC/core/display.js b/systemvm/agent/noVNC/core/display.js index 1528384d3af..8eaa8001cc6 100644 --- a/systemvm/agent/noVNC/core/display.js +++ b/systemvm/agent/noVNC/core/display.js @@ -1,6 +1,6 @@ /* * noVNC: HTML5 VNC client - * Copyright (C) 2018 The noVNC Authors + * Copyright (C) 2019 The noVNC Authors * Licensed under MPL 2.0 (see LICENSE.txt) * * See README.md for usage and integration instructions. @@ -9,24 +9,20 @@ import * as Log from './util/logging.js'; import Base64 from "./base64.js"; import { supportsImageMetadata } from './util/browser.js'; +import { toSigned32bit } from './util/int.js'; export default class Display { constructor(target) { this._drawCtx = null; - this._c_forceCanvas = false; this._renderQ = []; // queue drawing actions for in-oder rendering this._flushing = false; // the full frame buffer (logical canvas) size - this._fb_width = 0; - this._fb_height = 0; + this._fbWidth = 0; + this._fbHeight = 0; this._prevDrawStyle = ""; - this._tile = null; - this._tile16x16 = null; - this._tile_x = 0; - this._tile_y = 0; Log.Debug(">> Display.constructor"); @@ -60,21 +56,17 @@ export default class Display { Log.Debug("User Agent: " + navigator.userAgent); - this.clear(); - // Check canvas features if (!('createImageData' in this._drawCtx)) { throw new Error("Canvas does not support createImageData"); } - this._tile16x16 = this._drawCtx.createImageData(16, 16); Log.Debug("<< Display.constructor"); // ===== PROPERTIES ===== this._scale = 1.0; this._clipViewport = false; - this.logo = null; // ===== EVENT HANDLERS ===== @@ -98,11 +90,11 @@ export default class Display { } get width() { - return this._fb_width; + return this._fbWidth; } get height() { - return this._fb_height; + return this._fbHeight; } // ===== PUBLIC METHODS ===== @@ -125,15 +117,15 @@ export default class Display { if (deltaX < 0 && vp.x + deltaX < 0) { deltaX = -vp.x; } - if (vx2 + deltaX >= this._fb_width) { - deltaX -= vx2 + deltaX - this._fb_width + 1; + if (vx2 + deltaX >= this._fbWidth) { + deltaX -= vx2 + deltaX - this._fbWidth + 1; } if (vp.y + deltaY < 0) { deltaY = -vp.y; } - if (vy2 + deltaY >= this._fb_height) { - deltaY -= (vy2 + deltaY - this._fb_height + 1); + if (vy2 + deltaY >= this._fbHeight) { + deltaY -= (vy2 + deltaY - this._fbHeight + 1); } if (deltaX === 0 && deltaY === 0) { @@ -156,18 +148,18 @@ export default class Display { typeof(height) === "undefined") { Log.Debug("Setting viewport to full display region"); - width = this._fb_width; - height = this._fb_height; + width = this._fbWidth; + height = this._fbHeight; } width = Math.floor(width); height = Math.floor(height); - if (width > this._fb_width) { - width = this._fb_width; + if (width > this._fbWidth) { + width = this._fbWidth; } - if (height > this._fb_height) { - height = this._fb_height; + if (height > this._fbHeight) { + height = this._fbHeight; } const vp = this._viewportLoc; @@ -194,21 +186,21 @@ export default class Display { if (this._scale === 0) { return 0; } - return x / this._scale + this._viewportLoc.x; + return toSigned32bit(x / this._scale + this._viewportLoc.x); } absY(y) { if (this._scale === 0) { return 0; } - return y / this._scale + this._viewportLoc.y; + return toSigned32bit(y / this._scale + this._viewportLoc.y); } resize(width, height) { this._prevDrawStyle = ""; - this._fb_width = width; - this._fb_height = height; + this._fbWidth = width; + this._fbHeight = height; const canvas = this._backbuffer; if (canvas.width !== width || canvas.height !== height) { @@ -256,9 +248,9 @@ export default class Display { // Update the visible canvas with the contents of the // rendering canvas - flip(from_queue) { - if (this._renderQ.length !== 0 && !from_queue) { - this._renderQ_push({ + flip(fromQueue) { + if (this._renderQ.length !== 0 && !fromQueue) { + this._renderQPush({ 'type': 'flip' }); } else { @@ -302,17 +294,6 @@ export default class Display { } } - clear() { - if (this._logo) { - this.resize(this._logo.width, this._logo.height); - this.imageRect(0, 0, this._logo.type, this._logo.data); - } else { - this.resize(240, 20); - this._drawCtx.clearRect(0, 0, this._fb_width, this._fb_height); - } - this.flip(); - } - pending() { return this._renderQ.length > 0; } @@ -325,9 +306,9 @@ export default class Display { } } - fillRect(x, y, width, height, color, from_queue) { - if (this._renderQ.length !== 0 && !from_queue) { - this._renderQ_push({ + fillRect(x, y, width, height, color, fromQueue) { + if (this._renderQ.length !== 0 && !fromQueue) { + this._renderQPush({ 'type': 'fill', 'x': x, 'y': y, @@ -342,14 +323,14 @@ export default class Display { } } - copyImage(old_x, old_y, new_x, new_y, w, h, from_queue) { - if (this._renderQ.length !== 0 && !from_queue) { - this._renderQ_push({ + copyImage(oldX, oldY, newX, newY, w, h, fromQueue) { + if (this._renderQ.length !== 0 && !fromQueue) { + this._renderQPush({ 'type': 'copy', - 'old_x': old_x, - 'old_y': old_y, - 'x': new_x, - 'y': new_y, + 'oldX': oldX, + 'oldY': oldY, + 'x': newX, + 'y': newY, 'width': w, 'height': h, }); @@ -367,131 +348,60 @@ export default class Display { this._drawCtx.imageSmoothingEnabled = false; this._drawCtx.drawImage(this._backbuffer, - old_x, old_y, w, h, - new_x, new_y, w, h); - this._damage(new_x, new_y, w, h); + oldX, oldY, w, h, + newX, newY, w, h); + this._damage(newX, newY, w, h); } } - imageRect(x, y, mime, arr) { + imageRect(x, y, width, height, mime, arr) { + /* The internal logic cannot handle empty images, so bail early */ + if ((width === 0) || (height === 0)) { + return; + } + const img = new Image(); img.src = "data: " + mime + ";base64," + Base64.encode(arr); - this._renderQ_push({ + + this._renderQPush({ 'type': 'img', 'img': img, 'x': x, - 'y': y + 'y': y, + 'width': width, + 'height': height }); } - // start updating a tile - startTile(x, y, width, height, color) { - this._tile_x = x; - this._tile_y = y; - if (width === 16 && height === 16) { - this._tile = this._tile16x16; - } else { - this._tile = this._drawCtx.createImageData(width, height); - } - - const red = color[2]; - const green = color[1]; - const blue = color[0]; - - const data = this._tile.data; - for (let i = 0; i < width * height * 4; i += 4) { - data[i] = red; - data[i + 1] = green; - data[i + 2] = blue; - data[i + 3] = 255; - } - } - - // update sub-rectangle of the current tile - subTile(x, y, w, h, color) { - const red = color[2]; - const green = color[1]; - const blue = color[0]; - const xend = x + w; - const yend = y + h; - - const data = this._tile.data; - const width = this._tile.width; - for (let j = y; j < yend; j++) { - for (let i = x; i < xend; i++) { - const p = (i + (j * width)) * 4; - data[p] = red; - data[p + 1] = green; - data[p + 2] = blue; - data[p + 3] = 255; - } - } - } - - // draw the current tile to the screen - finishTile() { - this._drawCtx.putImageData(this._tile, this._tile_x, this._tile_y); - this._damage(this._tile_x, this._tile_y, - this._tile.width, this._tile.height); - } - - blitImage(x, y, width, height, arr, offset, from_queue) { - if (this._renderQ.length !== 0 && !from_queue) { + blitImage(x, y, width, height, arr, offset, fromQueue) { + if (this._renderQ.length !== 0 && !fromQueue) { // NB(directxman12): it's technically more performant here to use preallocated arrays, // but it's a lot of extra work for not a lot of payoff -- if we're using the render queue, // this probably isn't getting called *nearly* as much - const new_arr = new Uint8Array(width * height * 4); - new_arr.set(new Uint8Array(arr.buffer, 0, new_arr.length)); - this._renderQ_push({ + const newArr = new Uint8Array(width * height * 4); + newArr.set(new Uint8Array(arr.buffer, 0, newArr.length)); + this._renderQPush({ 'type': 'blit', - 'data': new_arr, + 'data': newArr, 'x': x, 'y': y, 'width': width, 'height': height, }); } else { - this._bgrxImageData(x, y, width, height, arr, offset); - } - } - - blitRgbImage(x, y, width, height, arr, offset, from_queue) { - if (this._renderQ.length !== 0 && !from_queue) { - // NB(directxman12): it's technically more performant here to use preallocated arrays, - // but it's a lot of extra work for not a lot of payoff -- if we're using the render queue, - // this probably isn't getting called *nearly* as much - const new_arr = new Uint8Array(width * height * 3); - new_arr.set(new Uint8Array(arr.buffer, 0, new_arr.length)); - this._renderQ_push({ - 'type': 'blitRgb', - 'data': new_arr, - 'x': x, - 'y': y, - 'width': width, - 'height': height, - }); - } else { - this._rgbImageData(x, y, width, height, arr, offset); - } - } - - blitRgbxImage(x, y, width, height, arr, offset, from_queue) { - if (this._renderQ.length !== 0 && !from_queue) { - // NB(directxman12): it's technically more performant here to use preallocated arrays, - // but it's a lot of extra work for not a lot of payoff -- if we're using the render queue, - // this probably isn't getting called *nearly* as much - const new_arr = new Uint8Array(width * height * 4); - new_arr.set(new Uint8Array(arr.buffer, 0, new_arr.length)); - this._renderQ_push({ - 'type': 'blitRgbx', - 'data': new_arr, - 'x': x, - 'y': y, - 'width': width, - 'height': height, - }); - } else { - this._rgbxImageData(x, y, width, height, arr, offset); + // NB(directxman12): arr must be an Type Array view + let data = new Uint8ClampedArray(arr.buffer, + arr.byteOffset + offset, + width * height * 4); + let img; + if (supportsImageMetadata) { + img = new ImageData(data, width, height); + } else { + img = this._drawCtx.createImageData(width, height); + img.data.set(data); + } + this._drawCtx.putImageData(img, x, y); + this._damage(x, y, width, height); } } @@ -543,69 +453,30 @@ export default class Display { } _setFillColor(color) { - const newStyle = 'rgb(' + color[2] + ',' + color[1] + ',' + color[0] + ')'; + const newStyle = 'rgb(' + color[0] + ',' + color[1] + ',' + color[2] + ')'; if (newStyle !== this._prevDrawStyle) { this._drawCtx.fillStyle = newStyle; this._prevDrawStyle = newStyle; } } - _rgbImageData(x, y, width, height, arr, offset) { - const img = this._drawCtx.createImageData(width, height); - const data = img.data; - for (let i = 0, j = offset; i < width * height * 4; i += 4, j += 3) { - data[i] = arr[j]; - data[i + 1] = arr[j + 1]; - data[i + 2] = arr[j + 2]; - data[i + 3] = 255; // Alpha - } - this._drawCtx.putImageData(img, x, y); - this._damage(x, y, img.width, img.height); - } - - _bgrxImageData(x, y, width, height, arr, offset) { - const img = this._drawCtx.createImageData(width, height); - const data = img.data; - for (let i = 0, j = offset; i < width * height * 4; i += 4, j += 4) { - data[i] = arr[j + 2]; - data[i + 1] = arr[j + 1]; - data[i + 2] = arr[j]; - data[i + 3] = 255; // Alpha - } - this._drawCtx.putImageData(img, x, y); - this._damage(x, y, img.width, img.height); - } - - _rgbxImageData(x, y, width, height, arr, offset) { - // NB(directxman12): arr must be an Type Array view - let img; - if (supportsImageMetadata) { - img = new ImageData(new Uint8ClampedArray(arr.buffer, arr.byteOffset, width * height * 4), width, height); - } else { - img = this._drawCtx.createImageData(width, height); - img.data.set(new Uint8ClampedArray(arr.buffer, arr.byteOffset, width * height * 4)); - } - this._drawCtx.putImageData(img, x, y); - this._damage(x, y, img.width, img.height); - } - - _renderQ_push(action) { + _renderQPush(action) { this._renderQ.push(action); if (this._renderQ.length === 1) { // If this can be rendered immediately it will be, otherwise // the scanner will wait for the relevant event - this._scan_renderQ(); + this._scanRenderQ(); } } - _resume_renderQ() { + _resumeRenderQ() { // "this" is the object that is ready, not the // display object - this.removeEventListener('load', this._noVNC_display._resume_renderQ); - this._noVNC_display._scan_renderQ(); + this.removeEventListener('load', this._noVNCDisplay._resumeRenderQ); + this._noVNCDisplay._scanRenderQ(); } - _scan_renderQ() { + _scanRenderQ() { let ready = true; while (ready && this._renderQ.length > 0) { const a = this._renderQ[0]; @@ -614,7 +485,7 @@ export default class Display { this.flip(true); break; case 'copy': - this.copyImage(a.old_x, a.old_y, a.x, a.y, a.width, a.height, true); + this.copyImage(a.oldX, a.oldY, a.x, a.y, a.width, a.height, true); break; case 'fill': this.fillRect(a.x, a.y, a.width, a.height, a.color, true); @@ -622,18 +493,19 @@ export default class Display { case 'blit': this.blitImage(a.x, a.y, a.width, a.height, a.data, 0, true); break; - case 'blitRgb': - this.blitRgbImage(a.x, a.y, a.width, a.height, a.data, 0, true); - break; - case 'blitRgbx': - this.blitRgbxImage(a.x, a.y, a.width, a.height, a.data, 0, true); - break; case 'img': - if (a.img.complete) { + /* IE tends to set "complete" prematurely, so check dimensions */ + if (a.img.complete && (a.img.width !== 0) && (a.img.height !== 0)) { + if (a.img.width !== a.width || a.img.height !== a.height) { + Log.Error("Decoded image has incorrect dimensions. Got " + + a.img.width + "x" + a.img.height + ". Expected " + + a.width + "x" + a.height + "."); + return; + } this.drawImage(a.img, a.x, a.y); } else { - a.img._noVNC_display = this; - a.img.addEventListener('load', this._resume_renderQ); + a.img._noVNCDisplay = this; + a.img.addEventListener('load', this._resumeRenderQ); // We need to wait for this image to 'load' // to keep things in-order ready = false; diff --git a/systemvm/agent/noVNC/core/encodings.js b/systemvm/agent/noVNC/core/encodings.js index 9fd38d58fcc..51c09929168 100644 --- a/systemvm/agent/noVNC/core/encodings.js +++ b/systemvm/agent/noVNC/core/encodings.js @@ -1,6 +1,6 @@ /* * noVNC: HTML5 VNC client - * Copyright (C) 2018 The noVNC Authors + * Copyright (C) 2019 The noVNC Authors * Licensed under MPL 2.0 (see LICENSE.txt) * * See README.md for usage and integration instructions. @@ -20,12 +20,15 @@ export const encodings = { pseudoEncodingLastRect: -224, pseudoEncodingCursor: -239, pseudoEncodingQEMUExtendedKeyEvent: -258, + pseudoEncodingDesktopName: -307, pseudoEncodingExtendedDesktopSize: -308, pseudoEncodingXvp: -309, pseudoEncodingFence: -312, pseudoEncodingContinuousUpdates: -313, pseudoEncodingCompressLevel9: -247, pseudoEncodingCompressLevel0: -256, + pseudoEncodingVMwareCursor: 0x574d5664, + pseudoEncodingExtendedClipboard: 0xc0a1e5ce }; export function encodingName(num) { diff --git a/systemvm/agent/noVNC/core/inflator.js b/systemvm/agent/noVNC/core/inflator.js index 0eab8fe48c2..4b337607b03 100644 --- a/systemvm/agent/noVNC/core/inflator.js +++ b/systemvm/agent/noVNC/core/inflator.js @@ -1,3 +1,11 @@ +/* + * noVNC: HTML5 VNC client + * Copyright (C) 2020 The noVNC Authors + * Licensed under MPL 2.0 (see LICENSE.txt) + * + * See README.md for usage and integration instructions. + */ + import { inflateInit, inflate, inflateReset } from "../vendor/pako/lib/zlib/inflate.js"; import ZStream from "../vendor/pako/lib/zlib/zstream.js"; @@ -11,12 +19,22 @@ export default class Inflate { inflateInit(this.strm, this.windowBits); } - inflate(data, flush, expected) { - this.strm.input = data; - this.strm.avail_in = this.strm.input.length; - this.strm.next_in = 0; - this.strm.next_out = 0; + setInput(data) { + if (!data) { + //FIXME: flush remaining data. + /* eslint-disable camelcase */ + this.strm.input = null; + this.strm.avail_in = 0; + this.strm.next_in = 0; + } else { + this.strm.input = data; + this.strm.avail_in = this.strm.input.length; + this.strm.next_in = 0; + /* eslint-enable camelcase */ + } + } + inflate(expected) { // resize our output buffer if it's too small // (we could just use multiple chunks, but that would cause an extra // allocation each time to flatten the chunks) @@ -25,9 +43,19 @@ export default class Inflate { this.strm.output = new Uint8Array(this.chunkSize); } - this.strm.avail_out = this.chunkSize; + /* eslint-disable camelcase */ + this.strm.next_out = 0; + this.strm.avail_out = expected; + /* eslint-enable camelcase */ - inflate(this.strm, flush); + let ret = inflate(this.strm, 0); // Flush argument not used. + if (ret < 0) { + throw new Error("zlib inflate failed"); + } + + if (this.strm.next_out != expected) { + throw new Error("Incomplete zlib block"); + } return new Uint8Array(this.strm.output.buffer, 0, this.strm.next_out); } diff --git a/systemvm/agent/noVNC/core/input/domkeytable.js b/systemvm/agent/noVNC/core/input/domkeytable.js index 60ae3f91902..b84ad45de55 100644 --- a/systemvm/agent/noVNC/core/input/domkeytable.js +++ b/systemvm/agent/noVNC/core/input/domkeytable.js @@ -43,12 +43,10 @@ addStandard("CapsLock", KeyTable.XK_Caps_Lock); addLeftRight("Control", KeyTable.XK_Control_L, KeyTable.XK_Control_R); // - Fn // - FnLock -addLeftRight("Hyper", KeyTable.XK_Super_L, KeyTable.XK_Super_R); addLeftRight("Meta", KeyTable.XK_Super_L, KeyTable.XK_Super_R); addStandard("NumLock", KeyTable.XK_Num_Lock); addStandard("ScrollLock", KeyTable.XK_Scroll_Lock); addLeftRight("Shift", KeyTable.XK_Shift_L, KeyTable.XK_Shift_R); -addLeftRight("Super", KeyTable.XK_Super_L, KeyTable.XK_Super_R); // - Symbol // - SymbolLock @@ -72,6 +70,9 @@ addNumpad("PageUp", KeyTable.XK_Prior, KeyTable.XK_KP_Prior); // 2.5. Editing Keys addStandard("Backspace", KeyTable.XK_BackSpace); +// Browsers send "Clear" for the numpad 5 without NumLock because +// Windows uses VK_Clear for that key. But Unix expects KP_Begin for +// that scenario. addNumpad("Clear", KeyTable.XK_Clear, KeyTable.XK_KP_Begin); addStandard("Copy", KeyTable.XF86XK_Copy); // - CrSel @@ -194,7 +195,8 @@ addStandard("F35", KeyTable.XK_F35); addStandard("Close", KeyTable.XF86XK_Close); addStandard("MailForward", KeyTable.XF86XK_MailForward); addStandard("MailReply", KeyTable.XF86XK_Reply); -addStandard("MainSend", KeyTable.XF86XK_Send); +addStandard("MailSend", KeyTable.XF86XK_Send); +// - MediaClose addStandard("MediaFastForward", KeyTable.XF86XK_AudioForward); addStandard("MediaPause", KeyTable.XF86XK_AudioPause); addStandard("MediaPlay", KeyTable.XF86XK_AudioPlay); @@ -218,11 +220,9 @@ addStandard("SpellCheck", KeyTable.XF86XK_Spell); // - AudioBalanceLeft // - AudioBalanceRight -// - AudioBassDown // - AudioBassBoostDown // - AudioBassBoostToggle // - AudioBassBoostUp -// - AudioBassUp // - AudioFaderFront // - AudioFaderRear // - AudioSurroundModeNext @@ -243,12 +243,12 @@ addStandard("MicrophoneVolumeMute", KeyTable.XF86XK_AudioMicMute); // 2.14. Application Keys -addStandard("LaunchCalculator", KeyTable.XF86XK_Calculator); +addStandard("LaunchApplication1", KeyTable.XF86XK_MyComputer); +addStandard("LaunchApplication2", KeyTable.XF86XK_Calculator); addStandard("LaunchCalendar", KeyTable.XF86XK_Calendar); addStandard("LaunchMail", KeyTable.XF86XK_Mail); addStandard("LaunchMediaPlayer", KeyTable.XF86XK_AudioMedia); addStandard("LaunchMusicPlayer", KeyTable.XF86XK_Music); -addStandard("LaunchMyComputer", KeyTable.XF86XK_MyComputer); addStandard("LaunchPhone", KeyTable.XF86XK_Phone); addStandard("LaunchScreenSaver", KeyTable.XF86XK_ScreenSaver); addStandard("LaunchSpreadsheet", KeyTable.XF86XK_Excel); diff --git a/systemvm/agent/noVNC/core/input/gesturehandler.js b/systemvm/agent/noVNC/core/input/gesturehandler.js new file mode 100644 index 00000000000..6fa72d2aac5 --- /dev/null +++ b/systemvm/agent/noVNC/core/input/gesturehandler.js @@ -0,0 +1,567 @@ +/* + * noVNC: HTML5 VNC client + * Copyright (C) 2020 The noVNC Authors + * Licensed under MPL 2.0 (see LICENSE.txt) + * + * See README.md for usage and integration instructions. + * + */ + +const GH_NOGESTURE = 0; +const GH_ONETAP = 1; +const GH_TWOTAP = 2; +const GH_THREETAP = 4; +const GH_DRAG = 8; +const GH_LONGPRESS = 16; +const GH_TWODRAG = 32; +const GH_PINCH = 64; + +const GH_INITSTATE = 127; + +const GH_MOVE_THRESHOLD = 50; +const GH_ANGLE_THRESHOLD = 90; // Degrees + +// Timeout when waiting for gestures (ms) +const GH_MULTITOUCH_TIMEOUT = 250; + +// Maximum time between press and release for a tap (ms) +const GH_TAP_TIMEOUT = 1000; + +// Timeout when waiting for longpress (ms) +const GH_LONGPRESS_TIMEOUT = 1000; + +// Timeout when waiting to decide between PINCH and TWODRAG (ms) +const GH_TWOTOUCH_TIMEOUT = 50; + +export default class GestureHandler { + constructor() { + this._target = null; + + this._state = GH_INITSTATE; + + this._tracked = []; + this._ignored = []; + + this._waitingRelease = false; + this._releaseStart = 0.0; + + this._longpressTimeoutId = null; + this._twoTouchTimeoutId = null; + + this._boundEventHandler = this._eventHandler.bind(this); + } + + attach(target) { + this.detach(); + + this._target = target; + this._target.addEventListener('touchstart', + this._boundEventHandler); + this._target.addEventListener('touchmove', + this._boundEventHandler); + this._target.addEventListener('touchend', + this._boundEventHandler); + this._target.addEventListener('touchcancel', + this._boundEventHandler); + } + + detach() { + if (!this._target) { + return; + } + + this._stopLongpressTimeout(); + this._stopTwoTouchTimeout(); + + this._target.removeEventListener('touchstart', + this._boundEventHandler); + this._target.removeEventListener('touchmove', + this._boundEventHandler); + this._target.removeEventListener('touchend', + this._boundEventHandler); + this._target.removeEventListener('touchcancel', + this._boundEventHandler); + this._target = null; + } + + _eventHandler(e) { + let fn; + + e.stopPropagation(); + e.preventDefault(); + + switch (e.type) { + case 'touchstart': + fn = this._touchStart; + break; + case 'touchmove': + fn = this._touchMove; + break; + case 'touchend': + case 'touchcancel': + fn = this._touchEnd; + break; + } + + for (let i = 0; i < e.changedTouches.length; i++) { + let touch = e.changedTouches[i]; + fn.call(this, touch.identifier, touch.clientX, touch.clientY); + } + } + + _touchStart(id, x, y) { + // Ignore any new touches if there is already an active gesture, + // or we're in a cleanup state + if (this._hasDetectedGesture() || (this._state === GH_NOGESTURE)) { + this._ignored.push(id); + return; + } + + // Did it take too long between touches that we should no longer + // consider this a single gesture? + if ((this._tracked.length > 0) && + ((Date.now() - this._tracked[0].started) > GH_MULTITOUCH_TIMEOUT)) { + this._state = GH_NOGESTURE; + this._ignored.push(id); + return; + } + + // If we're waiting for fingers to release then we should no longer + // recognize new touches + if (this._waitingRelease) { + this._state = GH_NOGESTURE; + this._ignored.push(id); + return; + } + + this._tracked.push({ + id: id, + started: Date.now(), + active: true, + firstX: x, + firstY: y, + lastX: x, + lastY: y, + angle: 0 + }); + + switch (this._tracked.length) { + case 1: + this._startLongpressTimeout(); + break; + + case 2: + this._state &= ~(GH_ONETAP | GH_DRAG | GH_LONGPRESS); + this._stopLongpressTimeout(); + break; + + case 3: + this._state &= ~(GH_TWOTAP | GH_TWODRAG | GH_PINCH); + break; + + default: + this._state = GH_NOGESTURE; + } + } + + _touchMove(id, x, y) { + let touch = this._tracked.find(t => t.id === id); + + // If this is an update for a touch we're not tracking, ignore it + if (touch === undefined) { + return; + } + + // Update the touches last position with the event coordinates + touch.lastX = x; + touch.lastY = y; + + let deltaX = x - touch.firstX; + let deltaY = y - touch.firstY; + + // Update angle when the touch has moved + if ((touch.firstX !== touch.lastX) || + (touch.firstY !== touch.lastY)) { + touch.angle = Math.atan2(deltaY, deltaX) * 180 / Math.PI; + } + + if (!this._hasDetectedGesture()) { + // Ignore moves smaller than the minimum threshold + if (Math.hypot(deltaX, deltaY) < GH_MOVE_THRESHOLD) { + return; + } + + // Can't be a tap or long press as we've seen movement + this._state &= ~(GH_ONETAP | GH_TWOTAP | GH_THREETAP | GH_LONGPRESS); + this._stopLongpressTimeout(); + + if (this._tracked.length !== 1) { + this._state &= ~(GH_DRAG); + } + if (this._tracked.length !== 2) { + this._state &= ~(GH_TWODRAG | GH_PINCH); + } + + // We need to figure out which of our different two touch gestures + // this might be + if (this._tracked.length === 2) { + + // The other touch is the one where the id doesn't match + let prevTouch = this._tracked.find(t => t.id !== id); + + // How far the previous touch point has moved since start + let prevDeltaMove = Math.hypot(prevTouch.firstX - prevTouch.lastX, + prevTouch.firstY - prevTouch.lastY); + + // We know that the current touch moved far enough, + // but unless both touches moved further than their + // threshold we don't want to disqualify any gestures + if (prevDeltaMove > GH_MOVE_THRESHOLD) { + + // The angle difference between the direction of the touch points + let deltaAngle = Math.abs(touch.angle - prevTouch.angle); + deltaAngle = Math.abs(((deltaAngle + 180) % 360) - 180); + + // PINCH or TWODRAG can be eliminated depending on the angle + if (deltaAngle > GH_ANGLE_THRESHOLD) { + this._state &= ~GH_TWODRAG; + } else { + this._state &= ~GH_PINCH; + } + + if (this._isTwoTouchTimeoutRunning()) { + this._stopTwoTouchTimeout(); + } + } else if (!this._isTwoTouchTimeoutRunning()) { + // We can't determine the gesture right now, let's + // wait and see if more events are on their way + this._startTwoTouchTimeout(); + } + } + + if (!this._hasDetectedGesture()) { + return; + } + + this._pushEvent('gesturestart'); + } + + this._pushEvent('gesturemove'); + } + + _touchEnd(id, x, y) { + // Check if this is an ignored touch + if (this._ignored.indexOf(id) !== -1) { + // Remove this touch from ignored + this._ignored.splice(this._ignored.indexOf(id), 1); + + // And reset the state if there are no more touches + if ((this._ignored.length === 0) && + (this._tracked.length === 0)) { + this._state = GH_INITSTATE; + this._waitingRelease = false; + } + return; + } + + // We got a touchend before the timer triggered, + // this cannot result in a gesture anymore. + if (!this._hasDetectedGesture() && + this._isTwoTouchTimeoutRunning()) { + this._stopTwoTouchTimeout(); + this._state = GH_NOGESTURE; + } + + // Some gestures don't trigger until a touch is released + if (!this._hasDetectedGesture()) { + // Can't be a gesture that relies on movement + this._state &= ~(GH_DRAG | GH_TWODRAG | GH_PINCH); + // Or something that relies on more time + this._state &= ~GH_LONGPRESS; + this._stopLongpressTimeout(); + + if (!this._waitingRelease) { + this._releaseStart = Date.now(); + this._waitingRelease = true; + + // Can't be a tap that requires more touches than we current have + switch (this._tracked.length) { + case 1: + this._state &= ~(GH_TWOTAP | GH_THREETAP); + break; + + case 2: + this._state &= ~(GH_ONETAP | GH_THREETAP); + break; + } + } + } + + // Waiting for all touches to release? (i.e. some tap) + if (this._waitingRelease) { + // Were all touches released at roughly the same time? + if ((Date.now() - this._releaseStart) > GH_MULTITOUCH_TIMEOUT) { + this._state = GH_NOGESTURE; + } + + // Did too long time pass between press and release? + if (this._tracked.some(t => (Date.now() - t.started) > GH_TAP_TIMEOUT)) { + this._state = GH_NOGESTURE; + } + + let touch = this._tracked.find(t => t.id === id); + touch.active = false; + + // Are we still waiting for more releases? + if (this._hasDetectedGesture()) { + this._pushEvent('gesturestart'); + } else { + // Have we reached a dead end? + if (this._state !== GH_NOGESTURE) { + return; + } + } + } + + if (this._hasDetectedGesture()) { + this._pushEvent('gestureend'); + } + + // Ignore any remaining touches until they are ended + for (let i = 0; i < this._tracked.length; i++) { + if (this._tracked[i].active) { + this._ignored.push(this._tracked[i].id); + } + } + this._tracked = []; + + this._state = GH_NOGESTURE; + + // Remove this touch from ignored if it's in there + if (this._ignored.indexOf(id) !== -1) { + this._ignored.splice(this._ignored.indexOf(id), 1); + } + + // We reset the state if ignored is empty + if ((this._ignored.length === 0)) { + this._state = GH_INITSTATE; + this._waitingRelease = false; + } + } + + _hasDetectedGesture() { + if (this._state === GH_NOGESTURE) { + return false; + } + // Check to see if the bitmask value is a power of 2 + // (i.e. only one bit set). If it is, we have a state. + if (this._state & (this._state - 1)) { + return false; + } + + // For taps we also need to have all touches released + // before we've fully detected the gesture + if (this._state & (GH_ONETAP | GH_TWOTAP | GH_THREETAP)) { + if (this._tracked.some(t => t.active)) { + return false; + } + } + + return true; + } + + _startLongpressTimeout() { + this._stopLongpressTimeout(); + this._longpressTimeoutId = setTimeout(() => this._longpressTimeout(), + GH_LONGPRESS_TIMEOUT); + } + + _stopLongpressTimeout() { + clearTimeout(this._longpressTimeoutId); + this._longpressTimeoutId = null; + } + + _longpressTimeout() { + if (this._hasDetectedGesture()) { + throw new Error("A longpress gesture failed, conflict with a different gesture"); + } + + this._state = GH_LONGPRESS; + this._pushEvent('gesturestart'); + } + + _startTwoTouchTimeout() { + this._stopTwoTouchTimeout(); + this._twoTouchTimeoutId = setTimeout(() => this._twoTouchTimeout(), + GH_TWOTOUCH_TIMEOUT); + } + + _stopTwoTouchTimeout() { + clearTimeout(this._twoTouchTimeoutId); + this._twoTouchTimeoutId = null; + } + + _isTwoTouchTimeoutRunning() { + return this._twoTouchTimeoutId !== null; + } + + _twoTouchTimeout() { + if (this._tracked.length === 0) { + throw new Error("A pinch or two drag gesture failed, no tracked touches"); + } + + // How far each touch point has moved since start + let avgM = this._getAverageMovement(); + let avgMoveH = Math.abs(avgM.x); + let avgMoveV = Math.abs(avgM.y); + + // The difference in the distance between where + // the touch points started and where they are now + let avgD = this._getAverageDistance(); + let deltaTouchDistance = Math.abs(Math.hypot(avgD.first.x, avgD.first.y) - + Math.hypot(avgD.last.x, avgD.last.y)); + + if ((avgMoveV < deltaTouchDistance) && + (avgMoveH < deltaTouchDistance)) { + this._state = GH_PINCH; + } else { + this._state = GH_TWODRAG; + } + + this._pushEvent('gesturestart'); + this._pushEvent('gesturemove'); + } + + _pushEvent(type) { + let detail = { type: this._stateToGesture(this._state) }; + + // For most gesture events the current (average) position is the + // most useful + let avg = this._getPosition(); + let pos = avg.last; + + // However we have a slight distance to detect gestures, so for the + // first gesture event we want to use the first positions we saw + if (type === 'gesturestart') { + pos = avg.first; + } + + // For these gestures, we always want the event coordinates + // to be where the gesture began, not the current touch location. + switch (this._state) { + case GH_TWODRAG: + case GH_PINCH: + pos = avg.first; + break; + } + + detail['clientX'] = pos.x; + detail['clientY'] = pos.y; + + // FIXME: other coordinates? + + // Some gestures also have a magnitude + if (this._state === GH_PINCH) { + let distance = this._getAverageDistance(); + if (type === 'gesturestart') { + detail['magnitudeX'] = distance.first.x; + detail['magnitudeY'] = distance.first.y; + } else { + detail['magnitudeX'] = distance.last.x; + detail['magnitudeY'] = distance.last.y; + } + } else if (this._state === GH_TWODRAG) { + if (type === 'gesturestart') { + detail['magnitudeX'] = 0.0; + detail['magnitudeY'] = 0.0; + } else { + let movement = this._getAverageMovement(); + detail['magnitudeX'] = movement.x; + detail['magnitudeY'] = movement.y; + } + } + + let gev = new CustomEvent(type, { detail: detail }); + this._target.dispatchEvent(gev); + } + + _stateToGesture(state) { + switch (state) { + case GH_ONETAP: + return 'onetap'; + case GH_TWOTAP: + return 'twotap'; + case GH_THREETAP: + return 'threetap'; + case GH_DRAG: + return 'drag'; + case GH_LONGPRESS: + return 'longpress'; + case GH_TWODRAG: + return 'twodrag'; + case GH_PINCH: + return 'pinch'; + } + + throw new Error("Unknown gesture state: " + state); + } + + _getPosition() { + if (this._tracked.length === 0) { + throw new Error("Failed to get gesture position, no tracked touches"); + } + + let size = this._tracked.length; + let fx = 0, fy = 0, lx = 0, ly = 0; + + for (let i = 0; i < this._tracked.length; i++) { + fx += this._tracked[i].firstX; + fy += this._tracked[i].firstY; + lx += this._tracked[i].lastX; + ly += this._tracked[i].lastY; + } + + return { first: { x: fx / size, + y: fy / size }, + last: { x: lx / size, + y: ly / size } }; + } + + _getAverageMovement() { + if (this._tracked.length === 0) { + throw new Error("Failed to get gesture movement, no tracked touches"); + } + + let totalH, totalV; + totalH = totalV = 0; + let size = this._tracked.length; + + for (let i = 0; i < this._tracked.length; i++) { + totalH += this._tracked[i].lastX - this._tracked[i].firstX; + totalV += this._tracked[i].lastY - this._tracked[i].firstY; + } + + return { x: totalH / size, + y: totalV / size }; + } + + _getAverageDistance() { + if (this._tracked.length === 0) { + throw new Error("Failed to get gesture distance, no tracked touches"); + } + + // Distance between the first and last tracked touches + + let first = this._tracked[0]; + let last = this._tracked[this._tracked.length - 1]; + + let fdx = Math.abs(last.firstX - first.firstX); + let fdy = Math.abs(last.firstY - first.firstY); + + let ldx = Math.abs(last.lastX - first.lastX); + let ldy = Math.abs(last.lastY - first.lastY); + + return { first: { x: fdx, y: fdy }, + last: { x: ldx, y: ldy } }; + } +} diff --git a/systemvm/agent/noVNC/core/input/keyboard.js b/systemvm/agent/noVNC/core/input/keyboard.js index 9dbc8d6e6ed..9e6af2ac753 100644 --- a/systemvm/agent/noVNC/core/input/keyboard.js +++ b/systemvm/agent/noVNC/core/input/keyboard.js @@ -1,6 +1,6 @@ /* * noVNC: HTML5 VNC client - * Copyright (C) 2018 The noVNC Authors + * Copyright (C) 2019 The noVNC Authors * Licensed under MPL 2.0 or any later version (see LICENSE.txt) */ @@ -118,9 +118,7 @@ export default class Keyboard { // We cannot handle keys we cannot track, but we also need // to deal with virtual keyboards which omit key info - // (iOS omits tracking info on keyup events, which forces us to - // special treat that platform here) - if ((code === 'Unidentified') || browser.isIOS()) { + if (code === 'Unidentified') { if (keysym) { // If it's a virtual keyboard then it should be // sufficient to just send press and release right @@ -137,7 +135,7 @@ export default class Keyboard { // keys around a bit to make things more sane for the remote // server. This method is used by RealVNC and TigerVNC (and // possibly others). - if (browser.isMac()) { + if (browser.isMac() || browser.isIOS()) { switch (keysym) { case KeyTable.XK_Super_L: keysym = KeyTable.XK_Alt_L; @@ -164,7 +162,7 @@ export default class Keyboard { // state change events. That gets extra confusing for CapsLock // which toggles on each press, but not on release. So pretend // it was a quick press and release of the button. - if (browser.isMac() && (code === 'CapsLock')) { + if ((browser.isMac() || browser.isIOS()) && (code === 'CapsLock')) { this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', true); this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', false); stopEvent(e); @@ -276,13 +274,28 @@ export default class Keyboard { } // See comment in _handleKeyDown() - if (browser.isMac() && (code === 'CapsLock')) { + if ((browser.isMac() || browser.isIOS()) && (code === 'CapsLock')) { this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', true); this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', false); return; } this._sendKeyEvent(this._keyDownList[code], code, false); + + // Windows has a rather nasty bug where it won't send key + // release events for a Shift button if the other Shift is still + // pressed + if (browser.isWindows() && ((code === 'ShiftLeft') || + (code === 'ShiftRight'))) { + if ('ShiftRight' in this._keyDownList) { + this._sendKeyEvent(this._keyDownList['ShiftRight'], + 'ShiftRight', false); + } + if ('ShiftLeft' in this._keyDownList) { + this._sendKeyEvent(this._keyDownList['ShiftLeft'], + 'ShiftLeft', false); + } + } } _handleAltGrTimeout() { @@ -299,8 +312,11 @@ export default class Keyboard { Log.Debug("<< Keyboard.allKeysUp"); } - // Firefox Alt workaround, see below + // Alt workaround for Firefox on Windows, see below _checkAlt(e) { + if (e.skipCheckAlt) { + return; + } if (e.altKey) { return; } @@ -315,6 +331,7 @@ export default class Keyboard { const event = new KeyboardEvent('keyup', { key: downList[code], code: code }); + event.skipCheckAlt = true; target.dispatchEvent(event); }); } @@ -331,9 +348,10 @@ export default class Keyboard { // Release (key up) if window loses focus window.addEventListener('blur', this._eventHandlers.blur); - // Firefox has broken handling of Alt, so we need to poll as - // best we can for releases (still doesn't prevent the menu - // from popping up though as we can't call preventDefault()) + // Firefox on Windows has broken handling of Alt, so we need to + // poll as best we can for releases (still doesn't prevent the + // menu from popping up though as we can't call + // preventDefault()) if (browser.isWindows() && browser.isFirefox()) { const handler = this._eventHandlers.checkalt; ['mousedown', 'mouseup', 'mousemove', 'wheel', diff --git a/systemvm/agent/noVNC/core/input/mouse.js b/systemvm/agent/noVNC/core/input/mouse.js deleted file mode 100644 index 58a2982a961..00000000000 --- a/systemvm/agent/noVNC/core/input/mouse.js +++ /dev/null @@ -1,276 +0,0 @@ -/* - * noVNC: HTML5 VNC client - * Copyright (C) 2018 The noVNC Authors - * Licensed under MPL 2.0 or any later version (see LICENSE.txt) - */ - -import * as Log from '../util/logging.js'; -import { isTouchDevice } from '../util/browser.js'; -import { setCapture, stopEvent, getPointerEvent } from '../util/events.js'; - -const WHEEL_STEP = 10; // Delta threshold for a mouse wheel step -const WHEEL_STEP_TIMEOUT = 50; // ms -const WHEEL_LINE_HEIGHT = 19; - -export default class Mouse { - constructor(target) { - this._target = target || document; - - this._doubleClickTimer = null; - this._lastTouchPos = null; - - this._pos = null; - this._wheelStepXTimer = null; - this._wheelStepYTimer = null; - this._accumulatedWheelDeltaX = 0; - this._accumulatedWheelDeltaY = 0; - - this._eventHandlers = { - 'mousedown': this._handleMouseDown.bind(this), - 'mouseup': this._handleMouseUp.bind(this), - 'mousemove': this._handleMouseMove.bind(this), - 'mousewheel': this._handleMouseWheel.bind(this), - 'mousedisable': this._handleMouseDisable.bind(this) - }; - - // ===== PROPERTIES ===== - - this.touchButton = 1; // Button mask (1, 2, 4) for touch devices (0 means ignore clicks) - - // ===== EVENT HANDLERS ===== - - this.onmousebutton = () => {}; // Handler for mouse button click/release - this.onmousemove = () => {}; // Handler for mouse movement - } - - // ===== PRIVATE METHODS ===== - - _resetDoubleClickTimer() { - this._doubleClickTimer = null; - } - - _handleMouseButton(e, down) { - this._updateMousePosition(e); - let pos = this._pos; - - let bmask; - if (e.touches || e.changedTouches) { - // Touch device - - // When two touches occur within 500 ms of each other and are - // close enough together a double click is triggered. - if (down == 1) { - if (this._doubleClickTimer === null) { - this._lastTouchPos = pos; - } else { - clearTimeout(this._doubleClickTimer); - - // When the distance between the two touches is small enough - // force the position of the latter touch to the position of - // the first. - - const xs = this._lastTouchPos.x - pos.x; - const ys = this._lastTouchPos.y - pos.y; - const d = Math.sqrt((xs * xs) + (ys * ys)); - - // The goal is to trigger on a certain physical width, the - // devicePixelRatio brings us a bit closer but is not optimal. - const threshold = 20 * (window.devicePixelRatio || 1); - if (d < threshold) { - pos = this._lastTouchPos; - } - } - this._doubleClickTimer = setTimeout(this._resetDoubleClickTimer.bind(this), 500); - } - bmask = this.touchButton; - // If bmask is set - } else if (e.which) { - /* everything except IE */ - bmask = 1 << e.button; - } else { - /* IE including 9 */ - bmask = (e.button & 0x1) + // Left - (e.button & 0x2) * 2 + // Right - (e.button & 0x4) / 2; // Middle - } - - Log.Debug("onmousebutton " + (down ? "down" : "up") + - ", x: " + pos.x + ", y: " + pos.y + ", bmask: " + bmask); - this.onmousebutton(pos.x, pos.y, down, bmask); - - stopEvent(e); - } - - _handleMouseDown(e) { - // Touch events have implicit capture - if (e.type === "mousedown") { - setCapture(this._target); - } - - this._handleMouseButton(e, 1); - } - - _handleMouseUp(e) { - this._handleMouseButton(e, 0); - } - - // Mouse wheel events are sent in steps over VNC. This means that the VNC - // protocol can't handle a wheel event with specific distance or speed. - // Therefor, if we get a lot of small mouse wheel events we combine them. - _generateWheelStepX() { - - if (this._accumulatedWheelDeltaX < 0) { - this.onmousebutton(this._pos.x, this._pos.y, 1, 1 << 5); - this.onmousebutton(this._pos.x, this._pos.y, 0, 1 << 5); - } else if (this._accumulatedWheelDeltaX > 0) { - this.onmousebutton(this._pos.x, this._pos.y, 1, 1 << 6); - this.onmousebutton(this._pos.x, this._pos.y, 0, 1 << 6); - } - - this._accumulatedWheelDeltaX = 0; - } - - _generateWheelStepY() { - - if (this._accumulatedWheelDeltaY < 0) { - this.onmousebutton(this._pos.x, this._pos.y, 1, 1 << 3); - this.onmousebutton(this._pos.x, this._pos.y, 0, 1 << 3); - } else if (this._accumulatedWheelDeltaY > 0) { - this.onmousebutton(this._pos.x, this._pos.y, 1, 1 << 4); - this.onmousebutton(this._pos.x, this._pos.y, 0, 1 << 4); - } - - this._accumulatedWheelDeltaY = 0; - } - - _resetWheelStepTimers() { - window.clearTimeout(this._wheelStepXTimer); - window.clearTimeout(this._wheelStepYTimer); - this._wheelStepXTimer = null; - this._wheelStepYTimer = null; - } - - _handleMouseWheel(e) { - this._resetWheelStepTimers(); - - this._updateMousePosition(e); - - let dX = e.deltaX; - let dY = e.deltaY; - - // Pixel units unless it's non-zero. - // Note that if deltamode is line or page won't matter since we aren't - // sending the mouse wheel delta to the server anyway. - // The difference between pixel and line can be important however since - // we have a threshold that can be smaller than the line height. - if (e.deltaMode !== 0) { - dX *= WHEEL_LINE_HEIGHT; - dY *= WHEEL_LINE_HEIGHT; - } - - this._accumulatedWheelDeltaX += dX; - this._accumulatedWheelDeltaY += dY; - - // Generate a mouse wheel step event when the accumulated delta - // for one of the axes is large enough. - // Small delta events that do not pass the threshold get sent - // after a timeout. - if (Math.abs(this._accumulatedWheelDeltaX) > WHEEL_STEP) { - this._generateWheelStepX(); - } else { - this._wheelStepXTimer = - window.setTimeout(this._generateWheelStepX.bind(this), - WHEEL_STEP_TIMEOUT); - } - if (Math.abs(this._accumulatedWheelDeltaY) > WHEEL_STEP) { - this._generateWheelStepY(); - } else { - this._wheelStepYTimer = - window.setTimeout(this._generateWheelStepY.bind(this), - WHEEL_STEP_TIMEOUT); - } - - stopEvent(e); - } - - _handleMouseMove(e) { - this._updateMousePosition(e); - this.onmousemove(this._pos.x, this._pos.y); - stopEvent(e); - } - - _handleMouseDisable(e) { - /* - * Stop propagation if inside canvas area - * Note: This is only needed for the 'click' event as it fails - * to fire properly for the target element so we have - * to listen on the document element instead. - */ - if (e.target == this._target) { - stopEvent(e); - } - } - - // Update coordinates relative to target - _updateMousePosition(e) { - e = getPointerEvent(e); - const bounds = this._target.getBoundingClientRect(); - let x; - let y; - // Clip to target bounds - if (e.clientX < bounds.left) { - x = 0; - } else if (e.clientX >= bounds.right) { - x = bounds.width - 1; - } else { - x = e.clientX - bounds.left; - } - if (e.clientY < bounds.top) { - y = 0; - } else if (e.clientY >= bounds.bottom) { - y = bounds.height - 1; - } else { - y = e.clientY - bounds.top; - } - this._pos = {x: x, y: y}; - } - - // ===== PUBLIC METHODS ===== - - grab() { - if (isTouchDevice) { - this._target.addEventListener('touchstart', this._eventHandlers.mousedown); - this._target.addEventListener('touchend', this._eventHandlers.mouseup); - this._target.addEventListener('touchmove', this._eventHandlers.mousemove); - } - this._target.addEventListener('mousedown', this._eventHandlers.mousedown); - this._target.addEventListener('mouseup', this._eventHandlers.mouseup); - this._target.addEventListener('mousemove', this._eventHandlers.mousemove); - this._target.addEventListener('wheel', this._eventHandlers.mousewheel); - - /* Prevent middle-click pasting (see above for why we bind to document) */ - document.addEventListener('click', this._eventHandlers.mousedisable); - - /* preventDefault() on mousedown doesn't stop this event for some - reason so we have to explicitly block it */ - this._target.addEventListener('contextmenu', this._eventHandlers.mousedisable); - } - - ungrab() { - this._resetWheelStepTimers(); - - if (isTouchDevice) { - this._target.removeEventListener('touchstart', this._eventHandlers.mousedown); - this._target.removeEventListener('touchend', this._eventHandlers.mouseup); - this._target.removeEventListener('touchmove', this._eventHandlers.mousemove); - } - this._target.removeEventListener('mousedown', this._eventHandlers.mousedown); - this._target.removeEventListener('mouseup', this._eventHandlers.mouseup); - this._target.removeEventListener('mousemove', this._eventHandlers.mousemove); - this._target.removeEventListener('wheel', this._eventHandlers.mousewheel); - - document.removeEventListener('click', this._eventHandlers.mousedisable); - - this._target.removeEventListener('contextmenu', this._eventHandlers.mousedisable); - } -} diff --git a/systemvm/agent/noVNC/core/input/uskeysym.js b/systemvm/agent/noVNC/core/input/uskeysym.js new file mode 100644 index 00000000000..97c5ae499bd --- /dev/null +++ b/systemvm/agent/noVNC/core/input/uskeysym.js @@ -0,0 +1,57 @@ +export default { + '1': 0x0031, /* U+0031 DIGIT ONE */ + '2': 0x0032, /* U+0032 DIGIT TWO */ + '3': 0x0033, /* U+0033 DIGIT THREE */ + '4': 0x0034, /* U+0034 DIGIT FOUR */ + '5': 0x0035, /* U+0035 DIGIT FIVE */ + '6': 0x0036, /* U+0036 DIGIT SIX */ + '7': 0x0037, /* U+0037 DIGIT SEVEN */ + '8': 0x0038, /* U+0038 DIGIT EIGHT */ + '9': 0x0039, /* U+0039 DIGIT NINE */ + '0': 0x0030, /* U+0030 DIGIT ZERO */ + + 'a': 0x0061, /* U+0061 LATIN SMALL LETTER A */ + 'b': 0x0062, /* U+0062 LATIN SMALL LETTER B */ + 'c': 0x0063, /* U+0063 LATIN SMALL LETTER C */ + 'd': 0x0064, /* U+0064 LATIN SMALL LETTER D */ + 'e': 0x0065, /* U+0065 LATIN SMALL LETTER E */ + 'f': 0x0066, /* U+0066 LATIN SMALL LETTER F */ + 'g': 0x0067, /* U+0067 LATIN SMALL LETTER G */ + 'h': 0x0068, /* U+0068 LATIN SMALL LETTER H */ + 'i': 0x0069, /* U+0069 LATIN SMALL LETTER I */ + 'j': 0x006a, /* U+006A LATIN SMALL LETTER J */ + 'k': 0x006b, /* U+006B LATIN SMALL LETTER K */ + 'l': 0x006c, /* U+006C LATIN SMALL LETTER L */ + 'm': 0x006d, /* U+006D LATIN SMALL LETTER M */ + 'n': 0x006e, /* U+006E LATIN SMALL LETTER N */ + 'o': 0x006f, /* U+006F LATIN SMALL LETTER O */ + 'p': 0x0070, /* U+0070 LATIN SMALL LETTER P */ + 'q': 0x0071, /* U+0071 LATIN SMALL LETTER Q */ + 'r': 0x0072, /* U+0072 LATIN SMALL LETTER R */ + 's': 0x0073, /* U+0073 LATIN SMALL LETTER S */ + 't': 0x0074, /* U+0074 LATIN SMALL LETTER T */ + 'u': 0x0075, /* U+0075 LATIN SMALL LETTER U */ + 'v': 0x0076, /* U+0076 LATIN SMALL LETTER V */ + 'w': 0x0077, /* U+0077 LATIN SMALL LETTER W */ + 'x': 0x0078, /* U+0078 LATIN SMALL LETTER X */ + 'y': 0x0079, /* U+0079 LATIN SMALL LETTER Y */ + 'z': 0x007a, /* U+007A LATIN SMALL LETTER Z */ + + '`': 0x0060, /* U+0060 GRAVE ACCENT */ + '-': 0x002d, /* U+002D HYPHEN-MINUS */ + '=': 0x003d, /* U+003D EQUALS SIGN */ + + '[': 0x005b, /* U+005B LEFT SQUARE BRACKET */ + ']': 0x005d, /* U+005D RIGHT SQUARE BRACKET */ + '\\': 0x005c, /* U+005C REVERSE SOLIDUS */ + + ';': 0x003b, /* U+003B SEMICOLON */ + '\'': 0x0027, /* U+0027 APOSTROPHE */ + + ',': 0x002c, /* U+002C COMMA */ + '.': 0x002e, /* U+002E FULL STOP */ + '/': 0x002f, /* U+002F SOLIDUS */ + + ' ': 0x0020, /* U+0020 SPACE */ + '\n': 0xff0d +} \ No newline at end of file diff --git a/systemvm/agent/noVNC/core/input/util.js b/systemvm/agent/noVNC/core/input/util.js index f177ef53d36..1b98040be23 100644 --- a/systemvm/agent/noVNC/core/input/util.js +++ b/systemvm/agent/noVNC/core/input/util.js @@ -1,3 +1,4 @@ +import KeyTable from "./keysym.js"; import keysyms from "./keysymdef.js"; import vkeys from "./vkeys.js"; import fixedkeys from "./fixedkeys.js"; @@ -91,6 +92,8 @@ export function getKey(evt) { // Mozilla isn't fully in sync with the spec yet switch (evt.key) { case 'OS': return 'Meta'; + case 'LaunchMyComputer': return 'LaunchApplication1'; + case 'LaunchCalculator': return 'LaunchApplication2'; } // iOS leaks some OS names @@ -102,9 +105,21 @@ export function getKey(evt) { case 'UIKeyInputEscape': return 'Escape'; } - // IE and Edge have broken handling of AltGraph so we cannot - // trust them for printable characters - if ((evt.key.length !== 1) || (!browser.isIE() && !browser.isEdge())) { + // Broken behaviour in Chrome + if ((evt.key === '\x00') && (evt.code === 'NumpadDecimal')) { + return 'Delete'; + } + + // IE and Edge need special handling, but for everyone else we + // can trust the value provided + if (!browser.isIE() && !browser.isEdge()) { + return evt.key; + } + + // IE and Edge have broken handling of AltGraph so we can only + // trust them for non-printable characters (and unfortunately + // they also specify 'Unidentified' for some problem keys) + if ((evt.key.length !== 1) && (evt.key !== 'Unidentified')) { return evt.key; } } @@ -141,10 +156,39 @@ export function getKeysym(evt) { location = 2; } + // And for Clear + if ((key === 'Clear') && (location === 3)) { + let code = getKeycode(evt); + if (code === 'NumLock') { + location = 0; + } + } + if ((location === undefined) || (location > 3)) { location = 0; } + // The original Meta key now gets confused with the Windows key + // https://bugs.chromium.org/p/chromium/issues/detail?id=1020141 + // https://bugzilla.mozilla.org/show_bug.cgi?id=1232918 + if (key === 'Meta') { + let code = getKeycode(evt); + if (code === 'AltLeft') { + return KeyTable.XK_Meta_L; + } else if (code === 'AltRight') { + return KeyTable.XK_Meta_R; + } + } + + // macOS has Clear instead of NumLock, but the remote system is + // probably not macOS, so lying here is probably best... + if (key === 'Clear') { + let code = getKeycode(evt); + if (code === 'NumLock') { + return KeyTable.XK_Num_Lock; + } + } + return DOMKeyTable[key][location]; } diff --git a/systemvm/agent/noVNC/core/rfb.js b/systemvm/agent/noVNC/core/rfb.js index e40df6659e5..eda1597e6c0 100644 --- a/systemvm/agent/noVNC/core/rfb.js +++ b/systemvm/agent/noVNC/core/rfb.js @@ -1,23 +1,29 @@ /* * noVNC: HTML5 VNC client - * Copyright (C) 2018 The noVNC Authors + * Copyright (C) 2020 The noVNC Authors * Licensed under MPL 2.0 (see LICENSE.txt) * * See README.md for usage and integration instructions. * */ +import { toUnsigned32bit, toSigned32bit } from './util/int.js'; import * as Log from './util/logging.js'; -import { decodeUTF8 } from './util/strings.js'; +import { encodeUTF8, decodeUTF8 } from './util/strings.js'; import { dragThreshold } from './util/browser.js'; +import { clientToElement } from './util/element.js'; +import { setCapture } from './util/events.js'; import EventTargetMixin from './util/eventtarget.js'; import Display from "./display.js"; +import Inflator from "./inflator.js"; +import Deflator from "./deflator.js"; import Keyboard from "./input/keyboard.js"; -import Mouse from "./input/mouse.js"; +import GestureHandler from "./input/gesturehandler.js"; import Cursor from "./util/cursor.js"; import Websock from "./websock.js"; import DES from "./des.js"; import KeyTable from "./input/keysym.js"; +import USKeyTable from "./input/uskeysym.js"; import XtScancode from "./input/xtscancodes.js"; import { encodings } from "./encodings.js"; import "./util/polyfill.js"; @@ -33,6 +39,36 @@ import TightPNGDecoder from "./decoders/tightpng.js"; const DISCONNECT_TIMEOUT = 3; const DEFAULT_BACKGROUND = 'rgb(40, 40, 40)'; +// Minimum wait (ms) between two mouse moves +const MOUSE_MOVE_DELAY = 17; + +// Wheel thresholds +const WHEEL_STEP = 50; // Pixels needed for one step +const WHEEL_LINE_HEIGHT = 19; // Assumed pixels for one line step + +// Gesture thresholds +const GESTURE_ZOOMSENS = 75; +const GESTURE_SCRLSENS = 50; +const DOUBLE_TAP_TIMEOUT = 1000; +const DOUBLE_TAP_THRESHOLD = 50; + +// Extended clipboard pseudo-encoding formats +const extendedClipboardFormatText = 1; +/*eslint-disable no-unused-vars */ +const extendedClipboardFormatRtf = 1 << 1; +const extendedClipboardFormatHtml = 1 << 2; +const extendedClipboardFormatDib = 1 << 3; +const extendedClipboardFormatFiles = 1 << 4; +/*eslint-enable */ + +// Extended clipboard pseudo-encoding actions +const extendedClipboardActionCaps = 1 << 24; +const extendedClipboardActionRequest = 1 << 25; +const extendedClipboardActionPeek = 1 << 26; +const extendedClipboardActionNotify = 1 << 27; +const extendedClipboardActionProvide = 1 << 28; + + export default class RFB extends EventTargetMixin { constructor(target, url, options) { if (!target) { @@ -49,27 +85,28 @@ export default class RFB extends EventTargetMixin { // Connection details options = options || {}; - this._rfb_credentials = options.credentials || {}; - this._shared = false; + this._rfbCredentials = options.credentials || {}; + this._shared = 'shared' in options ? !!options.shared : true; this._repeaterID = options.repeaterID || ''; - this._showDotCursor = options.showDotCursor || false; + this._wsProtocols = ['binary']; // Internal state - this._rfb_connection_state = ''; - this._rfb_init_state = ''; - this._rfb_auth_scheme = -1; - this._rfb_clean_disconnect = true; + this._rfbConnectionState = ''; + this._rfbInitState = ''; + this._rfbAuthScheme = -1; + this._rfbCleanDisconnect = true; // Server capabilities - this._rfb_version = 0; - this._rfb_max_version = 3.8; - this._rfb_tightvnc = false; - this._rfb_xvp_ver = 0; + this._rfbVersion = 0; + this._rfbMaxVersion = 3.8; + this._rfbTightVNC = false; + this._rfbVeNCryptState = 0; + this._rfbXvpVer = 0; - this._fb_width = 0; - this._fb_height = 0; + this._fbWidth = 0; + this._fbHeight = 0; - this._fb_name = ""; + this._fbName = ""; this._capabilities = { power: false }; @@ -79,21 +116,26 @@ export default class RFB extends EventTargetMixin { this._enabledContinuousUpdates = false; this._supportsSetDesktopSize = false; - this._screen_id = 0; - this._screen_flags = 0; + this._screenID = 0; + this._screenFlags = 0; this._qemuExtKeyEventSupported = false; + this._clipboardText = null; + this._clipboardServerCapabilitiesActions = {}; + this._clipboardServerCapabilitiesFormats = {}; + // Internal objects this._sock = null; // Websock object this._display = null; // Display object this._flushing = false; // Display flushing state this._keyboard = null; // Keyboard input handler object - this._mouse = null; // Mouse input handler object + this._gestures = null; // Gesture input handler object // Timers this._disconnTimer = null; // disconnection timer this._resizeTimeout = null; // resize rate limiting + this._mouseMoveTimer = null; // Decoder states this._decoders = {}; @@ -108,16 +150,28 @@ export default class RFB extends EventTargetMixin { }; // Mouse state - this._mouse_buttonMask = 0; - this._mouse_arr = []; + this._mousePos = {}; + this._mouseButtonMask = 0; + this._mouseLastMoveTime = 0; this._viewportDragging = false; this._viewportDragPos = {}; this._viewportHasMoved = false; + this._accumulatedWheelDeltaX = 0; + this._accumulatedWheelDeltaY = 0; + + // Gesture state + this._gestureLastTapTime = null; + this._gestureFirstDoubleTapEv = null; + this._gestureLastMagnitudeX = 0; + this._gestureLastMagnitudeY = 0; // Bound event handlers this._eventHandlers = { focusCanvas: this._focusCanvas.bind(this), windowResize: this._windowResize.bind(this), + handleMouse: this._handleMouse.bind(this), + handleWheel: this._handleWheel.bind(this), + handleGesture: this._handleGesture.bind(this), }; // main setup @@ -172,27 +226,24 @@ export default class RFB extends EventTargetMixin { throw exc; } this._display.onflush = this._onFlush.bind(this); - this._display.clear(); this._keyboard = new Keyboard(this._canvas); this._keyboard.onkeyevent = this._handleKeyEvent.bind(this); - this._mouse = new Mouse(this._canvas); - this._mouse.onmousebutton = this._handleMouseButton.bind(this); - this._mouse.onmousemove = this._handleMouseMove.bind(this); + this._gestures = new GestureHandler(); this._sock = new Websock(); this._sock.on('message', () => { - this._handle_message(); + this._handleMessage(); }); this._sock.on('open', () => { - if ((this._rfb_connection_state === 'connecting') && - (this._rfb_init_state === '')) { - this._rfb_init_state = 'ProtocolVersion'; + if ((this._rfbConnectionState === 'connecting') && + (this._rfbInitState === '')) { + this._rfbInitState = 'ProtocolVersion'; Log.Debug("Starting VNC handshake"); } else { this._fail("Unexpected server connection while " + - this._rfb_connection_state); + this._rfbConnectionState); } }); this._sock.on('close', (e) => { @@ -205,7 +256,7 @@ export default class RFB extends EventTargetMixin { } msg += ")"; } - switch (this._rfb_connection_state) { + switch (this._rfbConnectionState) { case 'connecting': this._fail("Connection closed " + msg); break; @@ -246,6 +297,15 @@ export default class RFB extends EventTargetMixin { this._clipViewport = false; this._scaleViewport = false; this._resizeSession = false; + + this._showDotCursor = false; + if (options.showDotCursor !== undefined) { + Log.Warn("Specifying showDotCursor as a RFB constructor argument is deprecated"); + this._showDotCursor = options.showDotCursor; + } + + this._qualityLevel = 6; + this._compressionLevel = 2; } // ===== PROPERTIES ===== @@ -254,22 +314,20 @@ export default class RFB extends EventTargetMixin { set viewOnly(viewOnly) { this._viewOnly = viewOnly; - if (this._rfb_connection_state === "connecting" || - this._rfb_connection_state === "connected") { + if (this._rfbConnectionState === "connecting" || + this._rfbConnectionState === "connected") { if (viewOnly) { this._keyboard.ungrab(); - this._mouse.ungrab(); } else { this._keyboard.grab(); - this._mouse.grab(); } } } get capabilities() { return this._capabilities; } - get touchButton() { return this._mouse.touchButton; } - set touchButton(button) { this._mouse.touchButton = button; } + get touchButton() { return 0; } + set touchButton(button) { Log.Warn("Using old API!"); } get clipViewport() { return this._clipViewport; } set clipViewport(viewport) { @@ -308,6 +366,46 @@ export default class RFB extends EventTargetMixin { get background() { return this._screen.style.background; } set background(cssValue) { this._screen.style.background = cssValue; } + get qualityLevel() { + return this._qualityLevel; + } + set qualityLevel(qualityLevel) { + if (!Number.isInteger(qualityLevel) || qualityLevel < 0 || qualityLevel > 9) { + Log.Error("qualityLevel must be an integer between 0 and 9"); + return; + } + + if (this._qualityLevel === qualityLevel) { + return; + } + + this._qualityLevel = qualityLevel; + + if (this._rfbConnectionState === 'connected') { + this._sendEncodings(); + } + } + + get compressionLevel() { + return this._compressionLevel; + } + set compressionLevel(compressionLevel) { + if (!Number.isInteger(compressionLevel) || compressionLevel < 0 || compressionLevel > 9) { + Log.Error("compressionLevel must be an integer between 0 and 9"); + return; + } + + if (this._compressionLevel === compressionLevel) { + return; + } + + this._compressionLevel = compressionLevel; + + if (this._rfbConnectionState === 'connected') { + this._sendEncodings(); + } + } + // ===== PUBLIC METHODS ===== disconnect() { @@ -318,12 +416,29 @@ export default class RFB extends EventTargetMixin { } sendCredentials(creds) { - this._rfb_credentials = creds; - setTimeout(this._init_msg.bind(this), 0); + this._rfbCredentials = creds; + setTimeout(this._initMsg.bind(this), 0); + } + + sendText(text) { + for (var i = 0; i < text.length; i++) { + const character = text.charAt(i); + var charCode = USKeyTable[character] || false; + if (charCode) { + this.sendKey(charCode, character, true); + this.sendKey(charCode, character, false); + } else { + charCode = text.charCodeAt(i) + this.sendKey(KeyTable.XK_Shift_L, "ShiftLeft", true); + this.sendKey(charCode, character, true); + this.sendKey(charCode, character, false); + this.sendKey(KeyTable.XK_Shift_L, "ShiftLeft", false); + } + } } sendCtrlAltDel() { - if (this._rfb_connection_state !== 'connected' || this._viewOnly) { return; } + if (this._rfbConnectionState !== 'connected' || this._viewOnly) { return; } Log.Info("Sending Ctrl-Alt-Del"); this.sendKey(KeyTable.XK_Control_L, "ControlLeft", true); @@ -359,7 +474,7 @@ export default class RFB extends EventTargetMixin { // Send a key press. If 'down' is not specified then send a down key // followed by an up key. sendKey(keysym, code, down) { - if (this._rfb_connection_state !== 'connected' || this._viewOnly) { return; } + if (this._rfbConnectionState !== 'connected' || this._viewOnly) { return; } if (down === undefined) { this.sendKey(keysym, code, true); @@ -394,8 +509,22 @@ export default class RFB extends EventTargetMixin { } clipboardPasteFrom(text) { - if (this._rfb_connection_state !== 'connected' || this._viewOnly) { return; } - RFB.messages.clientCutText(this._sock, text); + if (this._rfbConnectionState !== 'connected' || this._viewOnly) { return; } + + if (this._clipboardServerCapabilitiesFormats[extendedClipboardFormatText] && + this._clipboardServerCapabilitiesActions[extendedClipboardActionNotify]) { + + this._clipboardText = text; + RFB.messages.extendedClipboardNotify(this._sock, [extendedClipboardFormatText]); + } else { + let data = new Uint8Array(text.length); + for (let i = 0; i < text.length; i++) { + // FIXME: text can have values outside of Latin1/Uint8 + data[i] = text.charCodeAt(i); + } + + RFB.messages.clientCutText(this._sock, data); + } } // ===== PRIVATE METHODS ===== @@ -407,7 +536,7 @@ export default class RFB extends EventTargetMixin { try { // WebSocket.onopen transitions to the RFB init states - this._sock.open(this._url, ['binary']); + this._sock.open(this._url, this._wsProtocols); } catch (e) { if (e.name === 'SyntaxError') { this._fail("Invalid host or port (" + e + ")"); @@ -419,6 +548,8 @@ export default class RFB extends EventTargetMixin { // Make our elements part of the page this._target.appendChild(this._screen); + this._gestures.attach(this._canvas); + this._cursor.attach(this._canvas); this._refreshCursor(); @@ -430,17 +561,44 @@ export default class RFB extends EventTargetMixin { this._canvas.addEventListener("mousedown", this._eventHandlers.focusCanvas); this._canvas.addEventListener("touchstart", this._eventHandlers.focusCanvas); + // Mouse events + this._canvas.addEventListener('mousedown', this._eventHandlers.handleMouse); + this._canvas.addEventListener('mouseup', this._eventHandlers.handleMouse); + this._canvas.addEventListener('mousemove', this._eventHandlers.handleMouse); + // Prevent middle-click pasting (see handler for why we bind to document) + this._canvas.addEventListener('click', this._eventHandlers.handleMouse); + // preventDefault() on mousedown doesn't stop this event for some + // reason so we have to explicitly block it + this._canvas.addEventListener('contextmenu', this._eventHandlers.handleMouse); + + // Wheel events + this._canvas.addEventListener("wheel", this._eventHandlers.handleWheel); + + // Gesture events + this._canvas.addEventListener("gesturestart", this._eventHandlers.handleGesture); + this._canvas.addEventListener("gesturemove", this._eventHandlers.handleGesture); + this._canvas.addEventListener("gestureend", this._eventHandlers.handleGesture); + Log.Debug("<< RFB.connect"); } _disconnect() { Log.Debug(">> RFB.disconnect"); this._cursor.detach(); + this._canvas.removeEventListener("gesturestart", this._eventHandlers.handleGesture); + this._canvas.removeEventListener("gesturemove", this._eventHandlers.handleGesture); + this._canvas.removeEventListener("gestureend", this._eventHandlers.handleGesture); + this._canvas.removeEventListener("wheel", this._eventHandlers.handleWheel); + this._canvas.removeEventListener('mousedown', this._eventHandlers.handleMouse); + this._canvas.removeEventListener('mouseup', this._eventHandlers.handleMouse); + this._canvas.removeEventListener('mousemove', this._eventHandlers.handleMouse); + this._canvas.removeEventListener('click', this._eventHandlers.handleMouse); + this._canvas.removeEventListener('contextmenu', this._eventHandlers.handleMouse); this._canvas.removeEventListener("mousedown", this._eventHandlers.focusCanvas); this._canvas.removeEventListener("touchstart", this._eventHandlers.focusCanvas); window.removeEventListener('resize', this._eventHandlers.windowResize); this._keyboard.ungrab(); - this._mouse.ungrab(); + this._gestures.detach(); this._sock.close(); try { this._target.removeChild(this._screen); @@ -453,15 +611,11 @@ export default class RFB extends EventTargetMixin { } } clearTimeout(this._resizeTimeout); + clearTimeout(this._mouseMoveTimer); Log.Debug("<< RFB.disconnect"); } _focusCanvas(event) { - // Respect earlier handlers' request to not do side-effects - if (event.defaultPrevented) { - return; - } - if (!this.focusOnClick) { return; } @@ -469,6 +623,13 @@ export default class RFB extends EventTargetMixin { this.focus(); } + _setDesktopName(name) { + this._fbName = name; + this.dispatchEvent(new CustomEvent( + "desktopname", + { detail: { name: this._fbName } })); + } + _windowResize(event) { // If the window resized then our screen element might have // as well. Update the viewport dimensions. @@ -491,19 +652,19 @@ export default class RFB extends EventTargetMixin { // Update state of clipping in Display object, and make sure the // configured viewport matches the current screen size _updateClip() { - const cur_clip = this._display.clipViewport; - let new_clip = this._clipViewport; + const curClip = this._display.clipViewport; + let newClip = this._clipViewport; if (this._scaleViewport) { // Disable viewport clipping if we are scaling - new_clip = false; + newClip = false; } - if (cur_clip !== new_clip) { - this._display.clipViewport = new_clip; + if (curClip !== newClip) { + this._display.clipViewport = newClip; } - if (new_clip) { + if (newClip) { // When clipping is enabled, the screen is limited to // the size of the container. const size = this._screenSize(); @@ -536,7 +697,7 @@ export default class RFB extends EventTargetMixin { const size = this._screenSize(); RFB.messages.setDesktopSize(this._sock, Math.floor(size.w), Math.floor(size.h), - this._screen_id, this._screen_flags); + this._screenID, this._screenFlags); Log.Debug('Requested new desktop size: ' + size.w + 'x' + size.h); @@ -568,7 +729,7 @@ export default class RFB extends EventTargetMixin { * disconnected - permanent state */ _updateConnectionState(state) { - const oldstate = this._rfb_connection_state; + const oldstate = this._rfbConnectionState; if (state === oldstate) { Log.Debug("Already in state '" + state + "', ignoring"); @@ -622,7 +783,7 @@ export default class RFB extends EventTargetMixin { // State change actions - this._rfb_connection_state = state; + this._rfbConnectionState = state; Log.Debug("New state '" + state + "', was '" + oldstate + "'."); @@ -656,7 +817,7 @@ export default class RFB extends EventTargetMixin { case 'disconnected': this.dispatchEvent(new CustomEvent( "disconnect", { detail: - { clean: this._rfb_clean_disconnect } })); + { clean: this._rfbCleanDisconnect } })); break; } } @@ -667,7 +828,7 @@ export default class RFB extends EventTargetMixin { * should be logged but not sent to the user interface. */ _fail(details) { - switch (this._rfb_connection_state) { + switch (this._rfbConnectionState) { case 'disconnecting': Log.Error("Failed when disconnecting: " + details); break; @@ -681,7 +842,7 @@ export default class RFB extends EventTargetMixin { Log.Error("RFB failure: " + details); break; } - this._rfb_clean_disconnect = false; //This is sent to the UI + this._rfbCleanDisconnect = false; //This is sent to the UI // Transition to disconnected without waiting for socket to close this._updateConnectionState('disconnecting'); @@ -696,13 +857,13 @@ export default class RFB extends EventTargetMixin { { detail: { capabilities: this._capabilities } })); } - _handle_message() { + _handleMessage() { if (this._sock.rQlen === 0) { - Log.Warn("handle_message called on an empty receive queue"); + Log.Warn("handleMessage called on an empty receive queue"); return; } - switch (this._rfb_connection_state) { + switch (this._rfbConnectionState) { case 'disconnected': Log.Error("Got data while disconnected"); break; @@ -711,7 +872,7 @@ export default class RFB extends EventTargetMixin { if (this._flushing) { break; } - if (!this._normal_msg()) { + if (!this._normalMsg()) { break; } if (this._sock.rQlen === 0) { @@ -720,7 +881,7 @@ export default class RFB extends EventTargetMixin { } break; default: - this._init_msg(); + this._initMsg(); break; } } @@ -729,13 +890,52 @@ export default class RFB extends EventTargetMixin { this.sendKey(keysym, code, down); } - _handleMouseButton(x, y, down, bmask) { - if (down) { - this._mouse_buttonMask |= bmask; - } else { - this._mouse_buttonMask &= ~bmask; + _handleMouse(ev) { + /* + * We don't check connection status or viewOnly here as the + * mouse events might be used to control the viewport + */ + + if (ev.type === 'click') { + /* + * Note: This is only needed for the 'click' event as it fails + * to fire properly for the target element so we have + * to listen on the document element instead. + */ + if (ev.target !== this._canvas) { + return; + } } + // FIXME: if we're in view-only and not dragging, + // should we stop events? + ev.stopPropagation(); + ev.preventDefault(); + + if ((ev.type === 'click') || (ev.type === 'contextmenu')) { + return; + } + + let pos = clientToElement(ev.clientX, ev.clientY, + this._canvas); + + switch (ev.type) { + case 'mousedown': + setCapture(this._canvas); + this._handleMouseButton(pos.x, pos.y, + true, 1 << ev.button); + break; + case 'mouseup': + this._handleMouseButton(pos.x, pos.y, + false, 1 << ev.button); + break; + case 'mousemove': + this._handleMouseMove(pos.x, pos.y); + break; + } + } + + _handleMouseButton(x, y, down, bmask) { if (this.dragViewport) { if (down && !this._viewportDragging) { this._viewportDragging = true; @@ -756,17 +956,24 @@ export default class RFB extends EventTargetMixin { // Otherwise we treat this as a mouse click event. // Send the button down event here, as the button up // event is sent at the end of this function. - RFB.messages.pointerEvent(this._sock, - this._display.absX(x), - this._display.absY(y), - bmask); + this._sendMouse(x, y, bmask); } } - if (this._viewOnly) { return; } // View only, skip mouse events + // Flush waiting move event first + if (this._mouseMoveTimer !== null) { + clearTimeout(this._mouseMoveTimer); + this._mouseMoveTimer = null; + this._sendMouse(x, y, this._mouseButtonMask); + } - if (this._rfb_connection_state !== 'connected') { return; } - RFB.messages.pointerEvent(this._sock, this._display.absX(x), this._display.absY(y), this._mouse_buttonMask); + if (down) { + this._mouseButtonMask |= bmask; + } else { + this._mouseButtonMask &= ~bmask; + } + + this._sendMouse(x, y, this._mouseButtonMask); } _handleMouseMove(x, y) { @@ -786,66 +993,304 @@ export default class RFB extends EventTargetMixin { return; } + this._mousePos = { 'x': x, 'y': y }; + + // Limit many mouse move events to one every MOUSE_MOVE_DELAY ms + if (this._mouseMoveTimer == null) { + + const timeSinceLastMove = Date.now() - this._mouseLastMoveTime; + if (timeSinceLastMove > MOUSE_MOVE_DELAY) { + this._sendMouse(x, y, this._mouseButtonMask); + this._mouseLastMoveTime = Date.now(); + } else { + // Too soon since the latest move, wait the remaining time + this._mouseMoveTimer = setTimeout(() => { + this._handleDelayedMouseMove(); + }, MOUSE_MOVE_DELAY - timeSinceLastMove); + } + } + } + + _handleDelayedMouseMove() { + this._mouseMoveTimer = null; + this._sendMouse(this._mousePos.x, this._mousePos.y, + this._mouseButtonMask); + this._mouseLastMoveTime = Date.now(); + } + + _sendMouse(x, y, mask) { + if (this._rfbConnectionState !== 'connected') { return; } if (this._viewOnly) { return; } // View only, skip mouse events - if (this._rfb_connection_state !== 'connected') { return; } - RFB.messages.pointerEvent(this._sock, this._display.absX(x), this._display.absY(y), this._mouse_buttonMask); + RFB.messages.pointerEvent(this._sock, this._display.absX(x), + this._display.absY(y), mask); + } + + _handleWheel(ev) { + if (this._rfbConnectionState !== 'connected') { return; } + if (this._viewOnly) { return; } // View only, skip mouse events + + ev.stopPropagation(); + ev.preventDefault(); + + let pos = clientToElement(ev.clientX, ev.clientY, + this._canvas); + + let dX = ev.deltaX; + let dY = ev.deltaY; + + // Pixel units unless it's non-zero. + // Note that if deltamode is line or page won't matter since we aren't + // sending the mouse wheel delta to the server anyway. + // The difference between pixel and line can be important however since + // we have a threshold that can be smaller than the line height. + if (ev.deltaMode !== 0) { + dX *= WHEEL_LINE_HEIGHT; + dY *= WHEEL_LINE_HEIGHT; + } + + // Mouse wheel events are sent in steps over VNC. This means that the VNC + // protocol can't handle a wheel event with specific distance or speed. + // Therefor, if we get a lot of small mouse wheel events we combine them. + this._accumulatedWheelDeltaX += dX; + this._accumulatedWheelDeltaY += dY; + + // Generate a mouse wheel step event when the accumulated delta + // for one of the axes is large enough. + if (Math.abs(this._accumulatedWheelDeltaX) >= WHEEL_STEP) { + if (this._accumulatedWheelDeltaX < 0) { + this._handleMouseButton(pos.x, pos.y, true, 1 << 5); + this._handleMouseButton(pos.x, pos.y, false, 1 << 5); + } else if (this._accumulatedWheelDeltaX > 0) { + this._handleMouseButton(pos.x, pos.y, true, 1 << 6); + this._handleMouseButton(pos.x, pos.y, false, 1 << 6); + } + + this._accumulatedWheelDeltaX = 0; + } + if (Math.abs(this._accumulatedWheelDeltaY) >= WHEEL_STEP) { + if (this._accumulatedWheelDeltaY < 0) { + this._handleMouseButton(pos.x, pos.y, true, 1 << 3); + this._handleMouseButton(pos.x, pos.y, false, 1 << 3); + } else if (this._accumulatedWheelDeltaY > 0) { + this._handleMouseButton(pos.x, pos.y, true, 1 << 4); + this._handleMouseButton(pos.x, pos.y, false, 1 << 4); + } + + this._accumulatedWheelDeltaY = 0; + } + } + + _fakeMouseMove(ev, elementX, elementY) { + this._handleMouseMove(elementX, elementY); + this._cursor.move(ev.detail.clientX, ev.detail.clientY); + } + + _handleTapEvent(ev, bmask) { + let pos = clientToElement(ev.detail.clientX, ev.detail.clientY, + this._canvas); + + // If the user quickly taps multiple times we assume they meant to + // hit the same spot, so slightly adjust coordinates + + if ((this._gestureLastTapTime !== null) && + ((Date.now() - this._gestureLastTapTime) < DOUBLE_TAP_TIMEOUT) && + (this._gestureFirstDoubleTapEv.detail.type === ev.detail.type)) { + let dx = this._gestureFirstDoubleTapEv.detail.clientX - ev.detail.clientX; + let dy = this._gestureFirstDoubleTapEv.detail.clientY - ev.detail.clientY; + let distance = Math.hypot(dx, dy); + + if (distance < DOUBLE_TAP_THRESHOLD) { + pos = clientToElement(this._gestureFirstDoubleTapEv.detail.clientX, + this._gestureFirstDoubleTapEv.detail.clientY, + this._canvas); + } else { + this._gestureFirstDoubleTapEv = ev; + } + } else { + this._gestureFirstDoubleTapEv = ev; + } + this._gestureLastTapTime = Date.now(); + + this._fakeMouseMove(this._gestureFirstDoubleTapEv, pos.x, pos.y); + this._handleMouseButton(pos.x, pos.y, true, bmask); + this._handleMouseButton(pos.x, pos.y, false, bmask); + } + + _handleGesture(ev) { + let magnitude; + + let pos = clientToElement(ev.detail.clientX, ev.detail.clientY, + this._canvas); + switch (ev.type) { + case 'gesturestart': + switch (ev.detail.type) { + case 'onetap': + this._handleTapEvent(ev, 0x1); + break; + case 'twotap': + this._handleTapEvent(ev, 0x4); + break; + case 'threetap': + this._handleTapEvent(ev, 0x2); + break; + case 'drag': + this._fakeMouseMove(ev, pos.x, pos.y); + this._handleMouseButton(pos.x, pos.y, true, 0x1); + break; + case 'longpress': + this._fakeMouseMove(ev, pos.x, pos.y); + this._handleMouseButton(pos.x, pos.y, true, 0x4); + break; + + case 'twodrag': + this._gestureLastMagnitudeX = ev.detail.magnitudeX; + this._gestureLastMagnitudeY = ev.detail.magnitudeY; + this._fakeMouseMove(ev, pos.x, pos.y); + break; + case 'pinch': + this._gestureLastMagnitudeX = Math.hypot(ev.detail.magnitudeX, + ev.detail.magnitudeY); + this._fakeMouseMove(ev, pos.x, pos.y); + break; + } + break; + + case 'gesturemove': + switch (ev.detail.type) { + case 'onetap': + case 'twotap': + case 'threetap': + break; + case 'drag': + case 'longpress': + this._fakeMouseMove(ev, pos.x, pos.y); + break; + case 'twodrag': + // Always scroll in the same position. + // We don't know if the mouse was moved so we need to move it + // every update. + this._fakeMouseMove(ev, pos.x, pos.y); + while ((ev.detail.magnitudeY - this._gestureLastMagnitudeY) > GESTURE_SCRLSENS) { + this._handleMouseButton(pos.x, pos.y, true, 0x8); + this._handleMouseButton(pos.x, pos.y, false, 0x8); + this._gestureLastMagnitudeY += GESTURE_SCRLSENS; + } + while ((ev.detail.magnitudeY - this._gestureLastMagnitudeY) < -GESTURE_SCRLSENS) { + this._handleMouseButton(pos.x, pos.y, true, 0x10); + this._handleMouseButton(pos.x, pos.y, false, 0x10); + this._gestureLastMagnitudeY -= GESTURE_SCRLSENS; + } + while ((ev.detail.magnitudeX - this._gestureLastMagnitudeX) > GESTURE_SCRLSENS) { + this._handleMouseButton(pos.x, pos.y, true, 0x20); + this._handleMouseButton(pos.x, pos.y, false, 0x20); + this._gestureLastMagnitudeX += GESTURE_SCRLSENS; + } + while ((ev.detail.magnitudeX - this._gestureLastMagnitudeX) < -GESTURE_SCRLSENS) { + this._handleMouseButton(pos.x, pos.y, true, 0x40); + this._handleMouseButton(pos.x, pos.y, false, 0x40); + this._gestureLastMagnitudeX -= GESTURE_SCRLSENS; + } + break; + case 'pinch': + // Always scroll in the same position. + // We don't know if the mouse was moved so we need to move it + // every update. + this._fakeMouseMove(ev, pos.x, pos.y); + magnitude = Math.hypot(ev.detail.magnitudeX, ev.detail.magnitudeY); + if (Math.abs(magnitude - this._gestureLastMagnitudeX) > GESTURE_ZOOMSENS) { + this._handleKeyEvent(KeyTable.XK_Control_L, "ControlLeft", true); + while ((magnitude - this._gestureLastMagnitudeX) > GESTURE_ZOOMSENS) { + this._handleMouseButton(pos.x, pos.y, true, 0x8); + this._handleMouseButton(pos.x, pos.y, false, 0x8); + this._gestureLastMagnitudeX += GESTURE_ZOOMSENS; + } + while ((magnitude - this._gestureLastMagnitudeX) < -GESTURE_ZOOMSENS) { + this._handleMouseButton(pos.x, pos.y, true, 0x10); + this._handleMouseButton(pos.x, pos.y, false, 0x10); + this._gestureLastMagnitudeX -= GESTURE_ZOOMSENS; + } + } + this._handleKeyEvent(KeyTable.XK_Control_L, "ControlLeft", false); + break; + } + break; + + case 'gestureend': + switch (ev.detail.type) { + case 'onetap': + case 'twotap': + case 'threetap': + case 'pinch': + case 'twodrag': + break; + case 'drag': + this._fakeMouseMove(ev, pos.x, pos.y); + this._handleMouseButton(pos.x, pos.y, false, 0x1); + break; + case 'longpress': + this._fakeMouseMove(ev, pos.x, pos.y); + this._handleMouseButton(pos.x, pos.y, false, 0x4); + break; + } + break; + } } // Message Handlers - _negotiate_protocol_version() { + _negotiateProtocolVersion() { if (this._sock.rQwait("version", 12)) { return false; } const sversion = this._sock.rQshiftStr(12).substr(4, 7); Log.Info("Server ProtocolVersion: " + sversion); - let is_repeater = 0; + let isRepeater = 0; switch (sversion) { case "000.000": // UltraVNC repeater - is_repeater = 1; + isRepeater = 1; break; case "003.003": case "003.006": // UltraVNC case "003.889": // Apple Remote Desktop - this._rfb_version = 3.3; + this._rfbVersion = 3.3; break; case "003.007": - this._rfb_version = 3.7; + this._rfbVersion = 3.7; break; case "003.008": case "004.000": // Intel AMT KVM case "004.001": // RealVNC 4.6 case "005.000": // RealVNC 5.3 - this._rfb_version = 3.8; + this._rfbVersion = 3.8; break; default: return this._fail("Invalid server version " + sversion); } - if (is_repeater) { + if (isRepeater) { let repeaterID = "ID:" + this._repeaterID; while (repeaterID.length < 250) { repeaterID += "\0"; } - this._sock.send_string(repeaterID); + this._sock.sendString(repeaterID); return true; } - if (this._rfb_version > this._rfb_max_version) { - this._rfb_version = this._rfb_max_version; + if (this._rfbVersion > this._rfbMaxVersion) { + this._rfbVersion = this._rfbMaxVersion; } - const cversion = "00" + parseInt(this._rfb_version, 10) + - ".00" + ((this._rfb_version * 10) % 10); - this._sock.send_string("RFB " + cversion + "\n"); + const cversion = "00" + parseInt(this._rfbVersion, 10) + + ".00" + ((this._rfbVersion * 10) % 10); + this._sock.sendString("RFB " + cversion + "\n"); Log.Debug('Sent ProtocolVersion: ' + cversion); - this._rfb_init_state = 'Security'; + this._rfbInitState = 'Security'; } - _negotiate_security() { + _negotiateSecurity() { // Polyfill since IE and PhantomJS doesn't have // TypedArray.includes() function includes(item, array) { @@ -857,55 +1302,57 @@ export default class RFB extends EventTargetMixin { return false; } - if (this._rfb_version >= 3.7) { + if (this._rfbVersion >= 3.7) { // Server sends supported list, client decides - const num_types = this._sock.rQshift8(); - if (this._sock.rQwait("security type", num_types, 1)) { return false; } + const numTypes = this._sock.rQshift8(); + if (this._sock.rQwait("security type", numTypes, 1)) { return false; } - if (num_types === 0) { - this._rfb_init_state = "SecurityReason"; - this._security_context = "no security types"; - this._security_status = 1; - return this._init_msg(); + if (numTypes === 0) { + this._rfbInitState = "SecurityReason"; + this._securityContext = "no security types"; + this._securityStatus = 1; + return this._initMsg(); } - const types = this._sock.rQshiftBytes(num_types); + const types = this._sock.rQshiftBytes(numTypes); Log.Debug("Server security types: " + types); // Look for each auth in preferred order if (includes(1, types)) { - this._rfb_auth_scheme = 1; // None + this._rfbAuthScheme = 1; // None } else if (includes(22, types)) { - this._rfb_auth_scheme = 22; // XVP + this._rfbAuthScheme = 22; // XVP } else if (includes(16, types)) { - this._rfb_auth_scheme = 16; // Tight + this._rfbAuthScheme = 16; // Tight } else if (includes(2, types)) { - this._rfb_auth_scheme = 2; // VNC Auth + this._rfbAuthScheme = 2; // VNC Auth + } else if (includes(19, types)) { + this._rfbAuthScheme = 19; // VeNCrypt Auth } else { return this._fail("Unsupported security types (types: " + types + ")"); } - this._sock.send([this._rfb_auth_scheme]); + this._sock.send([this._rfbAuthScheme]); } else { // Server decides if (this._sock.rQwait("security scheme", 4)) { return false; } - this._rfb_auth_scheme = this._sock.rQshift32(); + this._rfbAuthScheme = this._sock.rQshift32(); - if (this._rfb_auth_scheme == 0) { - this._rfb_init_state = "SecurityReason"; - this._security_context = "authentication scheme"; - this._security_status = 1; - return this._init_msg(); + if (this._rfbAuthScheme == 0) { + this._rfbInitState = "SecurityReason"; + this._securityContext = "authentication scheme"; + this._securityStatus = 1; + return this._initMsg(); } } - this._rfb_init_state = 'Authentication'; - Log.Debug('Authenticating using scheme: ' + this._rfb_auth_scheme); + this._rfbInitState = 'Authentication'; + Log.Debug('Authenticating using scheme: ' + this._rfbAuthScheme); - return this._init_msg(); // jump to authentication + return this._initMsg(); // jump to authentication } - _handle_security_reason() { + _handleSecurityReason() { if (this._sock.rQwait("reason length", 4)) { return false; } @@ -920,46 +1367,134 @@ export default class RFB extends EventTargetMixin { if (reason !== "") { this.dispatchEvent(new CustomEvent( "securityfailure", - { detail: { status: this._security_status, + { detail: { status: this._securityStatus, reason: reason } })); return this._fail("Security negotiation failed on " + - this._security_context + + this._securityContext + " (reason: " + reason + ")"); } else { this.dispatchEvent(new CustomEvent( "securityfailure", - { detail: { status: this._security_status } })); + { detail: { status: this._securityStatus } })); return this._fail("Security negotiation failed on " + - this._security_context); + this._securityContext); } } // authentication - _negotiate_xvp_auth() { - if (!this._rfb_credentials.username || - !this._rfb_credentials.password || - !this._rfb_credentials.target) { + _negotiateXvpAuth() { + if (this._rfbCredentials.username === undefined || + this._rfbCredentials.password === undefined || + this._rfbCredentials.target === undefined) { this.dispatchEvent(new CustomEvent( "credentialsrequired", { detail: { types: ["username", "password", "target"] } })); return false; } - const xvp_auth_str = String.fromCharCode(this._rfb_credentials.username.length) + - String.fromCharCode(this._rfb_credentials.target.length) + - this._rfb_credentials.username + - this._rfb_credentials.target; - this._sock.send_string(xvp_auth_str); - this._rfb_auth_scheme = 2; - return this._negotiate_authentication(); + const xvpAuthStr = String.fromCharCode(this._rfbCredentials.username.length) + + String.fromCharCode(this._rfbCredentials.target.length) + + this._rfbCredentials.username + + this._rfbCredentials.target; + this._sock.sendString(xvpAuthStr); + this._rfbAuthScheme = 2; + return this._negotiateAuthentication(); } - _negotiate_std_vnc_auth() { + // VeNCrypt authentication, currently only supports version 0.2 and only Plain subtype + _negotiateVeNCryptAuth() { + + // waiting for VeNCrypt version + if (this._rfbVeNCryptState == 0) { + if (this._sock.rQwait("vencrypt version", 2)) { return false; } + + const major = this._sock.rQshift8(); + const minor = this._sock.rQshift8(); + + if (!(major == 0 && minor == 2)) { + return this._fail("Unsupported VeNCrypt version " + major + "." + minor); + } + + this._sock.send([0, 2]); + this._rfbVeNCryptState = 1; + } + + // waiting for ACK + if (this._rfbVeNCryptState == 1) { + if (this._sock.rQwait("vencrypt ack", 1)) { return false; } + + const res = this._sock.rQshift8(); + + if (res != 0) { + return this._fail("VeNCrypt failure " + res); + } + + this._rfbVeNCryptState = 2; + } + // must fall through here (i.e. no "else if"), beacause we may have already received + // the subtypes length and won't be called again + + if (this._rfbVeNCryptState == 2) { // waiting for subtypes length + if (this._sock.rQwait("vencrypt subtypes length", 1)) { return false; } + + const subtypesLength = this._sock.rQshift8(); + if (subtypesLength < 1) { + return this._fail("VeNCrypt subtypes empty"); + } + + this._rfbVeNCryptSubtypesLength = subtypesLength; + this._rfbVeNCryptState = 3; + } + + // waiting for subtypes list + if (this._rfbVeNCryptState == 3) { + if (this._sock.rQwait("vencrypt subtypes", 4 * this._rfbVeNCryptSubtypesLength)) { return false; } + + const subtypes = []; + for (let i = 0; i < this._rfbVeNCryptSubtypesLength; i++) { + subtypes.push(this._sock.rQshift32()); + } + + // 256 = Plain subtype + if (subtypes.indexOf(256) != -1) { + // 0x100 = 256 + this._sock.send([0, 0, 1, 0]); + this._rfbVeNCryptState = 4; + } else { + return this._fail("VeNCrypt Plain subtype not offered by server"); + } + } + + // negotiated Plain subtype, server waits for password + if (this._rfbVeNCryptState == 4) { + if (!this._rfbCredentials.username || + !this._rfbCredentials.password) { + this.dispatchEvent(new CustomEvent( + "credentialsrequired", + { detail: { types: ["username", "password"] } })); + return false; + } + + const user = encodeUTF8(this._rfbCredentials.username); + const pass = encodeUTF8(this._rfbCredentials.password); + + // XXX we assume lengths are <= 255 (should not be an issue in the real world) + this._sock.send([0, 0, 0, user.length]); + this._sock.send([0, 0, 0, pass.length]); + this._sock.sendString(user); + this._sock.sendString(pass); + + this._rfbInitState = "SecurityResult"; + return true; + } + } + + _negotiateStdVNCAuth() { if (this._sock.rQwait("auth challenge", 16)) { return false; } - if (!this._rfb_credentials.password) { + if (this._rfbCredentials.password === undefined) { this.dispatchEvent(new CustomEvent( "credentialsrequired", { detail: { types: ["password"] } })); @@ -968,23 +1503,40 @@ export default class RFB extends EventTargetMixin { // TODO(directxman12): make genDES not require an Array const challenge = Array.prototype.slice.call(this._sock.rQshiftBytes(16)); - const response = RFB.genDES(this._rfb_credentials.password, challenge); + const response = RFB.genDES(this._rfbCredentials.password, challenge); this._sock.send(response); - this._rfb_init_state = "SecurityResult"; + this._rfbInitState = "SecurityResult"; return true; } - _negotiate_tight_tunnels(numTunnels) { + _negotiateTightUnixAuth() { + if (this._rfbCredentials.username === undefined || + this._rfbCredentials.password === undefined) { + this.dispatchEvent(new CustomEvent( + "credentialsrequired", + { detail: { types: ["username", "password"] } })); + return false; + } + + this._sock.send([0, 0, 0, this._rfbCredentials.username.length]); + this._sock.send([0, 0, 0, this._rfbCredentials.password.length]); + this._sock.sendString(this._rfbCredentials.username); + this._sock.sendString(this._rfbCredentials.password); + this._rfbInitState = "SecurityResult"; + return true; + } + + _negotiateTightTunnels(numTunnels) { const clientSupportedTunnelTypes = { 0: { vendor: 'TGHT', signature: 'NOTUNNEL' } }; const serverSupportedTunnelTypes = {}; // receive tunnel capabilities for (let i = 0; i < numTunnels; i++) { - const cap_code = this._sock.rQshift32(); - const cap_vendor = this._sock.rQshiftStr(4); - const cap_signature = this._sock.rQshiftStr(8); - serverSupportedTunnelTypes[cap_code] = { vendor: cap_vendor, signature: cap_signature }; + const capCode = this._sock.rQshift32(); + const capVendor = this._sock.rQshiftStr(4); + const capSignature = this._sock.rQshiftStr(8); + serverSupportedTunnelTypes[capCode] = { vendor: capVendor, signature: capSignature }; } Log.Debug("Server Tight tunnel types: " + serverSupportedTunnelTypes); @@ -1015,16 +1567,16 @@ export default class RFB extends EventTargetMixin { } } - _negotiate_tight_auth() { - if (!this._rfb_tightvnc) { // first pass, do the tunnel negotiation + _negotiateTightAuth() { + if (!this._rfbTightVNC) { // first pass, do the tunnel negotiation if (this._sock.rQwait("num tunnels", 4)) { return false; } const numTunnels = this._sock.rQshift32(); if (numTunnels > 0 && this._sock.rQwait("tunnel capabilities", 16 * numTunnels, 4)) { return false; } - this._rfb_tightvnc = true; + this._rfbTightVNC = true; if (numTunnels > 0) { - this._negotiate_tight_tunnels(numTunnels); + this._negotiateTightTunnels(numTunnels); return false; // wait until we receive the sub auth to continue } } @@ -1033,7 +1585,7 @@ export default class RFB extends EventTargetMixin { if (this._sock.rQwait("sub auth count", 4)) { return false; } const subAuthCount = this._sock.rQshift32(); if (subAuthCount === 0) { // empty sub-auth list received means 'no auth' subtype selected - this._rfb_init_state = 'SecurityResult'; + this._rfbInitState = 'SecurityResult'; return true; } @@ -1041,7 +1593,8 @@ export default class RFB extends EventTargetMixin { const clientSupportedTypes = { 'STDVNOAUTH__': 1, - 'STDVVNCAUTH_': 2 + 'STDVVNCAUTH_': 2, + 'TGHTULGNAUTH': 129 }; const serverSupportedTypes = []; @@ -1061,11 +1614,14 @@ export default class RFB extends EventTargetMixin { switch (authType) { case 'STDVNOAUTH__': // no auth - this._rfb_init_state = 'SecurityResult'; + this._rfbInitState = 'SecurityResult'; return true; case 'STDVVNCAUTH_': // VNC auth - this._rfb_auth_scheme = 2; - return this._init_msg(); + this._rfbAuthScheme = 2; + return this._initMsg(); + case 'TGHTULGNAUTH': // UNIX auth + this._rfbAuthScheme = 129; + return this._initMsg(); default: return this._fail("Unsupported tiny auth scheme " + "(scheme: " + authType + ")"); @@ -1076,46 +1632,52 @@ export default class RFB extends EventTargetMixin { return this._fail("No supported sub-auth types!"); } - _negotiate_authentication() { - switch (this._rfb_auth_scheme) { + _negotiateAuthentication() { + switch (this._rfbAuthScheme) { case 1: // no auth - if (this._rfb_version >= 3.8) { - this._rfb_init_state = 'SecurityResult'; + if (this._rfbVersion >= 3.8) { + this._rfbInitState = 'SecurityResult'; return true; } - this._rfb_init_state = 'ClientInitialisation'; - return this._init_msg(); + this._rfbInitState = 'ClientInitialisation'; + return this._initMsg(); case 22: // XVP auth - return this._negotiate_xvp_auth(); + return this._negotiateXvpAuth(); case 2: // VNC authentication - return this._negotiate_std_vnc_auth(); + return this._negotiateStdVNCAuth(); case 16: // TightVNC Security Type - return this._negotiate_tight_auth(); + return this._negotiateTightAuth(); + + case 19: // VeNCrypt Security Type + return this._negotiateVeNCryptAuth(); + + case 129: // TightVNC UNIX Security Type + return this._negotiateTightUnixAuth(); default: return this._fail("Unsupported auth scheme (scheme: " + - this._rfb_auth_scheme + ")"); + this._rfbAuthScheme + ")"); } } - _handle_security_result() { + _handleSecurityResult() { if (this._sock.rQwait('VNC auth response ', 4)) { return false; } const status = this._sock.rQshift32(); if (status === 0) { // OK - this._rfb_init_state = 'ClientInitialisation'; + this._rfbInitState = 'ClientInitialisation'; Log.Debug('Authentication OK'); - return this._init_msg(); + return this._initMsg(); } else { - if (this._rfb_version >= 3.8) { - this._rfb_init_state = "SecurityReason"; - this._security_context = "security result"; - this._security_status = status; - return this._init_msg(); + if (this._rfbVersion >= 3.8) { + this._rfbInitState = "SecurityReason"; + this._securityContext = "security result"; + this._securityStatus = status; + return this._initMsg(); } else { this.dispatchEvent(new CustomEvent( "securityfailure", @@ -1126,7 +1688,7 @@ export default class RFB extends EventTargetMixin { } } - _negotiate_server_init() { + _negotiateServerInit() { if (this._sock.rQwait("server initialization", 24)) { return false; } /* Screen size */ @@ -1136,27 +1698,28 @@ export default class RFB extends EventTargetMixin { /* PIXEL_FORMAT */ const bpp = this._sock.rQshift8(); const depth = this._sock.rQshift8(); - const big_endian = this._sock.rQshift8(); - const true_color = this._sock.rQshift8(); + const bigEndian = this._sock.rQshift8(); + const trueColor = this._sock.rQshift8(); - const red_max = this._sock.rQshift16(); - const green_max = this._sock.rQshift16(); - const blue_max = this._sock.rQshift16(); - const red_shift = this._sock.rQshift8(); - const green_shift = this._sock.rQshift8(); - const blue_shift = this._sock.rQshift8(); + const redMax = this._sock.rQshift16(); + const greenMax = this._sock.rQshift16(); + const blueMax = this._sock.rQshift16(); + const redShift = this._sock.rQshift8(); + const greenShift = this._sock.rQshift8(); + const blueShift = this._sock.rQshift8(); this._sock.rQskipBytes(3); // padding // NB(directxman12): we don't want to call any callbacks or print messages until // *after* we're past the point where we could backtrack /* Connection name/title */ - const name_length = this._sock.rQshift32(); - if (this._sock.rQwait('server init name', name_length, 24)) { return false; } - this._fb_name = decodeUTF8(this._sock.rQshiftStr(name_length)); + const nameLength = this._sock.rQshift32(); + if (this._sock.rQwait('server init name', nameLength, 24)) { return false; } + let name = this._sock.rQshiftStr(nameLength); + name = decodeUTF8(name, true); - if (this._rfb_tightvnc) { - if (this._sock.rQwait('TightVNC extended server init header', 8, 24 + name_length)) { return false; } + if (this._rfbTightVNC) { + if (this._sock.rQwait('TightVNC extended server init header', 8, 24 + nameLength)) { return false; } // In TightVNC mode, ServerInit message is extended const numServerMessages = this._sock.rQshift16(); const numClientMessages = this._sock.rQshift16(); @@ -1164,7 +1727,7 @@ export default class RFB extends EventTargetMixin { this._sock.rQskipBytes(2); // padding const totalMessagesLength = (numServerMessages + numClientMessages + numEncodings) * 16; - if (this._sock.rQwait('TightVNC extended server init header', totalMessagesLength, 32 + name_length)) { return false; } + if (this._sock.rQwait('TightVNC extended server init header', totalMessagesLength, 32 + nameLength)) { return false; } // we don't actually do anything with the capability information that TIGHT sends, // so we just skip the all of this. @@ -1183,42 +1746,31 @@ export default class RFB extends EventTargetMixin { // if we backtrack Log.Info("Screen: " + width + "x" + height + ", bpp: " + bpp + ", depth: " + depth + - ", big_endian: " + big_endian + - ", true_color: " + true_color + - ", red_max: " + red_max + - ", green_max: " + green_max + - ", blue_max: " + blue_max + - ", red_shift: " + red_shift + - ", green_shift: " + green_shift + - ", blue_shift: " + blue_shift); - - if (big_endian !== 0) { - Log.Warn("Server native endian is not little endian"); - } - - if (red_shift !== 16) { - Log.Warn("Server native red-shift is not 16"); - } - - if (blue_shift !== 0) { - Log.Warn("Server native blue-shift is not 0"); - } + ", bigEndian: " + bigEndian + + ", trueColor: " + trueColor + + ", redMax: " + redMax + + ", greenMax: " + greenMax + + ", blueMax: " + blueMax + + ", redShift: " + redShift + + ", greenShift: " + greenShift + + ", blueShift: " + blueShift); + // we're past the point where we could backtrack, so it's safe to call this + this._setDesktopName(name); this._resize(width, height); if (!this._viewOnly) { this._keyboard.grab(); } - if (!this._viewOnly) { this._mouse.grab(); } - this._fb_depth = 24; + this._fbDepth = 24; - if (this._fb_name === "Intel(r) AMT KVM") { + if (this._fbName === "Intel(r) AMT KVM") { Log.Warn("Intel AMT KVM only supports 8/16 bit depths. Using low color mode."); - this._fb_depth = 8; + this._fbDepth = 8; } - RFB.messages.pixelFormat(this._sock, this._fb_depth, true); + RFB.messages.pixelFormat(this._sock, this._fbDepth, true); this._sendEncodings(); - RFB.messages.fbUpdateRequest(this._sock, false, 0, 0, this._fb_width, this._fb_height); + RFB.messages.fbUpdateRequest(this._sock, false, 0, 0, this._fbWidth, this._fbHeight); this._updateConnectionState('connected'); return true; @@ -1230,7 +1782,7 @@ export default class RFB extends EventTargetMixin { // In preference order encs.push(encodings.encodingCopyRect); // Only supported with full depth support - if (this._fb_depth == 24) { + if (this._fbDepth == 24) { encs.push(encodings.encodingTight); encs.push(encodings.encodingTightPNG); encs.push(encodings.encodingHextile); @@ -1239,8 +1791,8 @@ export default class RFB extends EventTargetMixin { encs.push(encodings.encodingRaw); // Psuedo-encoding settings - encs.push(encodings.pseudoEncodingQualityLevel0 + 6); - encs.push(encodings.pseudoEncodingCompressLevel0 + 2); + encs.push(encodings.pseudoEncodingQualityLevel0 + this._qualityLevel); + encs.push(encodings.pseudoEncodingCompressLevel0 + this._compressionLevel); encs.push(encodings.pseudoEncodingDesktopSize); encs.push(encodings.pseudoEncodingLastRect); @@ -1249,8 +1801,11 @@ export default class RFB extends EventTargetMixin { encs.push(encodings.pseudoEncodingXvp); encs.push(encodings.pseudoEncodingFence); encs.push(encodings.pseudoEncodingContinuousUpdates); + encs.push(encodings.pseudoEncodingDesktopName); + encs.push(encodings.pseudoEncodingExtendedClipboard); - if (this._fb_depth == 24) { + if (this._fbDepth == 24) { + encs.push(encodings.pseudoEncodingVMwareCursor); encs.push(encodings.pseudoEncodingCursor); } @@ -1265,63 +1820,212 @@ export default class RFB extends EventTargetMixin { * ClientInitialization - not triggered by server message * ServerInitialization */ - _init_msg() { - switch (this._rfb_init_state) { + _initMsg() { + switch (this._rfbInitState) { case 'ProtocolVersion': - return this._negotiate_protocol_version(); + return this._negotiateProtocolVersion(); case 'Security': - return this._negotiate_security(); + return this._negotiateSecurity(); case 'Authentication': - return this._negotiate_authentication(); + return this._negotiateAuthentication(); case 'SecurityResult': - return this._handle_security_result(); + return this._handleSecurityResult(); case 'SecurityReason': - return this._handle_security_reason(); + return this._handleSecurityReason(); case 'ClientInitialisation': - this._sock.send([0]); // ClientInitialisation for exclusive access - this._rfb_init_state = 'ServerInitialisation'; + this._sock.send([this._shared ? 1 : 0]); // ClientInitialisation + this._rfbInitState = 'ServerInitialisation'; return true; case 'ServerInitialisation': - return this._negotiate_server_init(); + return this._negotiateServerInit(); default: return this._fail("Unknown init state (state: " + - this._rfb_init_state + ")"); + this._rfbInitState + ")"); } } - _handle_set_colour_map_msg() { + _handleSetColourMapMsg() { Log.Debug("SetColorMapEntries"); return this._fail("Unexpected SetColorMapEntries message"); } - _handle_server_cut_text() { + _handleServerCutText() { Log.Debug("ServerCutText"); if (this._sock.rQwait("ServerCutText header", 7, 1)) { return false; } + this._sock.rQskipBytes(3); // Padding - const length = this._sock.rQshift32(); - if (this._sock.rQwait("ServerCutText", length, 8)) { return false; } - const text = this._sock.rQshiftStr(length); + let length = this._sock.rQshift32(); + length = toSigned32bit(length); - if (this._viewOnly) { return true; } + if (this._sock.rQwait("ServerCutText content", Math.abs(length), 8)) { return false; } - this.dispatchEvent(new CustomEvent( - "clipboard", - { detail: { text: text } })); + if (length >= 0) { + //Standard msg + const text = this._sock.rQshiftStr(length); + if (this._viewOnly) { + return true; + } + this.dispatchEvent(new CustomEvent( + "clipboard", + { detail: { text: text } })); + + } else { + //Extended msg. + length = Math.abs(length); + const flags = this._sock.rQshift32(); + let formats = flags & 0x0000FFFF; + let actions = flags & 0xFF000000; + + let isCaps = (!!(actions & extendedClipboardActionCaps)); + if (isCaps) { + this._clipboardServerCapabilitiesFormats = {}; + this._clipboardServerCapabilitiesActions = {}; + + // Update our server capabilities for Formats + for (let i = 0; i <= 15; i++) { + let index = 1 << i; + + // Check if format flag is set. + if ((formats & index)) { + this._clipboardServerCapabilitiesFormats[index] = true; + // We don't send unsolicited clipboard, so we + // ignore the size + this._sock.rQshift32(); + } + } + + // Update our server capabilities for Actions + for (let i = 24; i <= 31; i++) { + let index = 1 << i; + this._clipboardServerCapabilitiesActions[index] = !!(actions & index); + } + + /* Caps handling done, send caps with the clients + capabilities set as a response */ + let clientActions = [ + extendedClipboardActionCaps, + extendedClipboardActionRequest, + extendedClipboardActionPeek, + extendedClipboardActionNotify, + extendedClipboardActionProvide + ]; + RFB.messages.extendedClipboardCaps(this._sock, clientActions, {extendedClipboardFormatText: 0}); + + } else if (actions === extendedClipboardActionRequest) { + if (this._viewOnly) { + return true; + } + + // Check if server has told us it can handle Provide and there is clipboard data to send. + if (this._clipboardText != null && + this._clipboardServerCapabilitiesActions[extendedClipboardActionProvide]) { + + if (formats & extendedClipboardFormatText) { + RFB.messages.extendedClipboardProvide(this._sock, [extendedClipboardFormatText], [this._clipboardText]); + } + } + + } else if (actions === extendedClipboardActionPeek) { + if (this._viewOnly) { + return true; + } + + if (this._clipboardServerCapabilitiesActions[extendedClipboardActionNotify]) { + + if (this._clipboardText != null) { + RFB.messages.extendedClipboardNotify(this._sock, [extendedClipboardFormatText]); + } else { + RFB.messages.extendedClipboardNotify(this._sock, []); + } + } + + } else if (actions === extendedClipboardActionNotify) { + if (this._viewOnly) { + return true; + } + + if (this._clipboardServerCapabilitiesActions[extendedClipboardActionRequest]) { + + if (formats & extendedClipboardFormatText) { + RFB.messages.extendedClipboardRequest(this._sock, [extendedClipboardFormatText]); + } + } + + } else if (actions === extendedClipboardActionProvide) { + if (this._viewOnly) { + return true; + } + + if (!(formats & extendedClipboardFormatText)) { + return true; + } + // Ignore what we had in our clipboard client side. + this._clipboardText = null; + + // FIXME: Should probably verify that this data was actually requested + let zlibStream = this._sock.rQshiftBytes(length - 4); + let streamInflator = new Inflator(); + let textData = null; + + streamInflator.setInput(zlibStream); + for (let i = 0; i <= 15; i++) { + let format = 1 << i; + + if (formats & format) { + + let size = 0x00; + let sizeArray = streamInflator.inflate(4); + + size |= (sizeArray[0] << 24); + size |= (sizeArray[1] << 16); + size |= (sizeArray[2] << 8); + size |= (sizeArray[3]); + let chunk = streamInflator.inflate(size); + + if (format === extendedClipboardFormatText) { + textData = chunk; + } + } + } + streamInflator.setInput(null); + + if (textData !== null) { + let tmpText = ""; + for (let i = 0; i < textData.length; i++) { + tmpText += String.fromCharCode(textData[i]); + } + textData = tmpText; + + textData = decodeUTF8(textData); + if ((textData.length > 0) && "\0" === textData.charAt(textData.length - 1)) { + textData = textData.slice(0, -1); + } + + textData = textData.replace("\r\n", "\n"); + + this.dispatchEvent(new CustomEvent( + "clipboard", + { detail: { text: textData } })); + } + } else { + return this._fail("Unexpected action in extended clipboard message: " + actions); + } + } return true; } - _handle_server_fence_msg() { + _handleServerFenceMsg() { if (this._sock.rQwait("ServerFence header", 8, 1)) { return false; } this._sock.rQskipBytes(3); // Padding let flags = this._sock.rQshift32(); @@ -1363,49 +2067,49 @@ export default class RFB extends EventTargetMixin { return true; } - _handle_xvp_msg() { + _handleXvpMsg() { if (this._sock.rQwait("XVP version and message", 3, 1)) { return false; } this._sock.rQskipBytes(1); // Padding - const xvp_ver = this._sock.rQshift8(); - const xvp_msg = this._sock.rQshift8(); + const xvpVer = this._sock.rQshift8(); + const xvpMsg = this._sock.rQshift8(); - switch (xvp_msg) { + switch (xvpMsg) { case 0: // XVP_FAIL Log.Error("XVP Operation Failed"); break; case 1: // XVP_INIT - this._rfb_xvp_ver = xvp_ver; - Log.Info("XVP extensions enabled (version " + this._rfb_xvp_ver + ")"); + this._rfbXvpVer = xvpVer; + Log.Info("XVP extensions enabled (version " + this._rfbXvpVer + ")"); this._setCapability("power", true); break; default: - this._fail("Illegal server XVP message (msg: " + xvp_msg + ")"); + this._fail("Illegal server XVP message (msg: " + xvpMsg + ")"); break; } return true; } - _normal_msg() { - let msg_type; + _normalMsg() { + let msgType; if (this._FBU.rects > 0) { - msg_type = 0; + msgType = 0; } else { - msg_type = this._sock.rQshift8(); + msgType = this._sock.rQshift8(); } let first, ret; - switch (msg_type) { + switch (msgType) { case 0: // FramebufferUpdate ret = this._framebufferUpdate(); if (ret && !this._enabledContinuousUpdates) { RFB.messages.fbUpdateRequest(this._sock, true, 0, 0, - this._fb_width, this._fb_height); + this._fbWidth, this._fbHeight); } return ret; case 1: // SetColorMapEntries - return this._handle_set_colour_map_msg(); + return this._handleSetColourMapMsg(); case 2: // Bell Log.Debug("Bell"); @@ -1415,7 +2119,7 @@ export default class RFB extends EventTargetMixin { return true; case 3: // ServerCutText - return this._handle_server_cut_text(); + return this._handleServerCutText(); case 150: // EndOfContinuousUpdates first = !this._supportsContinuousUpdates; @@ -1432,13 +2136,13 @@ export default class RFB extends EventTargetMixin { return true; case 248: // ServerFence - return this._handle_server_fence_msg(); + return this._handleServerFenceMsg(); case 250: // XVP - return this._handle_xvp_msg(); + return this._handleXvpMsg(); default: - this._fail("Unexpected server message (type " + msg_type + ")"); + this._fail("Unexpected server message (type " + msgType + ")"); Log.Debug("sock.rQslice(0, 30): " + this._sock.rQslice(0, 30)); return true; } @@ -1448,7 +2152,7 @@ export default class RFB extends EventTargetMixin { this._flushing = false; // Resume processing if (this._sock.rQlen > 0) { - this._handle_message(); + this._handleMessage(); } } @@ -1500,6 +2204,9 @@ export default class RFB extends EventTargetMixin { this._FBU.rects = 1; // Will be decreased when we return return true; + case encodings.pseudoEncodingVMwareCursor: + return this._handleVMwareCursor(); + case encodings.pseudoEncodingCursor: return this._handleCursor(); @@ -1515,6 +2222,9 @@ export default class RFB extends EventTargetMixin { } return true; + case encodings.pseudoEncodingDesktopName: + return this._handleDesktopName(); + case encodings.pseudoEncodingDesktopSize: this._resize(this._FBU.width, this._FBU.height); return true; @@ -1527,6 +2237,122 @@ export default class RFB extends EventTargetMixin { } } + _handleVMwareCursor() { + const hotx = this._FBU.x; // hotspot-x + const hoty = this._FBU.y; // hotspot-y + const w = this._FBU.width; + const h = this._FBU.height; + if (this._sock.rQwait("VMware cursor encoding", 1)) { + return false; + } + + const cursorType = this._sock.rQshift8(); + + this._sock.rQshift8(); //Padding + + let rgba; + const bytesPerPixel = 4; + + //Classic cursor + if (cursorType == 0) { + //Used to filter away unimportant bits. + //OR is used for correct conversion in js. + const PIXEL_MASK = 0xffffff00 | 0; + rgba = new Array(w * h * bytesPerPixel); + + if (this._sock.rQwait("VMware cursor classic encoding", + (w * h * bytesPerPixel) * 2, 2)) { + return false; + } + + let andMask = new Array(w * h); + for (let pixel = 0; pixel < (w * h); pixel++) { + andMask[pixel] = this._sock.rQshift32(); + } + + let xorMask = new Array(w * h); + for (let pixel = 0; pixel < (w * h); pixel++) { + xorMask[pixel] = this._sock.rQshift32(); + } + + for (let pixel = 0; pixel < (w * h); pixel++) { + if (andMask[pixel] == 0) { + //Fully opaque pixel + let bgr = xorMask[pixel]; + let r = bgr >> 8 & 0xff; + let g = bgr >> 16 & 0xff; + let b = bgr >> 24 & 0xff; + + rgba[(pixel * bytesPerPixel) ] = r; //r + rgba[(pixel * bytesPerPixel) + 1 ] = g; //g + rgba[(pixel * bytesPerPixel) + 2 ] = b; //b + rgba[(pixel * bytesPerPixel) + 3 ] = 0xff; //a + + } else if ((andMask[pixel] & PIXEL_MASK) == + PIXEL_MASK) { + //Only screen value matters, no mouse colouring + if (xorMask[pixel] == 0) { + //Transparent pixel + rgba[(pixel * bytesPerPixel) ] = 0x00; + rgba[(pixel * bytesPerPixel) + 1 ] = 0x00; + rgba[(pixel * bytesPerPixel) + 2 ] = 0x00; + rgba[(pixel * bytesPerPixel) + 3 ] = 0x00; + + } else if ((xorMask[pixel] & PIXEL_MASK) == + PIXEL_MASK) { + //Inverted pixel, not supported in browsers. + //Fully opaque instead. + rgba[(pixel * bytesPerPixel) ] = 0x00; + rgba[(pixel * bytesPerPixel) + 1 ] = 0x00; + rgba[(pixel * bytesPerPixel) + 2 ] = 0x00; + rgba[(pixel * bytesPerPixel) + 3 ] = 0xff; + + } else { + //Unhandled xorMask + rgba[(pixel * bytesPerPixel) ] = 0x00; + rgba[(pixel * bytesPerPixel) + 1 ] = 0x00; + rgba[(pixel * bytesPerPixel) + 2 ] = 0x00; + rgba[(pixel * bytesPerPixel) + 3 ] = 0xff; + } + + } else { + //Unhandled andMask + rgba[(pixel * bytesPerPixel) ] = 0x00; + rgba[(pixel * bytesPerPixel) + 1 ] = 0x00; + rgba[(pixel * bytesPerPixel) + 2 ] = 0x00; + rgba[(pixel * bytesPerPixel) + 3 ] = 0xff; + } + } + + //Alpha cursor. + } else if (cursorType == 1) { + if (this._sock.rQwait("VMware cursor alpha encoding", + (w * h * 4), 2)) { + return false; + } + + rgba = new Array(w * h * bytesPerPixel); + + for (let pixel = 0; pixel < (w * h); pixel++) { + let data = this._sock.rQshift32(); + + rgba[(pixel * 4) ] = data >> 24 & 0xff; //r + rgba[(pixel * 4) + 1 ] = data >> 16 & 0xff; //g + rgba[(pixel * 4) + 2 ] = data >> 8 & 0xff; //b + rgba[(pixel * 4) + 3 ] = data & 0xff; //a + } + + } else { + Log.Warn("The given cursor type is not supported: " + + cursorType + " given."); + return false; + } + + this._updateCursor(rgba, hotx, hoty, w, h); + + return true; + } + _handleCursor() { const hotx = this._FBU.x; // hotspot-x const hoty = this._FBU.y; // hotspot-y @@ -1546,16 +2372,16 @@ export default class RFB extends EventTargetMixin { const mask = this._sock.rQshiftBytes(masklength); let rgba = new Uint8Array(w * h * 4); - let pix_idx = 0; + let pixIdx = 0; for (let y = 0; y < h; y++) { for (let x = 0; x < w; x++) { - let mask_idx = y * Math.ceil(w / 8) + Math.floor(x / 8); - let alpha = (mask[mask_idx] << (x % 8)) & 0x80 ? 255 : 0; - rgba[pix_idx ] = pixels[pix_idx + 2]; - rgba[pix_idx + 1] = pixels[pix_idx + 1]; - rgba[pix_idx + 2] = pixels[pix_idx]; - rgba[pix_idx + 3] = alpha; - pix_idx += 4; + let maskIdx = y * Math.ceil(w / 8) + Math.floor(x / 8); + let alpha = (mask[maskIdx] << (x % 8)) & 0x80 ? 255 : 0; + rgba[pixIdx ] = pixels[pixIdx + 2]; + rgba[pixIdx + 1] = pixels[pixIdx + 1]; + rgba[pixIdx + 2] = pixels[pixIdx]; + rgba[pixIdx + 3] = alpha; + pixIdx += 4; } } @@ -1564,14 +2390,33 @@ export default class RFB extends EventTargetMixin { return true; } + _handleDesktopName() { + if (this._sock.rQwait("DesktopName", 4)) { + return false; + } + + let length = this._sock.rQshift32(); + + if (this._sock.rQwait("DesktopName", length, 4)) { + return false; + } + + let name = this._sock.rQshiftStr(length); + name = decodeUTF8(name, true); + + this._setDesktopName(name); + + return true; + } + _handleExtendedDesktopSize() { if (this._sock.rQwait("ExtendedDesktopSize", 4)) { return false; } - const number_of_screens = this._sock.rQpeek8(); + const numberOfScreens = this._sock.rQpeek8(); - let bytes = 4 + (number_of_screens * 16); + let bytes = 4 + (numberOfScreens * 16); if (this._sock.rQwait("ExtendedDesktopSize", bytes)) { return false; } @@ -1590,15 +2435,15 @@ export default class RFB extends EventTargetMixin { this._sock.rQskipBytes(1); // number-of-screens this._sock.rQskipBytes(3); // padding - for (let i = 0; i < number_of_screens; i += 1) { + for (let i = 0; i < numberOfScreens; i += 1) { // Save the id and flags of the first screen if (i === 0) { - this._screen_id = this._sock.rQshiftBytes(4); // id + this._screenID = this._sock.rQshiftBytes(4); // id this._sock.rQskipBytes(2); // x-position this._sock.rQskipBytes(2); // y-position this._sock.rQskipBytes(2); // width this._sock.rQskipBytes(2); // height - this._screen_flags = this._sock.rQshiftBytes(4); // flags + this._screenFlags = this._sock.rQshiftBytes(4); // flags } else { this._sock.rQskipBytes(16); } @@ -1651,7 +2496,7 @@ export default class RFB extends EventTargetMixin { return decoder.decodeRect(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, this._sock, this._display, - this._fb_depth); + this._fbDepth); } catch (err) { this._fail("Error decoding rect: " + err); return false; @@ -1662,14 +2507,14 @@ export default class RFB extends EventTargetMixin { if (!this._enabledContinuousUpdates) { return; } RFB.messages.enableContinuousUpdates(this._sock, true, 0, 0, - this._fb_width, this._fb_height); + this._fbWidth, this._fbHeight); } _resize(width, height) { - this._fb_width = width; - this._fb_height = height; + this._fbWidth = width; + this._fbHeight = height; - this._display.resize(this._fb_width, this._fb_height); + this._display.resize(this._fbWidth, this._fbHeight); // Adjust the visible viewport based on the new dimensions this._updateClip(); @@ -1679,7 +2524,7 @@ export default class RFB extends EventTargetMixin { } _xvpOp(ver, op) { - if (this._rfb_xvp_ver < ver) { return; } + if (this._rfbXvpVer < ver) { return; } Log.Info("Sending XVP operation " + op + " (version " + ver + ")"); RFB.messages.xvpOp(this._sock, ver, op); } @@ -1715,6 +2560,10 @@ export default class RFB extends EventTargetMixin { } _refreshCursor() { + if (this._rfbConnectionState !== "connecting" && + this._rfbConnectionState !== "connected") { + return; + } const image = this._shouldShowDotCursor() ? RFB.cursors.dot : this._cursorImage; this._cursor.change(image.rgbaPixels, image.hotx, image.hoty, @@ -1750,13 +2599,13 @@ RFB.messages = { }, QEMUExtendedKeyEvent(sock, keysym, down, keycode) { - function getRFBkeycode(xt_scancode) { + function getRFBkeycode(xtScanCode) { const upperByte = (keycode >> 8); const lowerByte = (keycode & 0x00ff); if (upperByte === 0xe0 && lowerByte < 0x7f) { return lowerByte | 0x80; } - return xt_scancode; + return xtScanCode; } const buff = sock._sQ; @@ -1802,8 +2651,102 @@ RFB.messages = { sock.flush(); }, - // TODO(directxman12): make this unicode compatible? - clientCutText(sock, text) { + // Used to build Notify and Request data. + _buildExtendedClipboardFlags(actions, formats) { + let data = new Uint8Array(4); + let formatFlag = 0x00000000; + let actionFlag = 0x00000000; + + for (let i = 0; i < actions.length; i++) { + actionFlag |= actions[i]; + } + + for (let i = 0; i < formats.length; i++) { + formatFlag |= formats[i]; + } + + data[0] = actionFlag >> 24; // Actions + data[1] = 0x00; // Reserved + data[2] = 0x00; // Reserved + data[3] = formatFlag; // Formats + + return data; + }, + + extendedClipboardProvide(sock, formats, inData) { + // Deflate incomming data and their sizes + let deflator = new Deflator(); + let dataToDeflate = []; + + for (let i = 0; i < formats.length; i++) { + // We only support the format Text at this time + if (formats[i] != extendedClipboardFormatText) { + throw new Error("Unsupported extended clipboard format for Provide message."); + } + + // Change lone \r or \n into \r\n as defined in rfbproto + inData[i] = inData[i].replace(/\r\n|\r|\n/gm, "\r\n"); + + // Check if it already has \0 + let text = encodeUTF8(inData[i] + "\0"); + + dataToDeflate.push( (text.length >> 24) & 0xFF, + (text.length >> 16) & 0xFF, + (text.length >> 8) & 0xFF, + (text.length & 0xFF)); + + for (let j = 0; j < text.length; j++) { + dataToDeflate.push(text.charCodeAt(j)); + } + } + + let deflatedData = deflator.deflate(new Uint8Array(dataToDeflate)); + + // Build data to send + let data = new Uint8Array(4 + deflatedData.length); + data.set(RFB.messages._buildExtendedClipboardFlags([extendedClipboardActionProvide], + formats)); + data.set(deflatedData, 4); + + RFB.messages.clientCutText(sock, data, true); + }, + + extendedClipboardNotify(sock, formats) { + let flags = RFB.messages._buildExtendedClipboardFlags([extendedClipboardActionNotify], + formats); + RFB.messages.clientCutText(sock, flags, true); + }, + + extendedClipboardRequest(sock, formats) { + let flags = RFB.messages._buildExtendedClipboardFlags([extendedClipboardActionRequest], + formats); + RFB.messages.clientCutText(sock, flags, true); + }, + + extendedClipboardCaps(sock, actions, formats) { + let formatKeys = Object.keys(formats); + let data = new Uint8Array(4 + (4 * formatKeys.length)); + + formatKeys.map(x => parseInt(x)); + formatKeys.sort((a, b) => a - b); + + data.set(RFB.messages._buildExtendedClipboardFlags(actions, [])); + + let loopOffset = 4; + for (let i = 0; i < formatKeys.length; i++) { + data[loopOffset] = formats[formatKeys[i]] >> 24; + data[loopOffset + 1] = formats[formatKeys[i]] >> 16; + data[loopOffset + 2] = formats[formatKeys[i]] >> 8; + data[loopOffset + 3] = formats[formatKeys[i]] >> 0; + + loopOffset += 4; + data[3] |= (1 << formatKeys[i]); // Update our format flags + } + + RFB.messages.clientCutText(sock, data, true); + }, + + clientCutText(sock, data, extended = false) { const buff = sock._sQ; const offset = sock._sQlen; @@ -1813,7 +2756,12 @@ RFB.messages = { buff[offset + 2] = 0; // padding buff[offset + 3] = 0; // padding - let length = text.length; + let length; + if (extended) { + length = toUnsigned32bit(-data.length); + } else { + length = data.length; + } buff[offset + 4] = length >> 24; buff[offset + 5] = length >> 16; @@ -1822,24 +2770,25 @@ RFB.messages = { sock._sQlen += 8; - // We have to keep track of from where in the text we begin creating the + // We have to keep track of from where in the data we begin creating the // buffer for the flush in the next iteration. - let textOffset = 0; + let dataOffset = 0; - let remaining = length; + let remaining = data.length; while (remaining > 0) { let flushSize = Math.min(remaining, (sock._sQbufferSize - sock._sQlen)); for (let i = 0; i < flushSize; i++) { - buff[sock._sQlen + i] = text.charCodeAt(textOffset + i); + buff[sock._sQlen + i] = data[dataOffset + i]; } sock._sQlen += flushSize; sock.flush(); remaining -= flushSize; - textOffset += flushSize; + dataOffset += flushSize; } + }, setDesktopSize(sock, width, height, id, flags) { @@ -1925,7 +2874,7 @@ RFB.messages = { sock.flush(); }, - pixelFormat(sock, depth, true_color) { + pixelFormat(sock, depth, trueColor) { const buff = sock._sQ; const offset = sock._sQlen; @@ -1950,7 +2899,7 @@ RFB.messages = { buff[offset + 4] = bpp; // bits-per-pixel buff[offset + 5] = depth; // depth buff[offset + 6] = 0; // little-endian - buff[offset + 7] = true_color ? 1 : 0; // true-color + buff[offset + 7] = trueColor ? 1 : 0; // true-color buff[offset + 8] = 0; // red-max buff[offset + 9] = (1 << bits) - 1; // red-max @@ -1961,9 +2910,9 @@ RFB.messages = { buff[offset + 12] = 0; // blue-max buff[offset + 13] = (1 << bits) - 1; // blue-max - buff[offset + 14] = bits * 2; // red-shift + buff[offset + 14] = bits * 0; // red-shift buff[offset + 15] = bits * 1; // green-shift - buff[offset + 16] = bits * 0; // blue-shift + buff[offset + 16] = bits * 2; // blue-shift buff[offset + 17] = 0; // padding buff[offset + 18] = 0; // padding diff --git a/systemvm/agent/noVNC/core/util/browser.js b/systemvm/agent/noVNC/core/util/browser.js index 8996cfeda71..15548014229 100644 --- a/systemvm/agent/noVNC/core/util/browser.js +++ b/systemvm/agent/noVNC/core/util/browser.js @@ -1,9 +1,11 @@ /* * noVNC: HTML5 VNC client - * Copyright (C) 2018 The noVNC Authors + * Copyright (C) 2019 The noVNC Authors * Licensed under MPL 2.0 (see LICENSE.txt) * * See README.md for usage and integration instructions. + * + * Browser feature support detection */ import * as Log from './logging.js'; @@ -31,7 +33,7 @@ try { const target = document.createElement('canvas'); target.style.cursor = 'url("data:image/x-icon;base64,AAACAAEACAgAAAIAAgA4AQAAFgAAACgAAAAIAAAAEAAAAAEAIAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAAAAAAAAAAAAAAAA==") 2 2, default'; - if (target.style.cursor) { + if (target.style.cursor.indexOf("url") === 0) { Log.Info("Data URI scheme cursor supported"); _supportsCursorURIs = true; } else { @@ -52,6 +54,38 @@ try { } export const supportsImageMetadata = _supportsImageMetadata; +let _hasScrollbarGutter = true; +try { + // Create invisible container + const container = document.createElement('div'); + container.style.visibility = 'hidden'; + container.style.overflow = 'scroll'; // forcing scrollbars + document.body.appendChild(container); + + // Create a div and place it in the container + const child = document.createElement('div'); + container.appendChild(child); + + // Calculate the difference between the container's full width + // and the child's width - the difference is the scrollbars + const scrollbarWidth = (container.offsetWidth - child.offsetWidth); + + // Clean up + container.parentNode.removeChild(container); + + _hasScrollbarGutter = scrollbarWidth != 0; +} catch (exc) { + Log.Error("Scrollbar test exception: " + exc); +} +export const hasScrollbarGutter = _hasScrollbarGutter; + +/* + * The functions for detection of platforms and browsers below are exported + * but the use of these should be minimized as much as possible. + * + * It's better to use feature detection than platform detection. + */ + export function isMac() { return navigator && !!(/mac/i).exec(navigator.platform); } @@ -67,10 +101,6 @@ export function isIOS() { !!(/ipod/i).exec(navigator.platform)); } -export function isAndroid() { - return navigator && !!(/android/i).exec(navigator.userAgent); -} - export function isSafari() { return navigator && (navigator.userAgent.indexOf('Safari') !== -1 && navigator.userAgent.indexOf('Chrome') === -1); diff --git a/systemvm/agent/noVNC/core/util/cursor.js b/systemvm/agent/noVNC/core/util/cursor.js index 0d0b754a863..4db1dab23fb 100644 --- a/systemvm/agent/noVNC/core/util/cursor.js +++ b/systemvm/agent/noVNC/core/util/cursor.js @@ -1,6 +1,6 @@ /* * noVNC: HTML5 VNC client - * Copyright (C) 2018 The noVNC Authors + * Copyright (C) 2019 The noVNC Authors * Licensed under MPL 2.0 or any later version (see LICENSE.txt) */ @@ -20,7 +20,6 @@ export default class Cursor { this._canvas.style.pointerEvents = 'none'; // Can't use "display" because of Firefox bug #1445997 this._canvas.style.visibility = 'hidden'; - document.body.appendChild(this._canvas); } this._position = { x: 0, y: 0 }; @@ -31,9 +30,6 @@ export default class Cursor { 'mouseleave': this._handleMouseLeave.bind(this), 'mousemove': this._handleMouseMove.bind(this), 'mouseup': this._handleMouseUp.bind(this), - 'touchstart': this._handleTouchStart.bind(this), - 'touchmove': this._handleTouchMove.bind(this), - 'touchend': this._handleTouchEnd.bind(this), }; } @@ -45,6 +41,8 @@ export default class Cursor { this._target = target; if (useFallback) { + document.body.appendChild(this._canvas); + // FIXME: These don't fire properly except for mouse /// movement in IE. We want to also capture element // movement, size changes, visibility, etc. @@ -53,17 +51,16 @@ export default class Cursor { this._target.addEventListener('mouseleave', this._eventHandlers.mouseleave, options); this._target.addEventListener('mousemove', this._eventHandlers.mousemove, options); this._target.addEventListener('mouseup', this._eventHandlers.mouseup, options); - - // There is no "touchleave" so we monitor touchstart globally - window.addEventListener('touchstart', this._eventHandlers.touchstart, options); - this._target.addEventListener('touchmove', this._eventHandlers.touchmove, options); - this._target.addEventListener('touchend', this._eventHandlers.touchend, options); } this.clear(); } detach() { + if (!this._target) { + return; + } + if (useFallback) { const options = { capture: true, passive: true }; this._target.removeEventListener('mouseover', this._eventHandlers.mouseover, options); @@ -71,9 +68,7 @@ export default class Cursor { this._target.removeEventListener('mousemove', this._eventHandlers.mousemove, options); this._target.removeEventListener('mouseup', this._eventHandlers.mouseup, options); - window.removeEventListener('touchstart', this._eventHandlers.touchstart, options); - this._target.removeEventListener('touchmove', this._eventHandlers.touchmove, options); - this._target.removeEventListener('touchend', this._eventHandlers.touchend, options); + document.body.removeChild(this._canvas); } this._target = null; @@ -124,6 +119,27 @@ export default class Cursor { this._hotSpot.y = 0; } + // Mouse events might be emulated, this allows + // moving the cursor in such cases + move(clientX, clientY) { + if (!useFallback) { + return; + } + // clientX/clientY are relative the _visual viewport_, + // but our position is relative the _layout viewport_, + // so try to compensate when we can + if (window.visualViewport) { + this._position.x = clientX + window.visualViewport.offsetLeft; + this._position.y = clientY + window.visualViewport.offsetTop; + } else { + this._position.x = clientX; + this._position.y = clientY; + } + this._updatePosition(); + let target = document.elementFromPoint(clientX, clientY); + this._updateVisibility(target); + } + _handleMouseOver(event) { // This event could be because we're entering the target, or // moving around amongst its sub elements. Let the move handler @@ -132,7 +148,8 @@ export default class Cursor { } _handleMouseLeave(event) { - this._hideCursor(); + // Check if we should show the cursor on the element we are leaving to + this._updateVisibility(event.relatedTarget); } _handleMouseMove(event) { @@ -150,27 +167,29 @@ export default class Cursor { // now and adjust visibility based on that. let target = document.elementFromPoint(event.clientX, event.clientY); this._updateVisibility(target); - } - _handleTouchStart(event) { - // Just as for mouseover, we let the move handler deal with it - this._handleTouchMove(event); - } - - _handleTouchMove(event) { - this._updateVisibility(event.target); - - this._position.x = event.changedTouches[0].clientX - this._hotSpot.x; - this._position.y = event.changedTouches[0].clientY - this._hotSpot.y; - - this._updatePosition(); - } - - _handleTouchEnd(event) { - // Same principle as for mouseup - let target = document.elementFromPoint(event.changedTouches[0].clientX, - event.changedTouches[0].clientY); - this._updateVisibility(target); + // Captures end with a mouseup but we can't know the event order of + // mouseup vs releaseCapture. + // + // In the cases when releaseCapture comes first, the code above is + // enough. + // + // In the cases when the mouseup comes first, we need wait for the + // browser to flush all events and then check again if the cursor + // should be visible. + if (this._captureIsActive()) { + window.setTimeout(() => { + // We might have detached at this point + if (!this._target) { + return; + } + // Refresh the target from elementFromPoint since queued events + // might have altered the DOM + target = document.elementFromPoint(event.clientX, + event.clientY); + this._updateVisibility(target); + }, 0); + } } _showCursor() { @@ -189,6 +208,9 @@ export default class Cursor { // (i.e. are we over the target, or a child of the target without a // different cursor set) _shouldShowCursor(target) { + if (!target) { + return false; + } // Easy case if (target === this._target) { return true; @@ -207,6 +229,11 @@ export default class Cursor { } _updateVisibility(target) { + // When the cursor target has capture we want to show the cursor. + // So, if a capture is active - look at the captured element instead. + if (this._captureIsActive()) { + target = document.captureElement; + } if (this._shouldShowCursor(target)) { this._showCursor(); } else { @@ -218,4 +245,9 @@ export default class Cursor { this._canvas.style.left = this._position.x + "px"; this._canvas.style.top = this._position.y + "px"; } + + _captureIsActive() { + return document.captureElement && + document.documentElement.contains(document.captureElement); + } } diff --git a/systemvm/agent/noVNC/core/util/element.js b/systemvm/agent/noVNC/core/util/element.js new file mode 100644 index 00000000000..466a7453e1d --- /dev/null +++ b/systemvm/agent/noVNC/core/util/element.js @@ -0,0 +1,32 @@ +/* + * noVNC: HTML5 VNC client + * Copyright (C) 2020 The noVNC Authors + * Licensed under MPL 2.0 (see LICENSE.txt) + * + * See README.md for usage and integration instructions. + */ + +/* + * HTML element utility functions + */ + +export function clientToElement(x, y, elem) { + const bounds = elem.getBoundingClientRect(); + let pos = { x: 0, y: 0 }; + // Clip to target bounds + if (x < bounds.left) { + pos.x = 0; + } else if (x >= bounds.right) { + pos.x = bounds.width - 1; + } else { + pos.x = x - bounds.left; + } + if (y < bounds.top) { + pos.y = 0; + } else if (y >= bounds.bottom) { + pos.y = bounds.height - 1; + } else { + pos.y = y - bounds.top; + } + return pos; +} diff --git a/systemvm/agent/noVNC/core/util/events.js b/systemvm/agent/noVNC/core/util/events.js index f1222796a7e..39eefd4596c 100644 --- a/systemvm/agent/noVNC/core/util/events.js +++ b/systemvm/agent/noVNC/core/util/events.js @@ -21,7 +21,8 @@ export function stopEvent(e) { // Emulate Element.setCapture() when not supported let _captureRecursion = false; -let _captureElem = null; +let _elementForUnflushedEvents = null; +document.captureElement = null; function _captureProxy(e) { // Recursion protection as we'll see our own event if (_captureRecursion) return; @@ -30,7 +31,11 @@ function _captureProxy(e) { const newEv = new e.constructor(e.type, e); _captureRecursion = true; - _captureElem.dispatchEvent(newEv); + if (document.captureElement) { + document.captureElement.dispatchEvent(newEv); + } else { + _elementForUnflushedEvents.dispatchEvent(newEv); + } _captureRecursion = false; // Avoid double events @@ -48,58 +53,56 @@ function _captureProxy(e) { } // Follow cursor style of target element -function _captureElemChanged() { - const captureElem = document.getElementById("noVNC_mouse_capture_elem"); - captureElem.style.cursor = window.getComputedStyle(_captureElem).cursor; +function _capturedElemChanged() { + const proxyElem = document.getElementById("noVNC_mouse_capture_elem"); + proxyElem.style.cursor = window.getComputedStyle(document.captureElement).cursor; } -const _captureObserver = new MutationObserver(_captureElemChanged); +const _captureObserver = new MutationObserver(_capturedElemChanged); -let _captureIndex = 0; +export function setCapture(target) { + if (target.setCapture) { -export function setCapture(elem) { - if (elem.setCapture) { - - elem.setCapture(); + target.setCapture(); + document.captureElement = target; // IE releases capture on 'click' events which might not trigger - elem.addEventListener('mouseup', releaseCapture); + target.addEventListener('mouseup', releaseCapture); } else { // Release any existing capture in case this method is // called multiple times without coordination releaseCapture(); - let captureElem = document.getElementById("noVNC_mouse_capture_elem"); + let proxyElem = document.getElementById("noVNC_mouse_capture_elem"); - if (captureElem === null) { - captureElem = document.createElement("div"); - captureElem.id = "noVNC_mouse_capture_elem"; - captureElem.style.position = "fixed"; - captureElem.style.top = "0px"; - captureElem.style.left = "0px"; - captureElem.style.width = "100%"; - captureElem.style.height = "100%"; - captureElem.style.zIndex = 10000; - captureElem.style.display = "none"; - document.body.appendChild(captureElem); + if (proxyElem === null) { + proxyElem = document.createElement("div"); + proxyElem.id = "noVNC_mouse_capture_elem"; + proxyElem.style.position = "fixed"; + proxyElem.style.top = "0px"; + proxyElem.style.left = "0px"; + proxyElem.style.width = "100%"; + proxyElem.style.height = "100%"; + proxyElem.style.zIndex = 10000; + proxyElem.style.display = "none"; + document.body.appendChild(proxyElem); // This is to make sure callers don't get confused by having // our blocking element as the target - captureElem.addEventListener('contextmenu', _captureProxy); + proxyElem.addEventListener('contextmenu', _captureProxy); - captureElem.addEventListener('mousemove', _captureProxy); - captureElem.addEventListener('mouseup', _captureProxy); + proxyElem.addEventListener('mousemove', _captureProxy); + proxyElem.addEventListener('mouseup', _captureProxy); } - _captureElem = elem; - _captureIndex++; + document.captureElement = target; // Track cursor and get initial cursor - _captureObserver.observe(elem, {attributes: true}); - _captureElemChanged(); + _captureObserver.observe(target, {attributes: true}); + _capturedElemChanged(); - captureElem.style.display = ""; + proxyElem.style.display = ""; // We listen to events on window in order to keep tracking if it // happens to leave the viewport @@ -112,26 +115,26 @@ export function releaseCapture() { if (document.releaseCapture) { document.releaseCapture(); + document.captureElement = null; } else { - if (!_captureElem) { + if (!document.captureElement) { return; } - // There might be events already queued, so we need to wait for - // them to flush. E.g. contextmenu in Microsoft Edge - window.setTimeout((expected) => { - // Only clear it if it's the expected grab (i.e. no one - // else has initiated a new grab) - if (_captureIndex === expected) { - _captureElem = null; - } - }, 0, _captureIndex); + // There might be events already queued. The event proxy needs + // access to the captured element for these queued events. + // E.g. contextmenu (right-click) in Microsoft Edge + // + // Before removing the capturedElem pointer we save it to a + // temporary variable that the unflushed events can use. + _elementForUnflushedEvents = document.captureElement; + document.captureElement = null; _captureObserver.disconnect(); - const captureElem = document.getElementById("noVNC_mouse_capture_elem"); - captureElem.style.display = "none"; + const proxyElem = document.getElementById("noVNC_mouse_capture_elem"); + proxyElem.style.display = "none"; window.removeEventListener('mousemove', _captureProxy); window.removeEventListener('mouseup', _captureProxy); diff --git a/systemvm/agent/noVNC/core/util/eventtarget.js b/systemvm/agent/noVNC/core/util/eventtarget.js index f54ca9bf112..a21aa5494d8 100644 --- a/systemvm/agent/noVNC/core/util/eventtarget.js +++ b/systemvm/agent/noVNC/core/util/eventtarget.js @@ -1,6 +1,6 @@ /* * noVNC: HTML5 VNC client - * Copyright (C) 2018 The noVNC Authors + * Copyright (C) 2019 The noVNC Authors * Licensed under MPL 2.0 (see LICENSE.txt) * * See README.md for usage and integration instructions. diff --git a/systemvm/agent/noVNC/core/util/int.js b/systemvm/agent/noVNC/core/util/int.js new file mode 100644 index 00000000000..001f40f2ab5 --- /dev/null +++ b/systemvm/agent/noVNC/core/util/int.js @@ -0,0 +1,15 @@ +/* + * noVNC: HTML5 VNC client + * Copyright (C) 2020 The noVNC Authors + * Licensed under MPL 2.0 (see LICENSE.txt) + * + * See README.md for usage and integration instructions. + */ + +export function toUnsigned32bit(toConvert) { + return toConvert >>> 0; +} + +export function toSigned32bit(toConvert) { + return toConvert | 0; +} diff --git a/systemvm/agent/noVNC/core/util/logging.js b/systemvm/agent/noVNC/core/util/logging.js index 4c8943d0054..fe449e93515 100644 --- a/systemvm/agent/noVNC/core/util/logging.js +++ b/systemvm/agent/noVNC/core/util/logging.js @@ -1,6 +1,6 @@ /* * noVNC: HTML5 VNC client - * Copyright (C) 2018 The noVNC Authors + * Copyright (C) 2019 The noVNC Authors * Licensed under MPL 2.0 (see LICENSE.txt) * * See README.md for usage and integration instructions. @@ -10,18 +10,18 @@ * Logging/debug routines */ -let _log_level = 'warn'; +let _logLevel = 'warn'; let Debug = () => {}; let Info = () => {}; let Warn = () => {}; let Error = () => {}; -export function init_logging(level) { +export function initLogging(level) { if (typeof level === 'undefined') { - level = _log_level; + level = _logLevel; } else { - _log_level = level; + _logLevel = level; } Debug = Info = Warn = Error = () => {}; @@ -46,11 +46,11 @@ export function init_logging(level) { } } -export function get_logging() { - return _log_level; +export function getLogging() { + return _logLevel; } export { Debug, Info, Warn, Error }; // Initialize logging level -init_logging(); +initLogging(); diff --git a/systemvm/agent/noVNC/core/util/polyfill.js b/systemvm/agent/noVNC/core/util/polyfill.js index 648ceebc3c2..0e458c8606b 100644 --- a/systemvm/agent/noVNC/core/util/polyfill.js +++ b/systemvm/agent/noVNC/core/util/polyfill.js @@ -1,6 +1,6 @@ /* * noVNC: HTML5 VNC client - * Copyright (C) 2018 The noVNC Authors + * Copyright (C) 2020 The noVNC Authors * Licensed under MPL 2.0 or any later version (see LICENSE.txt) */ @@ -52,3 +52,10 @@ if (typeof Object.assign != 'function') { window.CustomEvent = CustomEvent; } })(); + +/* Number.isInteger() (taken from MDN) */ +Number.isInteger = Number.isInteger || function isInteger(value) { + return typeof value === 'number' && + isFinite(value) && + Math.floor(value) === value; +}; diff --git a/systemvm/agent/noVNC/core/util/strings.js b/systemvm/agent/noVNC/core/util/strings.js index 61f4f237d93..3dd4b29fb02 100644 --- a/systemvm/agent/noVNC/core/util/strings.js +++ b/systemvm/agent/noVNC/core/util/strings.js @@ -1,14 +1,28 @@ /* * noVNC: HTML5 VNC client - * Copyright (C) 2018 The noVNC Authors + * Copyright (C) 2019 The noVNC Authors * Licensed under MPL 2.0 (see LICENSE.txt) * * See README.md for usage and integration instructions. */ -/* - * Decode from UTF-8 - */ -export function decodeUTF8(utf8string) { - return decodeURIComponent(escape(utf8string)); +// Decode from UTF-8 +export function decodeUTF8(utf8string, allowLatin1=false) { + try { + return decodeURIComponent(escape(utf8string)); + } catch (e) { + if (e instanceof URIError) { + if (allowLatin1) { + // If we allow Latin1 we can ignore any decoding fails + // and in these cases return the original string + return utf8string; + } + } + throw e; + } +} + +// Encode to UTF-8 +export function encodeUTF8(DOMString) { + return unescape(encodeURIComponent(DOMString)); } diff --git a/systemvm/agent/noVNC/core/websock.js b/systemvm/agent/noVNC/core/websock.js index 51b9a66fb68..3156aed6f5e 100644 --- a/systemvm/agent/noVNC/core/websock.js +++ b/systemvm/agent/noVNC/core/websock.js @@ -1,6 +1,6 @@ /* * Websock: high-performance binary WebSockets - * Copyright (C) 2018 The noVNC Authors + * Copyright (C) 2019 The noVNC Authors * Licensed under MPL 2.0 (see LICENSE.txt) * * Websock is similar to the standard WebSocket object but with extra @@ -17,6 +17,8 @@ import * as Log from './util/logging.js'; // this has performance issues in some versions Chromium, and // doesn't gain a tremendous amount of performance increase in Firefox // at the moment. It may be valuable to turn it on in the future. +// Also copyWithin() for TypedArrays is not supported in IE 11 or +// Safari 13 (at the moment we want to support Safari 11). const ENABLE_COPYWITHIN = false; const MAX_RQ_GROW_SIZE = 40 * 1024 * 1024; // 40 MiB @@ -27,7 +29,6 @@ export default class Websock { this._rQi = 0; // Receive queue index this._rQlen = 0; // Next write position in the receive queue this._rQbufferSize = 1024 * 1024 * 4; // Receive queue buffer size (4 MiB) - this._rQmax = this._rQbufferSize / 8; // called in init: this._rQ = new Uint8Array(this._rQbufferSize); this._rQ = null; // Receive queue @@ -143,7 +144,7 @@ export default class Websock { flush() { if (this._sQlen > 0 && this._websocket.readyState === WebSocket.OPEN) { - this._websocket.send(this._encode_message()); + this._websocket.send(this._encodeMessage()); this._sQlen = 0; } } @@ -154,7 +155,7 @@ export default class Websock { this.flush(); } - send_string(str) { + sendString(str) { this.send(str.split('').map(chr => chr.charCodeAt(0))); } @@ -167,13 +168,13 @@ export default class Websock { this._eventHandlers[evt] = handler; } - _allocate_buffers() { + _allocateBuffers() { this._rQ = new Uint8Array(this._rQbufferSize); this._sQ = new Uint8Array(this._sQbufferSize); } init() { - this._allocate_buffers(); + this._allocateBuffers(); this._rQi = 0; this._websocket = null; } @@ -184,7 +185,7 @@ export default class Websock { this._websocket = new WebSocket(uri, protocols); this._websocket.binaryType = 'arraybuffer'; - this._websocket.onmessage = this._recv_message.bind(this); + this._websocket.onmessage = this._recvMessage.bind(this); this._websocket.onopen = () => { Log.Debug('>> WebSock.onopen'); if (this._websocket.protocol) { @@ -219,42 +220,46 @@ export default class Websock { } // private methods - _encode_message() { + _encodeMessage() { // Put in a binary arraybuffer // according to the spec, you can send ArrayBufferViews with the send method return new Uint8Array(this._sQ.buffer, 0, this._sQlen); } - _expand_compact_rQ(min_fit) { - const resizeNeeded = min_fit || this.rQlen > this._rQbufferSize / 2; + // We want to move all the unread data to the start of the queue, + // e.g. compacting. + // The function also expands the receive que if needed, and for + // performance reasons we combine these two actions to avoid + // unneccessary copying. + _expandCompactRQ(minFit) { + // if we're using less than 1/8th of the buffer even with the incoming bytes, compact in place + // instead of resizing + const requiredBufferSize = (this._rQlen - this._rQi + minFit) * 8; + const resizeNeeded = this._rQbufferSize < requiredBufferSize; + if (resizeNeeded) { - if (!min_fit) { - // just double the size if we need to do compaction - this._rQbufferSize *= 2; - } else { - // otherwise, make sure we satisy rQlen - rQi + min_fit < rQbufferSize / 8 - this._rQbufferSize = (this.rQlen + min_fit) * 8; - } + // Make sure we always *at least* double the buffer size, and have at least space for 8x + // the current amount of data + this._rQbufferSize = Math.max(this._rQbufferSize * 2, requiredBufferSize); } // we don't want to grow unboundedly if (this._rQbufferSize > MAX_RQ_GROW_SIZE) { this._rQbufferSize = MAX_RQ_GROW_SIZE; - if (this._rQbufferSize - this.rQlen < min_fit) { + if (this._rQbufferSize - this.rQlen < minFit) { throw new Error("Receive Queue buffer exceeded " + MAX_RQ_GROW_SIZE + " bytes, and the new message could not fit"); } } if (resizeNeeded) { - const old_rQbuffer = this._rQ.buffer; - this._rQmax = this._rQbufferSize / 8; + const oldRQbuffer = this._rQ.buffer; this._rQ = new Uint8Array(this._rQbufferSize); - this._rQ.set(new Uint8Array(old_rQbuffer, this._rQi)); + this._rQ.set(new Uint8Array(oldRQbuffer, this._rQi, this._rQlen - this._rQi)); } else { if (ENABLE_COPYWITHIN) { - this._rQ.copyWithin(0, this._rQi); + this._rQ.copyWithin(0, this._rQi, this._rQlen); } else { - this._rQ.set(new Uint8Array(this._rQ.buffer, this._rQi)); + this._rQ.set(new Uint8Array(this._rQ.buffer, this._rQi, this._rQlen - this._rQi)); } } @@ -262,26 +267,25 @@ export default class Websock { this._rQi = 0; } - _decode_message(data) { - // push arraybuffer values onto the end + // push arraybuffer values onto the end of the receive que + _DecodeMessage(data) { const u8 = new Uint8Array(data); if (u8.length > this._rQbufferSize - this._rQlen) { - this._expand_compact_rQ(u8.length); + this._expandCompactRQ(u8.length); } this._rQ.set(u8, this._rQlen); this._rQlen += u8.length; } - _recv_message(e) { - this._decode_message(e.data); + _recvMessage(e) { + this._DecodeMessage(e.data); if (this.rQlen > 0) { this._eventHandlers.message(); - // Compact the receive queue if (this._rQlen == this._rQi) { + // All data has now been processed, this means we + // can reset the receive queue. this._rQlen = 0; this._rQi = 0; - } else if (this._rQlen > this._rQmax) { - this._expand_compact_rQ(); } } else { Log.Debug("Ignoring empty message"); diff --git a/systemvm/agent/noVNC/docs/API-internal.md b/systemvm/agent/noVNC/docs/API-internal.md deleted file mode 100644 index 0b29afb61fc..00000000000 --- a/systemvm/agent/noVNC/docs/API-internal.md +++ /dev/null @@ -1,122 +0,0 @@ -# 1. Internal Modules - -The noVNC client is composed of several internal modules that handle -rendering, input, networking, etc. Each of the modules is designed to -be cross-browser and independent from each other. - -Note however that the API of these modules is not guaranteed to be -stable, and this documentation is not maintained as well as the -official external API. - - -## 1.1 Module List - -* __Mouse__ (core/input/mouse.js): Mouse input event handler with -limited touch support. - -* __Keyboard__ (core/input/keyboard.js): Keyboard input event handler with -non-US keyboard support. Translates keyDown and keyUp events to X11 -keysym values. - -* __Display__ (core/display.js): Efficient 2D rendering abstraction -layered on the HTML5 canvas element. - -* __Websock__ (core/websock.js): Websock client from websockify -with transparent binary data support. -[Websock API](https://github.com/novnc/websockify/wiki/websock.js) wiki page. - - -## 1.2 Callbacks - -For the Mouse, Keyboard and Display objects the callback functions are -assigned to configuration attributes, just as for the RFB object. The -WebSock module has a method named 'on' that takes two parameters: the -callback event name, and the callback function. - -## 2. Modules - -## 2.1 Mouse Module - -### 2.1.1 Configuration Attributes - -| name | type | mode | default | description -| ----------- | ---- | ---- | -------- | ------------ -| touchButton | int | RW | 1 | Button mask (1, 2, 4) for which click to send on touch devices. 0 means ignore clicks. - -### 2.1.2 Methods - -| name | parameters | description -| ------ | ---------- | ------------ -| grab | () | Begin capturing mouse events -| ungrab | () | Stop capturing mouse events - -### 2.1.2 Callbacks - -| name | parameters | description -| ------------- | ------------------- | ------------ -| onmousebutton | (x, y, down, bmask) | Handler for mouse button click/release -| onmousemove | (x, y) | Handler for mouse movement - - -## 2.2 Keyboard Module - -### 2.2.1 Configuration Attributes - -None - -### 2.2.2 Methods - -| name | parameters | description -| ------ | ---------- | ------------ -| grab | () | Begin capturing keyboard events -| ungrab | () | Stop capturing keyboard events - -### 2.2.3 Callbacks - -| name | parameters | description -| ---------- | -------------------- | ------------ -| onkeypress | (keysym, code, down) | Handler for key press/release - - -## 2.3 Display Module - -### 2.3.1 Configuration Attributes - -| name | type | mode | default | description -| ------------ | ----- | ---- | ------- | ------------ -| logo | raw | RW | | Logo to display when cleared: {"width": width, "height": height, "type": mime-type, "data": data} -| scale | float | RW | 1.0 | Display area scale factor 0.0 - 1.0 -| clipViewport | bool | RW | false | Use viewport clipping -| width | int | RO | | Display area width -| height | int | RO | | Display area height - -### 2.3.2 Methods - -| name | parameters | description -| ------------------ | ------------------------------------------------------- | ------------ -| viewportChangePos | (deltaX, deltaY) | Move the viewport relative to the current location -| viewportChangeSize | (width, height) | Change size of the viewport -| absX | (x) | Return X relative to the remote display -| absY | (y) | Return Y relative to the remote display -| resize | (width, height) | Set width and height -| flip | (from_queue) | Update the visible canvas with the contents of the rendering canvas -| clear | () | Clear the display (show logo if set) -| pending | () | Check if there are waiting items in the render queue -| flush | () | Resume processing the render queue unless it's empty -| fillRect | (x, y, width, height, color, from_queue) | Draw a filled in rectangle -| copyImage | (old_x, old_y, new_x, new_y, width, height, from_queue) | Copy a rectangular area -| imageRect | (x, y, mime, arr) | Draw a rectangle with an image -| startTile | (x, y, width, height, color) | Begin updating a tile -| subTile | (tile, x, y, w, h, color) | Update a sub-rectangle within the given tile -| finishTile | () | Draw the current tile to the display -| blitImage | (x, y, width, height, arr, offset, from_queue) | Blit pixels (of R,G,B,A) to the display -| blitRgbImage | (x, y, width, height, arr, offset, from_queue) | Blit RGB encoded image to display -| blitRgbxImage | (x, y, width, height, arr, offset, from_queue) | Blit RGBX encoded image to display -| drawImage | (img, x, y) | Draw image and track damage -| autoscale | (containerWidth, containerHeight) | Scale the display - -### 2.3.3 Callbacks - -| name | parameters | description -| ------- | ---------- | ------------ -| onflush | () | A display flush has been requested and we are now ready to resume FBU processing diff --git a/systemvm/agent/noVNC/docs/API.md b/systemvm/agent/noVNC/docs/API.md deleted file mode 100644 index d587429c176..00000000000 --- a/systemvm/agent/noVNC/docs/API.md +++ /dev/null @@ -1,375 +0,0 @@ -# noVNC API - -The interface of the noVNC client consists of a single RFB object that -is instantiated once per connection. - -## RFB - -The `RFB` object represents a single connection to a VNC server. It -communicates using a WebSocket that must provide a standard RFB -protocol stream. - -### Constructor - -[`RFB()`](#rfb-1) - - Creates and returns a new `RFB` object. - -### Properties - -`viewOnly` - - Is a `boolean` indicating if any events (e.g. key presses or mouse - movement) should be prevented from being sent to the server. - Disabled by default. - -`focusOnClick` - - Is a `boolean` indicating if keyboard focus should automatically be - moved to the remote session when a `mousedown` or `touchstart` - event is received. - -`touchButton` - - Is a `long` controlling the button mask that should be simulated - when a touch event is recieved. Uses the same values as - [`MouseEvent.button`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button). - Is set to `1` by default. - -`clipViewport` - - Is a `boolean` indicating if the remote session should be clipped - to its container. When disabled scrollbars will be shown to handle - the resulting overflow. Disabled by default. - -`dragViewport` - - Is a `boolean` indicating if mouse events should control the - relative position of a clipped remote session. Only relevant if - `clipViewport` is enabled. Disabled by default. - -`scaleViewport` - - Is a `boolean` indicating if the remote session should be scaled - locally so it fits its container. When disabled it will be centered - if the remote session is smaller than its container, or handled - according to `clipViewport` if it is larger. Disabled by default. - -`resizeSession` - - Is a `boolean` indicating if a request to resize the remote session - should be sent whenever the container changes dimensions. Disabled - by default. - -`showDotCursor` - - Is a `boolean` indicating whether a dot cursor should be shown - instead of a zero-sized or fully-transparent cursor if the server - sets such invisible cursor. Disabled by default. - -`background` - - Is a valid CSS [background](https://developer.mozilla.org/en-US/docs/Web/CSS/background) - style value indicating which background style should be applied - to the element containing the remote session screen. The default value is `rgb(40, 40, 40)` - (solid gray color). - -`capabilities` *Read only* - - Is an `Object` indicating which optional extensions are available - on the server. Some methods may only be called if the corresponding - capability is set. The following capabilities are defined: - - | name | type | description - | -------- | --------- | ----------- - | `power` | `boolean` | Machine power control is available - -### Events - -[`connect`](#connect) - - The `connect` event is fired when the `RFB` object has completed - the connection and handshaking with the server. - -[`disconnect`](#disconnected) - - The `disconnect` event is fired when the `RFB` object disconnects. - -[`credentialsrequired`](#credentialsrequired) - - The `credentialsrequired` event is fired when more credentials must - be given to continue. - -[`securityfailure`](#securityfailure) - - The `securityfailure` event is fired when the security negotiation - with the server fails. - -[`clipboard`](#clipboard) - - The `clipboard` event is fired when clipboard data is received from - the server. - -[`bell`](#bell) - - The `bell` event is fired when a audible bell request is received - from the server. - -[`desktopname`](#desktopname) - - The `desktopname` event is fired when the remote desktop name - changes. - -[`capabilities`](#capabilities) - - The `capabilities` event is fired when `RFB.capabilities` is - updated. - -### Methods - -[`RFB.disconnect()`](#rfbdisconnect) - - Disconnect from the server. - -[`RFB.sendCredentials()`](#rfbsendcredentials) - - Send credentials to server. Should be called after the - [`credentialsrequired`](#credentialsrequired) event has fired. - -[`RFB.sendKey()`](#rfbsendKey) - - Send a key event. - -[`RFB.sendCtrlAltDel()`](#rfbsendctrlaltdel) - - Send Ctrl-Alt-Del key sequence. - -[`RFB.focus()`](#rfbfocus) - - Move keyboard focus to the remote session. - -[`RFB.blur()`](#rfbblur) - - Remove keyboard focus from the remote session. - -[`RFB.machineShutdown()`](#rfbmachineshutdown) - - Request a shutdown of the remote machine. - -[`RFB.machineReboot()`](#rfbmachinereboot) - - Request a reboot of the remote machine. - -[`RFB.machineReset()`](#rfbmachinereset) - - Request a reset of the remote machine. - -[`RFB.clipboardPasteFrom()`](#rfbclipboardPasteFrom) - - Send clipboard contents to server. - -### Details - -#### RFB() - -The `RFB()` constructor returns a new `RFB` object and initiates a new -connection to a specified VNC server. - -##### Syntax - - let rfb = new RFB( target, url [, options] ); - -###### Parameters - -**`target`** - - A block [`HTMLElement`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement) - that specifies where the `RFB` object should attach itself. The - existing contents of the `HTMLElement` will be untouched, but new - elements will be added during the lifetime of the `RFB` object. - -**`url`** - - A `DOMString` specifying the VNC server to connect to. This must be - a valid WebSocket URL. - -**`options`** *Optional* - - An `Object` specifying extra details about how the connection - should be made. - - Possible options: - - `shared` - - A `boolean` indicating if the remote server should be shared or - if any other connected clients should be disconnected. Enabled - by default. - - `credentials` - - An `Object` specifying the credentials to provide to the server - when authenticating. The following credentials are possible: - - | name | type | description - | ------------ | ----------- | ----------- - | `"username"` | `DOMString` | The user that authenticates - | `"password"` | `DOMString` | Password for the user - | `"target"` | `DOMString` | Target machine or session - - `repeaterID` - - A `DOMString` specifying the ID to provide to any VNC repeater - encountered. - -#### connect - -The `connect` event is fired after all the handshaking with the server -is completed and the connection is fully established. After this event -the `RFB` object is ready to recieve graphics updates and to send input. - -#### disconnect - -The `disconnect` event is fired when the connection has been -terminated. The `detail` property is an `Object` that contains the -property `clean`. `clean` is a `boolean` indicating if the termination -was clean or not. In the event of an unexpected termination or an error -`clean` will be set to false. - -#### credentialsrequired - -The `credentialsrequired` event is fired when the server requests more -credentials than were specified to [`RFB()`](#rfb-1). The `detail` -property is an `Object` containing the property `types` which is an -`Array` of `DOMString` listing the credentials that are required. - -#### securityfailure - -The `securityfailure` event is fired when the handshaking process with -the server fails during the security negotiation step. The `detail` -property is an `Object` containing the following properties: - -| Property | Type | Description -| -------- | ----------- | ----------- -| `status` | `long` | The failure status code -| `reason` | `DOMString` | The **optional** reason for the failure - -The property `status` corresponds to the -[SecurityResult](https://github.com/rfbproto/rfbproto/blob/master/rfbproto.rst#securityresult) -status code in cases of failure. A status of zero will not be sent in -this event since that indicates a successful security handshaking -process. The optional property `reason` is provided by the server and -thus the language of the string is not known. However most servers will -probably send English strings. The server can choose to not send a -reason and in these cases the `reason` property will be omitted. - -#### clipboard - -The `clipboard` event is fired when the server has sent clipboard data. -The `detail` property is an `Object` containing the property `text` -which is a `DOMString` with the clipboard data. - -#### bell - -The `bell` event is fired when the server has requested an audible -bell. - -#### desktopname - -The `desktopname` event is fired when the name of the remote desktop -changes. The `detail` property is an `Object` with the property `name` -which is a `DOMString` specifying the new name. - -#### capabilities - -The `capabilities` event is fired whenever an entry is added or removed -from `RFB.capabilities`. The `detail` property is an `Object` with the -property `capabilities` containing the new value of `RFB.capabilities`. - -#### RFB.disconnect() - -The `RFB.disconnect()` method is used to disconnect from the currently -connected server. - -##### Syntax - - RFB.disconnect( ); - -#### RFB.sendCredentials() - -The `RFB.sendCredentials()` method is used to provide the missing -credentials after a `credentialsrequired` event has been fired. - -##### Syntax - - RFB.sendCredentials( credentials ); - -###### Parameters - -**`credentials`** - - An `Object` specifying the credentials to provide to the server - when authenticating. See [`RFB()`](#rfb-1) for details. - -#### RFB.sendKey() - -The `RFB.sendKey()` method is used to send a key event to the server. - -##### Syntax - - RFB.sendKey( keysym, code [, down] ); - -###### Parameters - -**`keysym`** - - A `long` specifying the RFB keysym to send. Can be `0` if a valid - **`code`** is specified. - -**`code`** - - A `DOMString` specifying the physical key to send. Valid values are - those that can be specified to - [`KeyboardEvent.code`](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code). - If the physical key cannot be determined then `null` shall be - specified. - -**`down`** *Optional* - - A `boolean` specifying if a press or a release event should be - sent. If omitted then both a press and release event are sent. - -#### RFB.sendCtrlAltDel() - -The `RFB.sendCtrlAltDel()` method is used to send the key sequence -*left Control*, *left Alt*, *Delete*. This is a convenience wrapper -around [`RFB.sendKey()`](#rfbsendkey). - -##### Syntax - - RFB.sendCtrlAltDel( ); - -#### RFB.focus() - -The `RFB.focus()` method sets the keyboard focus on the remote session. -Keyboard events will be sent to the remote server after this point. - -##### Syntax - - RFB.focus( ); - -#### RFB.blur() - -The `RFB.blur()` method remove keyboard focus on the remote session. -Keyboard events will no longer be sent to the remote server after this -point. - -##### Syntax - - RFB.blur( ); - -#### RFB.machineShutdown() - -The `RFB.machineShutdown()` method is used to request to shut down the -remote machine. The capability `power` must be set for this method to -have any effect. - -##### Syntax - - RFB.machineShutdown( ); - -#### RFB.machineReboot() - -The `RFB.machineReboot()` method is used to request a clean reboot of -the remote machine. The capability `power` must be set for this method -to have any effect. - -##### Syntax - - RFB.machineReboot( ); - -#### RFB.machineReset() - -The `RFB.machineReset()` method is used to request a forced reset of -the remote machine. The capability `power` must be set for this method -to have any effect. - -##### Syntax - - RFB.machineReset( ); - -#### RFB.clipboardPasteFrom() - -The `RFB.clipboardPasteFrom()` method is used to send clipboard data -to the remote server. - -##### Syntax - - RFB.clipboardPasteFrom( text ); - -###### Parameters - -**`text`** - - A `DOMString` specifying the clipboard data to send. Currently only - characters from ISO 8859-1 are supported. diff --git a/systemvm/agent/noVNC/docs/EMBEDDING.md b/systemvm/agent/noVNC/docs/EMBEDDING.md deleted file mode 100644 index 5399b48ba76..00000000000 --- a/systemvm/agent/noVNC/docs/EMBEDDING.md +++ /dev/null @@ -1,119 +0,0 @@ -# Embedding and Deploying noVNC Application - -This document describes how to embed and deploy the noVNC application, which -includes settings and a full user interface. If you are looking for -documentation on how to use the core noVNC library in your own application, -then please see our [library documentation](LIBRARY.md). - -## Files - -The noVNC application consists of the following files and directories: - -* `vnc.html` - The main page for the application and where users should go. It - is possible to rename this file. - -* `app/` - Support files for the application. Contains code, images, styles and - translations. - -* `core/` - The core noVNC library. - -* `vendor/` - Third party support libraries used by the application and the - core library. - -The most basic deployment consists of simply serving these files from a web -server and setting up a WebSocket proxy to the VNC server. - -## Parameters - -The noVNC application can be controlled by including certain settings in the -query string. Currently the following options are available: - -* `autoconnect` - Automatically connect as soon as the page has finished - loading. - -* `reconnect` - If noVNC should automatically reconnect if the connection is - dropped. - -* `reconnect_delay` - How long to wait in milliseconds before attempting to - reconnect. - -* `host` - The WebSocket host to connect to. - -* `port` - The WebSocket port to connect to. - -* `encrypt` - If TLS should be used for the WebSocket connection. - -* `path` - The WebSocket path to use. - -* `password` - The password sent to the server, if required. - -* `repeaterID` - The repeater ID to use if a VNC repeater is detected. - -* `shared` - If other VNC clients should be disconnected when noVNC connects. - -* `bell` - If the keyboard bell should be enabled or not. - -* `view_only` - If the remote session should be in non-interactive mode. - -* `view_clip` - If the remote session should be clipped or use scrollbars if - it cannot fit in the browser. - -* `resize` - How to resize the remote session if it is not the same size as - the browser window. Can be one of `off`, `scale` and `remote`. - -* `show_dot` - If a dot cursor should be shown when the remote server provides - no local cursor, or provides a fully-transparent (invisible) cursor. - -* `logging` - The console log level. Can be one of `error`, `warn`, `info` or - `debug`. - -## Pre-conversion of Modules - -noVNC is written using ECMAScript 6 modules. Many of the major browsers support -these modules natively, but not all. By default the noVNC application includes -a script that can convert these modules to an older format as they are being -loaded. However this process can be slow and severely increases the load time -for the application. - -It is possible to perform this conversion ahead of time, avoiding the extra -load times. To do this please follow these steps: - - 1. Install Node.js - 2. Run `npm install` in the noVNC directory - 3. Run `./utils/use_require.js --with-app --as commonjs` - -This will produce a `build/` directory that includes everything needed to run -the noVNC application. - -## HTTP Serving Considerations -### Browser Cache Issue - -If you serve noVNC files using a web server that provides an ETag header, and -include any options in the query string, a nasty browser cache issue can bite -you on upgrade, resulting in a red error box. The issue is caused by a mismatch -between the new vnc.html (which is reloaded because the user has used it with -new query string after the upgrade) and the old javascript files (that the -browser reuses from its cache). To avoid this issue, the browser must be told -to always revalidate cached files using conditional requests. The correct -semantics are achieved via the (confusingly named) `Cache-Control: no-cache` -header that needs to be provided in the web server responses. - -### Example Server Configurations - -Apache: - -``` - # In the main configuration file - # (Debian/Ubuntu users: use "a2enmod headers" instead) - LoadModule headers_module modules/mod_headers.so - - # In the or block related to noVNC - Header set Cache-Control "no-cache" -``` - -Nginx: - -``` - # In the location block related to noVNC - add_header Cache-Control no-cache; -``` diff --git a/systemvm/agent/noVNC/docs/LIBRARY.md b/systemvm/agent/noVNC/docs/LIBRARY.md deleted file mode 100644 index 63f55e8f179..00000000000 --- a/systemvm/agent/noVNC/docs/LIBRARY.md +++ /dev/null @@ -1,35 +0,0 @@ -# Using the noVNC JavaScript library - -This document describes how to make use of the noVNC JavaScript library for -integration in your own VNC client application. If you wish to embed the more -complete noVNC application with its included user interface then please see -our [embedding documentation](EMBEDDING.md). - -## API - -The API of noVNC consists of a single object called `RFB`. The formal -documentation for that object can be found in our [API documentation](API.md). - -## Example - -noVNC includes a small example application called `vnc_lite.html`. This does -not make use of all the features of noVNC, but is a good start to see how to -do things. - -## Conversion of Modules - -noVNC is written using ECMAScript 6 modules. Many of the major browsers support -these modules natively, but not all. They are also not supported by Node.js. To -use noVNC in these places the library must first be converted. - -Fortunately noVNC includes a script to handle this conversion. Please follow -the following steps: - - 1. Install Node.js - 2. Run `npm install` in the noVNC directory - 3. Run `./utils/use_require.js --as ` - -Several module formats are available. Please run -`./utils/use_require.js --help` to see them all. - -The result of the conversion is available in the `lib/` directory. diff --git a/systemvm/agent/noVNC/docs/LICENSE.BSD-2-Clause b/systemvm/agent/noVNC/docs/LICENSE.BSD-2-Clause deleted file mode 100644 index 9d66ec911bf..00000000000 --- a/systemvm/agent/noVNC/docs/LICENSE.BSD-2-Clause +++ /dev/null @@ -1,22 +0,0 @@ -Copyright (c) , -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY -DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/systemvm/agent/noVNC/docs/LICENSE.BSD-3-Clause b/systemvm/agent/noVNC/docs/LICENSE.BSD-3-Clause deleted file mode 100644 index e160466c4e0..00000000000 --- a/systemvm/agent/noVNC/docs/LICENSE.BSD-3-Clause +++ /dev/null @@ -1,24 +0,0 @@ -Copyright (c) , -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the nor the - names of its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY -DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/systemvm/agent/noVNC/docs/LICENSE.MPL-2.0 b/systemvm/agent/noVNC/docs/LICENSE.MPL-2.0 deleted file mode 100644 index 14e2f777f6c..00000000000 --- a/systemvm/agent/noVNC/docs/LICENSE.MPL-2.0 +++ /dev/null @@ -1,373 +0,0 @@ -Mozilla Public License Version 2.0 -================================== - -1. Definitions --------------- - -1.1. "Contributor" - means each individual or legal entity that creates, contributes to - the creation of, or owns Covered Software. - -1.2. "Contributor Version" - means the combination of the Contributions of others (if any) used - by a Contributor and that particular Contributor's Contribution. - -1.3. "Contribution" - means Covered Software of a particular Contributor. - -1.4. "Covered Software" - means Source Code Form to which the initial Contributor has attached - the notice in Exhibit A, the Executable Form of such Source Code - Form, and Modifications of such Source Code Form, in each case - including portions thereof. - -1.5. "Incompatible With Secondary Licenses" - means - - (a) that the initial Contributor has attached the notice described - in Exhibit B to the Covered Software; or - - (b) that the Covered Software was made available under the terms of - version 1.1 or earlier of the License, but not also under the - terms of a Secondary License. - -1.6. "Executable Form" - means any form of the work other than Source Code Form. - -1.7. "Larger Work" - means a work that combines Covered Software with other material, in - a separate file or files, that is not Covered Software. - -1.8. "License" - means this document. - -1.9. "Licensable" - means having the right to grant, to the maximum extent possible, - whether at the time of the initial grant or subsequently, any and - all of the rights conveyed by this License. - -1.10. "Modifications" - means any of the following: - - (a) any file in Source Code Form that results from an addition to, - deletion from, or modification of the contents of Covered - Software; or - - (b) any new file in Source Code Form that contains any Covered - Software. - -1.11. "Patent Claims" of a Contributor - means any patent claim(s), including without limitation, method, - process, and apparatus claims, in any patent Licensable by such - Contributor that would be infringed, but for the grant of the - License, by the making, using, selling, offering for sale, having - made, import, or transfer of either its Contributions or its - Contributor Version. - -1.12. "Secondary License" - means either the GNU General Public License, Version 2.0, the GNU - Lesser General Public License, Version 2.1, the GNU Affero General - Public License, Version 3.0, or any later versions of those - licenses. - -1.13. "Source Code Form" - means the form of the work preferred for making modifications. - -1.14. "You" (or "Your") - means an individual or a legal entity exercising rights under this - License. For legal entities, "You" includes any entity that - controls, is controlled by, or is under common control with You. For - purposes of this definition, "control" means (a) the power, direct - or indirect, to cause the direction or management of such entity, - whether by contract or otherwise, or (b) ownership of more than - fifty percent (50%) of the outstanding shares or beneficial - ownership of such entity. - -2. License Grants and Conditions --------------------------------- - -2.1. Grants - -Each Contributor hereby grants You a world-wide, royalty-free, -non-exclusive license: - -(a) under intellectual property rights (other than patent or trademark) - Licensable by such Contributor to use, reproduce, make available, - modify, display, perform, distribute, and otherwise exploit its - Contributions, either on an unmodified basis, with Modifications, or - as part of a Larger Work; and - -(b) under Patent Claims of such Contributor to make, use, sell, offer - for sale, have made, import, and otherwise transfer either its - Contributions or its Contributor Version. - -2.2. Effective Date - -The licenses granted in Section 2.1 with respect to any Contribution -become effective for each Contribution on the date the Contributor first -distributes such Contribution. - -2.3. Limitations on Grant Scope - -The licenses granted in this Section 2 are the only rights granted under -this License. No additional rights or licenses will be implied from the -distribution or licensing of Covered Software under this License. -Notwithstanding Section 2.1(b) above, no patent license is granted by a -Contributor: - -(a) for any code that a Contributor has removed from Covered Software; - or - -(b) for infringements caused by: (i) Your and any other third party's - modifications of Covered Software, or (ii) the combination of its - Contributions with other software (except as part of its Contributor - Version); or - -(c) under Patent Claims infringed by Covered Software in the absence of - its Contributions. - -This License does not grant any rights in the trademarks, service marks, -or logos of any Contributor (except as may be necessary to comply with -the notice requirements in Section 3.4). - -2.4. Subsequent Licenses - -No Contributor makes additional grants as a result of Your choice to -distribute the Covered Software under a subsequent version of this -License (see Section 10.2) or under the terms of a Secondary License (if -permitted under the terms of Section 3.3). - -2.5. Representation - -Each Contributor represents that the Contributor believes its -Contributions are its original creation(s) or it has sufficient rights -to grant the rights to its Contributions conveyed by this License. - -2.6. Fair Use - -This License is not intended to limit any rights You have under -applicable copyright doctrines of fair use, fair dealing, or other -equivalents. - -2.7. Conditions - -Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted -in Section 2.1. - -3. Responsibilities -------------------- - -3.1. Distribution of Source Form - -All distribution of Covered Software in Source Code Form, including any -Modifications that You create or to which You contribute, must be under -the terms of this License. You must inform recipients that the Source -Code Form of the Covered Software is governed by the terms of this -License, and how they can obtain a copy of this License. You may not -attempt to alter or restrict the recipients' rights in the Source Code -Form. - -3.2. Distribution of Executable Form - -If You distribute Covered Software in Executable Form then: - -(a) such Covered Software must also be made available in Source Code - Form, as described in Section 3.1, and You must inform recipients of - the Executable Form how they can obtain a copy of such Source Code - Form by reasonable means in a timely manner, at a charge no more - than the cost of distribution to the recipient; and - -(b) You may distribute such Executable Form under the terms of this - License, or sublicense it under different terms, provided that the - license for the Executable Form does not attempt to limit or alter - the recipients' rights in the Source Code Form under this License. - -3.3. Distribution of a Larger Work - -You may create and distribute a Larger Work under terms of Your choice, -provided that You also comply with the requirements of this License for -the Covered Software. If the Larger Work is a combination of Covered -Software with a work governed by one or more Secondary Licenses, and the -Covered Software is not Incompatible With Secondary Licenses, this -License permits You to additionally distribute such Covered Software -under the terms of such Secondary License(s), so that the recipient of -the Larger Work may, at their option, further distribute the Covered -Software under the terms of either this License or such Secondary -License(s). - -3.4. Notices - -You may not remove or alter the substance of any license notices -(including copyright notices, patent notices, disclaimers of warranty, -or limitations of liability) contained within the Source Code Form of -the Covered Software, except that You may alter any license notices to -the extent required to remedy known factual inaccuracies. - -3.5. Application of Additional Terms - -You may choose to offer, and to charge a fee for, warranty, support, -indemnity or liability obligations to one or more recipients of Covered -Software. However, You may do so only on Your own behalf, and not on -behalf of any Contributor. You must make it absolutely clear that any -such warranty, support, indemnity, or liability obligation is offered by -You alone, and You hereby agree to indemnify every Contributor for any -liability incurred by such Contributor as a result of warranty, support, -indemnity or liability terms You offer. You may include additional -disclaimers of warranty and limitations of liability specific to any -jurisdiction. - -4. Inability to Comply Due to Statute or Regulation ---------------------------------------------------- - -If it is impossible for You to comply with any of the terms of this -License with respect to some or all of the Covered Software due to -statute, judicial order, or regulation then You must: (a) comply with -the terms of this License to the maximum extent possible; and (b) -describe the limitations and the code they affect. Such description must -be placed in a text file included with all distributions of the Covered -Software under this License. Except to the extent prohibited by statute -or regulation, such description must be sufficiently detailed for a -recipient of ordinary skill to be able to understand it. - -5. Termination --------------- - -5.1. The rights granted under this License will terminate automatically -if You fail to comply with any of its terms. However, if You become -compliant, then the rights granted under this License from a particular -Contributor are reinstated (a) provisionally, unless and until such -Contributor explicitly and finally terminates Your grants, and (b) on an -ongoing basis, if such Contributor fails to notify You of the -non-compliance by some reasonable means prior to 60 days after You have -come back into compliance. Moreover, Your grants from a particular -Contributor are reinstated on an ongoing basis if such Contributor -notifies You of the non-compliance by some reasonable means, this is the -first time You have received notice of non-compliance with this License -from such Contributor, and You become compliant prior to 30 days after -Your receipt of the notice. - -5.2. If You initiate litigation against any entity by asserting a patent -infringement claim (excluding declaratory judgment actions, -counter-claims, and cross-claims) alleging that a Contributor Version -directly or indirectly infringes any patent, then the rights granted to -You by any and all Contributors for the Covered Software under Section -2.1 of this License shall terminate. - -5.3. In the event of termination under Sections 5.1 or 5.2 above, all -end user license agreements (excluding distributors and resellers) which -have been validly granted by You or Your distributors under this License -prior to termination shall survive termination. - -************************************************************************ -* * -* 6. Disclaimer of Warranty * -* ------------------------- * -* * -* Covered Software is provided under this License on an "as is" * -* basis, without warranty of any kind, either expressed, implied, or * -* statutory, including, without limitation, warranties that the * -* Covered Software is free of defects, merchantable, fit for a * -* particular purpose or non-infringing. The entire risk as to the * -* quality and performance of the Covered Software is with You. * -* Should any Covered Software prove defective in any respect, You * -* (not any Contributor) assume the cost of any necessary servicing, * -* repair, or correction. This disclaimer of warranty constitutes an * -* essential part of this License. No use of any Covered Software is * -* authorized under this License except under this disclaimer. * -* * -************************************************************************ - -************************************************************************ -* * -* 7. Limitation of Liability * -* -------------------------- * -* * -* Under no circumstances and under no legal theory, whether tort * -* (including negligence), contract, or otherwise, shall any * -* Contributor, or anyone who distributes Covered Software as * -* permitted above, be liable to You for any direct, indirect, * -* special, incidental, or consequential damages of any character * -* including, without limitation, damages for lost profits, loss of * -* goodwill, work stoppage, computer failure or malfunction, or any * -* and all other commercial damages or losses, even if such party * -* shall have been informed of the possibility of such damages. This * -* limitation of liability shall not apply to liability for death or * -* personal injury resulting from such party's negligence to the * -* extent applicable law prohibits such limitation. Some * -* jurisdictions do not allow the exclusion or limitation of * -* incidental or consequential damages, so this exclusion and * -* limitation may not apply to You. * -* * -************************************************************************ - -8. Litigation -------------- - -Any litigation relating to this License may be brought only in the -courts of a jurisdiction where the defendant maintains its principal -place of business and such litigation shall be governed by laws of that -jurisdiction, without reference to its conflict-of-law provisions. -Nothing in this Section shall prevent a party's ability to bring -cross-claims or counter-claims. - -9. Miscellaneous ----------------- - -This License represents the complete agreement concerning the subject -matter hereof. If any provision of this License is held to be -unenforceable, such provision shall be reformed only to the extent -necessary to make it enforceable. Any law or regulation which provides -that the language of a contract shall be construed against the drafter -shall not be used to construe this License against a Contributor. - -10. Versions of the License ---------------------------- - -10.1. New Versions - -Mozilla Foundation is the license steward. Except as provided in Section -10.3, no one other than the license steward has the right to modify or -publish new versions of this License. Each version will be given a -distinguishing version number. - -10.2. Effect of New Versions - -You may distribute the Covered Software under the terms of the version -of the License under which You originally received the Covered Software, -or under the terms of any subsequent version published by the license -steward. - -10.3. Modified Versions - -If you create software not governed by this License, and you want to -create a new license for such software, you may create and use a -modified version of this License if you rename the license and remove -any references to the name of the license steward (except to note that -such modified license differs from this License). - -10.4. Distributing Source Code Form that is Incompatible With Secondary -Licenses - -If You choose to distribute Source Code Form that is Incompatible With -Secondary Licenses under the terms of this version of the License, the -notice described in Exhibit B of this License must be attached. - -Exhibit A - Source Code Form License Notice -------------------------------------------- - - This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. - -If it is not possible or desirable to put the notice in a particular -file, then You may include the notice in a location (such as a LICENSE -file in a relevant directory) where a recipient would be likely to look -for such a notice. - -You may add additional accurate notices of copyright ownership. - -Exhibit B - "Incompatible With Secondary Licenses" Notice ---------------------------------------------------------- - - This Source Code Form is "Incompatible With Secondary Licenses", as - defined by the Mozilla Public License, v. 2.0. diff --git a/systemvm/agent/noVNC/docs/LICENSE.OFL-1.1 b/systemvm/agent/noVNC/docs/LICENSE.OFL-1.1 deleted file mode 100644 index 77b17316cf1..00000000000 --- a/systemvm/agent/noVNC/docs/LICENSE.OFL-1.1 +++ /dev/null @@ -1,91 +0,0 @@ -This Font Software is licensed under the SIL Open Font License, Version 1.1. -This license is copied below, and is also available with a FAQ at: -http://scripts.sil.org/OFL - - ------------------------------------------------------------ -SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 ------------------------------------------------------------ - -PREAMBLE -The goals of the Open Font License (OFL) are to stimulate worldwide -development of collaborative font projects, to support the font creation -efforts of academic and linguistic communities, and to provide a free and -open framework in which fonts may be shared and improved in partnership -with others. - -The OFL allows the licensed fonts to be used, studied, modified and -redistributed freely as long as they are not sold by themselves. The -fonts, including any derivative works, can be bundled, embedded, -redistributed and/or sold with any software provided that any reserved -names are not used by derivative works. The fonts and derivatives, -however, cannot be released under any other type of license. The -requirement for fonts to remain under this license does not apply -to any document created using the fonts or their derivatives. - -DEFINITIONS -"Font Software" refers to the set of files released by the Copyright -Holder(s) under this license and clearly marked as such. This may -include source files, build scripts and documentation. - -"Reserved Font Name" refers to any names specified as such after the -copyright statement(s). - -"Original Version" refers to the collection of Font Software components as -distributed by the Copyright Holder(s). - -"Modified Version" refers to any derivative made by adding to, deleting, -or substituting -- in part or in whole -- any of the components of the -Original Version, by changing formats or by porting the Font Software to a -new environment. - -"Author" refers to any designer, engineer, programmer, technical -writer or other person who contributed to the Font Software. - -PERMISSION & CONDITIONS -Permission is hereby granted, free of charge, to any person obtaining -a copy of the Font Software, to use, study, copy, merge, embed, modify, -redistribute, and sell modified and unmodified copies of the Font -Software, subject to the following conditions: - -1) Neither the Font Software nor any of its individual components, -in Original or Modified Versions, may be sold by itself. - -2) Original or Modified Versions of the Font Software may be bundled, -redistributed and/or sold with any software, provided that each copy -contains the above copyright notice and this license. These can be -included either as stand-alone text files, human-readable headers or -in the appropriate machine-readable metadata fields within text or -binary files as long as those fields can be easily viewed by the user. - -3) No Modified Version of the Font Software may use the Reserved Font -Name(s) unless explicit written permission is granted by the corresponding -Copyright Holder. This restriction only applies to the primary font name as -presented to the users. - -4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font -Software shall not be used to promote, endorse or advertise any -Modified Version, except to acknowledge the contribution(s) of the -Copyright Holder(s) and the Author(s) or with their explicit written -permission. - -5) The Font Software, modified or unmodified, in part or in whole, -must be distributed entirely under this license, and must not be -distributed under any other license. The requirement for fonts to -remain under this license does not apply to any document created -using the Font Software. - -TERMINATION -This license becomes null and void if any of the above conditions are -not met. - -DISCLAIMER -THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT -OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE -COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL -DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM -OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/systemvm/agent/noVNC/docs/flash_policy.txt b/systemvm/agent/noVNC/docs/flash_policy.txt deleted file mode 100644 index df325c0ddf5..00000000000 --- a/systemvm/agent/noVNC/docs/flash_policy.txt +++ /dev/null @@ -1,4 +0,0 @@ -Manual setup: - -DATA="echo \'\'" -/usr/bin/socat -T 1 TCP-L:843,reuseaddr,fork,crlf SYSTEM:"$DATA" diff --git a/systemvm/agent/noVNC/docs/links b/systemvm/agent/noVNC/docs/links deleted file mode 100644 index 31544ce0e12..00000000000 --- a/systemvm/agent/noVNC/docs/links +++ /dev/null @@ -1,76 +0,0 @@ -New tight PNG protocol: - http://wiki.qemu.org/VNC_Tight_PNG - http://xf.iksaif.net/blog/index.php?post/2010/06/14/QEMU:-Tight-PNG-and-some-profiling - -RFB protocol and extensions: - http://tigervnc.org/cgi-bin/rfbproto - -Canvas Browser Compatibility: - http://philip.html5.org/tests/canvas/suite/tests/results.html - -WebSockets API standard: - http://www.whatwg.org/specs/web-apps/current-work/complete.html#websocket - http://dev.w3.org/html5/websockets/ - http://www.ietf.org/id/draft-ietf-hybi-thewebsocketprotocol-00.txt - -Browser Keyboard Events detailed: - http://unixpapa.com/js/key.html - -ActionScript (Flash) WebSocket implementation: - http://github.com/gimite/web-socket-js - -ActionScript (Flash) crypto/TLS library: - http://code.google.com/p/as3crypto - http://github.com/lyokato/as3crypto_patched - -TLS Protocol: - http://en.wikipedia.org/wiki/Transport_Layer_Security - -Generate self-signed certificate: - http://docs.python.org/dev/library/ssl.html#certificates - -Cursor appearance/style (for Cursor pseudo-encoding): - http://en.wikipedia.org/wiki/ICO_(file_format) - http://www.daubnet.com/en/file-format-cur - https://developer.mozilla.org/en/Using_URL_values_for_the_cursor_property - http://www.fileformat.info/format/bmp/egff.htm - -Icon/Cursor file format: - http://msdn.microsoft.com/en-us/library/ms997538 - http://msdn.microsoft.com/en-us/library/aa921550.aspx - http://msdn.microsoft.com/en-us/library/aa930622.aspx - - -RDP Protocol specification: - http://msdn.microsoft.com/en-us/library/cc240445(v=PROT.10).aspx - - -Related projects: - - guacamole: http://guacamole.sourceforge.net/ - - - Web client, but Java servlet does pre-processing - - jsvnc: http://code.google.com/p/jsvnc/ - - - No releases - - webvnc: http://code.google.com/p/webvnc/ - - - Jetty web server gateway, no updates since April 2008. - - RealVNC Java applet: http://www.realvnc.com/support/javavncviewer.html - - - Java applet - - Flashlight-VNC: http://www.wizhelp.com/flashlight-vnc/ - - - Adobe Flash implementation - - FVNC: http://osflash.org/fvnc - - - Adbove Flash implementation - - CanVNC: http://canvnc.sourceforge.net/ - - - HTML client with REST to VNC python proxy. Mostly vapor. diff --git a/systemvm/agent/noVNC/docs/notes b/systemvm/agent/noVNC/docs/notes deleted file mode 100644 index dfef0bd6afe..00000000000 --- a/systemvm/agent/noVNC/docs/notes +++ /dev/null @@ -1,5 +0,0 @@ -Rebuilding inflator.js - -- Download pako from npm -- Install browserify using npm -- browserify core/inflator.mod.js -o core/inflator.js -s Inflator diff --git a/systemvm/agent/noVNC/docs/rfb_notes b/systemvm/agent/noVNC/docs/rfb_notes deleted file mode 100644 index 643e16c01e7..00000000000 --- a/systemvm/agent/noVNC/docs/rfb_notes +++ /dev/null @@ -1,147 +0,0 @@ -5.1.1 ProtocolVersion: 12, 12 bytes - - - Sent by server, max supported - 12 ascii - "RFB 003.008\n" - - Response by client, version to use - 12 ascii - "RFB 003.003\n" - -5.1.2 Authentication: >=4, [16, 4] bytes - - - Sent by server - CARD32 - authentication-scheme - 0 - connection failed - CARD32 - length - length - reason - 1 - no authentication - - 2 - VNC authentication - 16 CARD8 - challenge (random bytes) - - - Response by client (if VNC authentication) - 16 CARD8 - client encrypts the challenge with DES, using user - password as key, sends resulting 16 byte response - - - Response by server (if VNC authentication) - CARD32 - 0 - OK - 1 - failed - 2 - too-many - -5.1.3 ClientInitialisation: 1 byte - - Sent by client - CARD8 - shared-flag, 0 exclusive, non-zero shared - -5.1.4 ServerInitialisation: >=24 bytes - - Sent by server - CARD16 - framebuffer-width - CARD16 - framebuffer-height - 16 byte PIXEL_FORMAT - server-pixel-format - CARD8 - bits-per-pixel - CARD8 - depth - CARD8 - big-endian-flag, non-zero is big endian - CARD8 - true-color-flag, non-zero then next 6 apply - CARD16 - red-max - CARD16 - green-max - CARD16 - blue-max - CARD8 - red-shift - CARD8 - green-shift - CARD8 - blue-shift - 3 bytes - padding - CARD32 - name-length - - CARD8[length] - name-string - - - -Client to Server Messages: - -5.2.1 SetPixelFormat: 20 bytes - CARD8: 0 - message-type - ... - -5.2.2 FixColourMapEntries: >=6 bytes - CARD8: 1 - message-type - ... - -5.2.3 SetEncodings: >=8 bytes - CARD8: 2 - message-type - CARD8 - padding - CARD16 - numer-of-encodings - - CARD32 - encoding-type in preference order - 0 - raw - 1 - copy-rectangle - 2 - RRE - 4 - CoRRE - 5 - hextile - -5.2.4 FramebufferUpdateRequest (10 bytes) - CARD8: 3 - message-type - CARD8 - incremental (0 for full-update, non-zero for incremental) - CARD16 - x-position - CARD16 - y-position - CARD16 - width - CARD16 - height - - -5.2.5 KeyEvent: 8 bytes - CARD8: 4 - message-type - CARD8 - down-flag - 2 bytes - padding - CARD32 - key (X-Windows keysym values) - -5.2.6 PointerEvent: 6 bytes - CARD8: 5 - message-type - CARD8 - button-mask - CARD16 - x-position - CARD16 - y-position - -5.2.7 ClientCutText: >=9 bytes - CARD8: 6 - message-type - ... - - -Server to Client Messages: - -5.3.1 FramebufferUpdate - CARD8: 0 - message-type - 1 byte - padding - CARD16 - number-of-rectangles - - CARD16 - x-position - CARD16 - y-position - CARD16 - width - CARD16 - height - CARD16 - encoding-type: - 0 - raw - 1 - copy rectangle - 2 - RRE - 4 - CoRRE - 5 - hextile - - raw: - - width x height pixel values - - copy rectangle: - CARD16 - src-x-position - CARD16 - src-y-position - - RRE: - CARD32 - N number-of-subrectangles - Nxd bytes - background-pixel-value (d bits-per-pixel) - - ... - -5.3.2 SetColourMapEntries (no support) - CARD8: 1 - message-type - ... - -5.3.3 Bell - CARD8: 2 - message-type - -5.3.4 ServerCutText - CARD8: 3 - message-type - - - - - diff --git a/systemvm/agent/noVNC/docs/rfbproto-3.3.pdf b/systemvm/agent/noVNC/docs/rfbproto-3.3.pdf deleted file mode 100644 index 56b876436a9..00000000000 Binary files a/systemvm/agent/noVNC/docs/rfbproto-3.3.pdf and /dev/null differ diff --git a/systemvm/agent/noVNC/docs/rfbproto-3.7.pdf b/systemvm/agent/noVNC/docs/rfbproto-3.7.pdf deleted file mode 100644 index 1ef54623c17..00000000000 Binary files a/systemvm/agent/noVNC/docs/rfbproto-3.7.pdf and /dev/null differ diff --git a/systemvm/agent/noVNC/docs/rfbproto-3.8.pdf b/systemvm/agent/noVNC/docs/rfbproto-3.8.pdf deleted file mode 100644 index 8f0730fb7f9..00000000000 Binary files a/systemvm/agent/noVNC/docs/rfbproto-3.8.pdf and /dev/null differ diff --git a/systemvm/agent/noVNC/karma.conf.js b/systemvm/agent/noVNC/karma.conf.js deleted file mode 100644 index 5cbd7a5de86..00000000000 --- a/systemvm/agent/noVNC/karma.conf.js +++ /dev/null @@ -1,134 +0,0 @@ -// Karma configuration - -module.exports = (config) => { - const customLaunchers = {}; - let browsers = []; - let useSauce = false; - - // use Sauce when running on Travis - if (process.env.TRAVIS_JOB_NUMBER) { - useSauce = true; - } - - if (useSauce && process.env.TEST_BROWSER_NAME && process.env.TEST_BROWSER_NAME != 'PhantomJS') { - const names = process.env.TEST_BROWSER_NAME.split(','); - const platforms = process.env.TEST_BROWSER_OS.split(','); - const versions = process.env.TEST_BROWSER_VERSION - ? process.env.TEST_BROWSER_VERSION.split(',') - : [null]; - - for (let i = 0; i < names.length; i++) { - for (let j = 0; j < platforms.length; j++) { - for (let k = 0; k < versions.length; k++) { - let launcher_name = 'sl_' + platforms[j].replace(/[^a-zA-Z0-9]/g, '') + '_' + names[i]; - if (versions[k]) { - launcher_name += '_' + versions[k]; - } - - customLaunchers[launcher_name] = { - base: 'SauceLabs', - browserName: names[i], - platform: platforms[j], - }; - - if (versions[i]) { - customLaunchers[launcher_name].version = versions[k]; - } - } - } - } - - browsers = Object.keys(customLaunchers); - } else { - useSauce = false; - //browsers = ['PhantomJS']; - browsers = []; - } - - const my_conf = { - - // base path that will be used to resolve all patterns (eg. files, exclude) - basePath: '', - - // frameworks to use - // available frameworks: https://npmjs.org/browse/keyword/karma-adapter - frameworks: ['mocha', 'sinon-chai'], - - // list of files / patterns to load in the browser (loaded in order) - files: [ - { pattern: 'app/localization.js', included: false }, - { pattern: 'app/webutil.js', included: false }, - { pattern: 'core/**/*.js', included: false }, - { pattern: 'vendor/pako/**/*.js', included: false }, - { pattern: 'vendor/browser-es-module-loader/dist/*.js*', included: false }, - { pattern: 'tests/test.*.js', included: false }, - { pattern: 'tests/fake.*.js', included: false }, - { pattern: 'tests/assertions.js', included: false }, - 'vendor/promise.js', - 'tests/karma-test-main.js', - ], - - client: { - mocha: { - // replace Karma debug page with mocha display - 'reporter': 'html', - 'ui': 'bdd' - } - }, - - // list of files to exclude - exclude: [ - ], - - customLaunchers: customLaunchers, - - // start these browsers - // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher - browsers: browsers, - - // test results reporter to use - // possible values: 'dots', 'progress' - // available reporters: https://npmjs.org/browse/keyword/karma-reporter - reporters: ['mocha'], - - - // web server port - port: 9876, - - - // enable / disable colors in the output (reporters and logs) - colors: true, - - - // level of logging - // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG - logLevel: config.LOG_INFO, - - - // enable / disable watching file and executing tests whenever any file changes - autoWatch: false, - - // Continuous Integration mode - // if true, Karma captures browsers, runs the tests and exits - singleRun: true, - - // Increase timeout in case connection is slow/we run more browsers than possible - // (we currently get 3 for free, and we try to run 7, so it can take a while) - captureTimeout: 240000, - - // similarly to above - browserNoActivityTimeout: 100000, - }; - - if (useSauce) { - my_conf.reporters.push('saucelabs'); - my_conf.captureTimeout = 0; // use SL timeout - my_conf.sauceLabs = { - testName: 'noVNC Tests (all)', - startConnect: false, - tunnelIdentifier: process.env.TRAVIS_JOB_NUMBER - }; - } - - config.set(my_conf); -}; diff --git a/systemvm/agent/noVNC/package.json b/systemvm/agent/noVNC/package.json index 2d84a5f38e5..8fc04e50ae2 100644 --- a/systemvm/agent/noVNC/package.json +++ b/systemvm/agent/noVNC/package.json @@ -1,6 +1,6 @@ { "name": "@novnc/novnc", - "version": "1.1.0", + "version": "1.2.0", "description": "An HTML5 VNC client", "browser": "lib/rfb", "directories": { @@ -19,7 +19,7 @@ "vendor/pako" ], "scripts": { - "lint": "eslint app core po tests utils", + "lint": "eslint app core po/po2js po/xgettext-html tests utils", "test": "karma start karma.conf.js", "prepublish": "node ./utils/use_require.js --as commonjs --clean" }, @@ -40,36 +40,42 @@ }, "homepage": "https://github.com/novnc/noVNC", "devDependencies": { - "babel-core": "^6.22.1", - "babel-plugin-add-module-exports": "^0.2.1", + "@babel/core": "*", + "@babel/plugin-syntax-dynamic-import": "*", + "@babel/plugin-transform-modules-amd": "*", + "@babel/plugin-transform-modules-commonjs": "*", + "@babel/plugin-transform-modules-systemjs": "*", + "@babel/plugin-transform-modules-umd": "*", + "@babel/preset-env": "*", + "@babel/cli": "*", "babel-plugin-import-redirect": "*", - "babel-plugin-syntax-dynamic-import": "^6.18.0", - "babel-plugin-transform-es2015-modules-amd": "^6.22.0", - "babel-plugin-transform-es2015-modules-commonjs": "^6.18.0", - "babel-plugin-transform-es2015-modules-systemjs": "^6.22.0", - "babel-plugin-transform-es2015-modules-umd": "^6.22.0", - "babel-preset-es2015": "^6.24.1", - "babelify": "^7.3.0", - "browserify": "^13.1.0", - "chai": "^3.5.0", - "commander": "^2.9.0", - "es-module-loader": "^2.1.0", - "eslint": "^4.16.0", - "fs-extra": "^1.0.0", + "browserify": "*", + "babelify": "*", + "core-js": "*", + "chai": "*", + "commander": "*", + "es-module-loader": "*", + "eslint": "*", + "fs-extra": "*", "jsdom": "*", - "karma": "^1.3.0", - "karma-mocha": "^1.3.0", - "karma-mocha-reporter": "^2.2.0", - "karma-sauce-launcher": "^1.0.0", - "karma-sinon-chai": "^2.0.0", - "mocha": "^3.1.2", + "karma": "*", + "karma-mocha": "*", + "karma-chrome-launcher": "*", + "@chiragrupani/karma-chromium-edge-launcher": "*", + "karma-firefox-launcher": "*", + "karma-ie-launcher": "*", + "karma-mocha-reporter": "*", + "karma-safari-launcher": "*", + "karma-script-launcher": "*", + "karma-sinon-chai": "*", + "mocha": "*", "node-getopt": "*", "po2json": "*", - "requirejs": "^2.3.2", - "rollup": "^0.41.4", - "rollup-plugin-node-resolve": "^2.0.0", - "sinon": "^4.0.0", - "sinon-chai": "^2.8.0" + "requirejs": "*", + "rollup": "*", + "rollup-plugin-node-resolve": "*", + "sinon": "*", + "sinon-chai": "*" }, "dependencies": {}, "keywords": [ diff --git a/systemvm/agent/noVNC/po/Makefile b/systemvm/agent/noVNC/po/Makefile deleted file mode 100644 index 6dbd83043f7..00000000000 --- a/systemvm/agent/noVNC/po/Makefile +++ /dev/null @@ -1,35 +0,0 @@ -all: -.PHONY: update-po update-js update-pot - -LINGUAS := cs de el es ko nl pl ru sv tr zh_CN zh_TW - -VERSION := $(shell grep '"version"' ../package.json | cut -d '"' -f 4) - -POFILES := $(addsuffix .po,$(LINGUAS)) -JSONFILES := $(addprefix ../app/locale/,$(addsuffix .json,$(LINGUAS))) - -update-po: $(POFILES) -update-js: $(JSONFILES) - -%.po: noVNC.pot - msgmerge --update --lang=$* $@ $< -../app/locale/%.json: %.po - ./po2js $< $@ - -update-pot: - xgettext --output=noVNC.js.pot \ - --copyright-holder="The noVNC Authors" \ - --package-name="noVNC" \ - --package-version="$(VERSION)" \ - --msgid-bugs-address="novnc@googlegroups.com" \ - --add-comments=TRANSLATORS: \ - --from-code=UTF-8 \ - --sort-by-file \ - ../app/*.js \ - ../core/*.js \ - ../core/input/*.js - ./xgettext-html --output=noVNC.html.pot \ - ../vnc.html - msgcat --output-file=noVNC.pot \ - --sort-by-file noVNC.js.pot noVNC.html.pot - rm -f noVNC.js.pot noVNC.html.pot diff --git a/systemvm/agent/noVNC/po/cs.po b/systemvm/agent/noVNC/po/cs.po deleted file mode 100644 index 2b1efd8d918..00000000000 --- a/systemvm/agent/noVNC/po/cs.po +++ /dev/null @@ -1,294 +0,0 @@ -# Czech translations for noVNC package. -# Copyright (C) 2018 The noVNC Authors -# This file is distributed under the same license as the noVNC package. -# Petr , 2018. -# -msgid "" -msgstr "" -"Project-Id-Version: noVNC 1.0.0-testing.2\n" -"Report-Msgid-Bugs-To: novnc@googlegroups.com\n" -"POT-Creation-Date: 2018-10-19 12:00+0200\n" -"PO-Revision-Date: 2018-10-19 12:00+0200\n" -"Last-Translator: Petr \n" -"Language-Team: Czech\n" -"Language: cs\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" - -#: ../app/ui.js:389 -msgid "Connecting..." -msgstr "Připojení..." - -#: ../app/ui.js:396 -msgid "Disconnecting..." -msgstr "Odpojení..." - -#: ../app/ui.js:402 -msgid "Reconnecting..." -msgstr "Obnova připojení..." - -#: ../app/ui.js:407 -msgid "Internal error" -msgstr "Vnitřní chyba" - -#: ../app/ui.js:997 -msgid "Must set host" -msgstr "Hostitel musí být nastavení" - -#: ../app/ui.js:1079 -msgid "Connected (encrypted) to " -msgstr "Připojení (šifrované) k " - -#: ../app/ui.js:1081 -msgid "Connected (unencrypted) to " -msgstr "Připojení (nešifrované) k " - -#: ../app/ui.js:1104 -msgid "Something went wrong, connection is closed" -msgstr "Něco se pokazilo, odpojeno" - -#: ../app/ui.js:1107 -msgid "Failed to connect to server" -msgstr "Chyba připojení k serveru" - -#: ../app/ui.js:1117 -msgid "Disconnected" -msgstr "Odpojeno" - -#: ../app/ui.js:1130 -msgid "New connection has been rejected with reason: " -msgstr "Nové připojení bylo odmítnuto s odůvodněním: " - -#: ../app/ui.js:1133 -msgid "New connection has been rejected" -msgstr "Nové připojení bylo odmítnuto" - -#: ../app/ui.js:1153 -msgid "Password is required" -msgstr "Je vyžadováno heslo" - -#: ../vnc.html:84 -msgid "noVNC encountered an error:" -msgstr "noVNC narazilo na chybu:" - -#: ../vnc.html:94 -msgid "Hide/Show the control bar" -msgstr "Skrýt/zobrazit ovládací panel" - -#: ../vnc.html:101 -msgid "Move/Drag Viewport" -msgstr "Přesunout/přetáhnout výřez" - -#: ../vnc.html:101 -msgid "viewport drag" -msgstr "přesun výřezu" - -#: ../vnc.html:107 ../vnc.html:110 ../vnc.html:113 ../vnc.html:116 -msgid "Active Mouse Button" -msgstr "Aktivní tlačítka myši" - -#: ../vnc.html:107 -msgid "No mousebutton" -msgstr "Žádné" - -#: ../vnc.html:110 -msgid "Left mousebutton" -msgstr "Levé tlačítko myši" - -#: ../vnc.html:113 -msgid "Middle mousebutton" -msgstr "Prostřední tlačítko myši" - -#: ../vnc.html:116 -msgid "Right mousebutton" -msgstr "Pravé tlačítko myši" - -#: ../vnc.html:119 -msgid "Keyboard" -msgstr "Klávesnice" - -#: ../vnc.html:119 -msgid "Show Keyboard" -msgstr "Zobrazit klávesnici" - -#: ../vnc.html:126 -msgid "Extra keys" -msgstr "Extra klávesy" - -#: ../vnc.html:126 -msgid "Show Extra Keys" -msgstr "Zobrazit extra klávesy" - -#: ../vnc.html:131 -msgid "Ctrl" -msgstr "Ctrl" - -#: ../vnc.html:131 -msgid "Toggle Ctrl" -msgstr "Přepnout Ctrl" - -#: ../vnc.html:134 -msgid "Alt" -msgstr "Alt" - -#: ../vnc.html:134 -msgid "Toggle Alt" -msgstr "Přepnout Alt" - -#: ../vnc.html:137 -msgid "Send Tab" -msgstr "Odeslat tabulátor" - -#: ../vnc.html:137 -msgid "Tab" -msgstr "Tab" - -#: ../vnc.html:140 -msgid "Esc" -msgstr "Esc" - -#: ../vnc.html:140 -msgid "Send Escape" -msgstr "Odeslat Esc" - -#: ../vnc.html:143 -msgid "Ctrl+Alt+Del" -msgstr "Ctrl+Alt+Del" - -#: ../vnc.html:143 -msgid "Send Ctrl-Alt-Del" -msgstr "Poslat Ctrl-Alt-Del" - -#: ../vnc.html:151 -msgid "Shutdown/Reboot" -msgstr "Vypnutí/Restart" - -#: ../vnc.html:151 -msgid "Shutdown/Reboot..." -msgstr "Vypnutí/Restart..." - -#: ../vnc.html:157 -msgid "Power" -msgstr "Napájení" - -#: ../vnc.html:159 -msgid "Shutdown" -msgstr "Vypnout" - -#: ../vnc.html:160 -msgid "Reboot" -msgstr "Restart" - -#: ../vnc.html:161 -msgid "Reset" -msgstr "Reset" - -#: ../vnc.html:166 ../vnc.html:172 -msgid "Clipboard" -msgstr "Schránka" - -#: ../vnc.html:176 -msgid "Clear" -msgstr "Vymazat" - -#: ../vnc.html:182 -msgid "Fullscreen" -msgstr "Celá obrazovka" - -#: ../vnc.html:187 ../vnc.html:194 -msgid "Settings" -msgstr "Nastavení" - -#: ../vnc.html:197 -msgid "Shared Mode" -msgstr "Sdílený režim" - -#: ../vnc.html:200 -msgid "View Only" -msgstr "Pouze prohlížení" - -#: ../vnc.html:204 -msgid "Clip to Window" -msgstr "Přizpůsobit oknu" - -#: ../vnc.html:207 -msgid "Scaling Mode:" -msgstr "Přizpůsobení velikosti" - -#: ../vnc.html:209 -msgid "None" -msgstr "Žádné" - -#: ../vnc.html:210 -msgid "Local Scaling" -msgstr "Místní" - -#: ../vnc.html:211 -msgid "Remote Resizing" -msgstr "Vzdálené" - -#: ../vnc.html:216 -msgid "Advanced" -msgstr "Pokročilé" - -#: ../vnc.html:219 -msgid "Repeater ID:" -msgstr "ID opakovače" - -#: ../vnc.html:223 -msgid "WebSocket" -msgstr "WebSocket" - -#: ../vnc.html:226 -msgid "Encrypt" -msgstr "Šifrování:" - -#: ../vnc.html:229 -msgid "Host:" -msgstr "Hostitel:" - -#: ../vnc.html:233 -msgid "Port:" -msgstr "Port:" - -#: ../vnc.html:237 -msgid "Path:" -msgstr "Cesta" - -#: ../vnc.html:244 -msgid "Automatic Reconnect" -msgstr "Automatická obnova připojení" - -#: ../vnc.html:247 -msgid "Reconnect Delay (ms):" -msgstr "Zpoždění připojení (ms)" - -#: ../vnc.html:252 -msgid "Show Dot when No Cursor" -msgstr "Tečka místo chybějícího kurzoru myši" - -#: ../vnc.html:257 -msgid "Logging:" -msgstr "Logování:" - -#: ../vnc.html:269 -msgid "Disconnect" -msgstr "Odpojit" - -#: ../vnc.html:288 -msgid "Connect" -msgstr "Připojit" - -#: ../vnc.html:298 -msgid "Password:" -msgstr "Heslo" - -#: ../vnc.html:302 -msgid "Send Password" -msgstr "Odeslat heslo" - -#: ../vnc.html:312 -msgid "Cancel" -msgstr "Zrušit" diff --git a/systemvm/agent/noVNC/po/de.po b/systemvm/agent/noVNC/po/de.po deleted file mode 100644 index 0c3fa0d482a..00000000000 --- a/systemvm/agent/noVNC/po/de.po +++ /dev/null @@ -1,303 +0,0 @@ -# German translations for noVNC package -# German translation for noVNC. -# Copyright (C) 2018 The noVNC Authors -# This file is distributed under the same license as the noVNC package. -# Loek Janssen , 2016. -# -msgid "" -msgstr "" -"Project-Id-Version: noVNC 0.6.1\n" -"Report-Msgid-Bugs-To: novnc@googlegroups.com\n" -"POT-Creation-Date: 2017-11-24 07:16+0000\n" -"PO-Revision-Date: 2017-11-24 08:20+0100\n" -"Last-Translator: Dominik Csapak \n" -"Language-Team: none\n" -"Language: de\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Poedit 1.8.11\n" - -#: ../app/ui.js:404 -msgid "Connecting..." -msgstr "Verbinden..." - -#: ../app/ui.js:411 -msgid "Disconnecting..." -msgstr "Verbindung trennen..." - -#: ../app/ui.js:417 -msgid "Reconnecting..." -msgstr "Verbindung wiederherstellen..." - -#: ../app/ui.js:422 -msgid "Internal error" -msgstr "Interner Fehler" - -#: ../app/ui.js:1019 -msgid "Must set host" -msgstr "Richten Sie den Server ein" - -#: ../app/ui.js:1099 -msgid "Connected (encrypted) to " -msgstr "Verbunden mit (verschlüsselt) " - -#: ../app/ui.js:1101 -msgid "Connected (unencrypted) to " -msgstr "Verbunden mit (unverschlüsselt) " - -#: ../app/ui.js:1119 -msgid "Something went wrong, connection is closed" -msgstr "Etwas lief schief, Verbindung wurde getrennt" - -#: ../app/ui.js:1129 -msgid "Disconnected" -msgstr "Verbindung zum Server getrennt" - -#: ../app/ui.js:1142 -msgid "New connection has been rejected with reason: " -msgstr "Verbindung wurde aus folgendem Grund abgelehnt: " - -#: ../app/ui.js:1145 -msgid "New connection has been rejected" -msgstr "Verbindung wurde abgelehnt" - -#: ../app/ui.js:1166 -msgid "Password is required" -msgstr "Passwort ist erforderlich" - -#: ../vnc.html:89 -msgid "noVNC encountered an error:" -msgstr "Ein Fehler ist aufgetreten:" - -#: ../vnc.html:99 -msgid "Hide/Show the control bar" -msgstr "Kontrollleiste verstecken/anzeigen" - -#: ../vnc.html:106 -msgid "Move/Drag Viewport" -msgstr "Ansichtsfenster verschieben/ziehen" - -#: ../vnc.html:106 -msgid "viewport drag" -msgstr "Ansichtsfenster ziehen" - -#: ../vnc.html:112 ../vnc.html:115 ../vnc.html:118 ../vnc.html:121 -msgid "Active Mouse Button" -msgstr "Aktive Maustaste" - -#: ../vnc.html:112 -msgid "No mousebutton" -msgstr "Keine Maustaste" - -#: ../vnc.html:115 -msgid "Left mousebutton" -msgstr "Linke Maustaste" - -#: ../vnc.html:118 -msgid "Middle mousebutton" -msgstr "Mittlere Maustaste" - -#: ../vnc.html:121 -msgid "Right mousebutton" -msgstr "Rechte Maustaste" - -#: ../vnc.html:124 -msgid "Keyboard" -msgstr "Tastatur" - -#: ../vnc.html:124 -msgid "Show Keyboard" -msgstr "Tastatur anzeigen" - -#: ../vnc.html:131 -msgid "Extra keys" -msgstr "Zusatztasten" - -#: ../vnc.html:131 -msgid "Show Extra Keys" -msgstr "Zusatztasten anzeigen" - -#: ../vnc.html:136 -msgid "Ctrl" -msgstr "Strg" - -#: ../vnc.html:136 -msgid "Toggle Ctrl" -msgstr "Strg umschalten" - -#: ../vnc.html:139 -msgid "Alt" -msgstr "Alt" - -#: ../vnc.html:139 -msgid "Toggle Alt" -msgstr "Alt umschalten" - -#: ../vnc.html:142 -msgid "Send Tab" -msgstr "Tab senden" - -#: ../vnc.html:142 -msgid "Tab" -msgstr "Tab" - -#: ../vnc.html:145 -msgid "Esc" -msgstr "Esc" - -#: ../vnc.html:145 -msgid "Send Escape" -msgstr "Escape senden" - -#: ../vnc.html:148 -msgid "Ctrl+Alt+Del" -msgstr "Strg+Alt+Entf" - -#: ../vnc.html:148 -msgid "Send Ctrl-Alt-Del" -msgstr "Strg+Alt+Entf senden" - -#: ../vnc.html:156 -msgid "Shutdown/Reboot" -msgstr "Herunterfahren/Neustarten" - -#: ../vnc.html:156 -msgid "Shutdown/Reboot..." -msgstr "Herunterfahren/Neustarten..." - -#: ../vnc.html:162 -msgid "Power" -msgstr "Energie" - -#: ../vnc.html:164 -msgid "Shutdown" -msgstr "Herunterfahren" - -#: ../vnc.html:165 -msgid "Reboot" -msgstr "Neustarten" - -#: ../vnc.html:166 -msgid "Reset" -msgstr "Zurücksetzen" - -#: ../vnc.html:171 ../vnc.html:177 -msgid "Clipboard" -msgstr "Zwischenablage" - -#: ../vnc.html:181 -msgid "Clear" -msgstr "Löschen" - -#: ../vnc.html:187 -msgid "Fullscreen" -msgstr "Vollbild" - -#: ../vnc.html:192 ../vnc.html:199 -msgid "Settings" -msgstr "Einstellungen" - -#: ../vnc.html:202 -msgid "Shared Mode" -msgstr "Geteilter Modus" - -#: ../vnc.html:205 -msgid "View Only" -msgstr "Nur betrachten" - -#: ../vnc.html:209 -msgid "Clip to Window" -msgstr "Auf Fenster begrenzen" - -#: ../vnc.html:212 -msgid "Scaling Mode:" -msgstr "Skalierungsmodus:" - -#: ../vnc.html:214 -msgid "None" -msgstr "Keiner" - -#: ../vnc.html:215 -msgid "Local Scaling" -msgstr "Lokales skalieren" - -#: ../vnc.html:216 -msgid "Remote Resizing" -msgstr "Serverseitiges skalieren" - -#: ../vnc.html:221 -msgid "Advanced" -msgstr "Erweitert" - -#: ../vnc.html:224 -msgid "Repeater ID:" -msgstr "Repeater ID:" - -#: ../vnc.html:228 -msgid "WebSocket" -msgstr "WebSocket" - -#: ../vnc.html:231 -msgid "Encrypt" -msgstr "Verschlüsselt" - -#: ../vnc.html:234 -msgid "Host:" -msgstr "Server:" - -#: ../vnc.html:238 -msgid "Port:" -msgstr "Port:" - -#: ../vnc.html:242 -msgid "Path:" -msgstr "Pfad:" - -#: ../vnc.html:249 -msgid "Automatic Reconnect" -msgstr "Automatisch wiederverbinden" - -#: ../vnc.html:252 -msgid "Reconnect Delay (ms):" -msgstr "Wiederverbindungsverzögerung (ms):" - -#: ../vnc.html:258 -msgid "Logging:" -msgstr "Protokollierung:" - -#: ../vnc.html:270 -msgid "Disconnect" -msgstr "Verbindung trennen" - -#: ../vnc.html:289 -msgid "Connect" -msgstr "Verbinden" - -#: ../vnc.html:299 -msgid "Password:" -msgstr "Passwort:" - -#: ../vnc.html:313 -msgid "Cancel" -msgstr "Abbrechen" - -#: ../vnc.html:329 -msgid "Canvas not supported." -msgstr "Canvas nicht unterstützt." - -#~ msgid "Disconnect timeout" -#~ msgstr "Zeitüberschreitung beim Trennen" - -#~ msgid "Local Downscaling" -#~ msgstr "Lokales herunterskalieren" - -#~ msgid "Local Cursor" -#~ msgstr "Lokaler Mauszeiger" - -#~ msgid "Forcing clipping mode since scrollbars aren't supported by IE in fullscreen" -#~ msgstr "'Clipping-Modus' aktiviert, Scrollbalken in 'IE-Vollbildmodus' werden nicht unterstützt" - -#~ msgid "True Color" -#~ msgstr "True Color" diff --git a/systemvm/agent/noVNC/po/el.po b/systemvm/agent/noVNC/po/el.po deleted file mode 100644 index 5213ae5423a..00000000000 --- a/systemvm/agent/noVNC/po/el.po +++ /dev/null @@ -1,323 +0,0 @@ -# Greek translations for noVNC package. -# Copyright (C) 2018 The noVNC Authors -# This file is distributed under the same license as the noVNC package. -# Giannis Kosmas , 2016. -# -msgid "" -msgstr "" -"Project-Id-Version: noVNC 0.6.1\n" -"Report-Msgid-Bugs-To: novnc@googlegroups.com\n" -"POT-Creation-Date: 2017-11-17 21:40+0200\n" -"PO-Revision-Date: 2017-10-11 16:16+0200\n" -"Last-Translator: Giannis Kosmas \n" -"Language-Team: none\n" -"Language: el\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -#: ../app/ui.js:404 -msgid "Connecting..." -msgstr "Συνδέεται..." - -#: ../app/ui.js:411 -msgid "Disconnecting..." -msgstr "Aποσυνδέεται..." - -#: ../app/ui.js:417 -msgid "Reconnecting..." -msgstr "Επανασυνδέεται..." - -#: ../app/ui.js:422 -msgid "Internal error" -msgstr "Εσωτερικό σφάλμα" - -#: ../app/ui.js:1019 -msgid "Must set host" -msgstr "Πρέπει να οριστεί ο διακομιστής" - -#: ../app/ui.js:1099 -msgid "Connected (encrypted) to " -msgstr "Συνδέθηκε (κρυπτογραφημένα) με το " - -#: ../app/ui.js:1101 -msgid "Connected (unencrypted) to " -msgstr "Συνδέθηκε (μη κρυπτογραφημένα) με το " - -#: ../app/ui.js:1119 -msgid "Something went wrong, connection is closed" -msgstr "Κάτι πήγε στραβά, η σύνδεση διακόπηκε" - -#: ../app/ui.js:1129 -msgid "Disconnected" -msgstr "Αποσυνδέθηκε" - -#: ../app/ui.js:1142 -msgid "New connection has been rejected with reason: " -msgstr "Η νέα σύνδεση απορρίφθηκε διότι: " - -#: ../app/ui.js:1145 -msgid "New connection has been rejected" -msgstr "Η νέα σύνδεση απορρίφθηκε " - -#: ../app/ui.js:1166 -msgid "Password is required" -msgstr "Απαιτείται ο κωδικός πρόσβασης" - -#: ../vnc.html:89 -msgid "noVNC encountered an error:" -msgstr "το noVNC αντιμετώπισε ένα σφάλμα:" - -#: ../vnc.html:99 -msgid "Hide/Show the control bar" -msgstr "Απόκρυψη/Εμφάνιση γραμμής ελέγχου" - -#: ../vnc.html:106 -msgid "Move/Drag Viewport" -msgstr "Μετακίνηση/Σύρσιμο Θεατού πεδίου" - -#: ../vnc.html:106 -msgid "viewport drag" -msgstr "σύρσιμο θεατού πεδίου" - -#: ../vnc.html:112 ../vnc.html:115 ../vnc.html:118 ../vnc.html:121 -msgid "Active Mouse Button" -msgstr "Ενεργό Πλήκτρο Ποντικιού" - -#: ../vnc.html:112 -msgid "No mousebutton" -msgstr "Χωρίς Πλήκτρο Ποντικιού" - -#: ../vnc.html:115 -msgid "Left mousebutton" -msgstr "Αριστερό Πλήκτρο Ποντικιού" - -#: ../vnc.html:118 -msgid "Middle mousebutton" -msgstr "Μεσαίο Πλήκτρο Ποντικιού" - -#: ../vnc.html:121 -msgid "Right mousebutton" -msgstr "Δεξί Πλήκτρο Ποντικιού" - -#: ../vnc.html:124 -msgid "Keyboard" -msgstr "Πληκτρολόγιο" - -#: ../vnc.html:124 -msgid "Show Keyboard" -msgstr "Εμφάνιση Πληκτρολογίου" - -#: ../vnc.html:131 -msgid "Extra keys" -msgstr "Επιπλέον πλήκτρα" - -#: ../vnc.html:131 -msgid "Show Extra Keys" -msgstr "Εμφάνιση Επιπλέον Πλήκτρων" - -#: ../vnc.html:136 -msgid "Ctrl" -msgstr "Ctrl" - -#: ../vnc.html:136 -msgid "Toggle Ctrl" -msgstr "Εναλλαγή Ctrl" - -#: ../vnc.html:139 -msgid "Alt" -msgstr "Alt" - -#: ../vnc.html:139 -msgid "Toggle Alt" -msgstr "Εναλλαγή Alt" - -#: ../vnc.html:142 -msgid "Send Tab" -msgstr "Αποστολή Tab" - -#: ../vnc.html:142 -msgid "Tab" -msgstr "Tab" - -#: ../vnc.html:145 -msgid "Esc" -msgstr "Esc" - -#: ../vnc.html:145 -msgid "Send Escape" -msgstr "Αποστολή Escape" - -#: ../vnc.html:148 -msgid "Ctrl+Alt+Del" -msgstr "Ctrl+Alt+Del" - -#: ../vnc.html:148 -msgid "Send Ctrl-Alt-Del" -msgstr "Αποστολή Ctrl-Alt-Del" - -#: ../vnc.html:156 -msgid "Shutdown/Reboot" -msgstr "Κλείσιμο/Επανεκκίνηση" - -#: ../vnc.html:156 -msgid "Shutdown/Reboot..." -msgstr "Κλείσιμο/Επανεκκίνηση..." - -#: ../vnc.html:162 -msgid "Power" -msgstr "Απενεργοποίηση" - -#: ../vnc.html:164 -msgid "Shutdown" -msgstr "Κλείσιμο" - -#: ../vnc.html:165 -msgid "Reboot" -msgstr "Επανεκκίνηση" - -#: ../vnc.html:166 -msgid "Reset" -msgstr "Επαναφορά" - -#: ../vnc.html:171 ../vnc.html:177 -msgid "Clipboard" -msgstr "Πρόχειρο" - -#: ../vnc.html:181 -msgid "Clear" -msgstr "Καθάρισμα" - -#: ../vnc.html:187 -msgid "Fullscreen" -msgstr "Πλήρης Οθόνη" - -#: ../vnc.html:192 ../vnc.html:199 -msgid "Settings" -msgstr "Ρυθμίσεις" - -#: ../vnc.html:202 -msgid "Shared Mode" -msgstr "Κοινόχρηστη Λειτουργία" - -#: ../vnc.html:205 -msgid "View Only" -msgstr "Μόνο Θέαση" - -#: ../vnc.html:209 -msgid "Clip to Window" -msgstr "Αποκοπή στο όριο του Παράθυρου" - -#: ../vnc.html:212 -msgid "Scaling Mode:" -msgstr "Λειτουργία Κλιμάκωσης:" - -#: ../vnc.html:214 -msgid "None" -msgstr "Καμία" - -#: ../vnc.html:215 -msgid "Local Scaling" -msgstr "Τοπική Κλιμάκωση" - -#: ../vnc.html:216 -msgid "Remote Resizing" -msgstr "Απομακρυσμένη Αλλαγή μεγέθους" - -#: ../vnc.html:221 -msgid "Advanced" -msgstr "Για προχωρημένους" - -#: ../vnc.html:224 -msgid "Repeater ID:" -msgstr "Repeater ID:" - -#: ../vnc.html:228 -msgid "WebSocket" -msgstr "WebSocket" - -#: ../vnc.html:231 -msgid "Encrypt" -msgstr "Κρυπτογράφηση" - -#: ../vnc.html:234 -msgid "Host:" -msgstr "Όνομα διακομιστή:" - -#: ../vnc.html:238 -msgid "Port:" -msgstr "Πόρτα διακομιστή:" - -#: ../vnc.html:242 -msgid "Path:" -msgstr "Διαδρομή:" - -#: ../vnc.html:249 -msgid "Automatic Reconnect" -msgstr "Αυτόματη επανασύνδεση" - -#: ../vnc.html:252 -msgid "Reconnect Delay (ms):" -msgstr "Καθυστέρηση επανασύνδεσης (ms):" - -#: ../vnc.html:258 -msgid "Logging:" -msgstr "Καταγραφή:" - -#: ../vnc.html:270 -msgid "Disconnect" -msgstr "Αποσύνδεση" - -#: ../vnc.html:289 -msgid "Connect" -msgstr "Σύνδεση" - -#: ../vnc.html:299 -msgid "Password:" -msgstr "Κωδικός Πρόσβασης:" - -#: ../vnc.html:313 -msgid "Cancel" -msgstr "Ακύρωση" - -#: ../vnc.html:329 -msgid "Canvas not supported." -msgstr "Δεν υποστηρίζεται το στοιχείο Canvas" - -#~ msgid "Disconnect timeout" -#~ msgstr "Παρέλευση χρονικού ορίου αποσύνδεσης" - -#~ msgid "Local Downscaling" -#~ msgstr "Τοπική Συρρίκνωση" - -#~ msgid "Local Cursor" -#~ msgstr "Τοπικός Δρομέας" - -#~ msgid "" -#~ "Forcing clipping mode since scrollbars aren't supported by IE in " -#~ "fullscreen" -#~ msgstr "" -#~ "Εφαρμογή λειτουργίας αποκοπής αφού δεν υποστηρίζονται οι λωρίδες κύλισης " -#~ "σε πλήρη οθόνη στον IE" - -#~ msgid "True Color" -#~ msgstr "Πραγματικά Χρώματα" - -#~ msgid "Style:" -#~ msgstr "Στυλ:" - -#~ msgid "default" -#~ msgstr "προεπιλεγμένο" - -#~ msgid "Apply" -#~ msgstr "Εφαρμογή" - -#~ msgid "Connection" -#~ msgstr "Σύνδεση" - -#~ msgid "Token:" -#~ msgstr "Διακριτικό:" - -#~ msgid "Send Password" -#~ msgstr "Αποστολή Κωδικού Πρόσβασης" diff --git a/systemvm/agent/noVNC/po/es.po b/systemvm/agent/noVNC/po/es.po deleted file mode 100644 index e15655fbfc9..00000000000 --- a/systemvm/agent/noVNC/po/es.po +++ /dev/null @@ -1,283 +0,0 @@ -# Spanish translations for noVNC package -# Traducciones al español para el paquete noVNC. -# Copyright (C) 2018 The noVNC Authors -# This file is distributed under the same license as the noVNC package. -# Juanjo Diaz , 2018. -# -msgid "" -msgstr "" -"Project-Id-Version: noVNC 1.0.0-testing.2\n" -"Report-Msgid-Bugs-To: novnc@googlegroups.com\n" -"POT-Creation-Date: 2017-10-06 10:07+0200\n" -"PO-Revision-Date: 2018-01-30 19:14-0800\n" -"Last-Translator: Juanjo Diaz \n" -"Language-Team: Spanish\n" -"Language: es\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -#: ../app/ui.js:430 -msgid "Connecting..." -msgstr "Conectando..." - -#: ../app/ui.js:438 -msgid "Connected (encrypted) to " -msgstr "Conectado (con encriptación) a" - -#: ../app/ui.js:440 -msgid "Connected (unencrypted) to " -msgstr "Conectado (sin encriptación) a" - -#: ../app/ui.js:446 -msgid "Disconnecting..." -msgstr "Desconectando..." - -#: ../app/ui.js:450 -msgid "Disconnected" -msgstr "Desconectado" - -#: ../app/ui.js:1052 ../core/rfb.js:248 -msgid "Must set host" -msgstr "Debes configurar el host" - -#: ../app/ui.js:1101 -msgid "Reconnecting..." -msgstr "Reconectando..." - -#: ../app/ui.js:1140 -msgid "Password is required" -msgstr "Contraseña es obligatoria" - -#: ../core/rfb.js:548 -msgid "Disconnect timeout" -msgstr "Tiempo de desconexión agotado" - -#: ../vnc.html:89 -msgid "noVNC encountered an error:" -msgstr "noVNC ha encontrado un error:" - -#: ../vnc.html:99 -msgid "Hide/Show the control bar" -msgstr "Ocultar/Mostrar la barra de control" - -#: ../vnc.html:106 -msgid "Move/Drag Viewport" -msgstr "Mover/Arrastrar la ventana" - -#: ../vnc.html:106 -msgid "viewport drag" -msgstr "Arrastrar la ventana" - -#: ../vnc.html:112 ../vnc.html:115 ../vnc.html:118 ../vnc.html:121 -msgid "Active Mouse Button" -msgstr "Botón activo del ratón" - -#: ../vnc.html:112 -msgid "No mousebutton" -msgstr "Ningún botón del ratón" - -#: ../vnc.html:115 -msgid "Left mousebutton" -msgstr "Botón izquierdo del ratón" - -#: ../vnc.html:118 -msgid "Middle mousebutton" -msgstr "Botón central del ratón" - -#: ../vnc.html:121 -msgid "Right mousebutton" -msgstr "Botón derecho del ratón" - -#: ../vnc.html:124 -msgid "Keyboard" -msgstr "Teclado" - -#: ../vnc.html:124 -msgid "Show Keyboard" -msgstr "Mostrar teclado" - -#: ../vnc.html:131 -msgid "Extra keys" -msgstr "Teclas adicionales" - -#: ../vnc.html:131 -msgid "Show Extra Keys" -msgstr "Mostrar Teclas Adicionales" - -#: ../vnc.html:136 -msgid "Ctrl" -msgstr "Ctrl" - -#: ../vnc.html:136 -msgid "Toggle Ctrl" -msgstr "Pulsar/Soltar Ctrl" - -#: ../vnc.html:139 -msgid "Alt" -msgstr "Alt" - -#: ../vnc.html:139 -msgid "Toggle Alt" -msgstr "Pulsar/Soltar Alt" - -#: ../vnc.html:142 -msgid "Send Tab" -msgstr "Enviar Tabulación" - -#: ../vnc.html:142 -msgid "Tab" -msgstr "Tabulación" - -#: ../vnc.html:145 -msgid "Esc" -msgstr "Esc" - -#: ../vnc.html:145 -msgid "Send Escape" -msgstr "Enviar Escape" - -#: ../vnc.html:148 -msgid "Ctrl+Alt+Del" -msgstr "Ctrl+Alt+Del" - -#: ../vnc.html:148 -msgid "Send Ctrl-Alt-Del" -msgstr "Enviar Ctrl+Alt+Del" - -#: ../vnc.html:156 -msgid "Shutdown/Reboot" -msgstr "Apagar/Reiniciar" - -#: ../vnc.html:156 -msgid "Shutdown/Reboot..." -msgstr "Apagar/Reiniciar..." - -#: ../vnc.html:162 -msgid "Power" -msgstr "Encender" - -#: ../vnc.html:164 -msgid "Shutdown" -msgstr "Apagar" - -#: ../vnc.html:165 -msgid "Reboot" -msgstr "Reiniciar" - -#: ../vnc.html:166 -msgid "Reset" -msgstr "Restablecer" - -#: ../vnc.html:171 ../vnc.html:177 -msgid "Clipboard" -msgstr "Portapapeles" - -#: ../vnc.html:181 -msgid "Clear" -msgstr "Vaciar" - -#: ../vnc.html:187 -msgid "Fullscreen" -msgstr "Pantalla Completa" - -#: ../vnc.html:192 ../vnc.html:199 -msgid "Settings" -msgstr "Configuraciones" - -#: ../vnc.html:202 -msgid "Shared Mode" -msgstr "Modo Compartido" - -#: ../vnc.html:205 -msgid "View Only" -msgstr "Solo visualización" - -#: ../vnc.html:209 -msgid "Clip to Window" -msgstr "Recortar al tamaño de la ventana" - -#: ../vnc.html:212 -msgid "Scaling Mode:" -msgstr "Modo de escalado:" - -#: ../vnc.html:214 -msgid "None" -msgstr "Ninguno" - -#: ../vnc.html:215 -msgid "Local Scaling" -msgstr "Escalado Local" - -#: ../vnc.html:216 -msgid "Local Downscaling" -msgstr "Reducción de escala local" - -#: ../vnc.html:217 -msgid "Remote Resizing" -msgstr "Cambio de tamaño remoto" - -#: ../vnc.html:222 -msgid "Advanced" -msgstr "Avanzado" - -#: ../vnc.html:225 -msgid "Local Cursor" -msgstr "Cursor Local" - -#: ../vnc.html:229 -msgid "Repeater ID:" -msgstr "ID del Repetidor" - -#: ../vnc.html:233 -msgid "WebSocket" -msgstr "WebSocket" - -#: ../vnc.html:236 -msgid "Encrypt" -msgstr "" - -#: ../vnc.html:239 -msgid "Host:" -msgstr "Host" - -#: ../vnc.html:243 -msgid "Port:" -msgstr "Puesto" - -#: ../vnc.html:247 -msgid "Path:" -msgstr "Ruta" - -#: ../vnc.html:254 -msgid "Automatic Reconnect" -msgstr "Reconexión automática" - -#: ../vnc.html:257 -msgid "Reconnect Delay (ms):" -msgstr "Retraso en la reconexión (ms)" - -#: ../vnc.html:263 -msgid "Logging:" -msgstr "Logging" - -#: ../vnc.html:275 -msgid "Disconnect" -msgstr "Desconectar" - -#: ../vnc.html:294 -msgid "Connect" -msgstr "Conectar" - -#: ../vnc.html:304 -msgid "Password:" -msgstr "Contraseña" - -#: ../vnc.html:318 -msgid "Cancel" -msgstr "Cancelar" - -#: ../vnc.html:334 -msgid "Canvas not supported." -msgstr "Canvas no está soportado" diff --git a/systemvm/agent/noVNC/po/ko.po b/systemvm/agent/noVNC/po/ko.po deleted file mode 100644 index 87ae1069741..00000000000 --- a/systemvm/agent/noVNC/po/ko.po +++ /dev/null @@ -1,290 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) 2018 The noVNC Authors -# This file is distributed under the same license as the noVNC package. -# Baw Appie , 2018. -# -#, fuzzy -msgid "" -msgstr "" -"Project-Id-Version: noVNC 1.0.0-testing.2\n" -"Report-Msgid-Bugs-To: novnc@googlegroups.com\n" -"POT-Creation-Date: 2018-01-31 16:29+0100\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: Baw Appie \n" -"Language-Team: Korean\n" -"Language: ko\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" - -#: ../app/ui.js:395 -msgid "Connecting..." -msgstr "연결중..." - -#: ../app/ui.js:402 -msgid "Disconnecting..." -msgstr "연결 해제중..." - -#: ../app/ui.js:408 -msgid "Reconnecting..." -msgstr "재연결중..." - -#: ../app/ui.js:413 -msgid "Internal error" -msgstr "내부 오류" - -#: ../app/ui.js:1002 -msgid "Must set host" -msgstr "호스트는 설정되어야 합니다." - -#: ../app/ui.js:1083 -msgid "Connected (encrypted) to " -msgstr "다음과 (암호화되어) 연결되었습니다:" - -#: ../app/ui.js:1085 -msgid "Connected (unencrypted) to " -msgstr "다음과 (암호화 없이) 연결되었습니다:" - -#: ../app/ui.js:1108 -msgid "Something went wrong, connection is closed" -msgstr "무언가 잘못되었습니다, 연결이 닫혔습니다." - -#: ../app/ui.js:1111 -msgid "Failed to connect to server" -msgstr "서버에 연결하지 못했습니다." - -#: ../app/ui.js:1121 -msgid "Disconnected" -msgstr "연결이 해제되었습니다." - -#: ../app/ui.js:1134 -msgid "New connection has been rejected with reason: " -msgstr "새 연결이 다음 이유로 거부되었습니다:" - -#: ../app/ui.js:1137 -msgid "New connection has been rejected" -msgstr "새 연결이 거부되었습니다." - -#: ../app/ui.js:1158 -msgid "Password is required" -msgstr "비밀번호가 필요합니다." - -#: ../vnc.html:91 -msgid "noVNC encountered an error:" -msgstr "noVNC에 오류가 발생했습니다:" - -#: ../vnc.html:101 -msgid "Hide/Show the control bar" -msgstr "컨트롤 바 숨기기/보이기" - -#: ../vnc.html:108 -msgid "Move/Drag Viewport" -msgstr "움직이기/드래그 뷰포트" - -#: ../vnc.html:108 -msgid "viewport drag" -msgstr "뷰포트 드래그" - -#: ../vnc.html:114 ../vnc.html:117 ../vnc.html:120 ../vnc.html:123 -msgid "Active Mouse Button" -msgstr "마우스 버튼 활성화" - -#: ../vnc.html:114 -msgid "No mousebutton" -msgstr "마우스 버튼 없음" - -#: ../vnc.html:117 -msgid "Left mousebutton" -msgstr "왼쪽 마우스 버튼" - -#: ../vnc.html:120 -msgid "Middle mousebutton" -msgstr "중간 마우스 버튼" - -#: ../vnc.html:123 -msgid "Right mousebutton" -msgstr "오른쪽 마우스 버튼" - -#: ../vnc.html:126 -msgid "Keyboard" -msgstr "키보드" - -#: ../vnc.html:126 -msgid "Show Keyboard" -msgstr "키보드 보이기" - -#: ../vnc.html:133 -msgid "Extra keys" -msgstr "기타 키들" - -#: ../vnc.html:133 -msgid "Show Extra Keys" -msgstr "기타 키들 보이기" - -#: ../vnc.html:138 -msgid "Ctrl" -msgstr "Ctrl" - -#: ../vnc.html:138 -msgid "Toggle Ctrl" -msgstr "Ctrl 켜기/끄기" - -#: ../vnc.html:141 -msgid "Alt" -msgstr "Alt" - -#: ../vnc.html:141 -msgid "Toggle Alt" -msgstr "Alt 켜기/끄기" - -#: ../vnc.html:144 -msgid "Send Tab" -msgstr "Tab 보내기" - -#: ../vnc.html:144 -msgid "Tab" -msgstr "Tab" - -#: ../vnc.html:147 -msgid "Esc" -msgstr "Esc" - -#: ../vnc.html:147 -msgid "Send Escape" -msgstr "Esc 보내기" - -#: ../vnc.html:150 -msgid "Ctrl+Alt+Del" -msgstr "Ctrl+Alt+Del" - -#: ../vnc.html:150 -msgid "Send Ctrl-Alt-Del" -msgstr "Ctrl+Alt+Del 보내기" - -#: ../vnc.html:158 -msgid "Shutdown/Reboot" -msgstr "셧다운/리붓" - -#: ../vnc.html:158 -msgid "Shutdown/Reboot..." -msgstr "셧다운/리붓..." - -#: ../vnc.html:164 -msgid "Power" -msgstr "전원" - -#: ../vnc.html:166 -msgid "Shutdown" -msgstr "셧다운" - -#: ../vnc.html:167 -msgid "Reboot" -msgstr "리붓" - -#: ../vnc.html:168 -msgid "Reset" -msgstr "리셋" - -#: ../vnc.html:173 ../vnc.html:179 -msgid "Clipboard" -msgstr "클립보드" - -#: ../vnc.html:183 -msgid "Clear" -msgstr "지우기" - -#: ../vnc.html:189 -msgid "Fullscreen" -msgstr "전체화면" - -#: ../vnc.html:194 ../vnc.html:201 -msgid "Settings" -msgstr "설정" - -#: ../vnc.html:204 -msgid "Shared Mode" -msgstr "공유 모드" - -#: ../vnc.html:207 -msgid "View Only" -msgstr "보기 전용" - -#: ../vnc.html:211 -msgid "Clip to Window" -msgstr "창에 클립" - -#: ../vnc.html:214 -msgid "Scaling Mode:" -msgstr "스케일링 모드:" - -#: ../vnc.html:216 -msgid "None" -msgstr "없음" - -#: ../vnc.html:217 -msgid "Local Scaling" -msgstr "로컬 스케일링" - -#: ../vnc.html:218 -msgid "Remote Resizing" -msgstr "원격 크기 조절" - -#: ../vnc.html:223 -msgid "Advanced" -msgstr "고급" - -#: ../vnc.html:226 -msgid "Repeater ID:" -msgstr "중계 ID" - -#: ../vnc.html:230 -msgid "WebSocket" -msgstr "웹소켓" - -#: ../vnc.html:233 -msgid "Encrypt" -msgstr "암호화" - -#: ../vnc.html:236 -msgid "Host:" -msgstr "호스트:" - -#: ../vnc.html:240 -msgid "Port:" -msgstr "포트:" - -#: ../vnc.html:244 -msgid "Path:" -msgstr "위치:" - -#: ../vnc.html:251 -msgid "Automatic Reconnect" -msgstr "자동 재연결" - -#: ../vnc.html:254 -msgid "Reconnect Delay (ms):" -msgstr "재연결 지연 시간 (ms)" - -#: ../vnc.html:260 -msgid "Logging:" -msgstr "로깅" - -#: ../vnc.html:272 -msgid "Disconnect" -msgstr "연결 해제" - -#: ../vnc.html:291 -msgid "Connect" -msgstr "연결" - -#: ../vnc.html:301 -msgid "Password:" -msgstr "비밀번호:" - -#: ../vnc.html:305 -msgid "Send Password" -msgstr "비밀번호 전송" - -#: ../vnc.html:315 -msgid "Cancel" -msgstr "취소" diff --git a/systemvm/agent/noVNC/po/nl.po b/systemvm/agent/noVNC/po/nl.po deleted file mode 100644 index 343204a9fd2..00000000000 --- a/systemvm/agent/noVNC/po/nl.po +++ /dev/null @@ -1,322 +0,0 @@ -# Dutch translations for noVNC package -# Nederlandse vertalingen voor het pakket noVNC. -# Copyright (C) 2018 The noVNC Authors -# This file is distributed under the same license as the noVNC package. -# Loek Janssen , 2016. -# -msgid "" -msgstr "" -"Project-Id-Version: noVNC 1.1.0\n" -"Report-Msgid-Bugs-To: novnc@googlegroups.com\n" -"POT-Creation-Date: 2019-04-09 11:06+0100\n" -"PO-Revision-Date: 2019-04-09 17:17+0100\n" -"Last-Translator: Arend Lapere \n" -"Language-Team: none\n" -"Language: nl\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -#: ../app/ui.js:383 -msgid "Connecting..." -msgstr "Verbinden..." - -#: ../app/ui.js:390 -msgid "Disconnecting..." -msgstr "Verbinding verbreken..." - -#: ../app/ui.js:396 -msgid "Reconnecting..." -msgstr "Opnieuw verbinding maken..." - -#: ../app/ui.js:401 -msgid "Internal error" -msgstr "Interne fout" - -#: ../app/ui.js:991 -msgid "Must set host" -msgstr "Host moeten worden ingesteld" - -#: ../app/ui.js:1073 -msgid "Connected (encrypted) to " -msgstr "Verbonden (versleuteld) met " - -#: ../app/ui.js:1075 -msgid "Connected (unencrypted) to " -msgstr "Verbonden (onversleuteld) met " - -#: ../app/ui.js:1098 -msgid "Something went wrong, connection is closed" -msgstr "Er iets fout gelopen, verbinding werd verbroken" - -#: ../app/ui.js:1101 -msgid "Failed to connect to server" -msgstr "Verbinding maken met server is mislukt" - -#: ../app/ui.js:1111 -msgid "Disconnected" -msgstr "Verbinding verbroken" - -#: ../app/ui.js:1124 -msgid "New connection has been rejected with reason: " -msgstr "Nieuwe verbinding is geweigerd omwille van de volgende reden: " - -#: ../app/ui.js:1127 -msgid "New connection has been rejected" -msgstr "Nieuwe verbinding is geweigerd" - -#: ../app/ui.js:1147 -msgid "Password is required" -msgstr "Wachtwoord is vereist" - -#: ../vnc.html:80 -msgid "noVNC encountered an error:" -msgstr "noVNC heeft een fout bemerkt:" - -#: ../vnc.html:90 -msgid "Hide/Show the control bar" -msgstr "Verberg/Toon de bedieningsbalk" - -#: ../vnc.html:97 -msgid "Move/Drag Viewport" -msgstr "Verplaats/Versleep Kijkvenster" - -#: ../vnc.html:97 -msgid "viewport drag" -msgstr "kijkvenster slepen" - -#: ../vnc.html:103 ../vnc.html:106 ../vnc.html:109 ../vnc.html:112 -msgid "Active Mouse Button" -msgstr "Actieve Muisknop" - -#: ../vnc.html:103 -msgid "No mousebutton" -msgstr "Geen muisknop" - -#: ../vnc.html:106 -msgid "Left mousebutton" -msgstr "Linker muisknop" - -#: ../vnc.html:109 -msgid "Middle mousebutton" -msgstr "Middelste muisknop" - -#: ../vnc.html:112 -msgid "Right mousebutton" -msgstr "Rechter muisknop" - -#: ../vnc.html:115 -msgid "Keyboard" -msgstr "Toetsenbord" - -#: ../vnc.html:115 -msgid "Show Keyboard" -msgstr "Toon Toetsenbord" - -#: ../vnc.html:121 -msgid "Extra keys" -msgstr "Extra toetsen" - -#: ../vnc.html:121 -msgid "Show Extra Keys" -msgstr "Toon Extra Toetsen" - -#: ../vnc.html:126 -msgid "Ctrl" -msgstr "Ctrl" - -#: ../vnc.html:126 -msgid "Toggle Ctrl" -msgstr "Ctrl omschakelen" - -#: ../vnc.html:129 -msgid "Alt" -msgstr "Alt" - -#: ../vnc.html:129 -msgid "Toggle Alt" -msgstr "Alt omschakelen" - -#: ../vnc.html:132 -msgid "Toggle Windows" -msgstr "Windows omschakelen" - -#: ../vnc.html:132 -msgid "Windows" -msgstr "Windows" - -#: ../vnc.html:135 -msgid "Send Tab" -msgstr "Tab Sturen" - -#: ../vnc.html:135 -msgid "Tab" -msgstr "Tab" - -#: ../vnc.html:138 -msgid "Esc" -msgstr "Esc" - -#: ../vnc.html:138 -msgid "Send Escape" -msgstr "Escape Sturen" - -#: ../vnc.html:141 -msgid "Ctrl+Alt+Del" -msgstr "Ctrl-Alt-Del" - -#: ../vnc.html:141 -msgid "Send Ctrl-Alt-Del" -msgstr "Ctrl-Alt-Del Sturen" - -#: ../vnc.html:149 -msgid "Shutdown/Reboot" -msgstr "Uitschakelen/Herstarten" - -#: ../vnc.html:149 -msgid "Shutdown/Reboot..." -msgstr "Uitschakelen/Herstarten..." - -#: ../vnc.html:155 -msgid "Power" -msgstr "Systeem" - -#: ../vnc.html:157 -msgid "Shutdown" -msgstr "Uitschakelen" - -#: ../vnc.html:158 -msgid "Reboot" -msgstr "Herstarten" - -#: ../vnc.html:159 -msgid "Reset" -msgstr "Resetten" - -#: ../vnc.html:164 ../vnc.html:170 -msgid "Clipboard" -msgstr "Klembord" - -#: ../vnc.html:174 -msgid "Clear" -msgstr "Wissen" - -#: ../vnc.html:180 -msgid "Fullscreen" -msgstr "Volledig Scherm" - -#: ../vnc.html:185 ../vnc.html:192 -msgid "Settings" -msgstr "Instellingen" - -#: ../vnc.html:195 -msgid "Shared Mode" -msgstr "Gedeelde Modus" - -#: ../vnc.html:198 -msgid "View Only" -msgstr "Alleen Kijken" - -#: ../vnc.html:202 -msgid "Clip to Window" -msgstr "Randen buiten venster afsnijden" - -#: ../vnc.html:205 -msgid "Scaling Mode:" -msgstr "Schaalmodus:" - -#: ../vnc.html:207 -msgid "None" -msgstr "Geen" - -#: ../vnc.html:208 -msgid "Local Scaling" -msgstr "Lokaal Schalen" - -#: ../vnc.html:209 -msgid "Remote Resizing" -msgstr "Op Afstand Formaat Wijzigen" - -#: ../vnc.html:214 -msgid "Advanced" -msgstr "Geavanceerd" - -#: ../vnc.html:217 -msgid "Repeater ID:" -msgstr "Repeater ID:" - -#: ../vnc.html:221 -msgid "WebSocket" -msgstr "WebSocket" - -#: ../vnc.html:224 -msgid "Encrypt" -msgstr "Versleutelen" - -#: ../vnc.html:227 -msgid "Host:" -msgstr "Host:" - -#: ../vnc.html:231 -msgid "Port:" -msgstr "Poort:" - -#: ../vnc.html:235 -msgid "Path:" -msgstr "Pad:" - -#: ../vnc.html:242 -msgid "Automatic Reconnect" -msgstr "Automatisch Opnieuw Verbinden" - -#: ../vnc.html:245 -msgid "Reconnect Delay (ms):" -msgstr "Vertraging voor Opnieuw Verbinden (ms):" - -#: ../vnc.html:250 -msgid "Show Dot when No Cursor" -msgstr "Geef stip weer indien geen cursor" - -#: ../vnc.html:255 -msgid "Logging:" -msgstr "Logmeldingen:" - -#: ../vnc.html:267 -msgid "Disconnect" -msgstr "Verbinding verbreken" - -#: ../vnc.html:286 -msgid "Connect" -msgstr "Verbinden" - -#: ../vnc.html:296 -msgid "Password:" -msgstr "Wachtwoord:" - -#: ../vnc.html:300 -msgid "Send Password" -msgstr "Verzend Wachtwoord:" - -#: ../vnc.html:310 -msgid "Cancel" -msgstr "Annuleren" - -#~ msgid "Disconnect timeout" -#~ msgstr "Timeout tijdens verbreken van verbinding" - -#~ msgid "Local Downscaling" -#~ msgstr "Lokaal Neerschalen" - -#~ msgid "Local Cursor" -#~ msgstr "Lokale Cursor" - -#~ msgid "Canvas not supported." -#~ msgstr "Canvas wordt niet ondersteund." - -#~ msgid "" -#~ "Forcing clipping mode since scrollbars aren't supported by IE in " -#~ "fullscreen" -#~ msgstr "" -#~ "''Clipping mode' ingeschakeld, omdat schuifbalken in volledige-scherm-" -#~ "modus in IE niet worden ondersteund" diff --git a/systemvm/agent/noVNC/po/noVNC.pot b/systemvm/agent/noVNC/po/noVNC.pot deleted file mode 100644 index 200be01de6e..00000000000 --- a/systemvm/agent/noVNC/po/noVNC.pot +++ /dev/null @@ -1,302 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR The noVNC Authors -# This file is distributed under the same license as the noVNC package. -# FIRST AUTHOR , YEAR. -# -#, fuzzy -msgid "" -msgstr "" -"Project-Id-Version: noVNC 1.1.0\n" -"Report-Msgid-Bugs-To: novnc@googlegroups.com\n" -"POT-Creation-Date: 2019-01-16 11:06+0100\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME \n" -"Language-Team: LANGUAGE \n" -"Language: \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=CHARSET\n" -"Content-Transfer-Encoding: 8bit\n" - -#: ../app/ui.js:387 -msgid "Connecting..." -msgstr "" - -#: ../app/ui.js:394 -msgid "Disconnecting..." -msgstr "" - -#: ../app/ui.js:400 -msgid "Reconnecting..." -msgstr "" - -#: ../app/ui.js:405 -msgid "Internal error" -msgstr "" - -#: ../app/ui.js:995 -msgid "Must set host" -msgstr "" - -#: ../app/ui.js:1077 -msgid "Connected (encrypted) to " -msgstr "" - -#: ../app/ui.js:1079 -msgid "Connected (unencrypted) to " -msgstr "" - -#: ../app/ui.js:1102 -msgid "Something went wrong, connection is closed" -msgstr "" - -#: ../app/ui.js:1105 -msgid "Failed to connect to server" -msgstr "" - -#: ../app/ui.js:1115 -msgid "Disconnected" -msgstr "" - -#: ../app/ui.js:1128 -msgid "New connection has been rejected with reason: " -msgstr "" - -#: ../app/ui.js:1131 -msgid "New connection has been rejected" -msgstr "" - -#: ../app/ui.js:1151 -msgid "Password is required" -msgstr "" - -#: ../vnc.html:84 -msgid "noVNC encountered an error:" -msgstr "" - -#: ../vnc.html:94 -msgid "Hide/Show the control bar" -msgstr "" - -#: ../vnc.html:101 -msgid "Move/Drag Viewport" -msgstr "" - -#: ../vnc.html:101 -msgid "viewport drag" -msgstr "" - -#: ../vnc.html:107 ../vnc.html:110 ../vnc.html:113 ../vnc.html:116 -msgid "Active Mouse Button" -msgstr "" - -#: ../vnc.html:107 -msgid "No mousebutton" -msgstr "" - -#: ../vnc.html:110 -msgid "Left mousebutton" -msgstr "" - -#: ../vnc.html:113 -msgid "Middle mousebutton" -msgstr "" - -#: ../vnc.html:116 -msgid "Right mousebutton" -msgstr "" - -#: ../vnc.html:119 -msgid "Keyboard" -msgstr "" - -#: ../vnc.html:119 -msgid "Show Keyboard" -msgstr "" - -#: ../vnc.html:126 -msgid "Extra keys" -msgstr "" - -#: ../vnc.html:126 -msgid "Show Extra Keys" -msgstr "" - -#: ../vnc.html:131 -msgid "Ctrl" -msgstr "" - -#: ../vnc.html:131 -msgid "Toggle Ctrl" -msgstr "" - -#: ../vnc.html:134 -msgid "Alt" -msgstr "" - -#: ../vnc.html:134 -msgid "Toggle Alt" -msgstr "" - -#: ../vnc.html:137 -msgid "Toggle Windows" -msgstr "" - -#: ../vnc.html:137 -msgid "Windows" -msgstr "" - -#: ../vnc.html:140 -msgid "Send Tab" -msgstr "" - -#: ../vnc.html:140 -msgid "Tab" -msgstr "" - -#: ../vnc.html:143 -msgid "Esc" -msgstr "" - -#: ../vnc.html:143 -msgid "Send Escape" -msgstr "" - -#: ../vnc.html:146 -msgid "Ctrl+Alt+Del" -msgstr "" - -#: ../vnc.html:146 -msgid "Send Ctrl-Alt-Del" -msgstr "" - -#: ../vnc.html:154 -msgid "Shutdown/Reboot" -msgstr "" - -#: ../vnc.html:154 -msgid "Shutdown/Reboot..." -msgstr "" - -#: ../vnc.html:160 -msgid "Power" -msgstr "" - -#: ../vnc.html:162 -msgid "Shutdown" -msgstr "" - -#: ../vnc.html:163 -msgid "Reboot" -msgstr "" - -#: ../vnc.html:164 -msgid "Reset" -msgstr "" - -#: ../vnc.html:169 ../vnc.html:175 -msgid "Clipboard" -msgstr "" - -#: ../vnc.html:179 -msgid "Clear" -msgstr "" - -#: ../vnc.html:185 -msgid "Fullscreen" -msgstr "" - -#: ../vnc.html:190 ../vnc.html:197 -msgid "Settings" -msgstr "" - -#: ../vnc.html:200 -msgid "Shared Mode" -msgstr "" - -#: ../vnc.html:203 -msgid "View Only" -msgstr "" - -#: ../vnc.html:207 -msgid "Clip to Window" -msgstr "" - -#: ../vnc.html:210 -msgid "Scaling Mode:" -msgstr "" - -#: ../vnc.html:212 -msgid "None" -msgstr "" - -#: ../vnc.html:213 -msgid "Local Scaling" -msgstr "" - -#: ../vnc.html:214 -msgid "Remote Resizing" -msgstr "" - -#: ../vnc.html:219 -msgid "Advanced" -msgstr "" - -#: ../vnc.html:222 -msgid "Repeater ID:" -msgstr "" - -#: ../vnc.html:226 -msgid "WebSocket" -msgstr "" - -#: ../vnc.html:229 -msgid "Encrypt" -msgstr "" - -#: ../vnc.html:232 -msgid "Host:" -msgstr "" - -#: ../vnc.html:236 -msgid "Port:" -msgstr "" - -#: ../vnc.html:240 -msgid "Path:" -msgstr "" - -#: ../vnc.html:247 -msgid "Automatic Reconnect" -msgstr "" - -#: ../vnc.html:250 -msgid "Reconnect Delay (ms):" -msgstr "" - -#: ../vnc.html:255 -msgid "Show Dot when No Cursor" -msgstr "" - -#: ../vnc.html:260 -msgid "Logging:" -msgstr "" - -#: ../vnc.html:272 -msgid "Disconnect" -msgstr "" - -#: ../vnc.html:291 -msgid "Connect" -msgstr "" - -#: ../vnc.html:301 -msgid "Password:" -msgstr "" - -#: ../vnc.html:305 -msgid "Send Password" -msgstr "" - -#: ../vnc.html:315 -msgid "Cancel" -msgstr "" diff --git a/systemvm/agent/noVNC/po/pl.po b/systemvm/agent/noVNC/po/pl.po deleted file mode 100644 index 5acfdc4f4b8..00000000000 --- a/systemvm/agent/noVNC/po/pl.po +++ /dev/null @@ -1,325 +0,0 @@ -# Polish translations for noVNC package. -# Copyright (C) 2018 The noVNC Authors -# This file is distributed under the same license as the noVNC package. -# Mariusz Jamro , 2017. -# -msgid "" -msgstr "" -"Project-Id-Version: noVNC 0.6.1\n" -"Report-Msgid-Bugs-To: novnc@googlegroups.com\n" -"POT-Creation-Date: 2017-11-21 19:53+0100\n" -"PO-Revision-Date: 2017-11-21 19:54+0100\n" -"Last-Translator: Mariusz Jamro \n" -"Language-Team: Polish\n" -"Language: pl\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " -"|| n%100>=20) ? 1 : 2);\n" -"X-Generator: Poedit 2.0.1\n" - -#: ../app/ui.js:404 -msgid "Connecting..." -msgstr "Łączenie..." - -#: ../app/ui.js:411 -msgid "Disconnecting..." -msgstr "Rozłączanie..." - -#: ../app/ui.js:417 -msgid "Reconnecting..." -msgstr "Łączenie..." - -#: ../app/ui.js:422 -msgid "Internal error" -msgstr "Błąd wewnętrzny" - -#: ../app/ui.js:1019 -msgid "Must set host" -msgstr "Host i port są wymagane" - -#: ../app/ui.js:1099 -msgid "Connected (encrypted) to " -msgstr "Połączenie (szyfrowane) z " - -#: ../app/ui.js:1101 -msgid "Connected (unencrypted) to " -msgstr "Połączenie (nieszyfrowane) z " - -#: ../app/ui.js:1119 -msgid "Something went wrong, connection is closed" -msgstr "Coś poszło źle, połączenie zostało zamknięte" - -#: ../app/ui.js:1129 -msgid "Disconnected" -msgstr "Rozłączony" - -#: ../app/ui.js:1142 -msgid "New connection has been rejected with reason: " -msgstr "Nowe połączenie zostało odrzucone z powodu: " - -#: ../app/ui.js:1145 -msgid "New connection has been rejected" -msgstr "Nowe połączenie zostało odrzucone" - -#: ../app/ui.js:1166 -msgid "Password is required" -msgstr "Hasło jest wymagane" - -#: ../vnc.html:89 -msgid "noVNC encountered an error:" -msgstr "noVNC napotkało błąd:" - -#: ../vnc.html:99 -msgid "Hide/Show the control bar" -msgstr "Pokaż/Ukryj pasek ustawień" - -#: ../vnc.html:106 -msgid "Move/Drag Viewport" -msgstr "Ruszaj/Przeciągaj Viewport" - -#: ../vnc.html:106 -msgid "viewport drag" -msgstr "przeciągnij viewport" - -#: ../vnc.html:112 ../vnc.html:115 ../vnc.html:118 ../vnc.html:121 -msgid "Active Mouse Button" -msgstr "Aktywny Przycisk Myszy" - -#: ../vnc.html:112 -msgid "No mousebutton" -msgstr "Brak przycisku myszy" - -#: ../vnc.html:115 -msgid "Left mousebutton" -msgstr "Lewy przycisk myszy" - -#: ../vnc.html:118 -msgid "Middle mousebutton" -msgstr "Środkowy przycisk myszy" - -#: ../vnc.html:121 -msgid "Right mousebutton" -msgstr "Prawy przycisk myszy" - -#: ../vnc.html:124 -msgid "Keyboard" -msgstr "Klawiatura" - -#: ../vnc.html:124 -msgid "Show Keyboard" -msgstr "Pokaż klawiaturę" - -#: ../vnc.html:131 -msgid "Extra keys" -msgstr "Przyciski dodatkowe" - -#: ../vnc.html:131 -msgid "Show Extra Keys" -msgstr "Pokaż przyciski dodatkowe" - -#: ../vnc.html:136 -msgid "Ctrl" -msgstr "Ctrl" - -#: ../vnc.html:136 -msgid "Toggle Ctrl" -msgstr "Przełącz Ctrl" - -#: ../vnc.html:139 -msgid "Alt" -msgstr "Alt" - -#: ../vnc.html:139 -msgid "Toggle Alt" -msgstr "Przełącz Alt" - -#: ../vnc.html:142 -msgid "Send Tab" -msgstr "Wyślij Tab" - -#: ../vnc.html:142 -msgid "Tab" -msgstr "Tab" - -#: ../vnc.html:145 -msgid "Esc" -msgstr "Esc" - -#: ../vnc.html:145 -msgid "Send Escape" -msgstr "Wyślij Escape" - -#: ../vnc.html:148 -msgid "Ctrl+Alt+Del" -msgstr "Ctrl+Alt+Del" - -#: ../vnc.html:148 -msgid "Send Ctrl-Alt-Del" -msgstr "Wyślij Ctrl-Alt-Del" - -#: ../vnc.html:156 -msgid "Shutdown/Reboot" -msgstr "Wyłącz/Uruchom ponownie" - -#: ../vnc.html:156 -msgid "Shutdown/Reboot..." -msgstr "Wyłącz/Uruchom ponownie..." - -#: ../vnc.html:162 -msgid "Power" -msgstr "Włączony" - -#: ../vnc.html:164 -msgid "Shutdown" -msgstr "Wyłącz" - -#: ../vnc.html:165 -msgid "Reboot" -msgstr "Uruchom ponownie" - -#: ../vnc.html:166 -msgid "Reset" -msgstr "Resetuj" - -#: ../vnc.html:171 ../vnc.html:177 -msgid "Clipboard" -msgstr "Schowek" - -#: ../vnc.html:181 -msgid "Clear" -msgstr "Wyczyść" - -#: ../vnc.html:187 -msgid "Fullscreen" -msgstr "Pełny ekran" - -#: ../vnc.html:192 ../vnc.html:199 -msgid "Settings" -msgstr "Ustawienia" - -#: ../vnc.html:202 -msgid "Shared Mode" -msgstr "Tryb Współdzielenia" - -#: ../vnc.html:205 -msgid "View Only" -msgstr "Tylko Podgląd" - -#: ../vnc.html:209 -msgid "Clip to Window" -msgstr "Przytnij do Okna" - -#: ../vnc.html:212 -msgid "Scaling Mode:" -msgstr "Tryb Skalowania:" - -#: ../vnc.html:214 -msgid "None" -msgstr "Brak" - -#: ../vnc.html:215 -msgid "Local Scaling" -msgstr "Skalowanie lokalne" - -#: ../vnc.html:216 -msgid "Remote Resizing" -msgstr "Skalowanie zdalne" - -#: ../vnc.html:221 -msgid "Advanced" -msgstr "Zaawansowane" - -#: ../vnc.html:224 -msgid "Repeater ID:" -msgstr "ID Repeatera:" - -#: ../vnc.html:228 -msgid "WebSocket" -msgstr "WebSocket" - -#: ../vnc.html:231 -msgid "Encrypt" -msgstr "Szyfrowanie" - -#: ../vnc.html:234 -msgid "Host:" -msgstr "Host:" - -#: ../vnc.html:238 -msgid "Port:" -msgstr "Port:" - -#: ../vnc.html:242 -msgid "Path:" -msgstr "Ścieżka:" - -#: ../vnc.html:249 -msgid "Automatic Reconnect" -msgstr "Automatycznie wznawiaj połączenie" - -#: ../vnc.html:252 -msgid "Reconnect Delay (ms):" -msgstr "Opóźnienie wznawiania (ms):" - -#: ../vnc.html:258 -msgid "Logging:" -msgstr "Poziom logowania:" - -#: ../vnc.html:270 -msgid "Disconnect" -msgstr "Rozłącz" - -#: ../vnc.html:289 -msgid "Connect" -msgstr "Połącz" - -#: ../vnc.html:299 -msgid "Password:" -msgstr "Hasło:" - -#: ../vnc.html:313 -msgid "Cancel" -msgstr "Anuluj" - -#: ../vnc.html:329 -msgid "Canvas not supported." -msgstr "Element Canvas nie jest wspierany." - -#~ msgid "Disconnect timeout" -#~ msgstr "Timeout rozłączenia" - -#~ msgid "Local Downscaling" -#~ msgstr "Downscaling lokalny" - -#~ msgid "Local Cursor" -#~ msgstr "Lokalny kursor" - -#~ msgid "" -#~ "Forcing clipping mode since scrollbars aren't supported by IE in " -#~ "fullscreen" -#~ msgstr "" -#~ "Wymuszam clipping mode ponieważ paski przewijania nie są wspierane przez " -#~ "IE w trybie pełnoekranowym" - -#~ msgid "True Color" -#~ msgstr "True Color" - -#~ msgid "Style:" -#~ msgstr "Styl:" - -#~ msgid "default" -#~ msgstr "domyślny" - -#~ msgid "Apply" -#~ msgstr "Zapisz" - -#~ msgid "Connection" -#~ msgstr "Połączenie" - -#~ msgid "Token:" -#~ msgstr "Token:" - -#~ msgid "Send Password" -#~ msgstr "Wyślij Hasło" diff --git a/systemvm/agent/noVNC/po/po2js b/systemvm/agent/noVNC/po/po2js deleted file mode 100755 index 03c14900fff..00000000000 --- a/systemvm/agent/noVNC/po/po2js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -/* - * ps2js: gettext .po to noVNC .js converter - * Copyright (C) 2018 The noVNC Authors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -const getopt = require('node-getopt'); -const fs = require('fs'); -const po2json = require("po2json"); - -const opt = getopt.create([ - ['h' , 'help' , 'display this help'], -]).bindHelp().parseSystem(); - -if (opt.argv.length != 2) { - console.error("Incorrect number of arguments given"); - process.exit(1); -} - -const data = po2json.parseFileSync(opt.argv[0]); - -const bodyPart = Object.keys(data).filter((msgid) => msgid !== "").map((msgid) => { - if (msgid === "") return; - const msgstr = data[msgid][1]; - return " " + JSON.stringify(msgid) + ": " + JSON.stringify(msgstr); -}).join(",\n"); - -const output = "{\n" + bodyPart + "\n}"; - -fs.writeFileSync(opt.argv[1], output); diff --git a/systemvm/agent/noVNC/po/ru.po b/systemvm/agent/noVNC/po/ru.po deleted file mode 100644 index fb5d0875ef8..00000000000 --- a/systemvm/agent/noVNC/po/ru.po +++ /dev/null @@ -1,306 +0,0 @@ -# Russian translations for noVNC package -# Русский перевод для пакета noVNC. -# Copyright (C) 2019 Dmitriy Shweew -# This file is distributed under the same license as the noVNC package. -# Dmitriy Shweew , 2019. -# -msgid "" -msgstr "" -"Project-Id-Version: noVNC 1.1.0\n" -"Report-Msgid-Bugs-To: novnc@googlegroups.com\n" -"POT-Creation-Date: 2019-02-26 14:53+0400\n" -"PO-Revision-Date: 2019-02-17 17:29+0400\n" -"Last-Translator: Dmitriy Shweew \n" -"Language-Team: Russian\n" -"Language: ru\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" -"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" -"X-Generator: Poedit 2.2.1\n" -"X-Poedit-Flags-xgettext: --add-comments\n" - -#: ../app/ui.js:387 -msgid "Connecting..." -msgstr "Подключение..." - -#: ../app/ui.js:394 -msgid "Disconnecting..." -msgstr "Отключение..." - -#: ../app/ui.js:400 -msgid "Reconnecting..." -msgstr "Переподключение..." - -#: ../app/ui.js:405 -msgid "Internal error" -msgstr "Внутренняя ошибка" - -#: ../app/ui.js:995 -msgid "Must set host" -msgstr "Задайте имя сервера или IP" - -#: ../app/ui.js:1077 -msgid "Connected (encrypted) to " -msgstr "Подключено (с шифрованием) к " - -#: ../app/ui.js:1079 -msgid "Connected (unencrypted) to " -msgstr "Подключено (без шифрования) к " - -#: ../app/ui.js:1102 -msgid "Something went wrong, connection is closed" -msgstr "Что-то пошло не так, подключение разорвано" - -#: ../app/ui.js:1105 -msgid "Failed to connect to server" -msgstr "Ошибка подключения к серверу" - -#: ../app/ui.js:1115 -msgid "Disconnected" -msgstr "Отключено" - -#: ../app/ui.js:1128 -msgid "New connection has been rejected with reason: " -msgstr "Подключиться не удалось: " - -#: ../app/ui.js:1131 -msgid "New connection has been rejected" -msgstr "Подключиться не удалось" - -#: ../app/ui.js:1151 -msgid "Password is required" -msgstr "Требуется пароль" - -#: ../vnc.html:84 -msgid "noVNC encountered an error:" -msgstr "Ошибка noVNC: " - -#: ../vnc.html:94 -msgid "Hide/Show the control bar" -msgstr "Скрыть/Показать контрольную панель" - -#: ../vnc.html:101 -msgid "Move/Drag Viewport" -msgstr "Переместить окно" - -#: ../vnc.html:101 -msgid "viewport drag" -msgstr "Переместить окно" - -#: ../vnc.html:107 ../vnc.html:110 ../vnc.html:113 ../vnc.html:116 -msgid "Active Mouse Button" -msgstr "Активировать кнопки мыши" - -#: ../vnc.html:107 -msgid "No mousebutton" -msgstr "Отключить кнопки мыши" - -#: ../vnc.html:110 -msgid "Left mousebutton" -msgstr "Левая кнопка мыши" - -#: ../vnc.html:113 -msgid "Middle mousebutton" -msgstr "Средняя кнопка мыши" - -#: ../vnc.html:116 -msgid "Right mousebutton" -msgstr "Правая кнопка мыши" - -#: ../vnc.html:119 -msgid "Keyboard" -msgstr "Клавиатура" - -#: ../vnc.html:119 -msgid "Show Keyboard" -msgstr "Показать клавиатуру" - -#: ../vnc.html:126 -msgid "Extra keys" -msgstr "Доп. кнопки" - -#: ../vnc.html:126 -msgid "Show Extra Keys" -msgstr "Показать дополнительные кнопки" - -#: ../vnc.html:131 -msgid "Ctrl" -msgstr "Ctrl" - -#: ../vnc.html:131 -msgid "Toggle Ctrl" -msgstr "Передать нажатие Ctrl" - -#: ../vnc.html:134 -msgid "Alt" -msgstr "Alt" - -#: ../vnc.html:134 -msgid "Toggle Alt" -msgstr "Передать нажатие Alt" - -#: ../vnc.html:137 -msgid "Toggle Windows" -msgstr "Переключение вкладок" - -#: ../vnc.html:137 -msgid "Windows" -msgstr "Вкладка" - -#: ../vnc.html:140 -msgid "Send Tab" -msgstr "Передать нажатие Tab" - -#: ../vnc.html:140 -msgid "Tab" -msgstr "Tab" - -#: ../vnc.html:143 -msgid "Esc" -msgstr "Esc" - -#: ../vnc.html:143 -msgid "Send Escape" -msgstr "Передать нажатие Escape" - -#: ../vnc.html:146 -msgid "Ctrl+Alt+Del" -msgstr "Ctrl+Alt+Del" - -#: ../vnc.html:146 -msgid "Send Ctrl-Alt-Del" -msgstr "Передать нажатие Ctrl-Alt-Del" - -#: ../vnc.html:154 -msgid "Shutdown/Reboot" -msgstr "Выключить/Перезагрузить" - -#: ../vnc.html:154 -msgid "Shutdown/Reboot..." -msgstr "Выключить/Перезагрузить..." - -#: ../vnc.html:160 -msgid "Power" -msgstr "Питание" - -#: ../vnc.html:162 -msgid "Shutdown" -msgstr "Выключить" - -#: ../vnc.html:163 -msgid "Reboot" -msgstr "Перезагрузить" - -#: ../vnc.html:164 -msgid "Reset" -msgstr "Сброс" - -#: ../vnc.html:169 ../vnc.html:175 -msgid "Clipboard" -msgstr "Буфер обмена" - -#: ../vnc.html:179 -msgid "Clear" -msgstr "Очистить" - -#: ../vnc.html:185 -msgid "Fullscreen" -msgstr "Во весь экран" - -#: ../vnc.html:190 ../vnc.html:197 -msgid "Settings" -msgstr "Настройки" - -#: ../vnc.html:200 -msgid "Shared Mode" -msgstr "Общий режим" - -#: ../vnc.html:203 -msgid "View Only" -msgstr "Просмотр" - -#: ../vnc.html:207 -msgid "Clip to Window" -msgstr "В окно" - -#: ../vnc.html:210 -msgid "Scaling Mode:" -msgstr "Масштаб:" - -#: ../vnc.html:212 -msgid "None" -msgstr "Нет" - -#: ../vnc.html:213 -msgid "Local Scaling" -msgstr "Локльный масштаб" - -#: ../vnc.html:214 -msgid "Remote Resizing" -msgstr "Удаленный масштаб" - -#: ../vnc.html:219 -msgid "Advanced" -msgstr "Дополнительно" - -#: ../vnc.html:222 -msgid "Repeater ID:" -msgstr "Идентификатор ID:" - -#: ../vnc.html:226 -msgid "WebSocket" -msgstr "WebSocket" - -#: ../vnc.html:229 -msgid "Encrypt" -msgstr "Шифрование" - -#: ../vnc.html:232 -msgid "Host:" -msgstr "Сервер:" - -#: ../vnc.html:236 -msgid "Port:" -msgstr "Порт:" - -#: ../vnc.html:240 -msgid "Path:" -msgstr "Путь:" - -#: ../vnc.html:247 -msgid "Automatic Reconnect" -msgstr "Автоматическое переподключение" - -#: ../vnc.html:250 -msgid "Reconnect Delay (ms):" -msgstr "Задержка переподключения (мс):" - -#: ../vnc.html:255 -msgid "Show Dot when No Cursor" -msgstr "Показать точку вместо курсора" - -#: ../vnc.html:260 -msgid "Logging:" -msgstr "Лог:" - -#: ../vnc.html:272 -msgid "Disconnect" -msgstr "Отключение" - -#: ../vnc.html:291 -msgid "Connect" -msgstr "Подключение" - -#: ../vnc.html:301 -msgid "Password:" -msgstr "Пароль:" - -#: ../vnc.html:305 -msgid "Send Password" -msgstr "Пароль: " - -#: ../vnc.html:315 -msgid "Cancel" -msgstr "Выход" diff --git a/systemvm/agent/noVNC/po/sv.po b/systemvm/agent/noVNC/po/sv.po deleted file mode 100644 index f7955662957..00000000000 --- a/systemvm/agent/noVNC/po/sv.po +++ /dev/null @@ -1,316 +0,0 @@ -# Swedish translations for noVNC package -# Svenska översättningar för paket noVNC. -# Copyright (C) 2018 The noVNC Authors -# This file is distributed under the same license as the noVNC package. -# Samuel Mannehed , 2019. -# -msgid "" -msgstr "" -"Project-Id-Version: noVNC 1.1.0\n" -"Report-Msgid-Bugs-To: novnc@googlegroups.com\n" -"POT-Creation-Date: 2019-01-16 11:06+0100\n" -"PO-Revision-Date: 2019-04-08 10:18+0200\n" -"Last-Translator: Samuel Mannehed \n" -"Language-Team: none\n" -"Language: sv\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Poedit 2.0.3\n" - -#: ../app/ui.js:387 -msgid "Connecting..." -msgstr "Ansluter..." - -#: ../app/ui.js:394 -msgid "Disconnecting..." -msgstr "Kopplar ner..." - -#: ../app/ui.js:400 -msgid "Reconnecting..." -msgstr "Återansluter..." - -#: ../app/ui.js:405 -msgid "Internal error" -msgstr "Internt fel" - -#: ../app/ui.js:995 -msgid "Must set host" -msgstr "Du måste specifiera en värd" - -#: ../app/ui.js:1077 -msgid "Connected (encrypted) to " -msgstr "Ansluten (krypterat) till " - -#: ../app/ui.js:1079 -msgid "Connected (unencrypted) to " -msgstr "Ansluten (okrypterat) till " - -#: ../app/ui.js:1102 -msgid "Something went wrong, connection is closed" -msgstr "Något gick fel, anslutningen avslutades" - -#: ../app/ui.js:1105 -msgid "Failed to connect to server" -msgstr "Misslyckades att ansluta till servern" - -#: ../app/ui.js:1115 -msgid "Disconnected" -msgstr "Frånkopplad" - -#: ../app/ui.js:1128 -msgid "New connection has been rejected with reason: " -msgstr "Ny anslutning har blivit nekad med följande skäl: " - -#: ../app/ui.js:1131 -msgid "New connection has been rejected" -msgstr "Ny anslutning har blivit nekad" - -#: ../app/ui.js:1151 -msgid "Password is required" -msgstr "Lösenord krävs" - -#: ../vnc.html:84 -msgid "noVNC encountered an error:" -msgstr "noVNC stötte på ett problem:" - -#: ../vnc.html:94 -msgid "Hide/Show the control bar" -msgstr "Göm/Visa kontrollbaren" - -#: ../vnc.html:101 -msgid "Move/Drag Viewport" -msgstr "Flytta/Dra Vyn" - -#: ../vnc.html:101 -msgid "viewport drag" -msgstr "dra vy" - -#: ../vnc.html:107 ../vnc.html:110 ../vnc.html:113 ../vnc.html:116 -msgid "Active Mouse Button" -msgstr "Aktiv musknapp" - -#: ../vnc.html:107 -msgid "No mousebutton" -msgstr "Ingen musknapp" - -#: ../vnc.html:110 -msgid "Left mousebutton" -msgstr "Vänster musknapp" - -#: ../vnc.html:113 -msgid "Middle mousebutton" -msgstr "Mitten-musknapp" - -#: ../vnc.html:116 -msgid "Right mousebutton" -msgstr "Höger musknapp" - -#: ../vnc.html:119 -msgid "Keyboard" -msgstr "Tangentbord" - -#: ../vnc.html:119 -msgid "Show Keyboard" -msgstr "Visa Tangentbord" - -#: ../vnc.html:126 -msgid "Extra keys" -msgstr "Extraknappar" - -#: ../vnc.html:126 -msgid "Show Extra Keys" -msgstr "Visa Extraknappar" - -#: ../vnc.html:131 -msgid "Ctrl" -msgstr "Ctrl" - -#: ../vnc.html:131 -msgid "Toggle Ctrl" -msgstr "Växla Ctrl" - -#: ../vnc.html:134 -msgid "Alt" -msgstr "Alt" - -#: ../vnc.html:134 -msgid "Toggle Alt" -msgstr "Växla Alt" - -#: ../vnc.html:137 -msgid "Toggle Windows" -msgstr "Växla Windows" - -#: ../vnc.html:137 -msgid "Windows" -msgstr "Windows" - -#: ../vnc.html:140 -msgid "Send Tab" -msgstr "Skicka Tab" - -#: ../vnc.html:140 -msgid "Tab" -msgstr "Tab" - -#: ../vnc.html:143 -msgid "Esc" -msgstr "Esc" - -#: ../vnc.html:143 -msgid "Send Escape" -msgstr "Skicka Escape" - -#: ../vnc.html:146 -msgid "Ctrl+Alt+Del" -msgstr "Ctrl+Alt+Del" - -#: ../vnc.html:146 -msgid "Send Ctrl-Alt-Del" -msgstr "Skicka Ctrl-Alt-Del" - -#: ../vnc.html:154 -msgid "Shutdown/Reboot" -msgstr "Stäng av/Boota om" - -#: ../vnc.html:154 -msgid "Shutdown/Reboot..." -msgstr "Stäng av/Boota om..." - -#: ../vnc.html:160 -msgid "Power" -msgstr "Ström" - -#: ../vnc.html:162 -msgid "Shutdown" -msgstr "Stäng av" - -#: ../vnc.html:163 -msgid "Reboot" -msgstr "Boota om" - -#: ../vnc.html:164 -msgid "Reset" -msgstr "Återställ" - -#: ../vnc.html:169 ../vnc.html:175 -msgid "Clipboard" -msgstr "Urklipp" - -#: ../vnc.html:179 -msgid "Clear" -msgstr "Rensa" - -#: ../vnc.html:185 -msgid "Fullscreen" -msgstr "Fullskärm" - -#: ../vnc.html:190 ../vnc.html:197 -msgid "Settings" -msgstr "Inställningar" - -#: ../vnc.html:200 -msgid "Shared Mode" -msgstr "Delat Läge" - -#: ../vnc.html:203 -msgid "View Only" -msgstr "Endast Visning" - -#: ../vnc.html:207 -msgid "Clip to Window" -msgstr "Begränsa till Fönster" - -#: ../vnc.html:210 -msgid "Scaling Mode:" -msgstr "Skalningsläge:" - -#: ../vnc.html:212 -msgid "None" -msgstr "Ingen" - -#: ../vnc.html:213 -msgid "Local Scaling" -msgstr "Lokal Skalning" - -#: ../vnc.html:214 -msgid "Remote Resizing" -msgstr "Ändra Storlek" - -#: ../vnc.html:219 -msgid "Advanced" -msgstr "Avancerat" - -#: ../vnc.html:222 -msgid "Repeater ID:" -msgstr "Repeater-ID:" - -#: ../vnc.html:226 -msgid "WebSocket" -msgstr "WebSocket" - -#: ../vnc.html:229 -msgid "Encrypt" -msgstr "Kryptera" - -#: ../vnc.html:232 -msgid "Host:" -msgstr "Värd:" - -#: ../vnc.html:236 -msgid "Port:" -msgstr "Port:" - -#: ../vnc.html:240 -msgid "Path:" -msgstr "Sökväg:" - -#: ../vnc.html:247 -msgid "Automatic Reconnect" -msgstr "Automatisk Återanslutning" - -#: ../vnc.html:250 -msgid "Reconnect Delay (ms):" -msgstr "Fördröjning (ms):" - -#: ../vnc.html:255 -msgid "Show Dot when No Cursor" -msgstr "Visa prick när ingen muspekare finns" - -#: ../vnc.html:260 -msgid "Logging:" -msgstr "Loggning:" - -#: ../vnc.html:272 -msgid "Disconnect" -msgstr "Koppla från" - -#: ../vnc.html:291 -msgid "Connect" -msgstr "Anslut" - -#: ../vnc.html:301 -msgid "Password:" -msgstr "Lösenord:" - -#: ../vnc.html:305 -msgid "Send Password" -msgstr "Skicka lösenord" - -#: ../vnc.html:315 -msgid "Cancel" -msgstr "Avbryt" - -#~ msgid "Disconnect timeout" -#~ msgstr "Det tog för lång tid att koppla ner" - -#~ msgid "Local Downscaling" -#~ msgstr "Lokal Nedskalning" - -#~ msgid "Local Cursor" -#~ msgstr "Lokal Muspekare" - -#~ msgid "Canvas not supported." -#~ msgstr "Canvas stöds ej" diff --git a/systemvm/agent/noVNC/po/tr.po b/systemvm/agent/noVNC/po/tr.po deleted file mode 100644 index 8b5c1813455..00000000000 --- a/systemvm/agent/noVNC/po/tr.po +++ /dev/null @@ -1,288 +0,0 @@ -# Turkish translations for noVNC package -# Turkish translation for noVNC. -# Copyright (C) 2018 The noVNC Authors -# This file is distributed under the same license as the noVNC package. -# Ömer ÇAKMAK , 2018. -# -msgid "" -msgstr "" -"Project-Id-Version: noVNC 0.6.1\n" -"Report-Msgid-Bugs-To: novnc@googlegroups.com\n" -"POT-Creation-Date: 2017-11-24 07:16+0000\n" -"PO-Revision-Date: 2018-01-05 19:07+0300\n" -"Last-Translator: Ömer ÇAKMAK \n" -"Language-Team: Türkçe \n" -"Language: tr\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=1; plural=0;\n" -"X-Generator: Gtranslator 2.91.7\n" - -#: ../app/ui.js:404 -msgid "Connecting..." -msgstr "Bağlanıyor..." - -#: ../app/ui.js:411 -msgid "Disconnecting..." -msgstr "Bağlantı kesiliyor..." - -#: ../app/ui.js:417 -msgid "Reconnecting..." -msgstr "Yeniden bağlantı kuruluyor..." - -#: ../app/ui.js:422 -msgid "Internal error" -msgstr "İç hata" - -#: ../app/ui.js:1019 -msgid "Must set host" -msgstr "Sunucuyu kur" - -#: ../app/ui.js:1099 -msgid "Connected (encrypted) to " -msgstr "Bağlı (şifrelenmiş)" - -#: ../app/ui.js:1101 -msgid "Connected (unencrypted) to " -msgstr "Bağlandı (şifrelenmemiş)" - -#: ../app/ui.js:1119 -msgid "Something went wrong, connection is closed" -msgstr "Bir şeyler ters gitti, bağlantı kesildi" - -#: ../app/ui.js:1129 -msgid "Disconnected" -msgstr "Bağlantı kesildi" - -#: ../app/ui.js:1142 -msgid "New connection has been rejected with reason: " -msgstr "Bağlantı aşağıdaki nedenlerden dolayı reddedildi: " - -#: ../app/ui.js:1145 -msgid "New connection has been rejected" -msgstr "Bağlantı reddedildi" - -#: ../app/ui.js:1166 -msgid "Password is required" -msgstr "Şifre gerekli" - -#: ../vnc.html:89 -msgid "noVNC encountered an error:" -msgstr "Bir hata oluştu:" - -#: ../vnc.html:99 -msgid "Hide/Show the control bar" -msgstr "Denetim masasını Gizle/Göster" - -#: ../vnc.html:106 -msgid "Move/Drag Viewport" -msgstr "Görünümü Taşı/Sürükle" - -#: ../vnc.html:106 -msgid "viewport drag" -msgstr "Görüntü penceresini sürükle" - -#: ../vnc.html:112 ../vnc.html:115 ../vnc.html:118 ../vnc.html:121 -msgid "Active Mouse Button" -msgstr "Aktif Fare Düğmesi" - -#: ../vnc.html:112 -msgid "No mousebutton" -msgstr "Fare düğmesi yok" - -#: ../vnc.html:115 -msgid "Left mousebutton" -msgstr "Farenin sol düğmesi" - -#: ../vnc.html:118 -msgid "Middle mousebutton" -msgstr "Farenin orta düğmesi" - -#: ../vnc.html:121 -msgid "Right mousebutton" -msgstr "Farenin sağ düğmesi" - -#: ../vnc.html:124 -msgid "Keyboard" -msgstr "Klavye" - -#: ../vnc.html:124 -msgid "Show Keyboard" -msgstr "Klavye Düzenini Göster" - -#: ../vnc.html:131 -msgid "Extra keys" -msgstr "Ekstra tuşlar" - -#: ../vnc.html:131 -msgid "Show Extra Keys" -msgstr "Ekstra tuşları göster" - -#: ../vnc.html:136 -msgid "Ctrl" -msgstr "Ctrl" - -#: ../vnc.html:136 -msgid "Toggle Ctrl" -msgstr "Ctrl Değiştir " - -#: ../vnc.html:139 -msgid "Alt" -msgstr "Alt" - -#: ../vnc.html:139 -msgid "Toggle Alt" -msgstr "Alt Değiştir" - -#: ../vnc.html:142 -msgid "Send Tab" -msgstr "Sekme Gönder" - -#: ../vnc.html:142 -msgid "Tab" -msgstr "Sekme" - -#: ../vnc.html:145 -msgid "Esc" -msgstr "Esc" - -#: ../vnc.html:145 -msgid "Send Escape" -msgstr "Boşluk Gönder" - -#: ../vnc.html:148 -msgid "Ctrl+Alt+Del" -msgstr "Ctrl + Alt + Del" - -#: ../vnc.html:148 -msgid "Send Ctrl-Alt-Del" -msgstr "Ctrl-Alt-Del Gönder" - -#: ../vnc.html:156 -msgid "Shutdown/Reboot" -msgstr "Kapat/Yeniden Başlat" - -#: ../vnc.html:156 -msgid "Shutdown/Reboot..." -msgstr "Kapat/Yeniden Başlat..." - -#: ../vnc.html:162 -msgid "Power" -msgstr "Güç" - -#: ../vnc.html:164 -msgid "Shutdown" -msgstr "Kapat" - -#: ../vnc.html:165 -msgid "Reboot" -msgstr "Yeniden Başlat" - -#: ../vnc.html:166 -msgid "Reset" -msgstr "Sıfırla" - -#: ../vnc.html:171 ../vnc.html:177 -msgid "Clipboard" -msgstr "Pano" - -#: ../vnc.html:181 -msgid "Clear" -msgstr "Temizle" - -#: ../vnc.html:187 -msgid "Fullscreen" -msgstr "Tam Ekran" - -#: ../vnc.html:192 ../vnc.html:199 -msgid "Settings" -msgstr "Ayarlar" - -#: ../vnc.html:202 -msgid "Shared Mode" -msgstr "Paylaşım Modu" - -#: ../vnc.html:205 -msgid "View Only" -msgstr "Sadece Görüntüle" - -#: ../vnc.html:209 -msgid "Clip to Window" -msgstr "Pencereye Tıkla" - -#: ../vnc.html:212 -msgid "Scaling Mode:" -msgstr "Ölçekleme Modu:" - -#: ../vnc.html:214 -msgid "None" -msgstr "Bilinmeyen" - -#: ../vnc.html:215 -msgid "Local Scaling" -msgstr "Yerel Ölçeklendirme" - -#: ../vnc.html:216 -msgid "Remote Resizing" -msgstr "Uzaktan Yeniden Boyutlandırma" - -#: ../vnc.html:221 -msgid "Advanced" -msgstr "Gelişmiş" - -#: ../vnc.html:224 -msgid "Repeater ID:" -msgstr "Tekralayıcı ID:" - -#: ../vnc.html:228 -msgid "WebSocket" -msgstr "WebSocket" - -#: ../vnc.html:231 -msgid "Encrypt" -msgstr "Şifrele" - -#: ../vnc.html:234 -msgid "Host:" -msgstr "Ana makine:" - -#: ../vnc.html:238 -msgid "Port:" -msgstr "Port:" - -#: ../vnc.html:242 -msgid "Path:" -msgstr "Yol:" - -#: ../vnc.html:249 -msgid "Automatic Reconnect" -msgstr "Otomatik Yeniden Bağlan" - -#: ../vnc.html:252 -msgid "Reconnect Delay (ms):" -msgstr "Yeniden Bağlanma Süreci (ms):" - -#: ../vnc.html:258 -msgid "Logging:" -msgstr "Giriş yapılıyor:" - -#: ../vnc.html:270 -msgid "Disconnect" -msgstr "Bağlantıyı Kes" - -#: ../vnc.html:289 -msgid "Connect" -msgstr "Bağlan" - -#: ../vnc.html:299 -msgid "Password:" -msgstr "Parola:" - -#: ../vnc.html:313 -msgid "Cancel" -msgstr "Vazgeç" - -#: ../vnc.html:329 -msgid "Canvas not supported." -msgstr "Tuval desteklenmiyor." diff --git a/systemvm/agent/noVNC/po/xgettext-html b/systemvm/agent/noVNC/po/xgettext-html deleted file mode 100755 index 547f5687698..00000000000 --- a/systemvm/agent/noVNC/po/xgettext-html +++ /dev/null @@ -1,115 +0,0 @@ -#!/usr/bin/env node -/* - * xgettext-html: HTML gettext parser - * Copyright (C) 2018 The noVNC Authors - * Licensed under MPL 2.0 (see LICENSE.txt) - */ - -const getopt = require('node-getopt'); -const jsdom = require("jsdom"); -const fs = require("fs"); - -const opt = getopt.create([ - ['o' , 'output=FILE' , 'write output to specified file'], - ['h' , 'help' , 'display this help'], -]).bindHelp().parseSystem(); - -const strings = {}; - -function addString(str, location) { - if (str.length == 0) { - return; - } - - if (strings[str] === undefined) { - strings[str] = {} - } - strings[str][location] = null; -} - -// See https://html.spec.whatwg.org/multipage/dom.html#attr-translate -function process(elem, locator, enabled) { - function isAnyOf(searchElement, items) { - return items.indexOf(searchElement) !== -1; - } - - if (elem.hasAttribute("translate")) { - if (isAnyOf(elem.getAttribute("translate"), ["", "yes"])) { - enabled = true; - } else if (isAnyOf(elem.getAttribute("translate"), ["no"])) { - enabled = false; - } - } - - if (enabled) { - if (elem.hasAttribute("abbr") && - elem.tagName === "TH") { - addString(elem.getAttribute("abbr"), locator(elem)); - } - if (elem.hasAttribute("alt") && - isAnyOf(elem.tagName, ["AREA", "IMG", "INPUT"])) { - addString(elem.getAttribute("alt"), locator(elem)); - } - if (elem.hasAttribute("download") && - isAnyOf(elem.tagName, ["A", "AREA"])) { - addString(elem.getAttribute("download"), locator(elem)); - } - if (elem.hasAttribute("label") && - isAnyOf(elem.tagName, ["MENUITEM", "MENU", "OPTGROUP", - "OPTION", "TRACK"])) { - addString(elem.getAttribute("label"), locator(elem)); - } - if (elem.hasAttribute("placeholder") && - isAnyOf(elem.tagName in ["INPUT", "TEXTAREA"])) { - addString(elem.getAttribute("placeholder"), locator(elem)); - } - if (elem.hasAttribute("title")) { - addString(elem.getAttribute("title"), locator(elem)); - } - if (elem.hasAttribute("value") && - elem.tagName === "INPUT" && - isAnyOf(elem.getAttribute("type"), ["reset", "button", "submit"])) { - addString(elem.getAttribute("value"), locator(elem)); - } - } - - for (let i = 0; i < elem.childNodes.length; i++) { - node = elem.childNodes[i]; - if (node.nodeType === node.ELEMENT_NODE) { - process(node, locator, enabled); - } else if (node.nodeType === node.TEXT_NODE && enabled) { - addString(node.data.trim(), locator(node)); - } - } -} - -for (let i = 0; i < opt.argv.length; i++) { - const fn = opt.argv[i]; - const file = fs.readFileSync(fn, "utf8"); - const dom = new jsdom.JSDOM(file, { includeNodeLocations: true }); - const body = dom.window.document.body; - - function locator(elem) { - const offset = dom.nodeLocation(elem).startOffset; - const line = file.slice(0, offset).split("\n").length; - return fn + ":" + line; - } - - process(body, locator, true); -} - -let output = ""; - -for (str in strings) { - output += "#:"; - for (location in strings[str]) { - output += " " + location; - } - output += "\n"; - - output += "msgid " + JSON.stringify(str) + "\n"; - output += "msgstr \"\"\n"; - output += "\n"; -} - -fs.writeFileSync(opt.options.output, output); diff --git a/systemvm/agent/noVNC/po/zh_CN.po b/systemvm/agent/noVNC/po/zh_CN.po deleted file mode 100644 index 78bfb958d5d..00000000000 --- a/systemvm/agent/noVNC/po/zh_CN.po +++ /dev/null @@ -1,284 +0,0 @@ -# Simplified Chinese translations for noVNC package. -# Copyright (C) 2018 The noVNC Authors -# This file is distributed under the same license as the noVNC package. -# Peter Dave Hello , 2018. -# -msgid "" -msgstr "" -"Project-Id-Version: noVNC 1.0.0-testing.2\n" -"Report-Msgid-Bugs-To: novnc@googlegroups.com\n" -"POT-Creation-Date: 2018-01-10 00:53+0800\n" -"PO-Revision-Date: 2018-04-06 21:33+0800\n" -"Last-Translator: CUI Wei \n" -"Language: zh_CN\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" - -#: ../app/ui.js:395 -msgid "Connecting..." -msgstr "链接中..." - -#: ../app/ui.js:402 -msgid "Disconnecting..." -msgstr "正在中断连接..." - -#: ../app/ui.js:408 -msgid "Reconnecting..." -msgstr "重新链接中..." - -#: ../app/ui.js:413 -msgid "Internal error" -msgstr "内部错误" - -#: ../app/ui.js:1015 -msgid "Must set host" -msgstr "请提供主机名" - -#: ../app/ui.js:1097 -msgid "Connected (encrypted) to " -msgstr "已加密链接到" - -#: ../app/ui.js:1099 -msgid "Connected (unencrypted) to " -msgstr "未加密链接到" - -#: ../app/ui.js:1120 -msgid "Something went wrong, connection is closed" -msgstr "发生错误,链接已关闭" - -#: ../app/ui.js:1123 -msgid "Failed to connect to server" -msgstr "无法链接到服务器" - -#: ../app/ui.js:1133 -msgid "Disconnected" -msgstr "链接已中断" - -#: ../app/ui.js:1146 -msgid "New connection has been rejected with reason: " -msgstr "链接被拒绝,原因:" - -#: ../app/ui.js:1149 -msgid "New connection has been rejected" -msgstr "链接被拒绝" - -#: ../app/ui.js:1170 -msgid "Password is required" -msgstr "请提供密码" - -#: ../vnc.html:89 -msgid "noVNC encountered an error:" -msgstr "noVNC 遇到一个错误:" - -#: ../vnc.html:99 -msgid "Hide/Show the control bar" -msgstr "显示/隐藏控制列" - -#: ../vnc.html:106 -msgid "Move/Drag Viewport" -msgstr "拖放显示范围" - -#: ../vnc.html:106 -msgid "viewport drag" -msgstr "显示范围拖放" - -#: ../vnc.html:112 ../vnc.html:115 ../vnc.html:118 ../vnc.html:121 -msgid "Active Mouse Button" -msgstr "启动鼠标按鍵" - -#: ../vnc.html:112 -msgid "No mousebutton" -msgstr "禁用鼠标按鍵" - -#: ../vnc.html:115 -msgid "Left mousebutton" -msgstr "鼠标左鍵" - -#: ../vnc.html:118 -msgid "Middle mousebutton" -msgstr "鼠标中鍵" - -#: ../vnc.html:121 -msgid "Right mousebutton" -msgstr "鼠标右鍵" - -#: ../vnc.html:124 -msgid "Keyboard" -msgstr "键盘" - -#: ../vnc.html:124 -msgid "Show Keyboard" -msgstr "显示键盘" - -#: ../vnc.html:131 -msgid "Extra keys" -msgstr "额外按键" - -#: ../vnc.html:131 -msgid "Show Extra Keys" -msgstr "显示额外按键" - -#: ../vnc.html:136 -msgid "Ctrl" -msgstr "Ctrl" - -#: ../vnc.html:136 -msgid "Toggle Ctrl" -msgstr "切换 Ctrl" - -#: ../vnc.html:139 -msgid "Alt" -msgstr "Alt" - -#: ../vnc.html:139 -msgid "Toggle Alt" -msgstr "切换 Alt" - -#: ../vnc.html:142 -msgid "Send Tab" -msgstr "发送 Tab 键" - -#: ../vnc.html:142 -msgid "Tab" -msgstr "Tab" - -#: ../vnc.html:145 -msgid "Esc" -msgstr "Esc" - -#: ../vnc.html:145 -msgid "Send Escape" -msgstr "发送 Escape 键" - -#: ../vnc.html:148 -msgid "Ctrl+Alt+Del" -msgstr "Ctrl-Alt-Del" - -#: ../vnc.html:148 -msgid "Send Ctrl-Alt-Del" -msgstr "发送 Ctrl-Alt-Del 键" - -#: ../vnc.html:156 -msgid "Shutdown/Reboot" -msgstr "关机/重新启动" - -#: ../vnc.html:156 -msgid "Shutdown/Reboot..." -msgstr "关机/重新启动..." - -#: ../vnc.html:162 -msgid "Power" -msgstr "电源" - -#: ../vnc.html:164 -msgid "Shutdown" -msgstr "关机" - -#: ../vnc.html:165 -msgid "Reboot" -msgstr "重新启动" - -#: ../vnc.html:166 -msgid "Reset" -msgstr "重置" - -#: ../vnc.html:171 ../vnc.html:177 -msgid "Clipboard" -msgstr "剪贴板" - -#: ../vnc.html:181 -msgid "Clear" -msgstr "清除" - -#: ../vnc.html:187 -msgid "Fullscreen" -msgstr "全屏幕" - -#: ../vnc.html:192 ../vnc.html:199 -msgid "Settings" -msgstr "设置" - -#: ../vnc.html:202 -msgid "Shared Mode" -msgstr "分享模式" - -#: ../vnc.html:205 -msgid "View Only" -msgstr "仅检视" - -#: ../vnc.html:209 -msgid "Clip to Window" -msgstr "限制/裁切窗口大小" - -#: ../vnc.html:212 -msgid "Scaling Mode:" -msgstr "缩放模式:" - -#: ../vnc.html:214 -msgid "None" -msgstr "无" - -#: ../vnc.html:215 -msgid "Local Scaling" -msgstr "本地缩放" - -#: ../vnc.html:216 -msgid "Remote Resizing" -msgstr "远程调整大小" - -#: ../vnc.html:221 -msgid "Advanced" -msgstr "高级" - -#: ../vnc.html:224 -msgid "Repeater ID:" -msgstr "中继站 ID" - -#: ../vnc.html:228 -msgid "WebSocket" -msgstr "WebSocket" - -#: ../vnc.html:231 -msgid "Encrypt" -msgstr "加密" - -#: ../vnc.html:234 -msgid "Host:" -msgstr "主机:" - -#: ../vnc.html:238 -msgid "Port:" -msgstr "端口:" - -#: ../vnc.html:242 -msgid "Path:" -msgstr "路径:" - -#: ../vnc.html:249 -msgid "Automatic Reconnect" -msgstr "自动重新链接" - -#: ../vnc.html:252 -msgid "Reconnect Delay (ms):" -msgstr "重新链接间隔 (ms):" - -#: ../vnc.html:258 -msgid "Logging:" -msgstr "日志级别:" - -#: ../vnc.html:270 -msgid "Disconnect" -msgstr "终端链接" - -#: ../vnc.html:289 -msgid "Connect" -msgstr "链接" - -#: ../vnc.html:299 -msgid "Password:" -msgstr "密码:" - -#: ../vnc.html:313 -msgid "Cancel" -msgstr "取消" diff --git a/systemvm/agent/noVNC/po/zh_TW.po b/systemvm/agent/noVNC/po/zh_TW.po deleted file mode 100644 index 9ddf550c1d2..00000000000 --- a/systemvm/agent/noVNC/po/zh_TW.po +++ /dev/null @@ -1,285 +0,0 @@ -# Traditional Chinese translations for noVNC package. -# Copyright (C) 2018 The noVNC Authors -# This file is distributed under the same license as the noVNC package. -# Peter Dave Hello , 2018. -# -msgid "" -msgstr "" -"Project-Id-Version: noVNC 1.0.0-testing.2\n" -"Report-Msgid-Bugs-To: novnc@googlegroups.com\n" -"POT-Creation-Date: 2018-01-10 00:53+0800\n" -"PO-Revision-Date: 2018-01-10 01:33+0800\n" -"Last-Translator: Peter Dave Hello \n" -"Language-Team: Peter Dave Hello \n" -"Language: zh\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" - -#: ../app/ui.js:395 -msgid "Connecting..." -msgstr "連線中..." - -#: ../app/ui.js:402 -msgid "Disconnecting..." -msgstr "正在中斷連線..." - -#: ../app/ui.js:408 -msgid "Reconnecting..." -msgstr "重新連線中..." - -#: ../app/ui.js:413 -msgid "Internal error" -msgstr "內部錯誤" - -#: ../app/ui.js:1015 -msgid "Must set host" -msgstr "請提供主機資訊" - -#: ../app/ui.js:1097 -msgid "Connected (encrypted) to " -msgstr "已加密連線到" - -#: ../app/ui.js:1099 -msgid "Connected (unencrypted) to " -msgstr "未加密連線到" - -#: ../app/ui.js:1120 -msgid "Something went wrong, connection is closed" -msgstr "發生錯誤,連線已關閉" - -#: ../app/ui.js:1123 -msgid "Failed to connect to server" -msgstr "無法連線到伺服器" - -#: ../app/ui.js:1133 -msgid "Disconnected" -msgstr "連線已中斷" - -#: ../app/ui.js:1146 -msgid "New connection has been rejected with reason: " -msgstr "連線被拒絕,原因:" - -#: ../app/ui.js:1149 -msgid "New connection has been rejected" -msgstr "連線被拒絕" - -#: ../app/ui.js:1170 -msgid "Password is required" -msgstr "請提供密碼" - -#: ../vnc.html:89 -msgid "noVNC encountered an error:" -msgstr "noVNC 遇到一個錯誤:" - -#: ../vnc.html:99 -msgid "Hide/Show the control bar" -msgstr "顯示/隱藏控制列" - -#: ../vnc.html:106 -msgid "Move/Drag Viewport" -msgstr "拖放顯示範圍" - -#: ../vnc.html:106 -msgid "viewport drag" -msgstr "顯示範圍拖放" - -#: ../vnc.html:112 ../vnc.html:115 ../vnc.html:118 ../vnc.html:121 -msgid "Active Mouse Button" -msgstr "啟用滑鼠按鍵" - -#: ../vnc.html:112 -msgid "No mousebutton" -msgstr "無滑鼠按鍵" - -#: ../vnc.html:115 -msgid "Left mousebutton" -msgstr "滑鼠左鍵" - -#: ../vnc.html:118 -msgid "Middle mousebutton" -msgstr "滑鼠中鍵" - -#: ../vnc.html:121 -msgid "Right mousebutton" -msgstr "滑鼠右鍵" - -#: ../vnc.html:124 -msgid "Keyboard" -msgstr "鍵盤" - -#: ../vnc.html:124 -msgid "Show Keyboard" -msgstr "顯示鍵盤" - -#: ../vnc.html:131 -msgid "Extra keys" -msgstr "額外按鍵" - -#: ../vnc.html:131 -msgid "Show Extra Keys" -msgstr "顯示額外按鍵" - -#: ../vnc.html:136 -msgid "Ctrl" -msgstr "Ctrl" - -#: ../vnc.html:136 -msgid "Toggle Ctrl" -msgstr "切換 Ctrl" - -#: ../vnc.html:139 -msgid "Alt" -msgstr "Alt" - -#: ../vnc.html:139 -msgid "Toggle Alt" -msgstr "切換 Alt" - -#: ../vnc.html:142 -msgid "Send Tab" -msgstr "送出 Tab 鍵" - -#: ../vnc.html:142 -msgid "Tab" -msgstr "Tab" - -#: ../vnc.html:145 -msgid "Esc" -msgstr "Esc" - -#: ../vnc.html:145 -msgid "Send Escape" -msgstr "送出 Escape 鍵" - -#: ../vnc.html:148 -msgid "Ctrl+Alt+Del" -msgstr "Ctrl-Alt-Del" - -#: ../vnc.html:148 -msgid "Send Ctrl-Alt-Del" -msgstr "送出 Ctrl-Alt-Del 快捷鍵" - -#: ../vnc.html:156 -msgid "Shutdown/Reboot" -msgstr "關機/重新啟動" - -#: ../vnc.html:156 -msgid "Shutdown/Reboot..." -msgstr "關機/重新啟動..." - -#: ../vnc.html:162 -msgid "Power" -msgstr "電源" - -#: ../vnc.html:164 -msgid "Shutdown" -msgstr "關機" - -#: ../vnc.html:165 -msgid "Reboot" -msgstr "重新啟動" - -#: ../vnc.html:166 -msgid "Reset" -msgstr "重設" - -#: ../vnc.html:171 ../vnc.html:177 -msgid "Clipboard" -msgstr "剪貼簿" - -#: ../vnc.html:181 -msgid "Clear" -msgstr "清除" - -#: ../vnc.html:187 -msgid "Fullscreen" -msgstr "全螢幕" - -#: ../vnc.html:192 ../vnc.html:199 -msgid "Settings" -msgstr "設定" - -#: ../vnc.html:202 -msgid "Shared Mode" -msgstr "分享模式" - -#: ../vnc.html:205 -msgid "View Only" -msgstr "僅檢視" - -#: ../vnc.html:209 -msgid "Clip to Window" -msgstr "限制/裁切視窗大小" - -#: ../vnc.html:212 -msgid "Scaling Mode:" -msgstr "縮放模式:" - -#: ../vnc.html:214 -msgid "None" -msgstr "無" - -#: ../vnc.html:215 -msgid "Local Scaling" -msgstr "本機縮放" - -#: ../vnc.html:216 -msgid "Remote Resizing" -msgstr "遠端調整大小" - -#: ../vnc.html:221 -msgid "Advanced" -msgstr "進階" - -#: ../vnc.html:224 -msgid "Repeater ID:" -msgstr "中繼站 ID" - -#: ../vnc.html:228 -msgid "WebSocket" -msgstr "WebSocket" - -#: ../vnc.html:231 -msgid "Encrypt" -msgstr "加密" - -#: ../vnc.html:234 -msgid "Host:" -msgstr "主機:" - -#: ../vnc.html:238 -msgid "Port:" -msgstr "連接埠:" - -#: ../vnc.html:242 -msgid "Path:" -msgstr "路徑:" - -#: ../vnc.html:249 -msgid "Automatic Reconnect" -msgstr "自動重新連線" - -#: ../vnc.html:252 -msgid "Reconnect Delay (ms):" -msgstr "重新連線間隔 (ms):" - -#: ../vnc.html:258 -msgid "Logging:" -msgstr "日誌級別:" - -#: ../vnc.html:270 -msgid "Disconnect" -msgstr "中斷連線" - -#: ../vnc.html:289 -msgid "Connect" -msgstr "連線" - -#: ../vnc.html:299 -msgid "Password:" -msgstr "密碼:" - -#: ../vnc.html:313 -msgid "Cancel" -msgstr "取消" diff --git a/systemvm/agent/noVNC/tests/.eslintrc b/systemvm/agent/noVNC/tests/.eslintrc deleted file mode 100644 index 545fa2ed25e..00000000000 --- a/systemvm/agent/noVNC/tests/.eslintrc +++ /dev/null @@ -1,15 +0,0 @@ -{ - "env": { - "node": true, - "mocha": true - }, - "globals": { - "chai": false, - "sinon": false - }, - "rules": { - "prefer-arrow-callback": 0, - // Too many anonymous callbacks - "func-names": "off", - } -} diff --git a/systemvm/agent/noVNC/tests/assertions.js b/systemvm/agent/noVNC/tests/assertions.js deleted file mode 100644 index 07a5c297768..00000000000 --- a/systemvm/agent/noVNC/tests/assertions.js +++ /dev/null @@ -1,101 +0,0 @@ -// noVNC specific assertions -chai.use(function (_chai, utils) { - _chai.Assertion.addMethod('displayed', function (target_data) { - const obj = this._obj; - const ctx = obj._target.getContext('2d'); - const data_cl = ctx.getImageData(0, 0, obj._target.width, obj._target.height).data; - // NB(directxman12): PhantomJS 1.x doesn't implement Uint8ClampedArray, so work around that - const data = new Uint8Array(data_cl); - const len = data_cl.length; - new chai.Assertion(len).to.be.equal(target_data.length, "unexpected display size"); - let same = true; - for (let i = 0; i < len; i++) { - if (data[i] != target_data[i]) { - same = false; - break; - } - } - if (!same) { - // eslint-disable-next-line no-console - console.log("expected data: %o, actual data: %o", target_data, data); - } - this.assert(same, - "expected #{this} to have displayed the image #{exp}, but instead it displayed #{act}", - "expected #{this} not to have displayed the image #{act}", - target_data, - data); - }); - - _chai.Assertion.addMethod('sent', function (target_data) { - const obj = this._obj; - obj.inspect = () => { - const res = { _websocket: obj._websocket, rQi: obj._rQi, _rQ: new Uint8Array(obj._rQ.buffer, 0, obj._rQlen), - _sQ: new Uint8Array(obj._sQ.buffer, 0, obj._sQlen) }; - res.prototype = obj; - return res; - }; - const data = obj._websocket._get_sent_data(); - let same = true; - if (data.length != target_data.length) { - same = false; - } else { - for (let i = 0; i < data.length; i++) { - if (data[i] != target_data[i]) { - same = false; - break; - } - } - } - if (!same) { - // eslint-disable-next-line no-console - console.log("expected data: %o, actual data: %o", target_data, data); - } - this.assert(same, - "expected #{this} to have sent the data #{exp}, but it actually sent #{act}", - "expected #{this} not to have sent the data #{act}", - Array.prototype.slice.call(target_data), - Array.prototype.slice.call(data)); - }); - - _chai.Assertion.addProperty('array', function () { - utils.flag(this, 'array', true); - }); - - _chai.Assertion.overwriteMethod('equal', function (_super) { - return function assertArrayEqual(target) { - if (utils.flag(this, 'array')) { - const obj = this._obj; - - let same = true; - - if (utils.flag(this, 'deep')) { - for (let i = 0; i < obj.length; i++) { - if (!utils.eql(obj[i], target[i])) { - same = false; - break; - } - } - - this.assert(same, - "expected #{this} to have elements deeply equal to #{exp}", - "expected #{this} not to have elements deeply equal to #{exp}", - Array.prototype.slice.call(target)); - } else { - for (let i = 0; i < obj.length; i++) { - if (obj[i] != target[i]) { - same = false; - break; - } - } - - this.assert(same, - "expected #{this} to have elements equal to #{exp}", - "expected #{this} not to have elements equal to #{exp}", - Array.prototype.slice.call(target)); - } - } else { - _super.apply(this, arguments); - } - }; - }); -}); diff --git a/systemvm/agent/noVNC/tests/fake.websocket.js b/systemvm/agent/noVNC/tests/fake.websocket.js deleted file mode 100644 index 68ab3f8487d..00000000000 --- a/systemvm/agent/noVNC/tests/fake.websocket.js +++ /dev/null @@ -1,96 +0,0 @@ -import Base64 from '../core/base64.js'; - -// PhantomJS can't create Event objects directly, so we need to use this -function make_event(name, props) { - const evt = document.createEvent('Event'); - evt.initEvent(name, true, true); - if (props) { - for (let prop in props) { - evt[prop] = props[prop]; - } - } - return evt; -} - -export default class FakeWebSocket { - constructor(uri, protocols) { - this.url = uri; - this.binaryType = "arraybuffer"; - this.extensions = ""; - - if (!protocols || typeof protocols === 'string') { - this.protocol = protocols; - } else { - this.protocol = protocols[0]; - } - - this._send_queue = new Uint8Array(20000); - - this.readyState = FakeWebSocket.CONNECTING; - this.bufferedAmount = 0; - - this.__is_fake = true; - } - - close(code, reason) { - this.readyState = FakeWebSocket.CLOSED; - if (this.onclose) { - this.onclose(make_event("close", { 'code': code, 'reason': reason, 'wasClean': true })); - } - } - - send(data) { - if (this.protocol == 'base64') { - data = Base64.decode(data); - } else { - data = new Uint8Array(data); - } - this._send_queue.set(data, this.bufferedAmount); - this.bufferedAmount += data.length; - } - - _get_sent_data() { - const res = new Uint8Array(this._send_queue.buffer, 0, this.bufferedAmount); - this.bufferedAmount = 0; - return res; - } - - _open() { - this.readyState = FakeWebSocket.OPEN; - if (this.onopen) { - this.onopen(make_event('open')); - } - } - - _receive_data(data) { - // Break apart the data to expose bugs where we assume data is - // neatly packaged - for (let i = 0;i < data.length;i++) { - let buf = data.subarray(i, i+1); - this.onmessage(make_event("message", { 'data': buf })); - } - } -} - -FakeWebSocket.OPEN = WebSocket.OPEN; -FakeWebSocket.CONNECTING = WebSocket.CONNECTING; -FakeWebSocket.CLOSING = WebSocket.CLOSING; -FakeWebSocket.CLOSED = WebSocket.CLOSED; - -FakeWebSocket.__is_fake = true; - -FakeWebSocket.replace = () => { - if (!WebSocket.__is_fake) { - const real_version = WebSocket; - // eslint-disable-next-line no-global-assign - WebSocket = FakeWebSocket; - FakeWebSocket.__real_version = real_version; - } -}; - -FakeWebSocket.restore = () => { - if (WebSocket.__is_fake) { - // eslint-disable-next-line no-global-assign - WebSocket = WebSocket.__real_version; - } -}; diff --git a/systemvm/agent/noVNC/tests/karma-test-main.js b/systemvm/agent/noVNC/tests/karma-test-main.js deleted file mode 100644 index 28436667e6d..00000000000 --- a/systemvm/agent/noVNC/tests/karma-test-main.js +++ /dev/null @@ -1,48 +0,0 @@ -const TEST_REGEXP = /test\..*\.js/; -const allTestFiles = []; -const extraFiles = ['/base/tests/assertions.js']; - -Object.keys(window.__karma__.files).forEach(function (file) { - if (TEST_REGEXP.test(file)) { - // TODO: normalize? - allTestFiles.push(file); - } -}); - -// Stub out mocha's start function so we can run it once we're done loading -mocha.origRun = mocha.run; -mocha.run = function () {}; - -let script; - -// Script to import all our tests -script = document.createElement("script"); -script.type = "module"; -script.text = ""; -let allModules = allTestFiles.concat(extraFiles); -allModules.forEach(function (file) { - script.text += "import \"" + file + "\";\n"; -}); -script.text += "\nmocha.origRun();\n"; -document.body.appendChild(script); - -// Fallback code for browsers that don't support modules (IE) -script = document.createElement("script"); -script.type = "module"; -script.text = "window._noVNC_has_module_support = true;\n"; -document.body.appendChild(script); - -function fallback() { - if (!window._noVNC_has_module_support) { - /* eslint-disable no-console */ - if (console) { - console.log("No module support detected. Loading fallback..."); - } - /* eslint-enable no-console */ - let loader = document.createElement("script"); - loader.src = "base/vendor/browser-es-module-loader/dist/browser-es-module-loader.js"; - document.body.appendChild(loader); - } -} - -setTimeout(fallback, 500); diff --git a/systemvm/agent/noVNC/tests/playback-ui.js b/systemvm/agent/noVNC/tests/playback-ui.js deleted file mode 100644 index 65c715a9fe5..00000000000 --- a/systemvm/agent/noVNC/tests/playback-ui.js +++ /dev/null @@ -1,210 +0,0 @@ -/* global VNC_frame_data, VNC_frame_encoding */ - -import * as WebUtil from '../app/webutil.js'; -import RecordingPlayer from './playback.js'; -import Base64 from '../core/base64.js'; - -let frames = null; - -function message(str) { - const cell = document.getElementById('messages'); - cell.textContent += str + "\n"; - cell.scrollTop = cell.scrollHeight; -} - -function loadFile() { - const fname = WebUtil.getQueryVar('data', null); - - if (!fname) { - return Promise.reject("Must specify data=FOO in query string."); - } - - message("Loading " + fname + "..."); - - return new Promise((resolve, reject) => { - const script = document.createElement("script"); - script.onload = resolve; - script.onerror = reject; - document.body.appendChild(script); - script.src = "../recordings/" + fname; - }); -} - -function enableUI() { - const iterations = WebUtil.getQueryVar('iterations', 3); - document.getElementById('iterations').value = iterations; - - const mode = WebUtil.getQueryVar('mode', 3); - if (mode === 'realtime') { - document.getElementById('mode2').checked = true; - } else { - document.getElementById('mode1').checked = true; - } - - message("Loaded " + VNC_frame_data.length + " frames"); - - const startButton = document.getElementById('startButton'); - startButton.disabled = false; - startButton.addEventListener('click', start); - - message("Converting..."); - - frames = VNC_frame_data; - - let encoding; - // Only present in older recordings - if (window.VNC_frame_encoding) { - encoding = VNC_frame_encoding; - } else { - let frame = frames[0]; - let start = frame.indexOf('{', 1) + 1; - if (frame.slice(start, start+4) === 'UkZC') { - encoding = 'base64'; - } else { - encoding = 'binary'; - } - } - - for (let i = 0;i < frames.length;i++) { - let frame = frames[i]; - - if (frame === "EOF") { - frames.splice(i); - break; - } - - let dataIdx = frame.indexOf('{', 1) + 1; - - let time = parseInt(frame.slice(1, dataIdx - 1)); - - let u8; - if (encoding === 'base64') { - u8 = Base64.decode(frame.slice(dataIdx)); - } else { - u8 = new Uint8Array(frame.length - dataIdx); - for (let j = 0; j < frame.length - dataIdx; j++) { - u8[j] = frame.charCodeAt(dataIdx + j); - } - } - - frames[i] = { fromClient: frame[0] === '}', - timestamp: time, - data: u8 }; - } - - message("Ready"); -} - -class IterationPlayer { - constructor(iterations, frames) { - this._iterations = iterations; - - this._iteration = undefined; - this._player = undefined; - - this._start_time = undefined; - - this._frames = frames; - - this._state = 'running'; - - this.onfinish = () => {}; - this.oniterationfinish = () => {}; - this.rfbdisconnected = () => {}; - } - - start(realtime) { - this._iteration = 0; - this._start_time = (new Date()).getTime(); - - this._realtime = realtime; - - this._nextIteration(); - } - - _nextIteration() { - const player = new RecordingPlayer(this._frames, this._disconnected.bind(this)); - player.onfinish = this._iterationFinish.bind(this); - - if (this._state !== 'running') { return; } - - this._iteration++; - if (this._iteration > this._iterations) { - this._finish(); - return; - } - - player.run(this._realtime, false); - } - - _finish() { - const endTime = (new Date()).getTime(); - const totalDuration = endTime - this._start_time; - - const evt = new CustomEvent('finish', - { detail: - { duration: totalDuration, - iterations: this._iterations } } ); - this.onfinish(evt); - } - - _iterationFinish(duration) { - const evt = new CustomEvent('iterationfinish', - { detail: - { duration: duration, - number: this._iteration } } ); - this.oniterationfinish(evt); - - this._nextIteration(); - } - - _disconnected(clean, frame) { - if (!clean) { - this._state = 'failed'; - } - - const evt = new CustomEvent('rfbdisconnected', - { detail: - { clean: clean, - frame: frame, - iteration: this._iteration } } ); - this.onrfbdisconnected(evt); - } -} - -function start() { - document.getElementById('startButton').value = "Running"; - document.getElementById('startButton').disabled = true; - - const iterations = document.getElementById('iterations').value; - - let realtime; - - if (document.getElementById('mode1').checked) { - message(`Starting performance playback (fullspeed) [${iterations} iteration(s)]`); - realtime = false; - } else { - message(`Starting realtime playback [${iterations} iteration(s)]`); - realtime = true; - } - - const player = new IterationPlayer(iterations, frames); - player.oniterationfinish = (evt) => { - message(`Iteration ${evt.detail.number} took ${evt.detail.duration}ms`); - }; - player.onrfbdisconnected = (evt) => { - if (!evt.detail.clean) { - message(`noVNC sent disconnected during iteration ${evt.detail.iteration} frame ${evt.detail.frame}`); - } - }; - player.onfinish = (evt) => { - const iterTime = parseInt(evt.detail.duration / evt.detail.iterations, 10); - message(`${evt.detail.iterations} iterations took ${evt.detail.duration}ms (average ${iterTime}ms / iteration)`); - - document.getElementById('startButton').disabled = false; - document.getElementById('startButton').value = "Start"; - }; - player.start(realtime); -} - -loadFile().then(enableUI).catch(e => message("Error loading recording: " + e)); diff --git a/systemvm/agent/noVNC/tests/playback.js b/systemvm/agent/noVNC/tests/playback.js deleted file mode 100644 index 5bd8103a840..00000000000 --- a/systemvm/agent/noVNC/tests/playback.js +++ /dev/null @@ -1,172 +0,0 @@ -/* - * noVNC: HTML5 VNC client - * Copyright (C) 2018 The noVNC Authors - * Licensed under MPL 2.0 (see LICENSE.txt) - */ - -import RFB from '../core/rfb.js'; -import * as Log from '../core/util/logging.js'; - -// Immediate polyfill -if (window.setImmediate === undefined) { - let _immediateIdCounter = 1; - const _immediateFuncs = {}; - - window.setImmediate = (func) => { - const index = _immediateIdCounter++; - _immediateFuncs[index] = func; - window.postMessage("noVNC immediate trigger:" + index, "*"); - return index; - }; - - window.clearImmediate = (id) => { - _immediateFuncs[id]; - }; - - window.addEventListener("message", (event) => { - if ((typeof event.data !== "string") || - (event.data.indexOf("noVNC immediate trigger:") !== 0)) { - return; - } - - const index = event.data.slice("noVNC immediate trigger:".length); - - const callback = _immediateFuncs[index]; - if (callback === undefined) { - return; - } - - delete _immediateFuncs[index]; - - callback(); - }); -} - -export default class RecordingPlayer { - constructor(frames, disconnected) { - this._frames = frames; - - this._disconnected = disconnected; - - this._rfb = undefined; - this._frame_length = this._frames.length; - - this._frame_index = 0; - this._start_time = undefined; - this._realtime = true; - this._trafficManagement = true; - - this._running = false; - - this.onfinish = () => {}; - } - - run(realtime, trafficManagement) { - // initialize a new RFB - this._rfb = new RFB(document.getElementById('VNC_screen'), 'wss://test'); - this._rfb.viewOnly = true; - this._rfb.addEventListener("disconnect", - this._handleDisconnect.bind(this)); - this._rfb.addEventListener("credentialsrequired", - this._handleCredentials.bind(this)); - this._enablePlaybackMode(); - - // reset the frame index and timer - this._frame_index = 0; - this._start_time = (new Date()).getTime(); - - this._realtime = realtime; - this._trafficManagement = (trafficManagement === undefined) ? !realtime : trafficManagement; - - this._running = true; - } - - // _enablePlaybackMode mocks out things not required for running playback - _enablePlaybackMode() { - const self = this; - this._rfb._sock.send = () => {}; - this._rfb._sock.close = () => {}; - this._rfb._sock.flush = () => {}; - this._rfb._sock.open = function () { - this.init(); - this._eventHandlers.open(); - self._queueNextPacket(); - }; - } - - _queueNextPacket() { - if (!this._running) { return; } - - let frame = this._frames[this._frame_index]; - - // skip send frames - while (this._frame_index < this._frame_length && frame.fromClient) { - this._frame_index++; - frame = this._frames[this._frame_index]; - } - - if (this._frame_index >= this._frame_length) { - Log.Debug('Finished, no more frames'); - this._finish(); - return; - } - - if (this._realtime) { - const toffset = (new Date()).getTime() - this._start_time; - let delay = frame.timestamp - toffset; - if (delay < 1) delay = 1; - - setTimeout(this._doPacket.bind(this), delay); - } else { - setImmediate(this._doPacket.bind(this)); - } - } - - _doPacket() { - // Avoid having excessive queue buildup in non-realtime mode - if (this._trafficManagement && this._rfb._flushing) { - const orig = this._rfb._display.onflush; - this._rfb._display.onflush = () => { - this._rfb._display.onflush = orig; - this._rfb._onFlush(); - this._doPacket(); - }; - return; - } - - const frame = this._frames[this._frame_index]; - - this._rfb._sock._recv_message({'data': frame.data}); - this._frame_index++; - - this._queueNextPacket(); - } - - _finish() { - if (this._rfb._display.pending()) { - this._rfb._display.onflush = () => { - if (this._rfb._flushing) { - this._rfb._onFlush(); - } - this._finish(); - }; - this._rfb._display.flush(); - } else { - this._running = false; - this._rfb._sock._eventHandlers.close({code: 1000, reason: ""}); - delete this._rfb; - this.onfinish((new Date()).getTime() - this._start_time); - } - } - - _handleDisconnect(evt) { - this._running = false; - this._disconnected(evt.detail.clean, this._frame_index); - } - - _handleCredentials(evt) { - this._rfb.sendCredentials({"username": "Foo", - "password": "Bar", - "target": "Baz"}); - } -} diff --git a/systemvm/agent/noVNC/tests/test.base64.js b/systemvm/agent/noVNC/tests/test.base64.js deleted file mode 100644 index 04bd207b7cf..00000000000 --- a/systemvm/agent/noVNC/tests/test.base64.js +++ /dev/null @@ -1,33 +0,0 @@ -const expect = chai.expect; - -import Base64 from '../core/base64.js'; - -describe('Base64 Tools', function () { - "use strict"; - - const BIN_ARR = new Array(256); - for (let i = 0; i < 256; i++) { - BIN_ARR[i] = i; - } - - const B64_STR = "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w=="; - - - describe('encode', function () { - it('should encode a binary string into Base64', function () { - const encoded = Base64.encode(BIN_ARR); - expect(encoded).to.equal(B64_STR); - }); - }); - - describe('decode', function () { - it('should decode a Base64 string into a normal string', function () { - const decoded = Base64.decode(B64_STR); - expect(decoded).to.deep.equal(BIN_ARR); - }); - - it('should throw an error if we have extra characters at the end of the string', function () { - expect(() => Base64.decode(B64_STR+'abcdef')).to.throw(Error); - }); - }); -}); diff --git a/systemvm/agent/noVNC/tests/test.display.js b/systemvm/agent/noVNC/tests/test.display.js deleted file mode 100644 index b359550326d..00000000000 --- a/systemvm/agent/noVNC/tests/test.display.js +++ /dev/null @@ -1,486 +0,0 @@ -const expect = chai.expect; - -import Base64 from '../core/base64.js'; -import Display from '../core/display.js'; - -describe('Display/Canvas Helper', function () { - const checked_data = new Uint8Array([ - 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, - 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, - 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, - 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255 - ]); - - const basic_data = new Uint8Array([0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0xff, 0xff, 0xff, 255]); - - function make_image_canvas(input_data) { - const canvas = document.createElement('canvas'); - canvas.width = 4; - canvas.height = 4; - const ctx = canvas.getContext('2d'); - const data = ctx.createImageData(4, 4); - for (let i = 0; i < checked_data.length; i++) { data.data[i] = input_data[i]; } - ctx.putImageData(data, 0, 0); - return canvas; - } - - function make_image_png(input_data) { - const canvas = make_image_canvas(input_data); - const url = canvas.toDataURL(); - const data = url.split(",")[1]; - return Base64.decode(data); - } - - describe('viewport handling', function () { - let display; - beforeEach(function () { - display = new Display(document.createElement('canvas')); - display.clipViewport = true; - display.resize(5, 5); - display.viewportChangeSize(3, 3); - display.viewportChangePos(1, 1); - }); - - it('should take viewport location into consideration when drawing images', function () { - display.resize(4, 4); - display.viewportChangeSize(2, 2); - display.drawImage(make_image_canvas(basic_data), 1, 1); - display.flip(); - - const expected = new Uint8Array(16); - for (let i = 0; i < 8; i++) { expected[i] = basic_data[i]; } - for (let i = 8; i < 16; i++) { expected[i] = 0; } - expect(display).to.have.displayed(expected); - }); - - it('should resize the target canvas when resizing the viewport', function () { - display.viewportChangeSize(2, 2); - expect(display._target.width).to.equal(2); - expect(display._target.height).to.equal(2); - }); - - it('should move the viewport if necessary', function () { - display.viewportChangeSize(5, 5); - expect(display.absX(0)).to.equal(0); - expect(display.absY(0)).to.equal(0); - expect(display._target.width).to.equal(5); - expect(display._target.height).to.equal(5); - }); - - it('should limit the viewport to the framebuffer size', function () { - display.viewportChangeSize(6, 6); - expect(display._target.width).to.equal(5); - expect(display._target.height).to.equal(5); - }); - - it('should redraw when moving the viewport', function () { - display.flip = sinon.spy(); - display.viewportChangePos(-1, 1); - expect(display.flip).to.have.been.calledOnce; - }); - - it('should redraw when resizing the viewport', function () { - display.flip = sinon.spy(); - display.viewportChangeSize(2, 2); - expect(display.flip).to.have.been.calledOnce; - }); - - it('should show the entire framebuffer when disabling the viewport', function () { - display.clipViewport = false; - expect(display.absX(0)).to.equal(0); - expect(display.absY(0)).to.equal(0); - expect(display._target.width).to.equal(5); - expect(display._target.height).to.equal(5); - }); - - it('should ignore viewport changes when the viewport is disabled', function () { - display.clipViewport = false; - display.viewportChangeSize(2, 2); - display.viewportChangePos(1, 1); - expect(display.absX(0)).to.equal(0); - expect(display.absY(0)).to.equal(0); - expect(display._target.width).to.equal(5); - expect(display._target.height).to.equal(5); - }); - - it('should show the entire framebuffer just after enabling the viewport', function () { - display.clipViewport = false; - display.clipViewport = true; - expect(display.absX(0)).to.equal(0); - expect(display.absY(0)).to.equal(0); - expect(display._target.width).to.equal(5); - expect(display._target.height).to.equal(5); - }); - }); - - describe('resizing', function () { - let display; - beforeEach(function () { - display = new Display(document.createElement('canvas')); - display.clipViewport = false; - display.resize(4, 4); - }); - - it('should change the size of the logical canvas', function () { - display.resize(5, 7); - expect(display._fb_width).to.equal(5); - expect(display._fb_height).to.equal(7); - }); - - it('should keep the framebuffer data', function () { - display.fillRect(0, 0, 4, 4, [0, 0, 0xff]); - display.resize(2, 2); - display.flip(); - const expected = []; - for (let i = 0; i < 4 * 2*2; i += 4) { - expected[i] = 0xff; - expected[i+1] = expected[i+2] = 0; - expected[i+3] = 0xff; - } - expect(display).to.have.displayed(new Uint8Array(expected)); - }); - - describe('viewport', function () { - beforeEach(function () { - display.clipViewport = true; - display.viewportChangeSize(3, 3); - display.viewportChangePos(1, 1); - }); - - it('should keep the viewport position and size if possible', function () { - display.resize(6, 6); - expect(display.absX(0)).to.equal(1); - expect(display.absY(0)).to.equal(1); - expect(display._target.width).to.equal(3); - expect(display._target.height).to.equal(3); - }); - - it('should move the viewport if necessary', function () { - display.resize(3, 3); - expect(display.absX(0)).to.equal(0); - expect(display.absY(0)).to.equal(0); - expect(display._target.width).to.equal(3); - expect(display._target.height).to.equal(3); - }); - - it('should shrink the viewport if necessary', function () { - display.resize(2, 2); - expect(display.absX(0)).to.equal(0); - expect(display.absY(0)).to.equal(0); - expect(display._target.width).to.equal(2); - expect(display._target.height).to.equal(2); - }); - }); - }); - - describe('rescaling', function () { - let display; - let canvas; - - beforeEach(function () { - canvas = document.createElement('canvas'); - display = new Display(canvas); - display.clipViewport = true; - display.resize(4, 4); - display.viewportChangeSize(3, 3); - display.viewportChangePos(1, 1); - document.body.appendChild(canvas); - }); - - afterEach(function () { - document.body.removeChild(canvas); - }); - - it('should not change the bitmap size of the canvas', function () { - display.scale = 2.0; - expect(canvas.width).to.equal(3); - expect(canvas.height).to.equal(3); - }); - - it('should change the effective rendered size of the canvas', function () { - display.scale = 2.0; - expect(canvas.clientWidth).to.equal(6); - expect(canvas.clientHeight).to.equal(6); - }); - - it('should not change when resizing', function () { - display.scale = 2.0; - display.resize(5, 5); - expect(display.scale).to.equal(2.0); - expect(canvas.width).to.equal(3); - expect(canvas.height).to.equal(3); - expect(canvas.clientWidth).to.equal(6); - expect(canvas.clientHeight).to.equal(6); - }); - }); - - describe('autoscaling', function () { - let display; - let canvas; - - beforeEach(function () { - canvas = document.createElement('canvas'); - display = new Display(canvas); - display.clipViewport = true; - display.resize(4, 3); - document.body.appendChild(canvas); - }); - - afterEach(function () { - document.body.removeChild(canvas); - }); - - it('should preserve aspect ratio while autoscaling', function () { - display.autoscale(16, 9); - expect(canvas.clientWidth / canvas.clientHeight).to.equal(4 / 3); - }); - - it('should use width to determine scale when the current aspect ratio is wider than the target', function () { - display.autoscale(9, 16); - expect(display.absX(9)).to.equal(4); - expect(display.absY(18)).to.equal(8); - expect(canvas.clientWidth).to.equal(9); - expect(canvas.clientHeight).to.equal(7); // round 9 / (4 / 3) - }); - - it('should use height to determine scale when the current aspect ratio is taller than the target', function () { - display.autoscale(16, 9); - expect(display.absX(9)).to.equal(3); - expect(display.absY(18)).to.equal(6); - expect(canvas.clientWidth).to.equal(12); // 16 * (4 / 3) - expect(canvas.clientHeight).to.equal(9); - - }); - - it('should not change the bitmap size of the canvas', function () { - display.autoscale(16, 9); - expect(canvas.width).to.equal(4); - expect(canvas.height).to.equal(3); - }); - }); - - describe('drawing', function () { - - // TODO(directxman12): improve the tests for each of the drawing functions to cover more than just the - // basic cases - let display; - beforeEach(function () { - display = new Display(document.createElement('canvas')); - display.resize(4, 4); - }); - - it('should clear the screen on #clear without a logo set', function () { - display.fillRect(0, 0, 4, 4, [0x00, 0x00, 0xff]); - display._logo = null; - display.clear(); - display.resize(4, 4); - const empty = []; - for (let i = 0; i < 4 * display._fb_width * display._fb_height; i++) { empty[i] = 0; } - expect(display).to.have.displayed(new Uint8Array(empty)); - }); - - it('should draw the logo on #clear with a logo set', function (done) { - display._logo = { width: 4, height: 4, type: "image/png", data: make_image_png(checked_data) }; - display.clear(); - display.onflush = () => { - expect(display).to.have.displayed(checked_data); - expect(display._fb_width).to.equal(4); - expect(display._fb_height).to.equal(4); - done(); - }; - display.flush(); - }); - - it('should not draw directly on the target canvas', function () { - display.fillRect(0, 0, 4, 4, [0, 0, 0xff]); - display.flip(); - display.fillRect(0, 0, 4, 4, [0, 0xff, 0]); - const expected = []; - for (let i = 0; i < 4 * display._fb_width * display._fb_height; i += 4) { - expected[i] = 0xff; - expected[i+1] = expected[i+2] = 0; - expected[i+3] = 0xff; - } - expect(display).to.have.displayed(new Uint8Array(expected)); - }); - - it('should support filling a rectangle with particular color via #fillRect', function () { - display.fillRect(0, 0, 4, 4, [0, 0xff, 0]); - display.fillRect(0, 0, 2, 2, [0xff, 0, 0]); - display.fillRect(2, 2, 2, 2, [0xff, 0, 0]); - display.flip(); - expect(display).to.have.displayed(checked_data); - }); - - it('should support copying an portion of the canvas via #copyImage', function () { - display.fillRect(0, 0, 4, 4, [0, 0xff, 0]); - display.fillRect(0, 0, 2, 2, [0xff, 0, 0x00]); - display.copyImage(0, 0, 2, 2, 2, 2); - display.flip(); - expect(display).to.have.displayed(checked_data); - }); - - it('should support drawing images via #imageRect', function (done) { - display.imageRect(0, 0, "image/png", make_image_png(checked_data)); - display.flip(); - display.onflush = () => { - expect(display).to.have.displayed(checked_data); - done(); - }; - display.flush(); - }); - - it('should support drawing tile data with a background color and sub tiles', function () { - display.startTile(0, 0, 4, 4, [0, 0xff, 0]); - display.subTile(0, 0, 2, 2, [0xff, 0, 0]); - display.subTile(2, 2, 2, 2, [0xff, 0, 0]); - display.finishTile(); - display.flip(); - expect(display).to.have.displayed(checked_data); - }); - - // We have a special cache for 16x16 tiles that we need to test - it('should support drawing a 16x16 tile', function () { - const large_checked_data = new Uint8Array(16*16*4); - display.resize(16, 16); - - for (let y = 0;y < 16;y++) { - for (let x = 0;x < 16;x++) { - let pixel; - if ((x < 4) && (y < 4)) { - // NB: of course IE11 doesn't support #slice on ArrayBufferViews... - pixel = Array.prototype.slice.call(checked_data, (y*4+x)*4, (y*4+x+1)*4); - } else { - pixel = [0, 0xff, 0, 255]; - } - large_checked_data.set(pixel, (y*16+x)*4); - } - } - - display.startTile(0, 0, 16, 16, [0, 0xff, 0]); - display.subTile(0, 0, 2, 2, [0xff, 0, 0]); - display.subTile(2, 2, 2, 2, [0xff, 0, 0]); - display.finishTile(); - display.flip(); - expect(display).to.have.displayed(large_checked_data); - }); - - it('should support drawing BGRX blit images with true color via #blitImage', function () { - const data = []; - for (let i = 0; i < 16; i++) { - data[i * 4] = checked_data[i * 4 + 2]; - data[i * 4 + 1] = checked_data[i * 4 + 1]; - data[i * 4 + 2] = checked_data[i * 4]; - data[i * 4 + 3] = checked_data[i * 4 + 3]; - } - display.blitImage(0, 0, 4, 4, data, 0); - display.flip(); - expect(display).to.have.displayed(checked_data); - }); - - it('should support drawing RGB blit images with true color via #blitRgbImage', function () { - const data = []; - for (let i = 0; i < 16; i++) { - data[i * 3] = checked_data[i * 4]; - data[i * 3 + 1] = checked_data[i * 4 + 1]; - data[i * 3 + 2] = checked_data[i * 4 + 2]; - } - display.blitRgbImage(0, 0, 4, 4, data, 0); - display.flip(); - expect(display).to.have.displayed(checked_data); - }); - - it('should support drawing an image object via #drawImage', function () { - const img = make_image_canvas(checked_data); - display.drawImage(img, 0, 0); - display.flip(); - expect(display).to.have.displayed(checked_data); - }); - }); - - describe('the render queue processor', function () { - let display; - beforeEach(function () { - display = new Display(document.createElement('canvas')); - display.resize(4, 4); - sinon.spy(display, '_scan_renderQ'); - }); - - afterEach(function () { - window.requestAnimationFrame = this.old_requestAnimationFrame; - }); - - it('should try to process an item when it is pushed on, if nothing else is on the queue', function () { - display._renderQ_push({ type: 'noop' }); // does nothing - expect(display._scan_renderQ).to.have.been.calledOnce; - }); - - it('should not try to process an item when it is pushed on if we are waiting for other items', function () { - display._renderQ.length = 2; - display._renderQ_push({ type: 'noop' }); - expect(display._scan_renderQ).to.not.have.been.called; - }); - - it('should wait until an image is loaded to attempt to draw it and the rest of the queue', function () { - const img = { complete: false, addEventListener: sinon.spy() }; - display._renderQ = [{ type: 'img', x: 3, y: 4, img: img }, - { type: 'fill', x: 1, y: 2, width: 3, height: 4, color: 5 }]; - display.drawImage = sinon.spy(); - display.fillRect = sinon.spy(); - - display._scan_renderQ(); - expect(display.drawImage).to.not.have.been.called; - expect(display.fillRect).to.not.have.been.called; - expect(img.addEventListener).to.have.been.calledOnce; - - display._renderQ[0].img.complete = true; - display._scan_renderQ(); - expect(display.drawImage).to.have.been.calledOnce; - expect(display.fillRect).to.have.been.calledOnce; - expect(img.addEventListener).to.have.been.calledOnce; - }); - - it('should call callback when queue is flushed', function () { - display.onflush = sinon.spy(); - display.fillRect(0, 0, 4, 4, [0, 0xff, 0]); - expect(display.onflush).to.not.have.been.called; - display.flush(); - expect(display.onflush).to.have.been.calledOnce; - }); - - it('should draw a blit image on type "blit"', function () { - display.blitImage = sinon.spy(); - display._renderQ_push({ type: 'blit', x: 3, y: 4, width: 5, height: 6, data: [7, 8, 9] }); - expect(display.blitImage).to.have.been.calledOnce; - expect(display.blitImage).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9], 0); - }); - - it('should draw a blit RGB image on type "blitRgb"', function () { - display.blitRgbImage = sinon.spy(); - display._renderQ_push({ type: 'blitRgb', x: 3, y: 4, width: 5, height: 6, data: [7, 8, 9] }); - expect(display.blitRgbImage).to.have.been.calledOnce; - expect(display.blitRgbImage).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9], 0); - }); - - it('should copy a region on type "copy"', function () { - display.copyImage = sinon.spy(); - display._renderQ_push({ type: 'copy', x: 3, y: 4, width: 5, height: 6, old_x: 7, old_y: 8 }); - expect(display.copyImage).to.have.been.calledOnce; - expect(display.copyImage).to.have.been.calledWith(7, 8, 3, 4, 5, 6); - }); - - it('should fill a rect with a given color on type "fill"', function () { - display.fillRect = sinon.spy(); - display._renderQ_push({ type: 'fill', x: 3, y: 4, width: 5, height: 6, color: [7, 8, 9]}); - expect(display.fillRect).to.have.been.calledOnce; - expect(display.fillRect).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9]); - }); - - it('should draw an image from an image object on type "img" (if complete)', function () { - display.drawImage = sinon.spy(); - display._renderQ_push({ type: 'img', x: 3, y: 4, img: { complete: true } }); - expect(display.drawImage).to.have.been.calledOnce; - expect(display.drawImage).to.have.been.calledWith({ complete: true }, 3, 4); - }); - }); -}); diff --git a/systemvm/agent/noVNC/tests/test.helper.js b/systemvm/agent/noVNC/tests/test.helper.js deleted file mode 100644 index d44bab0fe2d..00000000000 --- a/systemvm/agent/noVNC/tests/test.helper.js +++ /dev/null @@ -1,223 +0,0 @@ -const expect = chai.expect; - -import keysyms from '../core/input/keysymdef.js'; -import * as KeyboardUtil from "../core/input/util.js"; -import * as browser from '../core/util/browser.js'; - -describe('Helpers', function () { - "use strict"; - - describe('keysyms.lookup', function () { - it('should map ASCII characters to keysyms', function () { - expect(keysyms.lookup('a'.charCodeAt())).to.be.equal(0x61); - expect(keysyms.lookup('A'.charCodeAt())).to.be.equal(0x41); - }); - it('should map Latin-1 characters to keysyms', function () { - expect(keysyms.lookup('ø'.charCodeAt())).to.be.equal(0xf8); - - expect(keysyms.lookup('é'.charCodeAt())).to.be.equal(0xe9); - }); - it('should map characters that are in Windows-1252 but not in Latin-1 to keysyms', function () { - expect(keysyms.lookup('Š'.charCodeAt())).to.be.equal(0x01a9); - }); - it('should map characters which aren\'t in Latin1 *or* Windows-1252 to keysyms', function () { - expect(keysyms.lookup('ũ'.charCodeAt())).to.be.equal(0x03fd); - }); - it('should map unknown codepoints to the Unicode range', function () { - expect(keysyms.lookup('\n'.charCodeAt())).to.be.equal(0x100000a); - expect(keysyms.lookup('\u262D'.charCodeAt())).to.be.equal(0x100262d); - }); - // This requires very recent versions of most browsers... skipping for now - it.skip('should map UCS-4 codepoints to the Unicode range', function () { - //expect(keysyms.lookup('\u{1F686}'.codePointAt())).to.be.equal(0x101f686); - }); - }); - - describe('getKeycode', function () { - it('should pass through proper code', function () { - expect(KeyboardUtil.getKeycode({code: 'Semicolon'})).to.be.equal('Semicolon'); - }); - it('should map legacy values', function () { - expect(KeyboardUtil.getKeycode({code: ''})).to.be.equal('Unidentified'); - expect(KeyboardUtil.getKeycode({code: 'OSLeft'})).to.be.equal('MetaLeft'); - }); - it('should map keyCode to code when possible', function () { - expect(KeyboardUtil.getKeycode({keyCode: 0x14})).to.be.equal('CapsLock'); - expect(KeyboardUtil.getKeycode({keyCode: 0x5b})).to.be.equal('MetaLeft'); - expect(KeyboardUtil.getKeycode({keyCode: 0x35})).to.be.equal('Digit5'); - expect(KeyboardUtil.getKeycode({keyCode: 0x65})).to.be.equal('Numpad5'); - }); - it('should map keyCode left/right side', function () { - expect(KeyboardUtil.getKeycode({keyCode: 0x10, location: 1})).to.be.equal('ShiftLeft'); - expect(KeyboardUtil.getKeycode({keyCode: 0x10, location: 2})).to.be.equal('ShiftRight'); - expect(KeyboardUtil.getKeycode({keyCode: 0x11, location: 1})).to.be.equal('ControlLeft'); - expect(KeyboardUtil.getKeycode({keyCode: 0x11, location: 2})).to.be.equal('ControlRight'); - }); - it('should map keyCode on numpad', function () { - expect(KeyboardUtil.getKeycode({keyCode: 0x0d, location: 0})).to.be.equal('Enter'); - expect(KeyboardUtil.getKeycode({keyCode: 0x0d, location: 3})).to.be.equal('NumpadEnter'); - expect(KeyboardUtil.getKeycode({keyCode: 0x23, location: 0})).to.be.equal('End'); - expect(KeyboardUtil.getKeycode({keyCode: 0x23, location: 3})).to.be.equal('Numpad1'); - }); - it('should return Unidentified when it cannot map the keyCode', function () { - expect(KeyboardUtil.getKeycode({keycode: 0x42})).to.be.equal('Unidentified'); - }); - - describe('Fix Meta on macOS', function () { - let origNavigator; - beforeEach(function () { - // window.navigator is a protected read-only property in many - // environments, so we need to redefine it whilst running these - // tests. - origNavigator = Object.getOwnPropertyDescriptor(window, "navigator"); - if (origNavigator === undefined) { - // Object.getOwnPropertyDescriptor() doesn't work - // properly in any version of IE - this.skip(); - } - - Object.defineProperty(window, "navigator", {value: {}}); - if (window.navigator.platform !== undefined) { - // Object.defineProperty() doesn't work properly in old - // versions of Chrome - this.skip(); - } - - window.navigator.platform = "Mac x86_64"; - }); - afterEach(function () { - Object.defineProperty(window, "navigator", origNavigator); - }); - - it('should respect ContextMenu on modern browser', function () { - expect(KeyboardUtil.getKeycode({code: 'ContextMenu', keyCode: 0x5d})).to.be.equal('ContextMenu'); - }); - it('should translate legacy ContextMenu to MetaRight', function () { - expect(KeyboardUtil.getKeycode({keyCode: 0x5d})).to.be.equal('MetaRight'); - }); - }); - }); - - describe('getKey', function () { - it('should prefer key', function () { - if (browser.isIE() || browser.isEdge()) this.skip(); - expect(KeyboardUtil.getKey({key: 'a', charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43})).to.be.equal('a'); - }); - it('should map legacy values', function () { - expect(KeyboardUtil.getKey({key: 'Spacebar'})).to.be.equal(' '); - expect(KeyboardUtil.getKey({key: 'Left'})).to.be.equal('ArrowLeft'); - expect(KeyboardUtil.getKey({key: 'OS'})).to.be.equal('Meta'); - expect(KeyboardUtil.getKey({key: 'Win'})).to.be.equal('Meta'); - expect(KeyboardUtil.getKey({key: 'UIKeyInputLeftArrow'})).to.be.equal('ArrowLeft'); - }); - it('should use code if no key', function () { - expect(KeyboardUtil.getKey({code: 'NumpadBackspace'})).to.be.equal('Backspace'); - }); - it('should not use code fallback for character keys', function () { - expect(KeyboardUtil.getKey({code: 'KeyA'})).to.be.equal('Unidentified'); - expect(KeyboardUtil.getKey({code: 'Digit1'})).to.be.equal('Unidentified'); - expect(KeyboardUtil.getKey({code: 'Period'})).to.be.equal('Unidentified'); - expect(KeyboardUtil.getKey({code: 'Numpad1'})).to.be.equal('Unidentified'); - }); - it('should use charCode if no key', function () { - expect(KeyboardUtil.getKey({charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43})).to.be.equal('Š'); - }); - it('should return Unidentified when it cannot map the key', function () { - expect(KeyboardUtil.getKey({keycode: 0x42})).to.be.equal('Unidentified'); - }); - - describe('Broken key AltGraph on IE/Edge', function () { - let origNavigator; - beforeEach(function () { - // window.navigator is a protected read-only property in many - // environments, so we need to redefine it whilst running these - // tests. - origNavigator = Object.getOwnPropertyDescriptor(window, "navigator"); - if (origNavigator === undefined) { - // Object.getOwnPropertyDescriptor() doesn't work - // properly in any version of IE - this.skip(); - } - - Object.defineProperty(window, "navigator", {value: {}}); - if (window.navigator.platform !== undefined) { - // Object.defineProperty() doesn't work properly in old - // versions of Chrome - this.skip(); - } - }); - afterEach(function () { - Object.defineProperty(window, "navigator", origNavigator); - }); - - it('should ignore printable character key on IE', function () { - window.navigator.userAgent = "Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko"; - expect(KeyboardUtil.getKey({key: 'a'})).to.be.equal('Unidentified'); - }); - it('should ignore printable character key on Edge', function () { - window.navigator.userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36 Edge/14.14393"; - expect(KeyboardUtil.getKey({key: 'a'})).to.be.equal('Unidentified'); - }); - it('should allow non-printable character key on IE', function () { - window.navigator.userAgent = "Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko"; - expect(KeyboardUtil.getKey({key: 'Shift'})).to.be.equal('Shift'); - }); - it('should allow non-printable character key on Edge', function () { - window.navigator.userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36 Edge/14.14393"; - expect(KeyboardUtil.getKey({key: 'Shift'})).to.be.equal('Shift'); - }); - }); - }); - - describe('getKeysym', function () { - describe('Non-character keys', function () { - it('should recognize the right keys', function () { - expect(KeyboardUtil.getKeysym({key: 'Enter'})).to.be.equal(0xFF0D); - expect(KeyboardUtil.getKeysym({key: 'Backspace'})).to.be.equal(0xFF08); - expect(KeyboardUtil.getKeysym({key: 'Tab'})).to.be.equal(0xFF09); - expect(KeyboardUtil.getKeysym({key: 'Shift'})).to.be.equal(0xFFE1); - expect(KeyboardUtil.getKeysym({key: 'Control'})).to.be.equal(0xFFE3); - expect(KeyboardUtil.getKeysym({key: 'Alt'})).to.be.equal(0xFFE9); - expect(KeyboardUtil.getKeysym({key: 'Meta'})).to.be.equal(0xFFEB); - expect(KeyboardUtil.getKeysym({key: 'Escape'})).to.be.equal(0xFF1B); - expect(KeyboardUtil.getKeysym({key: 'ArrowUp'})).to.be.equal(0xFF52); - }); - it('should map left/right side', function () { - expect(KeyboardUtil.getKeysym({key: 'Shift', location: 1})).to.be.equal(0xFFE1); - expect(KeyboardUtil.getKeysym({key: 'Shift', location: 2})).to.be.equal(0xFFE2); - expect(KeyboardUtil.getKeysym({key: 'Control', location: 1})).to.be.equal(0xFFE3); - expect(KeyboardUtil.getKeysym({key: 'Control', location: 2})).to.be.equal(0xFFE4); - }); - it('should handle AltGraph', function () { - expect(KeyboardUtil.getKeysym({code: 'AltRight', key: 'Alt', location: 2})).to.be.equal(0xFFEA); - expect(KeyboardUtil.getKeysym({code: 'AltRight', key: 'AltGraph', location: 2})).to.be.equal(0xFE03); - }); - it('should return null for unknown keys', function () { - expect(KeyboardUtil.getKeysym({key: 'Semicolon'})).to.be.null; - expect(KeyboardUtil.getKeysym({key: 'BracketRight'})).to.be.null; - }); - it('should handle remappings', function () { - expect(KeyboardUtil.getKeysym({code: 'ControlLeft', key: 'Tab'})).to.be.equal(0xFF09); - }); - }); - - describe('Numpad', function () { - it('should handle Numpad numbers', function () { - if (browser.isIE() || browser.isEdge()) this.skip(); - expect(KeyboardUtil.getKeysym({code: 'Digit5', key: '5', location: 0})).to.be.equal(0x0035); - expect(KeyboardUtil.getKeysym({code: 'Numpad5', key: '5', location: 3})).to.be.equal(0xFFB5); - }); - it('should handle Numpad non-character keys', function () { - expect(KeyboardUtil.getKeysym({code: 'Home', key: 'Home', location: 0})).to.be.equal(0xFF50); - expect(KeyboardUtil.getKeysym({code: 'Numpad5', key: 'Home', location: 3})).to.be.equal(0xFF95); - expect(KeyboardUtil.getKeysym({code: 'Delete', key: 'Delete', location: 0})).to.be.equal(0xFFFF); - expect(KeyboardUtil.getKeysym({code: 'NumpadDecimal', key: 'Delete', location: 3})).to.be.equal(0xFF9F); - }); - it('should handle Numpad Decimal key', function () { - if (browser.isIE() || browser.isEdge()) this.skip(); - expect(KeyboardUtil.getKeysym({code: 'NumpadDecimal', key: '.', location: 3})).to.be.equal(0xFFAE); - expect(KeyboardUtil.getKeysym({code: 'NumpadDecimal', key: ',', location: 3})).to.be.equal(0xFFAC); - }); - }); - }); -}); diff --git a/systemvm/agent/noVNC/tests/test.keyboard.js b/systemvm/agent/noVNC/tests/test.keyboard.js deleted file mode 100644 index 77fe3f6f968..00000000000 --- a/systemvm/agent/noVNC/tests/test.keyboard.js +++ /dev/null @@ -1,510 +0,0 @@ -const expect = chai.expect; - -import Keyboard from '../core/input/keyboard.js'; -import * as browser from '../core/util/browser.js'; - -describe('Key Event Handling', function () { - "use strict"; - - // The real KeyboardEvent constructor might not work everywhere we - // want to run these tests - function keyevent(typeArg, KeyboardEventInit) { - const e = { type: typeArg }; - for (let key in KeyboardEventInit) { - e[key] = KeyboardEventInit[key]; - } - e.stopPropagation = sinon.spy(); - e.preventDefault = sinon.spy(); - return e; - } - - describe('Decode Keyboard Events', function () { - it('should decode keydown events', function (done) { - if (browser.isIE() || browser.isEdge()) this.skip(); - const kbd = new Keyboard(document); - kbd.onkeyevent = (keysym, code, down) => { - expect(keysym).to.be.equal(0x61); - expect(code).to.be.equal('KeyA'); - expect(down).to.be.equal(true); - done(); - }; - kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'})); - }); - it('should decode keyup events', function (done) { - if (browser.isIE() || browser.isEdge()) this.skip(); - let calls = 0; - const kbd = new Keyboard(document); - kbd.onkeyevent = (keysym, code, down) => { - expect(keysym).to.be.equal(0x61); - expect(code).to.be.equal('KeyA'); - if (calls++ === 1) { - expect(down).to.be.equal(false); - done(); - } - }; - kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'})); - kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA', key: 'a'})); - }); - - describe('Legacy keypress Events', function () { - it('should wait for keypress when needed', function () { - const kbd = new Keyboard(document); - kbd.onkeyevent = sinon.spy(); - kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41})); - expect(kbd.onkeyevent).to.not.have.been.called; - }); - it('should decode keypress events', function (done) { - const kbd = new Keyboard(document); - kbd.onkeyevent = (keysym, code, down) => { - expect(keysym).to.be.equal(0x61); - expect(code).to.be.equal('KeyA'); - expect(down).to.be.equal(true); - done(); - }; - kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41})); - kbd._handleKeyPress(keyevent('keypress', {code: 'KeyA', charCode: 0x61})); - }); - it('should ignore keypress with different code', function () { - const kbd = new Keyboard(document); - kbd.onkeyevent = sinon.spy(); - kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41})); - kbd._handleKeyPress(keyevent('keypress', {code: 'KeyB', charCode: 0x61})); - expect(kbd.onkeyevent).to.not.have.been.called; - }); - it('should handle keypress with missing code', function (done) { - const kbd = new Keyboard(document); - kbd.onkeyevent = (keysym, code, down) => { - expect(keysym).to.be.equal(0x61); - expect(code).to.be.equal('KeyA'); - expect(down).to.be.equal(true); - done(); - }; - kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41})); - kbd._handleKeyPress(keyevent('keypress', {charCode: 0x61})); - }); - it('should guess key if no keypress and numeric key', function (done) { - const kbd = new Keyboard(document); - kbd.onkeyevent = (keysym, code, down) => { - expect(keysym).to.be.equal(0x32); - expect(code).to.be.equal('Digit2'); - expect(down).to.be.equal(true); - done(); - }; - kbd._handleKeyDown(keyevent('keydown', {code: 'Digit2', keyCode: 0x32})); - }); - it('should guess key if no keypress and alpha key', function (done) { - const kbd = new Keyboard(document); - kbd.onkeyevent = (keysym, code, down) => { - expect(keysym).to.be.equal(0x61); - expect(code).to.be.equal('KeyA'); - expect(down).to.be.equal(true); - done(); - }; - kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41, shiftKey: false})); - }); - it('should guess key if no keypress and alpha key (with shift)', function (done) { - const kbd = new Keyboard(document); - kbd.onkeyevent = (keysym, code, down) => { - expect(keysym).to.be.equal(0x41); - expect(code).to.be.equal('KeyA'); - expect(down).to.be.equal(true); - done(); - }; - kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41, shiftKey: true})); - }); - it('should not guess key if no keypress and unknown key', function (done) { - const kbd = new Keyboard(document); - kbd.onkeyevent = (keysym, code, down) => { - expect(keysym).to.be.equal(0); - expect(code).to.be.equal('KeyA'); - expect(down).to.be.equal(true); - done(); - }; - kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x09})); - }); - }); - - describe('suppress the right events at the right time', function () { - beforeEach(function () { - if (browser.isIE() || browser.isEdge()) this.skip(); - }); - it('should suppress anything with a valid key', function () { - const kbd = new Keyboard(document, {}); - const evt1 = keyevent('keydown', {code: 'KeyA', key: 'a'}); - kbd._handleKeyDown(evt1); - expect(evt1.preventDefault).to.have.been.called; - const evt2 = keyevent('keyup', {code: 'KeyA', key: 'a'}); - kbd._handleKeyUp(evt2); - expect(evt2.preventDefault).to.have.been.called; - }); - it('should not suppress keys without key', function () { - const kbd = new Keyboard(document, {}); - const evt = keyevent('keydown', {code: 'KeyA', keyCode: 0x41}); - kbd._handleKeyDown(evt); - expect(evt.preventDefault).to.not.have.been.called; - }); - it('should suppress the following keypress event', function () { - const kbd = new Keyboard(document, {}); - const evt1 = keyevent('keydown', {code: 'KeyA', keyCode: 0x41}); - kbd._handleKeyDown(evt1); - const evt2 = keyevent('keypress', {code: 'KeyA', charCode: 0x41}); - kbd._handleKeyPress(evt2); - expect(evt2.preventDefault).to.have.been.called; - }); - }); - }); - - describe('Fake keyup', function () { - it('should fake keyup events for virtual keyboards', function (done) { - if (browser.isIE() || browser.isEdge()) this.skip(); - let count = 0; - const kbd = new Keyboard(document); - kbd.onkeyevent = (keysym, code, down) => { - switch (count++) { - case 0: - expect(keysym).to.be.equal(0x61); - expect(code).to.be.equal('Unidentified'); - expect(down).to.be.equal(true); - break; - case 1: - expect(keysym).to.be.equal(0x61); - expect(code).to.be.equal('Unidentified'); - expect(down).to.be.equal(false); - done(); - } - }; - kbd._handleKeyDown(keyevent('keydown', {code: 'Unidentified', key: 'a'})); - }); - - describe('iOS', function () { - let origNavigator; - beforeEach(function () { - // window.navigator is a protected read-only property in many - // environments, so we need to redefine it whilst running these - // tests. - origNavigator = Object.getOwnPropertyDescriptor(window, "navigator"); - if (origNavigator === undefined) { - // Object.getOwnPropertyDescriptor() doesn't work - // properly in any version of IE - this.skip(); - } - - Object.defineProperty(window, "navigator", {value: {}}); - if (window.navigator.platform !== undefined) { - // Object.defineProperty() doesn't work properly in old - // versions of Chrome - this.skip(); - } - - window.navigator.platform = "iPhone 9.0"; - }); - afterEach(function () { - Object.defineProperty(window, "navigator", origNavigator); - }); - - it('should fake keyup events on iOS', function (done) { - if (browser.isIE() || browser.isEdge()) this.skip(); - let count = 0; - const kbd = new Keyboard(document); - kbd.onkeyevent = (keysym, code, down) => { - switch (count++) { - case 0: - expect(keysym).to.be.equal(0x61); - expect(code).to.be.equal('KeyA'); - expect(down).to.be.equal(true); - break; - case 1: - expect(keysym).to.be.equal(0x61); - expect(code).to.be.equal('KeyA'); - expect(down).to.be.equal(false); - done(); - } - }; - kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'})); - }); - }); - }); - - describe('Track Key State', function () { - beforeEach(function () { - if (browser.isIE() || browser.isEdge()) this.skip(); - }); - it('should send release using the same keysym as the press', function (done) { - const kbd = new Keyboard(document); - kbd.onkeyevent = (keysym, code, down) => { - expect(keysym).to.be.equal(0x61); - expect(code).to.be.equal('KeyA'); - if (!down) { - done(); - } - }; - kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'})); - kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA', key: 'b'})); - }); - it('should send the same keysym for multiple presses', function () { - let count = 0; - const kbd = new Keyboard(document); - kbd.onkeyevent = (keysym, code, down) => { - expect(keysym).to.be.equal(0x61); - expect(code).to.be.equal('KeyA'); - expect(down).to.be.equal(true); - count++; - }; - kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'})); - kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'b'})); - expect(count).to.be.equal(2); - }); - it('should do nothing on keyup events if no keys are down', function () { - const kbd = new Keyboard(document); - kbd.onkeyevent = sinon.spy(); - kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA', key: 'a'})); - expect(kbd.onkeyevent).to.not.have.been.called; - }); - - describe('Legacy Events', function () { - it('should track keys using keyCode if no code', function (done) { - const kbd = new Keyboard(document); - kbd.onkeyevent = (keysym, code, down) => { - expect(keysym).to.be.equal(0x61); - expect(code).to.be.equal('Platform65'); - if (!down) { - done(); - } - }; - kbd._handleKeyDown(keyevent('keydown', {keyCode: 65, key: 'a'})); - kbd._handleKeyUp(keyevent('keyup', {keyCode: 65, key: 'b'})); - }); - it('should ignore compositing code', function () { - const kbd = new Keyboard(document); - kbd.onkeyevent = (keysym, code, down) => { - expect(keysym).to.be.equal(0x61); - expect(code).to.be.equal('Unidentified'); - }; - kbd._handleKeyDown(keyevent('keydown', {keyCode: 229, key: 'a'})); - }); - it('should track keys using keyIdentifier if no code', function (done) { - const kbd = new Keyboard(document); - kbd.onkeyevent = (keysym, code, down) => { - expect(keysym).to.be.equal(0x61); - expect(code).to.be.equal('Platform65'); - if (!down) { - done(); - } - }; - kbd._handleKeyDown(keyevent('keydown', {keyIdentifier: 'U+0041', key: 'a'})); - kbd._handleKeyUp(keyevent('keyup', {keyIdentifier: 'U+0041', key: 'b'})); - }); - }); - }); - - describe('Shuffle modifiers on macOS', function () { - let origNavigator; - beforeEach(function () { - // window.navigator is a protected read-only property in many - // environments, so we need to redefine it whilst running these - // tests. - origNavigator = Object.getOwnPropertyDescriptor(window, "navigator"); - if (origNavigator === undefined) { - // Object.getOwnPropertyDescriptor() doesn't work - // properly in any version of IE - this.skip(); - } - - Object.defineProperty(window, "navigator", {value: {}}); - if (window.navigator.platform !== undefined) { - // Object.defineProperty() doesn't work properly in old - // versions of Chrome - this.skip(); - } - - window.navigator.platform = "Mac x86_64"; - }); - afterEach(function () { - Object.defineProperty(window, "navigator", origNavigator); - }); - - it('should change Alt to AltGraph', function () { - let count = 0; - const kbd = new Keyboard(document); - kbd.onkeyevent = (keysym, code, down) => { - switch (count++) { - case 0: - expect(keysym).to.be.equal(0xFF7E); - expect(code).to.be.equal('AltLeft'); - break; - case 1: - expect(keysym).to.be.equal(0xFE03); - expect(code).to.be.equal('AltRight'); - break; - } - }; - kbd._handleKeyDown(keyevent('keydown', {code: 'AltLeft', key: 'Alt', location: 1})); - kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'Alt', location: 2})); - expect(count).to.be.equal(2); - }); - it('should change left Super to Alt', function (done) { - const kbd = new Keyboard(document); - kbd.onkeyevent = (keysym, code, down) => { - expect(keysym).to.be.equal(0xFFE9); - expect(code).to.be.equal('MetaLeft'); - done(); - }; - kbd._handleKeyDown(keyevent('keydown', {code: 'MetaLeft', key: 'Meta', location: 1})); - }); - it('should change right Super to left Super', function (done) { - const kbd = new Keyboard(document); - kbd.onkeyevent = (keysym, code, down) => { - expect(keysym).to.be.equal(0xFFEB); - expect(code).to.be.equal('MetaRight'); - done(); - }; - kbd._handleKeyDown(keyevent('keydown', {code: 'MetaRight', key: 'Meta', location: 2})); - }); - }); - - describe('Escape AltGraph on Windows', function () { - let origNavigator; - beforeEach(function () { - // window.navigator is a protected read-only property in many - // environments, so we need to redefine it whilst running these - // tests. - origNavigator = Object.getOwnPropertyDescriptor(window, "navigator"); - if (origNavigator === undefined) { - // Object.getOwnPropertyDescriptor() doesn't work - // properly in any version of IE - this.skip(); - } - - Object.defineProperty(window, "navigator", {value: {}}); - if (window.navigator.platform !== undefined) { - // Object.defineProperty() doesn't work properly in old - // versions of Chrome - this.skip(); - } - - window.navigator.platform = "Windows x86_64"; - - this.clock = sinon.useFakeTimers(); - }); - afterEach(function () { - Object.defineProperty(window, "navigator", origNavigator); - this.clock.restore(); - }); - - it('should supress ControlLeft until it knows if it is AltGr', function () { - const kbd = new Keyboard(document); - kbd.onkeyevent = sinon.spy(); - kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1})); - expect(kbd.onkeyevent).to.not.have.been.called; - }); - - it('should not trigger on repeating ControlLeft', function () { - const kbd = new Keyboard(document); - kbd.onkeyevent = sinon.spy(); - kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1})); - kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1})); - expect(kbd.onkeyevent).to.have.been.calledTwice; - expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0xffe3, "ControlLeft", true); - expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0xffe3, "ControlLeft", true); - }); - - it('should not supress ControlRight', function () { - const kbd = new Keyboard(document); - kbd.onkeyevent = sinon.spy(); - kbd._handleKeyDown(keyevent('keydown', {code: 'ControlRight', key: 'Control', location: 2})); - expect(kbd.onkeyevent).to.have.been.calledOnce; - expect(kbd.onkeyevent).to.have.been.calledWith(0xffe4, "ControlRight", true); - }); - - it('should release ControlLeft after 100 ms', function () { - const kbd = new Keyboard(document); - kbd.onkeyevent = sinon.spy(); - kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1})); - expect(kbd.onkeyevent).to.not.have.been.called; - this.clock.tick(100); - expect(kbd.onkeyevent).to.have.been.calledOnce; - expect(kbd.onkeyevent).to.have.been.calledWith(0xffe3, "ControlLeft", true); - }); - - it('should release ControlLeft on other key press', function () { - const kbd = new Keyboard(document); - kbd.onkeyevent = sinon.spy(); - kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1})); - expect(kbd.onkeyevent).to.not.have.been.called; - kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'})); - expect(kbd.onkeyevent).to.have.been.calledTwice; - expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0xffe3, "ControlLeft", true); - expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0x61, "KeyA", true); - - // Check that the timer is properly dead - kbd.onkeyevent.reset(); - this.clock.tick(100); - expect(kbd.onkeyevent).to.not.have.been.called; - }); - - it('should release ControlLeft on other key release', function () { - const kbd = new Keyboard(document); - kbd.onkeyevent = sinon.spy(); - kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'})); - kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1})); - expect(kbd.onkeyevent).to.have.been.calledOnce; - expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0x61, "KeyA", true); - kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA', key: 'a'})); - expect(kbd.onkeyevent).to.have.been.calledThrice; - expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0xffe3, "ControlLeft", true); - expect(kbd.onkeyevent.thirdCall).to.have.been.calledWith(0x61, "KeyA", false); - - // Check that the timer is properly dead - kbd.onkeyevent.reset(); - this.clock.tick(100); - expect(kbd.onkeyevent).to.not.have.been.called; - }); - - it('should generate AltGraph for quick Ctrl+Alt sequence', function () { - const kbd = new Keyboard(document); - kbd.onkeyevent = sinon.spy(); - kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1, timeStamp: Date.now()})); - this.clock.tick(20); - kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'Alt', location: 2, timeStamp: Date.now()})); - expect(kbd.onkeyevent).to.have.been.calledOnce; - expect(kbd.onkeyevent).to.have.been.calledWith(0xfe03, 'AltRight', true); - - // Check that the timer is properly dead - kbd.onkeyevent.reset(); - this.clock.tick(100); - expect(kbd.onkeyevent).to.not.have.been.called; - }); - - it('should generate Ctrl, Alt for slow Ctrl+Alt sequence', function () { - const kbd = new Keyboard(document); - kbd.onkeyevent = sinon.spy(); - kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1, timeStamp: Date.now()})); - this.clock.tick(60); - kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'Alt', location: 2, timeStamp: Date.now()})); - expect(kbd.onkeyevent).to.have.been.calledTwice; - expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0xffe3, "ControlLeft", true); - expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0xffea, "AltRight", true); - - // Check that the timer is properly dead - kbd.onkeyevent.reset(); - this.clock.tick(100); - expect(kbd.onkeyevent).to.not.have.been.called; - }); - - it('should pass through single Alt', function () { - const kbd = new Keyboard(document); - kbd.onkeyevent = sinon.spy(); - kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'Alt', location: 2})); - expect(kbd.onkeyevent).to.have.been.calledOnce; - expect(kbd.onkeyevent).to.have.been.calledWith(0xffea, 'AltRight', true); - }); - - it('should pass through single AltGr', function () { - const kbd = new Keyboard(document); - kbd.onkeyevent = sinon.spy(); - kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'AltGraph', location: 2})); - expect(kbd.onkeyevent).to.have.been.calledOnce; - expect(kbd.onkeyevent).to.have.been.calledWith(0xfe03, 'AltRight', true); - }); - }); -}); diff --git a/systemvm/agent/noVNC/tests/test.localization.js b/systemvm/agent/noVNC/tests/test.localization.js deleted file mode 100644 index 9570c1798a9..00000000000 --- a/systemvm/agent/noVNC/tests/test.localization.js +++ /dev/null @@ -1,72 +0,0 @@ -const expect = chai.expect; -import { l10n } from '../app/localization.js'; - -describe('Localization', function () { - "use strict"; - - describe('language selection', function () { - let origNavigator; - beforeEach(function () { - // window.navigator is a protected read-only property in many - // environments, so we need to redefine it whilst running these - // tests. - origNavigator = Object.getOwnPropertyDescriptor(window, "navigator"); - if (origNavigator === undefined) { - // Object.getOwnPropertyDescriptor() doesn't work - // properly in any version of IE - this.skip(); - } - - Object.defineProperty(window, "navigator", {value: {}}); - if (window.navigator.languages !== undefined) { - // Object.defineProperty() doesn't work properly in old - // versions of Chrome - this.skip(); - } - - window.navigator.languages = []; - }); - afterEach(function () { - Object.defineProperty(window, "navigator", origNavigator); - }); - - it('should use English by default', function () { - expect(l10n.language).to.equal('en'); - }); - it('should use English if no user language matches', function () { - window.navigator.languages = ["nl", "de"]; - l10n.setup(["es", "fr"]); - expect(l10n.language).to.equal('en'); - }); - it('should use the most preferred user language', function () { - window.navigator.languages = ["nl", "de", "fr"]; - l10n.setup(["es", "fr", "de"]); - expect(l10n.language).to.equal('de'); - }); - it('should prefer sub-languages languages', function () { - window.navigator.languages = ["pt-BR"]; - l10n.setup(["pt", "pt-BR"]); - expect(l10n.language).to.equal('pt-BR'); - }); - it('should fall back to language "parents"', function () { - window.navigator.languages = ["pt-BR"]; - l10n.setup(["fr", "pt", "de"]); - expect(l10n.language).to.equal('pt'); - }); - it('should not use specific language when user asks for a generic language', function () { - window.navigator.languages = ["pt", "de"]; - l10n.setup(["fr", "pt-BR", "de"]); - expect(l10n.language).to.equal('de'); - }); - it('should handle underscore as a separator', function () { - window.navigator.languages = ["pt-BR"]; - l10n.setup(["pt_BR"]); - expect(l10n.language).to.equal('pt_BR'); - }); - it('should handle difference in case', function () { - window.navigator.languages = ["pt-br"]; - l10n.setup(["pt-BR"]); - expect(l10n.language).to.equal('pt-BR'); - }); - }); -}); diff --git a/systemvm/agent/noVNC/tests/test.mouse.js b/systemvm/agent/noVNC/tests/test.mouse.js deleted file mode 100644 index 78c74f15724..00000000000 --- a/systemvm/agent/noVNC/tests/test.mouse.js +++ /dev/null @@ -1,304 +0,0 @@ -const expect = chai.expect; - -import Mouse from '../core/input/mouse.js'; - -describe('Mouse Event Handling', function () { - "use strict"; - - let target; - - beforeEach(function () { - // For these tests we can assume that the canvas is 100x100 - // located at coordinates 10x10 - target = document.createElement('canvas'); - target.style.position = "absolute"; - target.style.top = "10px"; - target.style.left = "10px"; - target.style.width = "100px"; - target.style.height = "100px"; - document.body.appendChild(target); - }); - afterEach(function () { - document.body.removeChild(target); - target = null; - }); - - // The real constructors might not work everywhere we - // want to run these tests - const mouseevent = (typeArg, MouseEventInit) => { - const e = { type: typeArg }; - for (let key in MouseEventInit) { - e[key] = MouseEventInit[key]; - } - e.stopPropagation = sinon.spy(); - e.preventDefault = sinon.spy(); - return e; - }; - const touchevent = mouseevent; - - describe('Decode Mouse Events', function () { - it('should decode mousedown events', function (done) { - const mouse = new Mouse(target); - mouse.onmousebutton = (x, y, down, bmask) => { - expect(bmask).to.be.equal(0x01); - expect(down).to.be.equal(1); - done(); - }; - mouse._handleMouseDown(mouseevent('mousedown', { button: '0x01' })); - }); - it('should decode mouseup events', function (done) { - let calls = 0; - const mouse = new Mouse(target); - mouse.onmousebutton = (x, y, down, bmask) => { - expect(bmask).to.be.equal(0x01); - if (calls++ === 1) { - expect(down).to.not.be.equal(1); - done(); - } - }; - mouse._handleMouseDown(mouseevent('mousedown', { button: '0x01' })); - mouse._handleMouseUp(mouseevent('mouseup', { button: '0x01' })); - }); - it('should decode mousemove events', function (done) { - const mouse = new Mouse(target); - mouse.onmousemove = (x, y) => { - // Note that target relative coordinates are sent - expect(x).to.be.equal(40); - expect(y).to.be.equal(10); - done(); - }; - mouse._handleMouseMove(mouseevent('mousemove', - { clientX: 50, clientY: 20 })); - }); - it('should decode mousewheel events', function (done) { - let calls = 0; - const mouse = new Mouse(target); - mouse.onmousebutton = (x, y, down, bmask) => { - calls++; - expect(bmask).to.be.equal(1<<6); - if (calls === 1) { - expect(down).to.be.equal(1); - } else if (calls === 2) { - expect(down).to.not.be.equal(1); - done(); - } - }; - mouse._handleMouseWheel(mouseevent('mousewheel', - { deltaX: 50, deltaY: 0, - deltaMode: 0})); - }); - }); - - describe('Double-click for Touch', function () { - - beforeEach(function () { this.clock = sinon.useFakeTimers(); }); - afterEach(function () { this.clock.restore(); }); - - it('should use same pos for 2nd tap if close enough', function (done) { - let calls = 0; - const mouse = new Mouse(target); - mouse.onmousebutton = (x, y, down, bmask) => { - calls++; - if (calls === 1) { - expect(down).to.be.equal(1); - expect(x).to.be.equal(68); - expect(y).to.be.equal(36); - } else if (calls === 3) { - expect(down).to.be.equal(1); - expect(x).to.be.equal(68); - expect(y).to.be.equal(36); - done(); - } - }; - // touch events are sent in an array of events - // with one item for each touch point - mouse._handleMouseDown(touchevent( - 'touchstart', { touches: [{ clientX: 78, clientY: 46 }]})); - this.clock.tick(10); - mouse._handleMouseUp(touchevent( - 'touchend', { touches: [{ clientX: 79, clientY: 45 }]})); - this.clock.tick(200); - mouse._handleMouseDown(touchevent( - 'touchstart', { touches: [{ clientX: 67, clientY: 35 }]})); - this.clock.tick(10); - mouse._handleMouseUp(touchevent( - 'touchend', { touches: [{ clientX: 66, clientY: 36 }]})); - }); - - it('should not modify 2nd tap pos if far apart', function (done) { - let calls = 0; - const mouse = new Mouse(target); - mouse.onmousebutton = (x, y, down, bmask) => { - calls++; - if (calls === 1) { - expect(down).to.be.equal(1); - expect(x).to.be.equal(68); - expect(y).to.be.equal(36); - } else if (calls === 3) { - expect(down).to.be.equal(1); - expect(x).to.not.be.equal(68); - expect(y).to.not.be.equal(36); - done(); - } - }; - mouse._handleMouseDown(touchevent( - 'touchstart', { touches: [{ clientX: 78, clientY: 46 }]})); - this.clock.tick(10); - mouse._handleMouseUp(touchevent( - 'touchend', { touches: [{ clientX: 79, clientY: 45 }]})); - this.clock.tick(200); - mouse._handleMouseDown(touchevent( - 'touchstart', { touches: [{ clientX: 57, clientY: 35 }]})); - this.clock.tick(10); - mouse._handleMouseUp(touchevent( - 'touchend', { touches: [{ clientX: 56, clientY: 36 }]})); - }); - - it('should not modify 2nd tap pos if not soon enough', function (done) { - let calls = 0; - const mouse = new Mouse(target); - mouse.onmousebutton = (x, y, down, bmask) => { - calls++; - if (calls === 1) { - expect(down).to.be.equal(1); - expect(x).to.be.equal(68); - expect(y).to.be.equal(36); - } else if (calls === 3) { - expect(down).to.be.equal(1); - expect(x).to.not.be.equal(68); - expect(y).to.not.be.equal(36); - done(); - } - }; - mouse._handleMouseDown(touchevent( - 'touchstart', { touches: [{ clientX: 78, clientY: 46 }]})); - this.clock.tick(10); - mouse._handleMouseUp(touchevent( - 'touchend', { touches: [{ clientX: 79, clientY: 45 }]})); - this.clock.tick(500); - mouse._handleMouseDown(touchevent( - 'touchstart', { touches: [{ clientX: 67, clientY: 35 }]})); - this.clock.tick(10); - mouse._handleMouseUp(touchevent( - 'touchend', { touches: [{ clientX: 66, clientY: 36 }]})); - }); - - it('should not modify 2nd tap pos if not touch', function (done) { - let calls = 0; - const mouse = new Mouse(target); - mouse.onmousebutton = (x, y, down, bmask) => { - calls++; - if (calls === 1) { - expect(down).to.be.equal(1); - expect(x).to.be.equal(68); - expect(y).to.be.equal(36); - } else if (calls === 3) { - expect(down).to.be.equal(1); - expect(x).to.not.be.equal(68); - expect(y).to.not.be.equal(36); - done(); - } - }; - mouse._handleMouseDown(mouseevent( - 'mousedown', { button: '0x01', clientX: 78, clientY: 46 })); - this.clock.tick(10); - mouse._handleMouseUp(mouseevent( - 'mouseup', { button: '0x01', clientX: 79, clientY: 45 })); - this.clock.tick(200); - mouse._handleMouseDown(mouseevent( - 'mousedown', { button: '0x01', clientX: 67, clientY: 35 })); - this.clock.tick(10); - mouse._handleMouseUp(mouseevent( - 'mouseup', { button: '0x01', clientX: 66, clientY: 36 })); - }); - - }); - - describe('Accumulate mouse wheel events with small delta', function () { - - beforeEach(function () { this.clock = sinon.useFakeTimers(); }); - afterEach(function () { this.clock.restore(); }); - - it('should accumulate wheel events if small enough', function () { - const mouse = new Mouse(target); - mouse.onmousebutton = sinon.spy(); - - mouse._handleMouseWheel(mouseevent( - 'mousewheel', { clientX: 18, clientY: 40, - deltaX: 4, deltaY: 0, deltaMode: 0 })); - this.clock.tick(10); - mouse._handleMouseWheel(mouseevent( - 'mousewheel', { clientX: 18, clientY: 40, - deltaX: 4, deltaY: 0, deltaMode: 0 })); - - // threshold is 10 - expect(mouse._accumulatedWheelDeltaX).to.be.equal(8); - - this.clock.tick(10); - mouse._handleMouseWheel(mouseevent( - 'mousewheel', { clientX: 18, clientY: 40, - deltaX: 4, deltaY: 0, deltaMode: 0 })); - - expect(mouse.onmousebutton).to.have.callCount(2); // mouse down and up - - this.clock.tick(10); - mouse._handleMouseWheel(mouseevent( - 'mousewheel', { clientX: 18, clientY: 40, - deltaX: 4, deltaY: 9, deltaMode: 0 })); - - expect(mouse._accumulatedWheelDeltaX).to.be.equal(4); - expect(mouse._accumulatedWheelDeltaY).to.be.equal(9); - - expect(mouse.onmousebutton).to.have.callCount(2); // still - }); - - it('should not accumulate large wheel events', function () { - const mouse = new Mouse(target); - mouse.onmousebutton = sinon.spy(); - - mouse._handleMouseWheel(mouseevent( - 'mousewheel', { clientX: 18, clientY: 40, - deltaX: 11, deltaY: 0, deltaMode: 0 })); - this.clock.tick(10); - mouse._handleMouseWheel(mouseevent( - 'mousewheel', { clientX: 18, clientY: 40, - deltaX: 0, deltaY: 70, deltaMode: 0 })); - this.clock.tick(10); - mouse._handleMouseWheel(mouseevent( - 'mousewheel', { clientX: 18, clientY: 40, - deltaX: 400, deltaY: 400, deltaMode: 0 })); - - expect(mouse.onmousebutton).to.have.callCount(8); // mouse down and up - }); - - it('should send even small wheel events after a timeout', function () { - const mouse = new Mouse(target); - mouse.onmousebutton = sinon.spy(); - - mouse._handleMouseWheel(mouseevent( - 'mousewheel', { clientX: 18, clientY: 40, - deltaX: 1, deltaY: 0, deltaMode: 0 })); - this.clock.tick(51); // timeout on 50 ms - - expect(mouse.onmousebutton).to.have.callCount(2); // mouse down and up - }); - - it('should account for non-zero deltaMode', function () { - const mouse = new Mouse(target); - mouse.onmousebutton = sinon.spy(); - - mouse._handleMouseWheel(mouseevent( - 'mousewheel', { clientX: 18, clientY: 40, - deltaX: 0, deltaY: 2, deltaMode: 1 })); - - this.clock.tick(10); - - mouse._handleMouseWheel(mouseevent( - 'mousewheel', { clientX: 18, clientY: 40, - deltaX: 1, deltaY: 0, deltaMode: 2 })); - - expect(mouse.onmousebutton).to.have.callCount(4); // mouse down and up - }); - }); - -}); diff --git a/systemvm/agent/noVNC/tests/test.rfb.js b/systemvm/agent/noVNC/tests/test.rfb.js deleted file mode 100644 index 99c9c90c8ff..00000000000 --- a/systemvm/agent/noVNC/tests/test.rfb.js +++ /dev/null @@ -1,2389 +0,0 @@ -const expect = chai.expect; - -import RFB from '../core/rfb.js'; -import Websock from '../core/websock.js'; -import { encodings } from '../core/encodings.js'; - -import FakeWebSocket from './fake.websocket.js'; - -/* UIEvent constructor polyfill for IE */ -(() => { - if (typeof window.UIEvent === "function") return; - - function UIEvent( event, params ) { - params = params || { bubbles: false, cancelable: false, view: window, detail: undefined }; - const evt = document.createEvent( 'UIEvent' ); - evt.initUIEvent( event, params.bubbles, params.cancelable, params.view, params.detail ); - return evt; - } - - UIEvent.prototype = window.UIEvent.prototype; - - window.UIEvent = UIEvent; -})(); - -function push8(arr, num) { - "use strict"; - arr.push(num & 0xFF); -} - -function push16(arr, num) { - "use strict"; - arr.push((num >> 8) & 0xFF, - num & 0xFF); -} - -function push32(arr, num) { - "use strict"; - arr.push((num >> 24) & 0xFF, - (num >> 16) & 0xFF, - (num >> 8) & 0xFF, - num & 0xFF); -} - -describe('Remote Frame Buffer Protocol Client', function () { - let clock; - let raf; - - before(FakeWebSocket.replace); - after(FakeWebSocket.restore); - - before(function () { - this.clock = clock = sinon.useFakeTimers(); - // sinon doesn't support this yet - raf = window.requestAnimationFrame; - window.requestAnimationFrame = setTimeout; - // Use a single set of buffers instead of reallocating to - // speed up tests - const sock = new Websock(); - const _sQ = new Uint8Array(sock._sQbufferSize); - const rQ = new Uint8Array(sock._rQbufferSize); - - Websock.prototype._old_allocate_buffers = Websock.prototype._allocate_buffers; - Websock.prototype._allocate_buffers = function () { - this._sQ = _sQ; - this._rQ = rQ; - }; - - }); - - after(function () { - Websock.prototype._allocate_buffers = Websock.prototype._old_allocate_buffers; - this.clock.restore(); - window.requestAnimationFrame = raf; - }); - - let container; - let rfbs; - - beforeEach(function () { - // Create a container element for all RFB objects to attach to - container = document.createElement('div'); - container.style.width = "100%"; - container.style.height = "100%"; - document.body.appendChild(container); - - // And track all created RFB objects - rfbs = []; - }); - afterEach(function () { - // Make sure every created RFB object is properly cleaned up - // or they might affect subsequent tests - rfbs.forEach(function (rfb) { - rfb.disconnect(); - expect(rfb._disconnect).to.have.been.called; - }); - rfbs = []; - - document.body.removeChild(container); - container = null; - }); - - function make_rfb(url, options) { - url = url || 'wss://host:8675'; - const rfb = new RFB(container, url, options); - clock.tick(); - rfb._sock._websocket._open(); - rfb._rfb_connection_state = 'connected'; - sinon.spy(rfb, "_disconnect"); - rfbs.push(rfb); - return rfb; - } - - describe('Connecting/Disconnecting', function () { - describe('#RFB', function () { - it('should set the current state to "connecting"', function () { - const client = new RFB(document.createElement('div'), 'wss://host:8675'); - client._rfb_connection_state = ''; - this.clock.tick(); - expect(client._rfb_connection_state).to.equal('connecting'); - }); - - it('should actually connect to the websocket', function () { - const client = new RFB(document.createElement('div'), 'ws://HOST:8675/PATH'); - sinon.spy(client._sock, 'open'); - this.clock.tick(); - expect(client._sock.open).to.have.been.calledOnce; - expect(client._sock.open).to.have.been.calledWith('ws://HOST:8675/PATH'); - }); - }); - - describe('#disconnect', function () { - let client; - beforeEach(function () { - client = make_rfb(); - }); - - it('should go to state "disconnecting" before "disconnected"', function () { - sinon.spy(client, '_updateConnectionState'); - client.disconnect(); - expect(client._updateConnectionState).to.have.been.calledTwice; - expect(client._updateConnectionState.getCall(0).args[0]) - .to.equal('disconnecting'); - expect(client._updateConnectionState.getCall(1).args[0]) - .to.equal('disconnected'); - expect(client._rfb_connection_state).to.equal('disconnected'); - }); - - it('should unregister error event handler', function () { - sinon.spy(client._sock, 'off'); - client.disconnect(); - expect(client._sock.off).to.have.been.calledWith('error'); - }); - - it('should unregister message event handler', function () { - sinon.spy(client._sock, 'off'); - client.disconnect(); - expect(client._sock.off).to.have.been.calledWith('message'); - }); - - it('should unregister open event handler', function () { - sinon.spy(client._sock, 'off'); - client.disconnect(); - expect(client._sock.off).to.have.been.calledWith('open'); - }); - }); - - describe('#sendCredentials', function () { - let client; - beforeEach(function () { - client = make_rfb(); - client._rfb_connection_state = 'connecting'; - }); - - it('should set the rfb credentials properly"', function () { - client.sendCredentials({ password: 'pass' }); - expect(client._rfb_credentials).to.deep.equal({ password: 'pass' }); - }); - - it('should call init_msg "soon"', function () { - client._init_msg = sinon.spy(); - client.sendCredentials({ password: 'pass' }); - this.clock.tick(5); - expect(client._init_msg).to.have.been.calledOnce; - }); - }); - }); - - describe('Public API Basic Behavior', function () { - let client; - beforeEach(function () { - client = make_rfb(); - }); - - describe('#sendCtrlAlDel', function () { - it('should sent ctrl[down]-alt[down]-del[down] then del[up]-alt[up]-ctrl[up]', function () { - const expected = {_sQ: new Uint8Array(48), _sQlen: 0, flush: () => {}}; - RFB.messages.keyEvent(expected, 0xFFE3, 1); - RFB.messages.keyEvent(expected, 0xFFE9, 1); - RFB.messages.keyEvent(expected, 0xFFFF, 1); - RFB.messages.keyEvent(expected, 0xFFFF, 0); - RFB.messages.keyEvent(expected, 0xFFE9, 0); - RFB.messages.keyEvent(expected, 0xFFE3, 0); - - client.sendCtrlAltDel(); - expect(client._sock).to.have.sent(expected._sQ); - }); - - it('should not send the keys if we are not in a normal state', function () { - sinon.spy(client._sock, 'flush'); - client._rfb_connection_state = "connecting"; - client.sendCtrlAltDel(); - expect(client._sock.flush).to.not.have.been.called; - }); - - it('should not send the keys if we are set as view_only', function () { - sinon.spy(client._sock, 'flush'); - client._viewOnly = true; - client.sendCtrlAltDel(); - expect(client._sock.flush).to.not.have.been.called; - }); - }); - - describe('#sendKey', function () { - it('should send a single key with the given code and state (down = true)', function () { - const expected = {_sQ: new Uint8Array(8), _sQlen: 0, flush: () => {}}; - RFB.messages.keyEvent(expected, 123, 1); - client.sendKey(123, 'Key123', true); - expect(client._sock).to.have.sent(expected._sQ); - }); - - it('should send both a down and up event if the state is not specified', function () { - const expected = {_sQ: new Uint8Array(16), _sQlen: 0, flush: () => {}}; - RFB.messages.keyEvent(expected, 123, 1); - RFB.messages.keyEvent(expected, 123, 0); - client.sendKey(123, 'Key123'); - expect(client._sock).to.have.sent(expected._sQ); - }); - - it('should not send the key if we are not in a normal state', function () { - sinon.spy(client._sock, 'flush'); - client._rfb_connection_state = "connecting"; - client.sendKey(123, 'Key123'); - expect(client._sock.flush).to.not.have.been.called; - }); - - it('should not send the key if we are set as view_only', function () { - sinon.spy(client._sock, 'flush'); - client._viewOnly = true; - client.sendKey(123, 'Key123'); - expect(client._sock.flush).to.not.have.been.called; - }); - - it('should send QEMU extended events if supported', function () { - client._qemuExtKeyEventSupported = true; - const expected = {_sQ: new Uint8Array(12), _sQlen: 0, flush: () => {}}; - RFB.messages.QEMUExtendedKeyEvent(expected, 0x20, true, 0x0039); - client.sendKey(0x20, 'Space', true); - expect(client._sock).to.have.sent(expected._sQ); - }); - - it('should not send QEMU extended events if unknown key code', function () { - client._qemuExtKeyEventSupported = true; - const expected = {_sQ: new Uint8Array(8), _sQlen: 0, flush: () => {}}; - RFB.messages.keyEvent(expected, 123, 1); - client.sendKey(123, 'FooBar', true); - expect(client._sock).to.have.sent(expected._sQ); - }); - }); - - describe('#focus', function () { - it('should move focus to canvas object', function () { - client._canvas.focus = sinon.spy(); - client.focus(); - expect(client._canvas.focus).to.have.been.called.once; - }); - }); - - describe('#blur', function () { - it('should remove focus from canvas object', function () { - client._canvas.blur = sinon.spy(); - client.blur(); - expect(client._canvas.blur).to.have.been.called.once; - }); - }); - - describe('#clipboardPasteFrom', function () { - it('should send the given text in a paste event', function () { - const expected = {_sQ: new Uint8Array(11), _sQlen: 0, - _sQbufferSize: 11, flush: () => {}}; - RFB.messages.clientCutText(expected, 'abc'); - client.clipboardPasteFrom('abc'); - expect(client._sock).to.have.sent(expected._sQ); - }); - - it('should flush multiple times for large clipboards', function () { - sinon.spy(client._sock, 'flush'); - let long_text = ""; - for (let i = 0; i < client._sock._sQbufferSize + 100; i++) { - long_text += 'a'; - } - client.clipboardPasteFrom(long_text); - expect(client._sock.flush).to.have.been.calledTwice; - }); - - it('should not send the text if we are not in a normal state', function () { - sinon.spy(client._sock, 'flush'); - client._rfb_connection_state = "connecting"; - client.clipboardPasteFrom('abc'); - expect(client._sock.flush).to.not.have.been.called; - }); - }); - - describe("XVP operations", function () { - beforeEach(function () { - client._rfb_xvp_ver = 1; - }); - - it('should send the shutdown signal on #machineShutdown', function () { - client.machineShutdown(); - expect(client._sock).to.have.sent(new Uint8Array([0xFA, 0x00, 0x01, 0x02])); - }); - - it('should send the reboot signal on #machineReboot', function () { - client.machineReboot(); - expect(client._sock).to.have.sent(new Uint8Array([0xFA, 0x00, 0x01, 0x03])); - }); - - it('should send the reset signal on #machineReset', function () { - client.machineReset(); - expect(client._sock).to.have.sent(new Uint8Array([0xFA, 0x00, 0x01, 0x04])); - }); - - it('should not send XVP operations with higher versions than we support', function () { - sinon.spy(client._sock, 'flush'); - client._xvpOp(2, 7); - expect(client._sock.flush).to.not.have.been.called; - }); - }); - }); - - describe('Clipping', function () { - let client; - beforeEach(function () { - client = make_rfb(); - container.style.width = '70px'; - container.style.height = '80px'; - client.clipViewport = true; - }); - - it('should update display clip state when changing the property', function () { - const spy = sinon.spy(client._display, "clipViewport", ["set"]); - - client.clipViewport = false; - expect(spy.set).to.have.been.calledOnce; - expect(spy.set).to.have.been.calledWith(false); - spy.set.reset(); - - client.clipViewport = true; - expect(spy.set).to.have.been.calledOnce; - expect(spy.set).to.have.been.calledWith(true); - }); - - it('should update the viewport when the container size changes', function () { - sinon.spy(client._display, "viewportChangeSize"); - - container.style.width = '40px'; - container.style.height = '50px'; - const event = new UIEvent('resize'); - window.dispatchEvent(event); - clock.tick(); - - expect(client._display.viewportChangeSize).to.have.been.calledOnce; - expect(client._display.viewportChangeSize).to.have.been.calledWith(40, 50); - }); - - it('should update the viewport when the remote session resizes', function () { - // Simple ExtendedDesktopSize FBU message - const incoming = [ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, - 0x00, 0xff, 0x00, 0xff, 0xff, 0xff, 0xfe, 0xcc, - 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, - 0x00, 0x00, 0x00, 0x00 ]; - - sinon.spy(client._display, "viewportChangeSize"); - - client._sock._websocket._receive_data(new Uint8Array(incoming)); - - // FIXME: Display implicitly calls viewportChangeSize() when - // resizing the framebuffer, hence calledTwice. - expect(client._display.viewportChangeSize).to.have.been.calledTwice; - expect(client._display.viewportChangeSize).to.have.been.calledWith(70, 80); - }); - - it('should not update the viewport if not clipping', function () { - client.clipViewport = false; - sinon.spy(client._display, "viewportChangeSize"); - - container.style.width = '40px'; - container.style.height = '50px'; - const event = new UIEvent('resize'); - window.dispatchEvent(event); - clock.tick(); - - expect(client._display.viewportChangeSize).to.not.have.been.called; - }); - - it('should not update the viewport if scaling', function () { - client.scaleViewport = true; - sinon.spy(client._display, "viewportChangeSize"); - - container.style.width = '40px'; - container.style.height = '50px'; - const event = new UIEvent('resize'); - window.dispatchEvent(event); - clock.tick(); - - expect(client._display.viewportChangeSize).to.not.have.been.called; - }); - - describe('Dragging', function () { - beforeEach(function () { - client.dragViewport = true; - sinon.spy(RFB.messages, "pointerEvent"); - }); - - afterEach(function () { - RFB.messages.pointerEvent.restore(); - }); - - it('should not send button messages when initiating viewport dragging', function () { - client._handleMouseButton(13, 9, 0x001); - expect(RFB.messages.pointerEvent).to.not.have.been.called; - }); - - it('should send button messages when release without movement', function () { - // Just up and down - client._handleMouseButton(13, 9, 0x001); - client._handleMouseButton(13, 9, 0x000); - expect(RFB.messages.pointerEvent).to.have.been.calledTwice; - - RFB.messages.pointerEvent.reset(); - - // Small movement - client._handleMouseButton(13, 9, 0x001); - client._handleMouseMove(15, 14); - client._handleMouseButton(15, 14, 0x000); - expect(RFB.messages.pointerEvent).to.have.been.calledTwice; - }); - - it('should send button message directly when drag is disabled', function () { - client.dragViewport = false; - client._handleMouseButton(13, 9, 0x001); - expect(RFB.messages.pointerEvent).to.have.been.calledOnce; - }); - - it('should be initiate viewport dragging on sufficient movement', function () { - sinon.spy(client._display, "viewportChangePos"); - - // Too small movement - - client._handleMouseButton(13, 9, 0x001); - client._handleMouseMove(18, 9); - - expect(RFB.messages.pointerEvent).to.not.have.been.called; - expect(client._display.viewportChangePos).to.not.have.been.called; - - // Sufficient movement - - client._handleMouseMove(43, 9); - - expect(RFB.messages.pointerEvent).to.not.have.been.called; - expect(client._display.viewportChangePos).to.have.been.calledOnce; - expect(client._display.viewportChangePos).to.have.been.calledWith(-30, 0); - - client._display.viewportChangePos.reset(); - - // Now a small movement should move right away - - client._handleMouseMove(43, 14); - - expect(RFB.messages.pointerEvent).to.not.have.been.called; - expect(client._display.viewportChangePos).to.have.been.calledOnce; - expect(client._display.viewportChangePos).to.have.been.calledWith(0, -5); - }); - - it('should not send button messages when dragging ends', function () { - // First the movement - - client._handleMouseButton(13, 9, 0x001); - client._handleMouseMove(43, 9); - client._handleMouseButton(43, 9, 0x000); - - expect(RFB.messages.pointerEvent).to.not.have.been.called; - }); - - it('should terminate viewport dragging on a button up event', function () { - // First the dragging movement - - client._handleMouseButton(13, 9, 0x001); - client._handleMouseMove(43, 9); - client._handleMouseButton(43, 9, 0x000); - - // Another movement now should not move the viewport - - sinon.spy(client._display, "viewportChangePos"); - - client._handleMouseMove(43, 59); - - expect(client._display.viewportChangePos).to.not.have.been.called; - }); - }); - }); - - describe('Scaling', function () { - let client; - beforeEach(function () { - client = make_rfb(); - container.style.width = '70px'; - container.style.height = '80px'; - client.scaleViewport = true; - }); - - it('should update display scale factor when changing the property', function () { - const spy = sinon.spy(client._display, "scale", ["set"]); - sinon.spy(client._display, "autoscale"); - - client.scaleViewport = false; - expect(spy.set).to.have.been.calledOnce; - expect(spy.set).to.have.been.calledWith(1.0); - expect(client._display.autoscale).to.not.have.been.called; - - client.scaleViewport = true; - expect(client._display.autoscale).to.have.been.calledOnce; - expect(client._display.autoscale).to.have.been.calledWith(70, 80); - }); - - it('should update the clipping setting when changing the property', function () { - client.clipViewport = true; - - const spy = sinon.spy(client._display, "clipViewport", ["set"]); - - client.scaleViewport = false; - expect(spy.set).to.have.been.calledOnce; - expect(spy.set).to.have.been.calledWith(true); - - spy.set.reset(); - - client.scaleViewport = true; - expect(spy.set).to.have.been.calledOnce; - expect(spy.set).to.have.been.calledWith(false); - }); - - it('should update the scaling when the container size changes', function () { - sinon.spy(client._display, "autoscale"); - - container.style.width = '40px'; - container.style.height = '50px'; - const event = new UIEvent('resize'); - window.dispatchEvent(event); - clock.tick(); - - expect(client._display.autoscale).to.have.been.calledOnce; - expect(client._display.autoscale).to.have.been.calledWith(40, 50); - }); - - it('should update the scaling when the remote session resizes', function () { - // Simple ExtendedDesktopSize FBU message - const incoming = [ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, - 0x00, 0xff, 0x00, 0xff, 0xff, 0xff, 0xfe, 0xcc, - 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, - 0x00, 0x00, 0x00, 0x00 ]; - - sinon.spy(client._display, "autoscale"); - - client._sock._websocket._receive_data(new Uint8Array(incoming)); - - expect(client._display.autoscale).to.have.been.calledOnce; - expect(client._display.autoscale).to.have.been.calledWith(70, 80); - }); - - it('should not update the display scale factor if not scaling', function () { - client.scaleViewport = false; - - sinon.spy(client._display, "autoscale"); - - container.style.width = '40px'; - container.style.height = '50px'; - const event = new UIEvent('resize'); - window.dispatchEvent(event); - clock.tick(); - - expect(client._display.autoscale).to.not.have.been.called; - }); - }); - - describe('Remote resize', function () { - let client; - beforeEach(function () { - client = make_rfb(); - client._supportsSetDesktopSize = true; - client.resizeSession = true; - container.style.width = '70px'; - container.style.height = '80px'; - sinon.spy(RFB.messages, "setDesktopSize"); - }); - - afterEach(function () { - RFB.messages.setDesktopSize.restore(); - }); - - it('should only request a resize when turned on', function () { - client.resizeSession = false; - expect(RFB.messages.setDesktopSize).to.not.have.been.called; - client.resizeSession = true; - expect(RFB.messages.setDesktopSize).to.have.been.calledOnce; - }); - - it('should request a resize when initially connecting', function () { - // Simple ExtendedDesktopSize FBU message - const incoming = [ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x04, 0x00, 0x04, 0xff, 0xff, 0xfe, 0xcc, - 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04, - 0x00, 0x00, 0x00, 0x00 ]; - - // First message should trigger a resize - - client._supportsSetDesktopSize = false; - - client._sock._websocket._receive_data(new Uint8Array(incoming)); - - expect(RFB.messages.setDesktopSize).to.have.been.calledOnce; - expect(RFB.messages.setDesktopSize).to.have.been.calledWith(sinon.match.object, 70, 80, 0, 0); - - RFB.messages.setDesktopSize.reset(); - - // Second message should not trigger a resize - - client._sock._websocket._receive_data(new Uint8Array(incoming)); - - expect(RFB.messages.setDesktopSize).to.not.have.been.called; - }); - - it('should request a resize when the container resizes', function () { - container.style.width = '40px'; - container.style.height = '50px'; - const event = new UIEvent('resize'); - window.dispatchEvent(event); - clock.tick(1000); - - expect(RFB.messages.setDesktopSize).to.have.been.calledOnce; - expect(RFB.messages.setDesktopSize).to.have.been.calledWith(sinon.match.object, 40, 50, 0, 0); - }); - - it('should not resize until the container size is stable', function () { - container.style.width = '20px'; - container.style.height = '30px'; - const event1 = new UIEvent('resize'); - window.dispatchEvent(event1); - clock.tick(400); - - expect(RFB.messages.setDesktopSize).to.not.have.been.called; - - container.style.width = '40px'; - container.style.height = '50px'; - const event2 = new UIEvent('resize'); - window.dispatchEvent(event2); - clock.tick(400); - - expect(RFB.messages.setDesktopSize).to.not.have.been.called; - - clock.tick(200); - - expect(RFB.messages.setDesktopSize).to.have.been.calledOnce; - expect(RFB.messages.setDesktopSize).to.have.been.calledWith(sinon.match.object, 40, 50, 0, 0); - }); - - it('should not resize when resize is disabled', function () { - client._resizeSession = false; - - container.style.width = '40px'; - container.style.height = '50px'; - const event = new UIEvent('resize'); - window.dispatchEvent(event); - clock.tick(1000); - - expect(RFB.messages.setDesktopSize).to.not.have.been.called; - }); - - it('should not resize when resize is not supported', function () { - client._supportsSetDesktopSize = false; - - container.style.width = '40px'; - container.style.height = '50px'; - const event = new UIEvent('resize'); - window.dispatchEvent(event); - clock.tick(1000); - - expect(RFB.messages.setDesktopSize).to.not.have.been.called; - }); - - it('should not resize when in view only mode', function () { - client._viewOnly = true; - - container.style.width = '40px'; - container.style.height = '50px'; - const event = new UIEvent('resize'); - window.dispatchEvent(event); - clock.tick(1000); - - expect(RFB.messages.setDesktopSize).to.not.have.been.called; - }); - - it('should not try to override a server resize', function () { - // Simple ExtendedDesktopSize FBU message - const incoming = [ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x04, 0x00, 0x04, 0xff, 0xff, 0xfe, 0xcc, - 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04, - 0x00, 0x00, 0x00, 0x00 ]; - - client._sock._websocket._receive_data(new Uint8Array(incoming)); - - expect(RFB.messages.setDesktopSize).to.not.have.been.called; - }); - }); - - describe('Misc Internals', function () { - describe('#_updateConnectionState', function () { - let client; - beforeEach(function () { - client = make_rfb(); - }); - - it('should clear the disconnect timer if the state is not "disconnecting"', function () { - const spy = sinon.spy(); - client._disconnTimer = setTimeout(spy, 50); - client._rfb_connection_state = 'connecting'; - client._updateConnectionState('connected'); - this.clock.tick(51); - expect(spy).to.not.have.been.called; - expect(client._disconnTimer).to.be.null; - }); - - it('should set the rfb_connection_state', function () { - client._rfb_connection_state = 'connecting'; - client._updateConnectionState('connected'); - expect(client._rfb_connection_state).to.equal('connected'); - }); - - it('should not change the state when we are disconnected', function () { - client.disconnect(); - expect(client._rfb_connection_state).to.equal('disconnected'); - client._updateConnectionState('connecting'); - expect(client._rfb_connection_state).to.not.equal('connecting'); - }); - - it('should ignore state changes to the same state', function () { - const connectSpy = sinon.spy(); - client.addEventListener("connect", connectSpy); - - expect(client._rfb_connection_state).to.equal('connected'); - client._updateConnectionState('connected'); - expect(connectSpy).to.not.have.been.called; - - client.disconnect(); - - const disconnectSpy = sinon.spy(); - client.addEventListener("disconnect", disconnectSpy); - - expect(client._rfb_connection_state).to.equal('disconnected'); - client._updateConnectionState('disconnected'); - expect(disconnectSpy).to.not.have.been.called; - }); - - it('should ignore illegal state changes', function () { - const spy = sinon.spy(); - client.addEventListener("disconnect", spy); - client._updateConnectionState('disconnected'); - expect(client._rfb_connection_state).to.not.equal('disconnected'); - expect(spy).to.not.have.been.called; - }); - }); - - describe('#_fail', function () { - let client; - beforeEach(function () { - client = make_rfb(); - }); - - it('should close the WebSocket connection', function () { - sinon.spy(client._sock, 'close'); - client._fail(); - expect(client._sock.close).to.have.been.calledOnce; - }); - - it('should transition to disconnected', function () { - sinon.spy(client, '_updateConnectionState'); - client._fail(); - this.clock.tick(2000); - expect(client._updateConnectionState).to.have.been.called; - expect(client._rfb_connection_state).to.equal('disconnected'); - }); - - it('should set clean_disconnect variable', function () { - client._rfb_clean_disconnect = true; - client._rfb_connection_state = 'connected'; - client._fail(); - expect(client._rfb_clean_disconnect).to.be.false; - }); - - it('should result in disconnect event with clean set to false', function () { - client._rfb_connection_state = 'connected'; - const spy = sinon.spy(); - client.addEventListener("disconnect", spy); - client._fail(); - this.clock.tick(2000); - expect(spy).to.have.been.calledOnce; - expect(spy.args[0][0].detail.clean).to.be.false; - }); - - }); - }); - - describe('Connection States', function () { - describe('connecting', function () { - it('should open the websocket connection', function () { - const client = new RFB(document.createElement('div'), - 'ws://HOST:8675/PATH'); - sinon.spy(client._sock, 'open'); - this.clock.tick(); - expect(client._sock.open).to.have.been.calledOnce; - }); - }); - - describe('connected', function () { - let client; - beforeEach(function () { - client = make_rfb(); - }); - - it('should result in a connect event if state becomes connected', function () { - const spy = sinon.spy(); - client.addEventListener("connect", spy); - client._rfb_connection_state = 'connecting'; - client._updateConnectionState('connected'); - expect(spy).to.have.been.calledOnce; - }); - - it('should not result in a connect event if the state is not "connected"', function () { - const spy = sinon.spy(); - client.addEventListener("connect", spy); - client._sock._websocket.open = () => {}; // explicitly don't call onopen - client._updateConnectionState('connecting'); - expect(spy).to.not.have.been.called; - }); - }); - - describe('disconnecting', function () { - let client; - beforeEach(function () { - client = make_rfb(); - }); - - it('should force disconnect if we do not call Websock.onclose within the disconnection timeout', function () { - sinon.spy(client, '_updateConnectionState'); - client._sock._websocket.close = () => {}; // explicitly don't call onclose - client._updateConnectionState('disconnecting'); - this.clock.tick(3 * 1000); - expect(client._updateConnectionState).to.have.been.calledTwice; - expect(client._rfb_disconnect_reason).to.not.equal(""); - expect(client._rfb_connection_state).to.equal("disconnected"); - }); - - it('should not fail if Websock.onclose gets called within the disconnection timeout', function () { - client._updateConnectionState('disconnecting'); - this.clock.tick(3 * 1000 / 2); - client._sock._websocket.close(); - this.clock.tick(3 * 1000 / 2 + 1); - expect(client._rfb_connection_state).to.equal('disconnected'); - }); - - it('should close the WebSocket connection', function () { - sinon.spy(client._sock, 'close'); - client._updateConnectionState('disconnecting'); - expect(client._sock.close).to.have.been.calledOnce; - }); - - it('should not result in a disconnect event', function () { - const spy = sinon.spy(); - client.addEventListener("disconnect", spy); - client._sock._websocket.close = () => {}; // explicitly don't call onclose - client._updateConnectionState('disconnecting'); - expect(spy).to.not.have.been.called; - }); - }); - - describe('disconnected', function () { - let client; - beforeEach(function () { - client = new RFB(document.createElement('div'), 'ws://HOST:8675/PATH'); - }); - - it('should result in a disconnect event if state becomes "disconnected"', function () { - const spy = sinon.spy(); - client.addEventListener("disconnect", spy); - client._rfb_connection_state = 'disconnecting'; - client._updateConnectionState('disconnected'); - expect(spy).to.have.been.calledOnce; - expect(spy.args[0][0].detail.clean).to.be.true; - }); - - it('should result in a disconnect event without msg when no reason given', function () { - const spy = sinon.spy(); - client.addEventListener("disconnect", spy); - client._rfb_connection_state = 'disconnecting'; - client._rfb_disconnect_reason = ""; - client._updateConnectionState('disconnected'); - expect(spy).to.have.been.calledOnce; - expect(spy.args[0].length).to.equal(1); - }); - }); - }); - - describe('Protocol Initialization States', function () { - let client; - beforeEach(function () { - client = make_rfb(); - client._rfb_connection_state = 'connecting'; - }); - - describe('ProtocolVersion', function () { - function send_ver(ver, client) { - const arr = new Uint8Array(12); - for (let i = 0; i < ver.length; i++) { - arr[i+4] = ver.charCodeAt(i); - } - arr[0] = 'R'; arr[1] = 'F'; arr[2] = 'B'; arr[3] = ' '; - arr[11] = '\n'; - client._sock._websocket._receive_data(arr); - } - - describe('version parsing', function () { - it('should interpret version 003.003 as version 3.3', function () { - send_ver('003.003', client); - expect(client._rfb_version).to.equal(3.3); - }); - - it('should interpret version 003.006 as version 3.3', function () { - send_ver('003.006', client); - expect(client._rfb_version).to.equal(3.3); - }); - - it('should interpret version 003.889 as version 3.3', function () { - send_ver('003.889', client); - expect(client._rfb_version).to.equal(3.3); - }); - - it('should interpret version 003.007 as version 3.7', function () { - send_ver('003.007', client); - expect(client._rfb_version).to.equal(3.7); - }); - - it('should interpret version 003.008 as version 3.8', function () { - send_ver('003.008', client); - expect(client._rfb_version).to.equal(3.8); - }); - - it('should interpret version 004.000 as version 3.8', function () { - send_ver('004.000', client); - expect(client._rfb_version).to.equal(3.8); - }); - - it('should interpret version 004.001 as version 3.8', function () { - send_ver('004.001', client); - expect(client._rfb_version).to.equal(3.8); - }); - - it('should interpret version 005.000 as version 3.8', function () { - send_ver('005.000', client); - expect(client._rfb_version).to.equal(3.8); - }); - - it('should fail on an invalid version', function () { - sinon.spy(client, "_fail"); - send_ver('002.000', client); - expect(client._fail).to.have.been.calledOnce; - }); - }); - - it('should send back the interpreted version', function () { - send_ver('004.000', client); - - const expected_str = 'RFB 003.008\n'; - const expected = []; - for (let i = 0; i < expected_str.length; i++) { - expected[i] = expected_str.charCodeAt(i); - } - - expect(client._sock).to.have.sent(new Uint8Array(expected)); - }); - - it('should transition to the Security state on successful negotiation', function () { - send_ver('003.008', client); - expect(client._rfb_init_state).to.equal('Security'); - }); - - describe('Repeater', function () { - beforeEach(function () { - client = make_rfb('wss://host:8675', { repeaterID: "12345" }); - client._rfb_connection_state = 'connecting'; - }); - - it('should interpret version 000.000 as a repeater', function () { - send_ver('000.000', client); - expect(client._rfb_version).to.equal(0); - - const sent_data = client._sock._websocket._get_sent_data(); - expect(new Uint8Array(sent_data.buffer, 0, 9)).to.array.equal(new Uint8Array([73, 68, 58, 49, 50, 51, 52, 53, 0])); - expect(sent_data).to.have.length(250); - }); - - it('should handle two step repeater negotiation', function () { - send_ver('000.000', client); - send_ver('003.008', client); - expect(client._rfb_version).to.equal(3.8); - }); - }); - }); - - describe('Security', function () { - beforeEach(function () { - client._rfb_init_state = 'Security'; - }); - - it('should simply receive the auth scheme when for versions < 3.7', function () { - client._rfb_version = 3.6; - const auth_scheme_raw = [1, 2, 3, 4]; - const auth_scheme = (auth_scheme_raw[0] << 24) + (auth_scheme_raw[1] << 16) + - (auth_scheme_raw[2] << 8) + auth_scheme_raw[3]; - client._sock._websocket._receive_data(new Uint8Array(auth_scheme_raw)); - expect(client._rfb_auth_scheme).to.equal(auth_scheme); - }); - - it('should prefer no authentication is possible', function () { - client._rfb_version = 3.7; - const auth_schemes = [2, 1, 3]; - client._sock._websocket._receive_data(new Uint8Array(auth_schemes)); - expect(client._rfb_auth_scheme).to.equal(1); - expect(client._sock).to.have.sent(new Uint8Array([1, 1])); - }); - - it('should choose for the most prefered scheme possible for versions >= 3.7', function () { - client._rfb_version = 3.7; - const auth_schemes = [2, 22, 16]; - client._sock._websocket._receive_data(new Uint8Array(auth_schemes)); - expect(client._rfb_auth_scheme).to.equal(22); - expect(client._sock).to.have.sent(new Uint8Array([22])); - }); - - it('should fail if there are no supported schemes for versions >= 3.7', function () { - sinon.spy(client, "_fail"); - client._rfb_version = 3.7; - const auth_schemes = [1, 32]; - client._sock._websocket._receive_data(new Uint8Array(auth_schemes)); - expect(client._fail).to.have.been.calledOnce; - }); - - it('should fail with the appropriate message if no types are sent for versions >= 3.7', function () { - client._rfb_version = 3.7; - const failure_data = [0, 0, 0, 0, 6, 119, 104, 111, 111, 112, 115]; - sinon.spy(client, '_fail'); - client._sock._websocket._receive_data(new Uint8Array(failure_data)); - - expect(client._fail).to.have.been.calledOnce; - expect(client._fail).to.have.been.calledWith( - 'Security negotiation failed on no security types (reason: whoops)'); - }); - - it('should transition to the Authentication state and continue on successful negotiation', function () { - client._rfb_version = 3.7; - const auth_schemes = [1, 1]; - client._negotiate_authentication = sinon.spy(); - client._sock._websocket._receive_data(new Uint8Array(auth_schemes)); - expect(client._rfb_init_state).to.equal('Authentication'); - expect(client._negotiate_authentication).to.have.been.calledOnce; - }); - }); - - describe('Authentication', function () { - beforeEach(function () { - client._rfb_init_state = 'Security'; - }); - - function send_security(type, cl) { - cl._sock._websocket._receive_data(new Uint8Array([1, type])); - } - - it('should fail on auth scheme 0 (pre 3.7) with the given message', function () { - client._rfb_version = 3.6; - const err_msg = "Whoopsies"; - const data = [0, 0, 0, 0]; - const err_len = err_msg.length; - push32(data, err_len); - for (let i = 0; i < err_len; i++) { - data.push(err_msg.charCodeAt(i)); - } - - sinon.spy(client, '_fail'); - client._sock._websocket._receive_data(new Uint8Array(data)); - expect(client._fail).to.have.been.calledWith( - 'Security negotiation failed on authentication scheme (reason: Whoopsies)'); - }); - - it('should transition straight to SecurityResult on "no auth" (1) for versions >= 3.8', function () { - client._rfb_version = 3.8; - send_security(1, client); - expect(client._rfb_init_state).to.equal('SecurityResult'); - }); - - it('should transition straight to ServerInitialisation on "no auth" for versions < 3.8', function () { - client._rfb_version = 3.7; - send_security(1, client); - expect(client._rfb_init_state).to.equal('ServerInitialisation'); - }); - - it('should fail on an unknown auth scheme', function () { - sinon.spy(client, "_fail"); - client._rfb_version = 3.8; - send_security(57, client); - expect(client._fail).to.have.been.calledOnce; - }); - - describe('VNC Authentication (type 2) Handler', function () { - beforeEach(function () { - client._rfb_init_state = 'Security'; - client._rfb_version = 3.8; - }); - - it('should fire the credentialsrequired event if missing a password', function () { - const spy = sinon.spy(); - client.addEventListener("credentialsrequired", spy); - send_security(2, client); - - const challenge = []; - for (let i = 0; i < 16; i++) { challenge[i] = i; } - client._sock._websocket._receive_data(new Uint8Array(challenge)); - - expect(client._rfb_credentials).to.be.empty; - expect(spy).to.have.been.calledOnce; - expect(spy.args[0][0].detail.types).to.have.members(["password"]); - }); - - it('should encrypt the password with DES and then send it back', function () { - client._rfb_credentials = { password: 'passwd' }; - send_security(2, client); - client._sock._websocket._get_sent_data(); // skip the choice of auth reply - - const challenge = []; - for (let i = 0; i < 16; i++) { challenge[i] = i; } - client._sock._websocket._receive_data(new Uint8Array(challenge)); - - const des_pass = RFB.genDES('passwd', challenge); - expect(client._sock).to.have.sent(new Uint8Array(des_pass)); - }); - - it('should transition to SecurityResult immediately after sending the password', function () { - client._rfb_credentials = { password: 'passwd' }; - send_security(2, client); - - const challenge = []; - for (let i = 0; i < 16; i++) { challenge[i] = i; } - client._sock._websocket._receive_data(new Uint8Array(challenge)); - - expect(client._rfb_init_state).to.equal('SecurityResult'); - }); - }); - - describe('XVP Authentication (type 22) Handler', function () { - beforeEach(function () { - client._rfb_init_state = 'Security'; - client._rfb_version = 3.8; - }); - - it('should fall through to standard VNC authentication upon completion', function () { - client._rfb_credentials = { username: 'user', - target: 'target', - password: 'password' }; - client._negotiate_std_vnc_auth = sinon.spy(); - send_security(22, client); - expect(client._negotiate_std_vnc_auth).to.have.been.calledOnce; - }); - - it('should fire the credentialsrequired event if all credentials are missing', function () { - const spy = sinon.spy(); - client.addEventListener("credentialsrequired", spy); - client._rfb_credentials = {}; - send_security(22, client); - - expect(client._rfb_credentials).to.be.empty; - expect(spy).to.have.been.calledOnce; - expect(spy.args[0][0].detail.types).to.have.members(["username", "password", "target"]); - }); - - it('should fire the credentialsrequired event if some credentials are missing', function () { - const spy = sinon.spy(); - client.addEventListener("credentialsrequired", spy); - client._rfb_credentials = { username: 'user', - target: 'target' }; - send_security(22, client); - - expect(spy).to.have.been.calledOnce; - expect(spy.args[0][0].detail.types).to.have.members(["username", "password", "target"]); - }); - - it('should send user and target separately', function () { - client._rfb_credentials = { username: 'user', - target: 'target', - password: 'password' }; - client._negotiate_std_vnc_auth = sinon.spy(); - - send_security(22, client); - - const expected = [22, 4, 6]; // auth selection, len user, len target - for (let i = 0; i < 10; i++) { expected[i+3] = 'usertarget'.charCodeAt(i); } - - expect(client._sock).to.have.sent(new Uint8Array(expected)); - }); - }); - - describe('TightVNC Authentication (type 16) Handler', function () { - beforeEach(function () { - client._rfb_init_state = 'Security'; - client._rfb_version = 3.8; - send_security(16, client); - client._sock._websocket._get_sent_data(); // skip the security reply - }); - - function send_num_str_pairs(pairs, client) { - const data = []; - push32(data, pairs.length); - - for (let i = 0; i < pairs.length; i++) { - push32(data, pairs[i][0]); - for (let j = 0; j < 4; j++) { - data.push(pairs[i][1].charCodeAt(j)); - } - for (let j = 0; j < 8; j++) { - data.push(pairs[i][2].charCodeAt(j)); - } - } - - client._sock._websocket._receive_data(new Uint8Array(data)); - } - - it('should skip tunnel negotiation if no tunnels are requested', function () { - client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 0])); - expect(client._rfb_tightvnc).to.be.true; - }); - - it('should fail if no supported tunnels are listed', function () { - sinon.spy(client, "_fail"); - send_num_str_pairs([[123, 'OTHR', 'SOMETHNG']], client); - expect(client._fail).to.have.been.calledOnce; - }); - - it('should choose the notunnel tunnel type', function () { - send_num_str_pairs([[0, 'TGHT', 'NOTUNNEL'], [123, 'OTHR', 'SOMETHNG']], client); - expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 0])); - }); - - it('should choose the notunnel tunnel type for Siemens devices', function () { - send_num_str_pairs([[1, 'SICR', 'SCHANNEL'], [2, 'SICR', 'SCHANLPW']], client); - expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 0])); - }); - - it('should continue to sub-auth negotiation after tunnel negotiation', function () { - send_num_str_pairs([[0, 'TGHT', 'NOTUNNEL']], client); - client._sock._websocket._get_sent_data(); // skip the tunnel choice here - send_num_str_pairs([[1, 'STDV', 'NOAUTH__']], client); - expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 1])); - expect(client._rfb_init_state).to.equal('SecurityResult'); - }); - - /*it('should attempt to use VNC auth over no auth when possible', function () { - client._rfb_tightvnc = true; - client._negotiate_std_vnc_auth = sinon.spy(); - send_num_str_pairs([[1, 'STDV', 'NOAUTH__'], [2, 'STDV', 'VNCAUTH_']], client); - expect(client._sock).to.have.sent([0, 0, 0, 1]); - expect(client._negotiate_std_vnc_auth).to.have.been.calledOnce; - expect(client._rfb_auth_scheme).to.equal(2); - });*/ // while this would make sense, the original code doesn't actually do this - - it('should accept the "no auth" auth type and transition to SecurityResult', function () { - client._rfb_tightvnc = true; - send_num_str_pairs([[1, 'STDV', 'NOAUTH__']], client); - expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 1])); - expect(client._rfb_init_state).to.equal('SecurityResult'); - }); - - it('should accept VNC authentication and transition to that', function () { - client._rfb_tightvnc = true; - client._negotiate_std_vnc_auth = sinon.spy(); - send_num_str_pairs([[2, 'STDV', 'VNCAUTH__']], client); - expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 2])); - expect(client._negotiate_std_vnc_auth).to.have.been.calledOnce; - expect(client._rfb_auth_scheme).to.equal(2); - }); - - it('should fail if there are no supported auth types', function () { - sinon.spy(client, "_fail"); - client._rfb_tightvnc = true; - send_num_str_pairs([[23, 'stdv', 'badval__']], client); - expect(client._fail).to.have.been.calledOnce; - }); - }); - }); - - describe('SecurityResult', function () { - beforeEach(function () { - client._rfb_init_state = 'SecurityResult'; - }); - - it('should fall through to ServerInitialisation on a response code of 0', function () { - client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 0])); - expect(client._rfb_init_state).to.equal('ServerInitialisation'); - }); - - it('should fail on an error code of 1 with the given message for versions >= 3.8', function () { - client._rfb_version = 3.8; - sinon.spy(client, '_fail'); - const failure_data = [0, 0, 0, 1, 0, 0, 0, 6, 119, 104, 111, 111, 112, 115]; - client._sock._websocket._receive_data(new Uint8Array(failure_data)); - expect(client._fail).to.have.been.calledWith( - 'Security negotiation failed on security result (reason: whoops)'); - }); - - it('should fail on an error code of 1 with a standard message for version < 3.8', function () { - sinon.spy(client, '_fail'); - client._rfb_version = 3.7; - client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 1])); - expect(client._fail).to.have.been.calledWith( - 'Security handshake failed'); - }); - - it('should result in securityfailure event when receiving a non zero status', function () { - const spy = sinon.spy(); - client.addEventListener("securityfailure", spy); - client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 2])); - expect(spy).to.have.been.calledOnce; - expect(spy.args[0][0].detail.status).to.equal(2); - }); - - it('should include reason when provided in securityfailure event', function () { - client._rfb_version = 3.8; - const spy = sinon.spy(); - client.addEventListener("securityfailure", spy); - const failure_data = [0, 0, 0, 1, 0, 0, 0, 12, 115, 117, 99, 104, - 32, 102, 97, 105, 108, 117, 114, 101]; - client._sock._websocket._receive_data(new Uint8Array(failure_data)); - expect(spy.args[0][0].detail.status).to.equal(1); - expect(spy.args[0][0].detail.reason).to.equal('such failure'); - }); - - it('should not include reason when length is zero in securityfailure event', function () { - client._rfb_version = 3.9; - const spy = sinon.spy(); - client.addEventListener("securityfailure", spy); - const failure_data = [0, 0, 0, 1, 0, 0, 0, 0]; - client._sock._websocket._receive_data(new Uint8Array(failure_data)); - expect(spy.args[0][0].detail.status).to.equal(1); - expect('reason' in spy.args[0][0].detail).to.be.false; - }); - - it('should not include reason in securityfailure event for version < 3.8', function () { - client._rfb_version = 3.6; - const spy = sinon.spy(); - client.addEventListener("securityfailure", spy); - client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 2])); - expect(spy.args[0][0].detail.status).to.equal(2); - expect('reason' in spy.args[0][0].detail).to.be.false; - }); - }); - - describe('ClientInitialisation', function () { - it('should transition to the ServerInitialisation state', function () { - const client = make_rfb(); - client._rfb_connection_state = 'connecting'; - client._rfb_init_state = 'SecurityResult'; - client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 0])); - expect(client._rfb_init_state).to.equal('ServerInitialisation'); - }); - - it('should send 1 if we are in shared mode', function () { - const client = make_rfb('wss://host:8675', { shared: true }); - client._rfb_connection_state = 'connecting'; - client._rfb_init_state = 'SecurityResult'; - client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 0])); - expect(client._sock).to.have.sent(new Uint8Array([1])); - }); - - it('should send 0 if we are not in shared mode', function () { - const client = make_rfb('wss://host:8675', { shared: false }); - client._rfb_connection_state = 'connecting'; - client._rfb_init_state = 'SecurityResult'; - client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 0])); - expect(client._sock).to.have.sent(new Uint8Array([0])); - }); - }); - - describe('ServerInitialisation', function () { - beforeEach(function () { - client._rfb_init_state = 'ServerInitialisation'; - }); - - function send_server_init(opts, client) { - const full_opts = { width: 10, height: 12, bpp: 24, depth: 24, big_endian: 0, - true_color: 1, red_max: 255, green_max: 255, blue_max: 255, - red_shift: 16, green_shift: 8, blue_shift: 0, name: 'a name' }; - for (let opt in opts) { - full_opts[opt] = opts[opt]; - } - const data = []; - - push16(data, full_opts.width); - push16(data, full_opts.height); - - data.push(full_opts.bpp); - data.push(full_opts.depth); - data.push(full_opts.big_endian); - data.push(full_opts.true_color); - - push16(data, full_opts.red_max); - push16(data, full_opts.green_max); - push16(data, full_opts.blue_max); - push8(data, full_opts.red_shift); - push8(data, full_opts.green_shift); - push8(data, full_opts.blue_shift); - - // padding - push8(data, 0); - push8(data, 0); - push8(data, 0); - - client._sock._websocket._receive_data(new Uint8Array(data)); - - const name_data = []; - push32(name_data, full_opts.name.length); - for (let i = 0; i < full_opts.name.length; i++) { - name_data.push(full_opts.name.charCodeAt(i)); - } - client._sock._websocket._receive_data(new Uint8Array(name_data)); - } - - it('should set the framebuffer width and height', function () { - send_server_init({ width: 32, height: 84 }, client); - expect(client._fb_width).to.equal(32); - expect(client._fb_height).to.equal(84); - }); - - // NB(sross): we just warn, not fail, for endian-ness and shifts, so we don't test them - - it('should set the framebuffer name and call the callback', function () { - const spy = sinon.spy(); - client.addEventListener("desktopname", spy); - send_server_init({ name: 'some name' }, client); - - expect(client._fb_name).to.equal('some name'); - expect(spy).to.have.been.calledOnce; - expect(spy.args[0][0].detail.name).to.equal('some name'); - }); - - it('should handle the extended init message of the tight encoding', function () { - // NB(sross): we don't actually do anything with it, so just test that we can - // read it w/o throwing an error - client._rfb_tightvnc = true; - send_server_init({}, client); - - const tight_data = []; - push16(tight_data, 1); - push16(tight_data, 2); - push16(tight_data, 3); - push16(tight_data, 0); - for (let i = 0; i < 16 + 32 + 48; i++) { - tight_data.push(i); - } - client._sock._websocket._receive_data(new Uint8Array(tight_data)); - - expect(client._rfb_connection_state).to.equal('connected'); - }); - - it('should resize the display', function () { - sinon.spy(client._display, 'resize'); - send_server_init({ width: 27, height: 32 }, client); - - expect(client._display.resize).to.have.been.calledOnce; - expect(client._display.resize).to.have.been.calledWith(27, 32); - }); - - it('should grab the mouse and keyboard', function () { - sinon.spy(client._keyboard, 'grab'); - sinon.spy(client._mouse, 'grab'); - send_server_init({}, client); - expect(client._keyboard.grab).to.have.been.calledOnce; - expect(client._mouse.grab).to.have.been.calledOnce; - }); - - describe('Initial Update Request', function () { - beforeEach(function () { - sinon.spy(RFB.messages, "pixelFormat"); - sinon.spy(RFB.messages, "clientEncodings"); - sinon.spy(RFB.messages, "fbUpdateRequest"); - }); - - afterEach(function () { - RFB.messages.pixelFormat.restore(); - RFB.messages.clientEncodings.restore(); - RFB.messages.fbUpdateRequest.restore(); - }); - - // TODO(directxman12): test the various options in this configuration matrix - it('should reply with the pixel format, client encodings, and initial update request', function () { - send_server_init({ width: 27, height: 32 }, client); - - expect(RFB.messages.pixelFormat).to.have.been.calledOnce; - expect(RFB.messages.pixelFormat).to.have.been.calledWith(client._sock, 24, true); - expect(RFB.messages.pixelFormat).to.have.been.calledBefore(RFB.messages.clientEncodings); - expect(RFB.messages.clientEncodings).to.have.been.calledOnce; - expect(RFB.messages.clientEncodings.getCall(0).args[1]).to.include(encodings.encodingTight); - expect(RFB.messages.clientEncodings).to.have.been.calledBefore(RFB.messages.fbUpdateRequest); - expect(RFB.messages.fbUpdateRequest).to.have.been.calledOnce; - expect(RFB.messages.fbUpdateRequest).to.have.been.calledWith(client._sock, false, 0, 0, 27, 32); - }); - - it('should reply with restricted settings for Intel AMT servers', function () { - send_server_init({ width: 27, height: 32, name: "Intel(r) AMT KVM"}, client); - - expect(RFB.messages.pixelFormat).to.have.been.calledOnce; - expect(RFB.messages.pixelFormat).to.have.been.calledWith(client._sock, 8, true); - expect(RFB.messages.pixelFormat).to.have.been.calledBefore(RFB.messages.clientEncodings); - expect(RFB.messages.clientEncodings).to.have.been.calledOnce; - expect(RFB.messages.clientEncodings.getCall(0).args[1]).to.not.include(encodings.encodingTight); - expect(RFB.messages.clientEncodings.getCall(0).args[1]).to.not.include(encodings.encodingHextile); - expect(RFB.messages.clientEncodings).to.have.been.calledBefore(RFB.messages.fbUpdateRequest); - expect(RFB.messages.fbUpdateRequest).to.have.been.calledOnce; - expect(RFB.messages.fbUpdateRequest).to.have.been.calledWith(client._sock, false, 0, 0, 27, 32); - }); - }); - - it('should transition to the "connected" state', function () { - send_server_init({}, client); - expect(client._rfb_connection_state).to.equal('connected'); - }); - }); - }); - - describe('Protocol Message Processing After Completing Initialization', function () { - let client; - - beforeEach(function () { - client = make_rfb(); - client._fb_name = 'some device'; - client._fb_width = 640; - client._fb_height = 20; - }); - - describe('Framebuffer Update Handling', function () { - const target_data_arr = [ - 0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, - 0x00, 0xff, 0x00, 255, 0xff, 0x00, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, - 0xee, 0x00, 0xff, 255, 0x00, 0xee, 0xff, 255, 0xaa, 0xee, 0xff, 255, 0xab, 0xee, 0xff, 255, - 0xee, 0x00, 0xff, 255, 0x00, 0xee, 0xff, 255, 0xaa, 0xee, 0xff, 255, 0xab, 0xee, 0xff, 255 - ]; - let target_data; - - const target_data_check_arr = [ - 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, - 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, - 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, - 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255 - ]; - let target_data_check; - - before(function () { - // NB(directxman12): PhantomJS 1.x doesn't implement Uint8ClampedArray - target_data = new Uint8Array(target_data_arr); - target_data_check = new Uint8Array(target_data_check_arr); - }); - - function send_fbu_msg(rect_info, rect_data, client, rect_cnt) { - let data = []; - - if (!rect_cnt || rect_cnt > -1) { - // header - data.push(0); // msg type - data.push(0); // padding - push16(data, rect_cnt || rect_data.length); - } - - for (let i = 0; i < rect_data.length; i++) { - if (rect_info[i]) { - push16(data, rect_info[i].x); - push16(data, rect_info[i].y); - push16(data, rect_info[i].width); - push16(data, rect_info[i].height); - push32(data, rect_info[i].encoding); - } - data = data.concat(rect_data[i]); - } - - client._sock._websocket._receive_data(new Uint8Array(data)); - } - - it('should send an update request if there is sufficient data', function () { - const expected_msg = {_sQ: new Uint8Array(10), _sQlen: 0, flush: () => {}}; - RFB.messages.fbUpdateRequest(expected_msg, true, 0, 0, 640, 20); - - client._framebufferUpdate = () => true; - client._sock._websocket._receive_data(new Uint8Array([0])); - - expect(client._sock).to.have.sent(expected_msg._sQ); - }); - - it('should not send an update request if we need more data', function () { - client._sock._websocket._receive_data(new Uint8Array([0])); - expect(client._sock._websocket._get_sent_data()).to.have.length(0); - }); - - it('should resume receiving an update if we previously did not have enough data', function () { - const expected_msg = {_sQ: new Uint8Array(10), _sQlen: 0, flush: () => {}}; - RFB.messages.fbUpdateRequest(expected_msg, true, 0, 0, 640, 20); - - // just enough to set FBU.rects - client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 3])); - expect(client._sock._websocket._get_sent_data()).to.have.length(0); - - client._framebufferUpdate = function () { this._sock.rQskipBytes(1); return true; }; // we magically have enough data - // 247 should *not* be used as the message type here - client._sock._websocket._receive_data(new Uint8Array([247])); - expect(client._sock).to.have.sent(expected_msg._sQ); - }); - - it('should not send a request in continuous updates mode', function () { - client._enabledContinuousUpdates = true; - client._framebufferUpdate = () => true; - client._sock._websocket._receive_data(new Uint8Array([0])); - - expect(client._sock._websocket._get_sent_data()).to.have.length(0); - }); - - it('should fail on an unsupported encoding', function () { - sinon.spy(client, "_fail"); - const rect_info = { x: 8, y: 11, width: 27, height: 32, encoding: 234 }; - send_fbu_msg([rect_info], [[]], client); - expect(client._fail).to.have.been.calledOnce; - }); - - it('should be able to pause and resume receiving rects if not enought data', function () { - // seed some initial data to copy - client._fb_width = 4; - client._fb_height = 4; - client._display.resize(4, 4); - client._display.blitRgbxImage(0, 0, 4, 2, new Uint8Array(target_data_check_arr.slice(0, 32)), 0); - - const info = [{ x: 0, y: 2, width: 2, height: 2, encoding: 0x01}, - { x: 2, y: 2, width: 2, height: 2, encoding: 0x01}]; - // data says [{ old_x: 2, old_y: 0 }, { old_x: 0, old_y: 0 }] - const rects = [[0, 2, 0, 0], [0, 0, 0, 0]]; - send_fbu_msg([info[0]], [rects[0]], client, 2); - send_fbu_msg([info[1]], [rects[1]], client, -1); - expect(client._display).to.have.displayed(target_data_check); - }); - - describe('Message Encoding Handlers', function () { - beforeEach(function () { - // a really small frame - client._fb_width = 4; - client._fb_height = 4; - client._fb_depth = 24; - client._display.resize(4, 4); - }); - - it('should handle the RAW encoding', function () { - const info = [{ x: 0, y: 0, width: 2, height: 2, encoding: 0x00 }, - { x: 2, y: 0, width: 2, height: 2, encoding: 0x00 }, - { x: 0, y: 2, width: 4, height: 1, encoding: 0x00 }, - { x: 0, y: 3, width: 4, height: 1, encoding: 0x00 }]; - // data is in bgrx - const rects = [ - [0x00, 0x00, 0xff, 0, 0x00, 0xff, 0x00, 0, 0x00, 0xff, 0x00, 0, 0x00, 0x00, 0xff, 0], - [0xff, 0x00, 0x00, 0, 0xff, 0x00, 0x00, 0, 0xff, 0x00, 0x00, 0, 0xff, 0x00, 0x00, 0], - [0xff, 0x00, 0xee, 0, 0xff, 0xee, 0x00, 0, 0xff, 0xee, 0xaa, 0, 0xff, 0xee, 0xab, 0], - [0xff, 0x00, 0xee, 0, 0xff, 0xee, 0x00, 0, 0xff, 0xee, 0xaa, 0, 0xff, 0xee, 0xab, 0]]; - send_fbu_msg(info, rects, client); - expect(client._display).to.have.displayed(target_data); - }); - - it('should handle the RAW encoding in low colour mode', function () { - const info = [{ x: 0, y: 0, width: 2, height: 2, encoding: 0x00 }, - { x: 2, y: 0, width: 2, height: 2, encoding: 0x00 }, - { x: 0, y: 2, width: 4, height: 1, encoding: 0x00 }, - { x: 0, y: 3, width: 4, height: 1, encoding: 0x00 }]; - const rects = [ - [0x03, 0x03, 0x03, 0x03], - [0x0c, 0x0c, 0x0c, 0x0c], - [0x0c, 0x0c, 0x03, 0x03], - [0x0c, 0x0c, 0x03, 0x03]]; - client._fb_depth = 8; - send_fbu_msg(info, rects, client); - expect(client._display).to.have.displayed(target_data_check); - }); - - it('should handle the COPYRECT encoding', function () { - // seed some initial data to copy - client._display.blitRgbxImage(0, 0, 4, 2, new Uint8Array(target_data_check_arr.slice(0, 32)), 0); - - const info = [{ x: 0, y: 2, width: 2, height: 2, encoding: 0x01}, - { x: 2, y: 2, width: 2, height: 2, encoding: 0x01}]; - // data says [{ old_x: 0, old_y: 0 }, { old_x: 0, old_y: 0 }] - const rects = [[0, 2, 0, 0], [0, 0, 0, 0]]; - send_fbu_msg(info, rects, client); - expect(client._display).to.have.displayed(target_data_check); - }); - - // TODO(directxman12): for encodings with subrects, test resuming on partial send? - // TODO(directxman12): test rre_chunk_sz (related to above about subrects)? - - it('should handle the RRE encoding', function () { - const info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x02 }]; - const rect = []; - push32(rect, 2); // 2 subrects - push32(rect, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color - rect.push(0xff); // becomes ff0000ff --> #0000FF color - rect.push(0x00); - rect.push(0x00); - rect.push(0xff); - push16(rect, 0); // x: 0 - push16(rect, 0); // y: 0 - push16(rect, 2); // width: 2 - push16(rect, 2); // height: 2 - rect.push(0xff); // becomes ff0000ff --> #0000FF color - rect.push(0x00); - rect.push(0x00); - rect.push(0xff); - push16(rect, 2); // x: 2 - push16(rect, 2); // y: 2 - push16(rect, 2); // width: 2 - push16(rect, 2); // height: 2 - - send_fbu_msg(info, [rect], client); - expect(client._display).to.have.displayed(target_data_check); - }); - - describe('the HEXTILE encoding handler', function () { - it('should handle a tile with fg, bg specified, normal subrects', function () { - const info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x05 }]; - const rect = []; - rect.push(0x02 | 0x04 | 0x08); // bg spec, fg spec, anysubrects - push32(rect, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color - rect.push(0xff); // becomes ff0000ff --> #0000FF fg color - rect.push(0x00); - rect.push(0x00); - rect.push(0xff); - rect.push(2); // 2 subrects - rect.push(0); // x: 0, y: 0 - rect.push(1 | (1 << 4)); // width: 2, height: 2 - rect.push(2 | (2 << 4)); // x: 2, y: 2 - rect.push(1 | (1 << 4)); // width: 2, height: 2 - send_fbu_msg(info, [rect], client); - expect(client._display).to.have.displayed(target_data_check); - }); - - it('should handle a raw tile', function () { - const info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x05 }]; - const rect = []; - rect.push(0x01); // raw - for (let i = 0; i < target_data.length; i += 4) { - rect.push(target_data[i + 2]); - rect.push(target_data[i + 1]); - rect.push(target_data[i]); - rect.push(target_data[i + 3]); - } - send_fbu_msg(info, [rect], client); - expect(client._display).to.have.displayed(target_data); - }); - - it('should handle a tile with only bg specified (solid bg)', function () { - const info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x05 }]; - const rect = []; - rect.push(0x02); - push32(rect, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color - send_fbu_msg(info, [rect], client); - - const expected = []; - for (let i = 0; i < 16; i++) { push32(expected, 0xff00ff); } - expect(client._display).to.have.displayed(new Uint8Array(expected)); - }); - - it('should handle a tile with only bg specified and an empty frame afterwards', function () { - // set the width so we can have two tiles - client._fb_width = 8; - client._display.resize(8, 4); - - const info = [{ x: 0, y: 0, width: 32, height: 4, encoding: 0x05 }]; - - const rect = []; - - // send a bg frame - rect.push(0x02); - push32(rect, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color - - // send an empty frame - rect.push(0x00); - - send_fbu_msg(info, [rect], client); - - const expected = []; - for (let i = 0; i < 16; i++) { push32(expected, 0xff00ff); } // rect 1: solid - for (let i = 0; i < 16; i++) { push32(expected, 0xff00ff); } // rect 2: same bkground color - expect(client._display).to.have.displayed(new Uint8Array(expected)); - }); - - it('should handle a tile with bg and coloured subrects', function () { - const info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x05 }]; - const rect = []; - rect.push(0x02 | 0x08 | 0x10); // bg spec, anysubrects, colouredsubrects - push32(rect, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color - rect.push(2); // 2 subrects - rect.push(0xff); // becomes ff0000ff --> #0000FF fg color - rect.push(0x00); - rect.push(0x00); - rect.push(0xff); - rect.push(0); // x: 0, y: 0 - rect.push(1 | (1 << 4)); // width: 2, height: 2 - rect.push(0xff); // becomes ff0000ff --> #0000FF fg color - rect.push(0x00); - rect.push(0x00); - rect.push(0xff); - rect.push(2 | (2 << 4)); // x: 2, y: 2 - rect.push(1 | (1 << 4)); // width: 2, height: 2 - send_fbu_msg(info, [rect], client); - expect(client._display).to.have.displayed(target_data_check); - }); - - it('should carry over fg and bg colors from the previous tile if not specified', function () { - client._fb_width = 4; - client._fb_height = 17; - client._display.resize(4, 17); - - const info = [{ x: 0, y: 0, width: 4, height: 17, encoding: 0x05}]; - const rect = []; - rect.push(0x02 | 0x04 | 0x08); // bg spec, fg spec, anysubrects - push32(rect, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color - rect.push(0xff); // becomes ff0000ff --> #0000FF fg color - rect.push(0x00); - rect.push(0x00); - rect.push(0xff); - rect.push(8); // 8 subrects - for (let i = 0; i < 4; i++) { - rect.push((0 << 4) | (i * 4)); // x: 0, y: i*4 - rect.push(1 | (1 << 4)); // width: 2, height: 2 - rect.push((2 << 4) | (i * 4 + 2)); // x: 2, y: i * 4 + 2 - rect.push(1 | (1 << 4)); // width: 2, height: 2 - } - rect.push(0x08); // anysubrects - rect.push(1); // 1 subrect - rect.push(0); // x: 0, y: 0 - rect.push(1 | (1 << 4)); // width: 2, height: 2 - send_fbu_msg(info, [rect], client); - - let expected = []; - for (let i = 0; i < 4; i++) { expected = expected.concat(target_data_check_arr); } - expected = expected.concat(target_data_check_arr.slice(0, 16)); - expect(client._display).to.have.displayed(new Uint8Array(expected)); - }); - - it('should fail on an invalid subencoding', function () { - sinon.spy(client, "_fail"); - const info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x05 }]; - const rects = [[45]]; // an invalid subencoding - send_fbu_msg(info, rects, client); - expect(client._fail).to.have.been.calledOnce; - }); - }); - - it.skip('should handle the TIGHT encoding', function () { - // TODO(directxman12): test this - }); - - it.skip('should handle the TIGHT_PNG encoding', function () { - // TODO(directxman12): test this - }); - - it('should handle the DesktopSize pseduo-encoding', function () { - sinon.spy(client._display, 'resize'); - send_fbu_msg([{ x: 0, y: 0, width: 20, height: 50, encoding: -223 }], [[]], client); - - expect(client._fb_width).to.equal(20); - expect(client._fb_height).to.equal(50); - - expect(client._display.resize).to.have.been.calledOnce; - expect(client._display.resize).to.have.been.calledWith(20, 50); - }); - - describe('the ExtendedDesktopSize pseudo-encoding handler', function () { - beforeEach(function () { - // a really small frame - client._fb_width = 4; - client._fb_height = 4; - client._display.resize(4, 4); - sinon.spy(client._display, 'resize'); - }); - - function make_screen_data(nr_of_screens) { - const data = []; - push8(data, nr_of_screens); // number-of-screens - push8(data, 0); // padding - push16(data, 0); // padding - for (let i=0; i {}}; - const incoming_msg = {_sQ: new Uint8Array(16), _sQlen: 0, flush: () => {}}; - - const payload = "foo\x00ab9"; - - // ClientFence and ServerFence are identical in structure - RFB.messages.clientFence(expected_msg, (1<<0) | (1<<1), payload); - RFB.messages.clientFence(incoming_msg, 0xffffffff, payload); - - client._sock._websocket._receive_data(incoming_msg._sQ); - - expect(client._sock).to.have.sent(expected_msg._sQ); - - expected_msg._sQlen = 0; - incoming_msg._sQlen = 0; - - RFB.messages.clientFence(expected_msg, (1<<0), payload); - RFB.messages.clientFence(incoming_msg, (1<<0) | (1<<31), payload); - - client._sock._websocket._receive_data(incoming_msg._sQ); - - expect(client._sock).to.have.sent(expected_msg._sQ); - }); - - it('should enable continuous updates on first EndOfContinousUpdates', function () { - const expected_msg = {_sQ: new Uint8Array(10), _sQlen: 0, flush: () => {}}; - - RFB.messages.enableContinuousUpdates(expected_msg, true, 0, 0, 640, 20); - - expect(client._enabledContinuousUpdates).to.be.false; - - client._sock._websocket._receive_data(new Uint8Array([150])); - - expect(client._enabledContinuousUpdates).to.be.true; - expect(client._sock).to.have.sent(expected_msg._sQ); - }); - - it('should disable continuous updates on subsequent EndOfContinousUpdates', function () { - client._enabledContinuousUpdates = true; - client._supportsContinuousUpdates = true; - - client._sock._websocket._receive_data(new Uint8Array([150])); - - expect(client._enabledContinuousUpdates).to.be.false; - }); - - it('should update continuous updates on resize', function () { - const expected_msg = {_sQ: new Uint8Array(10), _sQlen: 0, flush: () => {}}; - RFB.messages.enableContinuousUpdates(expected_msg, true, 0, 0, 90, 700); - - client._resize(450, 160); - - expect(client._sock._websocket._get_sent_data()).to.have.length(0); - - client._enabledContinuousUpdates = true; - - client._resize(90, 700); - - expect(client._sock).to.have.sent(expected_msg._sQ); - }); - - it('should fail on an unknown message type', function () { - sinon.spy(client, "_fail"); - client._sock._websocket._receive_data(new Uint8Array([87])); - expect(client._fail).to.have.been.calledOnce; - }); - }); - - describe('Asynchronous Events', function () { - let client; - beforeEach(function () { - client = make_rfb(); - }); - - describe('Mouse event handlers', function () { - it('should not send button messages in view-only mode', function () { - client._viewOnly = true; - sinon.spy(client._sock, 'flush'); - client._handleMouseButton(0, 0, 1, 0x001); - expect(client._sock.flush).to.not.have.been.called; - }); - - it('should not send movement messages in view-only mode', function () { - client._viewOnly = true; - sinon.spy(client._sock, 'flush'); - client._handleMouseMove(0, 0); - expect(client._sock.flush).to.not.have.been.called; - }); - - it('should send a pointer event on mouse button presses', function () { - client._handleMouseButton(10, 12, 1, 0x001); - const pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0, flush: () => {}}; - RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x001); - expect(client._sock).to.have.sent(pointer_msg._sQ); - }); - - it('should send a mask of 1 on mousedown', function () { - client._handleMouseButton(10, 12, 1, 0x001); - const pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0, flush: () => {}}; - RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x001); - expect(client._sock).to.have.sent(pointer_msg._sQ); - }); - - it('should send a mask of 0 on mouseup', function () { - client._mouse_buttonMask = 0x001; - client._handleMouseButton(10, 12, 0, 0x001); - const pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0, flush: () => {}}; - RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x000); - expect(client._sock).to.have.sent(pointer_msg._sQ); - }); - - it('should send a pointer event on mouse movement', function () { - client._handleMouseMove(10, 12); - const pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0, flush: () => {}}; - RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x000); - expect(client._sock).to.have.sent(pointer_msg._sQ); - }); - - it('should set the button mask so that future mouse movements use it', function () { - client._handleMouseButton(10, 12, 1, 0x010); - client._handleMouseMove(13, 9); - const pointer_msg = {_sQ: new Uint8Array(12), _sQlen: 0, flush: () => {}}; - RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x010); - RFB.messages.pointerEvent(pointer_msg, 13, 9, 0x010); - expect(client._sock).to.have.sent(pointer_msg._sQ); - }); - }); - - describe('Keyboard Event Handlers', function () { - it('should send a key message on a key press', function () { - client._handleKeyEvent(0x41, 'KeyA', true); - const key_msg = {_sQ: new Uint8Array(8), _sQlen: 0, flush: () => {}}; - RFB.messages.keyEvent(key_msg, 0x41, 1); - expect(client._sock).to.have.sent(key_msg._sQ); - }); - - it('should not send messages in view-only mode', function () { - client._viewOnly = true; - sinon.spy(client._sock, 'flush'); - client._handleKeyEvent('a', 'KeyA', true); - expect(client._sock.flush).to.not.have.been.called; - }); - }); - - describe('WebSocket event handlers', function () { - // message events - it('should do nothing if we receive an empty message and have nothing in the queue', function () { - client._normal_msg = sinon.spy(); - client._sock._websocket._receive_data(new Uint8Array([])); - expect(client._normal_msg).to.not.have.been.called; - }); - - it('should handle a message in the connected state as a normal message', function () { - client._normal_msg = sinon.spy(); - client._sock._websocket._receive_data(new Uint8Array([1, 2, 3])); - expect(client._normal_msg).to.have.been.called; - }); - - it('should handle a message in any non-disconnected/failed state like an init message', function () { - client._rfb_connection_state = 'connecting'; - client._rfb_init_state = 'ProtocolVersion'; - client._init_msg = sinon.spy(); - client._sock._websocket._receive_data(new Uint8Array([1, 2, 3])); - expect(client._init_msg).to.have.been.called; - }); - - it('should process all normal messages directly', function () { - const spy = sinon.spy(); - client.addEventListener("bell", spy); - client._sock._websocket._receive_data(new Uint8Array([0x02, 0x02])); - expect(spy).to.have.been.calledTwice; - }); - - // open events - it('should update the state to ProtocolVersion on open (if the state is "connecting")', function () { - client = new RFB(document.createElement('div'), 'wss://host:8675'); - this.clock.tick(); - client._sock._websocket._open(); - expect(client._rfb_init_state).to.equal('ProtocolVersion'); - }); - - it('should fail if we are not currently ready to connect and we get an "open" event', function () { - sinon.spy(client, "_fail"); - client._rfb_connection_state = 'connected'; - client._sock._websocket._open(); - expect(client._fail).to.have.been.calledOnce; - }); - - // close events - it('should transition to "disconnected" from "disconnecting" on a close event', function () { - const real = client._sock._websocket.close; - client._sock._websocket.close = () => {}; - client.disconnect(); - expect(client._rfb_connection_state).to.equal('disconnecting'); - client._sock._websocket.close = real; - client._sock._websocket.close(); - expect(client._rfb_connection_state).to.equal('disconnected'); - }); - - it('should fail if we get a close event while connecting', function () { - sinon.spy(client, "_fail"); - client._rfb_connection_state = 'connecting'; - client._sock._websocket.close(); - expect(client._fail).to.have.been.calledOnce; - }); - - it('should unregister close event handler', function () { - sinon.spy(client._sock, 'off'); - client.disconnect(); - client._sock._websocket.close(); - expect(client._sock.off).to.have.been.calledWith('close'); - }); - - // error events do nothing - }); - }); -}); diff --git a/systemvm/agent/noVNC/tests/test.util.js b/systemvm/agent/noVNC/tests/test.util.js deleted file mode 100644 index 201acc8bb0d..00000000000 --- a/systemvm/agent/noVNC/tests/test.util.js +++ /dev/null @@ -1,69 +0,0 @@ -/* eslint-disable no-console */ -const expect = chai.expect; - -import * as Log from '../core/util/logging.js'; - -describe('Utils', function () { - "use strict"; - - describe('logging functions', function () { - beforeEach(function () { - sinon.spy(console, 'log'); - sinon.spy(console, 'debug'); - sinon.spy(console, 'warn'); - sinon.spy(console, 'error'); - sinon.spy(console, 'info'); - }); - - afterEach(function () { - console.log.restore(); - console.debug.restore(); - console.warn.restore(); - console.error.restore(); - console.info.restore(); - Log.init_logging(); - }); - - it('should use noop for levels lower than the min level', function () { - Log.init_logging('warn'); - Log.Debug('hi'); - Log.Info('hello'); - expect(console.log).to.not.have.been.called; - }); - - it('should use console.debug for Debug', function () { - Log.init_logging('debug'); - Log.Debug('dbg'); - expect(console.debug).to.have.been.calledWith('dbg'); - }); - - it('should use console.info for Info', function () { - Log.init_logging('debug'); - Log.Info('inf'); - expect(console.info).to.have.been.calledWith('inf'); - }); - - it('should use console.warn for Warn', function () { - Log.init_logging('warn'); - Log.Warn('wrn'); - expect(console.warn).to.have.been.called; - expect(console.warn).to.have.been.calledWith('wrn'); - }); - - it('should use console.error for Error', function () { - Log.init_logging('error'); - Log.Error('err'); - expect(console.error).to.have.been.called; - expect(console.error).to.have.been.calledWith('err'); - }); - }); - - // TODO(directxman12): test the conf_default and conf_defaults methods - // TODO(directxman12): test decodeUTF8 - // TODO(directxman12): test the event methods (addEvent, removeEvent, stopEvent) - // TODO(directxman12): figure out a good way to test getPosition and getEventPosition - // TODO(directxman12): figure out how to test the browser detection functions properly - // (we can't really test them against the browsers, except for Gecko - // via PhantomJS, the default test driver) -}); -/* eslint-enable no-console */ diff --git a/systemvm/agent/noVNC/tests/test.websock.js b/systemvm/agent/noVNC/tests/test.websock.js deleted file mode 100644 index 30e19e9de71..00000000000 --- a/systemvm/agent/noVNC/tests/test.websock.js +++ /dev/null @@ -1,441 +0,0 @@ -const expect = chai.expect; - -import Websock from '../core/websock.js'; -import FakeWebSocket from './fake.websocket.js'; - -describe('Websock', function () { - "use strict"; - - describe('Queue methods', function () { - let sock; - const RQ_TEMPLATE = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7]); - - beforeEach(function () { - sock = new Websock(); - // skip init - sock._allocate_buffers(); - sock._rQ.set(RQ_TEMPLATE); - sock._rQlen = RQ_TEMPLATE.length; - }); - describe('rQlen', function () { - it('should return the length of the receive queue', function () { - sock.rQi = 0; - - expect(sock.rQlen).to.equal(RQ_TEMPLATE.length); - }); - - it("should return the proper length if we read some from the receive queue", function () { - sock.rQi = 1; - - expect(sock.rQlen).to.equal(RQ_TEMPLATE.length - 1); - }); - }); - - describe('rQpeek8', function () { - it('should peek at the next byte without poping it off the queue', function () { - const bef_len = sock.rQlen; - const peek = sock.rQpeek8(); - expect(sock.rQpeek8()).to.equal(peek); - expect(sock.rQlen).to.equal(bef_len); - }); - }); - - describe('rQshift8()', function () { - it('should pop a single byte from the receive queue', function () { - const peek = sock.rQpeek8(); - const bef_len = sock.rQlen; - expect(sock.rQshift8()).to.equal(peek); - expect(sock.rQlen).to.equal(bef_len - 1); - }); - }); - - describe('rQshift16()', function () { - it('should pop two bytes from the receive queue and return a single number', function () { - const bef_len = sock.rQlen; - const expected = (RQ_TEMPLATE[0] << 8) + RQ_TEMPLATE[1]; - expect(sock.rQshift16()).to.equal(expected); - expect(sock.rQlen).to.equal(bef_len - 2); - }); - }); - - describe('rQshift32()', function () { - it('should pop four bytes from the receive queue and return a single number', function () { - const bef_len = sock.rQlen; - const expected = (RQ_TEMPLATE[0] << 24) + - (RQ_TEMPLATE[1] << 16) + - (RQ_TEMPLATE[2] << 8) + - RQ_TEMPLATE[3]; - expect(sock.rQshift32()).to.equal(expected); - expect(sock.rQlen).to.equal(bef_len - 4); - }); - }); - - describe('rQshiftStr', function () { - it('should shift the given number of bytes off of the receive queue and return a string', function () { - const bef_len = sock.rQlen; - const bef_rQi = sock.rQi; - const shifted = sock.rQshiftStr(3); - expect(shifted).to.be.a('string'); - expect(shifted).to.equal(String.fromCharCode.apply(null, Array.prototype.slice.call(new Uint8Array(RQ_TEMPLATE.buffer, bef_rQi, 3)))); - expect(sock.rQlen).to.equal(bef_len - 3); - }); - - it('should shift the entire rest of the queue off if no length is given', function () { - sock.rQshiftStr(); - expect(sock.rQlen).to.equal(0); - }); - - it('should be able to handle very large strings', function () { - const BIG_LEN = 500000; - const RQ_BIG = new Uint8Array(BIG_LEN); - let expected = ""; - let letterCode = 'a'.charCodeAt(0); - for (let i = 0; i < BIG_LEN; i++) { - RQ_BIG[i] = letterCode; - expected += String.fromCharCode(letterCode); - - if (letterCode < 'z'.charCodeAt(0)) { - letterCode++; - } else { - letterCode = 'a'.charCodeAt(0); - } - } - sock._rQ.set(RQ_BIG); - sock._rQlen = RQ_BIG.length; - - const shifted = sock.rQshiftStr(); - - expect(shifted).to.be.equal(expected); - expect(sock.rQlen).to.equal(0); - }); - }); - - describe('rQshiftBytes', function () { - it('should shift the given number of bytes of the receive queue and return an array', function () { - const bef_len = sock.rQlen; - const bef_rQi = sock.rQi; - const shifted = sock.rQshiftBytes(3); - expect(shifted).to.be.an.instanceof(Uint8Array); - expect(shifted).to.array.equal(new Uint8Array(RQ_TEMPLATE.buffer, bef_rQi, 3)); - expect(sock.rQlen).to.equal(bef_len - 3); - }); - - it('should shift the entire rest of the queue off if no length is given', function () { - sock.rQshiftBytes(); - expect(sock.rQlen).to.equal(0); - }); - }); - - describe('rQslice', function () { - beforeEach(function () { - sock.rQi = 0; - }); - - it('should not modify the receive queue', function () { - const bef_len = sock.rQlen; - sock.rQslice(0, 2); - expect(sock.rQlen).to.equal(bef_len); - }); - - it('should return an array containing the given slice of the receive queue', function () { - const sl = sock.rQslice(0, 2); - expect(sl).to.be.an.instanceof(Uint8Array); - expect(sl).to.array.equal(new Uint8Array(RQ_TEMPLATE.buffer, 0, 2)); - }); - - it('should use the rest of the receive queue if no end is given', function () { - const sl = sock.rQslice(1); - expect(sl).to.have.length(RQ_TEMPLATE.length - 1); - expect(sl).to.array.equal(new Uint8Array(RQ_TEMPLATE.buffer, 1)); - }); - - it('should take the current rQi in to account', function () { - sock.rQi = 1; - expect(sock.rQslice(0, 2)).to.array.equal(new Uint8Array(RQ_TEMPLATE.buffer, 1, 2)); - }); - }); - - describe('rQwait', function () { - beforeEach(function () { - sock.rQi = 0; - }); - - it('should return true if there are not enough bytes in the receive queue', function () { - expect(sock.rQwait('hi', RQ_TEMPLATE.length + 1)).to.be.true; - }); - - it('should return false if there are enough bytes in the receive queue', function () { - expect(sock.rQwait('hi', RQ_TEMPLATE.length)).to.be.false; - }); - - it('should return true and reduce rQi by "goback" if there are not enough bytes', function () { - sock.rQi = 5; - expect(sock.rQwait('hi', RQ_TEMPLATE.length, 4)).to.be.true; - expect(sock.rQi).to.equal(1); - }); - - it('should raise an error if we try to go back more than possible', function () { - sock.rQi = 5; - expect(() => sock.rQwait('hi', RQ_TEMPLATE.length, 6)).to.throw(Error); - }); - - it('should not reduce rQi if there are enough bytes', function () { - sock.rQi = 5; - sock.rQwait('hi', 1, 6); - expect(sock.rQi).to.equal(5); - }); - }); - - describe('flush', function () { - beforeEach(function () { - sock._websocket = { - send: sinon.spy() - }; - }); - - it('should actually send on the websocket', function () { - sock._websocket.bufferedAmount = 8; - sock._websocket.readyState = WebSocket.OPEN; - sock._sQ = new Uint8Array([1, 2, 3]); - sock._sQlen = 3; - const encoded = sock._encode_message(); - - sock.flush(); - expect(sock._websocket.send).to.have.been.calledOnce; - expect(sock._websocket.send).to.have.been.calledWith(encoded); - }); - - it('should not call send if we do not have anything queued up', function () { - sock._sQlen = 0; - sock._websocket.bufferedAmount = 8; - - sock.flush(); - - expect(sock._websocket.send).not.to.have.been.called; - }); - }); - - describe('send', function () { - beforeEach(function () { - sock.flush = sinon.spy(); - }); - - it('should add to the send queue', function () { - sock.send([1, 2, 3]); - const sq = sock.sQ; - expect(new Uint8Array(sq.buffer, sock._sQlen - 3, 3)).to.array.equal(new Uint8Array([1, 2, 3])); - }); - - it('should call flush', function () { - sock.send([1, 2, 3]); - expect(sock.flush).to.have.been.calledOnce; - }); - }); - - describe('send_string', function () { - beforeEach(function () { - sock.send = sinon.spy(); - }); - - it('should call send after converting the string to an array', function () { - sock.send_string("\x01\x02\x03"); - expect(sock.send).to.have.been.calledWith([1, 2, 3]); - }); - }); - }); - - describe('lifecycle methods', function () { - let old_WS; - before(function () { - old_WS = WebSocket; - }); - - let sock; - beforeEach(function () { - sock = new Websock(); - // eslint-disable-next-line no-global-assign - WebSocket = sinon.spy(); - WebSocket.OPEN = old_WS.OPEN; - WebSocket.CONNECTING = old_WS.CONNECTING; - WebSocket.CLOSING = old_WS.CLOSING; - WebSocket.CLOSED = old_WS.CLOSED; - - WebSocket.prototype.binaryType = 'arraybuffer'; - }); - - describe('opening', function () { - it('should pick the correct protocols if none are given', function () { - - }); - - it('should open the actual websocket', function () { - sock.open('ws://localhost:8675', 'binary'); - expect(WebSocket).to.have.been.calledWith('ws://localhost:8675', 'binary'); - }); - - // it('should initialize the event handlers')? - }); - - describe('closing', function () { - beforeEach(function () { - sock.open('ws://'); - sock._websocket.close = sinon.spy(); - }); - - it('should close the actual websocket if it is open', function () { - sock._websocket.readyState = WebSocket.OPEN; - sock.close(); - expect(sock._websocket.close).to.have.been.calledOnce; - }); - - it('should close the actual websocket if it is connecting', function () { - sock._websocket.readyState = WebSocket.CONNECTING; - sock.close(); - expect(sock._websocket.close).to.have.been.calledOnce; - }); - - it('should not try to close the actual websocket if closing', function () { - sock._websocket.readyState = WebSocket.CLOSING; - sock.close(); - expect(sock._websocket.close).not.to.have.been.called; - }); - - it('should not try to close the actual websocket if closed', function () { - sock._websocket.readyState = WebSocket.CLOSED; - sock.close(); - expect(sock._websocket.close).not.to.have.been.called; - }); - - it('should reset onmessage to not call _recv_message', function () { - sinon.spy(sock, '_recv_message'); - sock.close(); - sock._websocket.onmessage(null); - try { - expect(sock._recv_message).not.to.have.been.called; - } finally { - sock._recv_message.restore(); - } - }); - }); - - describe('event handlers', function () { - beforeEach(function () { - sock._recv_message = sinon.spy(); - sock.on('open', sinon.spy()); - sock.on('close', sinon.spy()); - sock.on('error', sinon.spy()); - sock.open('ws://'); - }); - - it('should call _recv_message on a message', function () { - sock._websocket.onmessage(null); - expect(sock._recv_message).to.have.been.calledOnce; - }); - - it('should call the open event handler on opening', function () { - sock._websocket.onopen(); - expect(sock._eventHandlers.open).to.have.been.calledOnce; - }); - - it('should call the close event handler on closing', function () { - sock._websocket.onclose(); - expect(sock._eventHandlers.close).to.have.been.calledOnce; - }); - - it('should call the error event handler on error', function () { - sock._websocket.onerror(); - expect(sock._eventHandlers.error).to.have.been.calledOnce; - }); - }); - - after(function () { - // eslint-disable-next-line no-global-assign - WebSocket = old_WS; - }); - }); - - describe('WebSocket Receiving', function () { - let sock; - beforeEach(function () { - sock = new Websock(); - sock._allocate_buffers(); - }); - - it('should support adding binary Uint8Array data to the receive queue', function () { - const msg = { data: new Uint8Array([1, 2, 3]) }; - sock._mode = 'binary'; - sock._recv_message(msg); - expect(sock.rQshiftStr(3)).to.equal('\x01\x02\x03'); - }); - - it('should call the message event handler if present', function () { - sock._eventHandlers.message = sinon.spy(); - const msg = { data: new Uint8Array([1, 2, 3]).buffer }; - sock._mode = 'binary'; - sock._recv_message(msg); - expect(sock._eventHandlers.message).to.have.been.calledOnce; - }); - - it('should not call the message event handler if there is nothing in the receive queue', function () { - sock._eventHandlers.message = sinon.spy(); - const msg = { data: new Uint8Array([]).buffer }; - sock._mode = 'binary'; - sock._recv_message(msg); - expect(sock._eventHandlers.message).not.to.have.been.called; - }); - - it('should compact the receive queue', function () { - // NB(sross): while this is an internal implementation detail, it's important to - // test, otherwise the receive queue could become very large very quickly - sock._rQ = new Uint8Array([0, 1, 2, 3, 4, 5, 0, 0, 0, 0]); - sock._rQlen = 6; - sock.rQi = 6; - sock._rQmax = 3; - const msg = { data: new Uint8Array([1, 2, 3]).buffer }; - sock._mode = 'binary'; - sock._recv_message(msg); - expect(sock._rQlen).to.equal(3); - expect(sock.rQi).to.equal(0); - }); - - it('should automatically resize the receive queue if the incoming message is too large', function () { - sock._rQ = new Uint8Array(20); - sock._rQlen = 0; - sock.rQi = 0; - sock._rQbufferSize = 20; - sock._rQmax = 2; - const msg = { data: new Uint8Array(30).buffer }; - sock._mode = 'binary'; - sock._recv_message(msg); - expect(sock._rQlen).to.equal(30); - expect(sock.rQi).to.equal(0); - expect(sock._rQ.length).to.equal(240); // keep the invariant that rQbufferSize / 8 >= rQlen - }); - }); - - describe('Data encoding', function () { - before(function () { FakeWebSocket.replace(); }); - after(function () { FakeWebSocket.restore(); }); - - describe('as binary data', function () { - let sock; - beforeEach(function () { - sock = new Websock(); - sock.open('ws://', 'binary'); - sock._websocket._open(); - }); - - it('should only send the send queue up to the send queue length', function () { - sock._sQ = new Uint8Array([1, 2, 3, 4, 5]); - sock._sQlen = 3; - const res = sock._encode_message(); - expect(res).to.array.equal(new Uint8Array([1, 2, 3])); - }); - - it('should properly pass the encoded data off to the actual WebSocket', function () { - sock.send([1, 2, 3]); - expect(sock._websocket._get_sent_data()).to.array.equal(new Uint8Array([1, 2, 3])); - }); - }); - }); -}); diff --git a/systemvm/agent/noVNC/tests/test.webutil.js b/systemvm/agent/noVNC/tests/test.webutil.js deleted file mode 100644 index 72e194210d5..00000000000 --- a/systemvm/agent/noVNC/tests/test.webutil.js +++ /dev/null @@ -1,184 +0,0 @@ -/* jshint expr: true */ - -const expect = chai.expect; - -import * as WebUtil from '../app/webutil.js'; - -describe('WebUtil', function () { - "use strict"; - - describe('settings', function () { - - describe('localStorage', function () { - let chrome = window.chrome; - before(function () { - chrome = window.chrome; - window.chrome = null; - }); - after(function () { - window.chrome = chrome; - }); - - let origLocalStorage; - beforeEach(function () { - origLocalStorage = Object.getOwnPropertyDescriptor(window, "localStorage"); - if (origLocalStorage === undefined) { - // Object.getOwnPropertyDescriptor() doesn't work - // properly in any version of IE - this.skip(); - } - - Object.defineProperty(window, "localStorage", {value: {}}); - if (window.localStorage.setItem !== undefined) { - // Object.defineProperty() doesn't work properly in old - // versions of Chrome - this.skip(); - } - - window.localStorage.setItem = sinon.stub(); - window.localStorage.getItem = sinon.stub(); - window.localStorage.removeItem = sinon.stub(); - - return WebUtil.initSettings(); - }); - afterEach(function () { - Object.defineProperty(window, "localStorage", origLocalStorage); - }); - - describe('writeSetting', function () { - it('should save the setting value to local storage', function () { - WebUtil.writeSetting('test', 'value'); - expect(window.localStorage.setItem).to.have.been.calledWithExactly('test', 'value'); - expect(WebUtil.readSetting('test')).to.equal('value'); - }); - }); - - describe('setSetting', function () { - it('should update the setting but not save to local storage', function () { - WebUtil.setSetting('test', 'value'); - expect(window.localStorage.setItem).to.not.have.been.called; - expect(WebUtil.readSetting('test')).to.equal('value'); - }); - }); - - describe('readSetting', function () { - it('should read the setting value from local storage', function () { - localStorage.getItem.returns('value'); - expect(WebUtil.readSetting('test')).to.equal('value'); - }); - - it('should return the default value when not in local storage', function () { - expect(WebUtil.readSetting('test', 'default')).to.equal('default'); - }); - - it('should return the cached value even if local storage changed', function () { - localStorage.getItem.returns('value'); - expect(WebUtil.readSetting('test')).to.equal('value'); - localStorage.getItem.returns('something else'); - expect(WebUtil.readSetting('test')).to.equal('value'); - }); - - it('should cache the value even if it is not initially in local storage', function () { - expect(WebUtil.readSetting('test')).to.be.null; - localStorage.getItem.returns('value'); - expect(WebUtil.readSetting('test')).to.be.null; - }); - - it('should return the default value always if the first read was not in local storage', function () { - expect(WebUtil.readSetting('test', 'default')).to.equal('default'); - localStorage.getItem.returns('value'); - expect(WebUtil.readSetting('test', 'another default')).to.equal('another default'); - }); - - it('should return the last local written value', function () { - localStorage.getItem.returns('value'); - expect(WebUtil.readSetting('test')).to.equal('value'); - WebUtil.writeSetting('test', 'something else'); - expect(WebUtil.readSetting('test')).to.equal('something else'); - }); - }); - - // this doesn't appear to be used anywhere - describe('eraseSetting', function () { - it('should remove the setting from local storage', function () { - WebUtil.eraseSetting('test'); - expect(window.localStorage.removeItem).to.have.been.calledWithExactly('test'); - }); - }); - }); - - describe('chrome.storage', function () { - let chrome = window.chrome; - let settings = {}; - before(function () { - chrome = window.chrome; - window.chrome = { - storage: { - sync: { - get(cb) { cb(settings); }, - set() {}, - remove() {} - } - } - }; - }); - after(function () { - window.chrome = chrome; - }); - - const csSandbox = sinon.createSandbox(); - - beforeEach(function () { - settings = {}; - csSandbox.spy(window.chrome.storage.sync, 'set'); - csSandbox.spy(window.chrome.storage.sync, 'remove'); - return WebUtil.initSettings(); - }); - afterEach(function () { - csSandbox.restore(); - }); - - describe('writeSetting', function () { - it('should save the setting value to chrome storage', function () { - WebUtil.writeSetting('test', 'value'); - expect(window.chrome.storage.sync.set).to.have.been.calledWithExactly(sinon.match({ test: 'value' })); - expect(WebUtil.readSetting('test')).to.equal('value'); - }); - }); - - describe('setSetting', function () { - it('should update the setting but not save to chrome storage', function () { - WebUtil.setSetting('test', 'value'); - expect(window.chrome.storage.sync.set).to.not.have.been.called; - expect(WebUtil.readSetting('test')).to.equal('value'); - }); - }); - - describe('readSetting', function () { - it('should read the setting value from chrome storage', function () { - settings.test = 'value'; - expect(WebUtil.readSetting('test')).to.equal('value'); - }); - - it('should return the default value when not in chrome storage', function () { - expect(WebUtil.readSetting('test', 'default')).to.equal('default'); - }); - - it('should return the last local written value', function () { - settings.test = 'value'; - expect(WebUtil.readSetting('test')).to.equal('value'); - WebUtil.writeSetting('test', 'something else'); - expect(WebUtil.readSetting('test')).to.equal('something else'); - }); - }); - - // this doesn't appear to be used anywhere - describe('eraseSetting', function () { - it('should remove the setting from chrome storage', function () { - WebUtil.eraseSetting('test'); - expect(window.chrome.storage.sync.remove).to.have.been.calledWithExactly('test'); - }); - }); - }); - }); -}); diff --git a/systemvm/agent/noVNC/tests/vnc_playback.html b/systemvm/agent/noVNC/tests/vnc_playback.html deleted file mode 100644 index 4fd74658053..00000000000 --- a/systemvm/agent/noVNC/tests/vnc_playback.html +++ /dev/null @@ -1,43 +0,0 @@ - - - - VNC Playback - - - - - - - - - - - Iterations:   - Perftest:  - Realtime:   - -   - -

- - Results:
- - -

- -
-
Loading
-
- - - - diff --git a/systemvm/agent/noVNC/utils/.eslintrc b/systemvm/agent/noVNC/utils/.eslintrc deleted file mode 100644 index b7dc129f139..00000000000 --- a/systemvm/agent/noVNC/utils/.eslintrc +++ /dev/null @@ -1,8 +0,0 @@ -{ - "env": { - "node": true - }, - "rules": { - "no-console": 0 - } -} \ No newline at end of file diff --git a/systemvm/agent/noVNC/utils/README.md b/systemvm/agent/noVNC/utils/README.md deleted file mode 100644 index 32582e65ea8..00000000000 --- a/systemvm/agent/noVNC/utils/README.md +++ /dev/null @@ -1,14 +0,0 @@ -## WebSockets Proxy/Bridge - -Websockify has been forked out into its own project. `launch.sh` wil -automatically download it here if it is not already present and not -installed as system-wide. - -For more detailed description and usage information please refer to -the [websockify README](https://github.com/novnc/websockify/blob/master/README.md). - -The other versions of websockify (C, Node.js) and the associated test -programs have been moved to -[websockify](https://github.com/novnc/websockify). Websockify was -formerly named wsproxy. - diff --git a/systemvm/agent/noVNC/utils/b64-to-binary.pl b/systemvm/agent/noVNC/utils/b64-to-binary.pl deleted file mode 100755 index 280e28c93f0..00000000000 --- a/systemvm/agent/noVNC/utils/b64-to-binary.pl +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env perl -use MIME::Base64; - -for (<>) { - unless (/^'([{}])(\d+)\1(.+?)',$/) { - print; - next; - } - - my ($dir, $amt, $b64) = ($1, $2, $3); - - my $decoded = MIME::Base64::decode($b64) or die "Could not base64-decode line `$_`"; - - my $decoded_escaped = join "", map { "\\x$_" } unpack("(H2)*", $decoded); - - print "'${dir}${amt}${dir}${decoded_escaped}',\n"; -} diff --git a/systemvm/agent/noVNC/utils/genkeysymdef.js b/systemvm/agent/noVNC/utils/genkeysymdef.js deleted file mode 100755 index d21773f9f65..00000000000 --- a/systemvm/agent/noVNC/utils/genkeysymdef.js +++ /dev/null @@ -1,127 +0,0 @@ -#!/usr/bin/env node -/* - * genkeysymdef: X11 keysymdef.h to JavaScript converter - * Copyright (C) 2018 The noVNC Authors - * Licensed under MPL 2.0 (see LICENSE.txt) - */ - -"use strict"; - -const fs = require('fs'); - -let show_help = process.argv.length === 2; -let filename; - -for (let i = 2; i < process.argv.length; ++i) { - switch (process.argv[i]) { - case "--help": - case "-h": - show_help = true; - break; - case "--file": - case "-f": - default: - filename = process.argv[i]; - } -} - -if (!filename) { - show_help = true; - console.log("Error: No filename specified\n"); -} - -if (show_help) { - console.log("Parses a *nix keysymdef.h to generate Unicode code point mappings"); - console.log("Usage: node parse.js [options] filename:"); - console.log(" -h [ --help ] Produce this help message"); - console.log(" filename The keysymdef.h file to parse"); - process.exit(0); -} - -const buf = fs.readFileSync(filename); -const str = buf.toString('utf8'); - -const re = /^#define XK_([a-zA-Z_0-9]+)\s+0x([0-9a-fA-F]+)\s*(\/\*\s*(.*)\s*\*\/)?\s*$/m; - -const arr = str.split('\n'); - -const codepoints = {}; - -for (let i = 0; i < arr.length; ++i) { - const result = re.exec(arr[i]); - if (result) { - const keyname = result[1]; - const keysym = parseInt(result[2], 16); - const remainder = result[3]; - - const unicodeRes = /U\+([0-9a-fA-F]+)/.exec(remainder); - if (unicodeRes) { - const unicode = parseInt(unicodeRes[1], 16); - // The first entry is the preferred one - if (!codepoints[unicode]) { - codepoints[unicode] = { keysym: keysym, name: keyname }; - } - } - } -} - -let out = -"/*\n" + -" * Mapping from Unicode codepoints to X11/RFB keysyms\n" + -" *\n" + -" * This file was automatically generated from keysymdef.h\n" + -" * DO NOT EDIT!\n" + -" */\n" + -"\n" + -"/* Functions at the bottom */\n" + -"\n" + -"const codepoints = {\n"; - -function toHex(num) { - let s = num.toString(16); - if (s.length < 4) { - s = ("0000" + s).slice(-4); - } - return "0x" + s; -} - -for (let codepoint in codepoints) { - codepoint = parseInt(codepoint); - - // Latin-1? - if ((codepoint >= 0x20) && (codepoint <= 0xff)) { - continue; - } - - // Handled by the general Unicode mapping? - if ((codepoint | 0x01000000) === codepoints[codepoint].keysym) { - continue; - } - - out += " " + toHex(codepoint) + ": " + - toHex(codepoints[codepoint].keysym) + - ", // XK_" + codepoints[codepoint].name + "\n"; -} - -out += -"};\n" + -"\n" + -"export default {\n" + -" lookup(u) {\n" + -" // Latin-1 is one-to-one mapping\n" + -" if ((u >= 0x20) && (u <= 0xff)) {\n" + -" return u;\n" + -" }\n" + -"\n" + -" // Lookup table (fairly random)\n" + -" const keysym = codepoints[u];\n" + -" if (keysym !== undefined) {\n" + -" return keysym;\n" + -" }\n" + -"\n" + -" // General mapping as final fallback\n" + -" return 0x01000000 | u;\n" + -" },\n" + -"};"; - -console.log(out); diff --git a/systemvm/agent/noVNC/utils/img2js.py b/systemvm/agent/noVNC/utils/img2js.py deleted file mode 100755 index ceab6bf7543..00000000000 --- a/systemvm/agent/noVNC/utils/img2js.py +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env python - -# -# Convert image to Javascript compatible base64 Data URI -# Copyright (C) 2018 The noVNC Authors -# Licensed under MPL 2.0 (see docs/LICENSE.MPL-2.0) -# - -import sys, base64 - -try: - from PIL import Image -except: - print "python PIL module required (python-imaging package)" - sys.exit(1) - - -if len(sys.argv) < 3: - print "Usage: %s IMAGE JS_VARIABLE" % sys.argv[0] - sys.exit(1) - -fname = sys.argv[1] -var = sys.argv[2] - -ext = fname.lower().split('.')[-1] -if ext == "png": mime = "image/png" -elif ext in ["jpg", "jpeg"]: mime = "image/jpeg" -elif ext == "gif": mime = "image/gif" -else: - print "Only PNG, JPEG and GIF images are supported" - sys.exit(1) -uri = "data:%s;base64," % mime - -im = Image.open(fname) -w, h = im.size - -raw = open(fname).read() - -print '%s = {"width": %s, "height": %s, "data": "%s%s"};' % ( - var, w, h, uri, base64.b64encode(raw)) diff --git a/systemvm/agent/noVNC/utils/json2graph.py b/systemvm/agent/noVNC/utils/json2graph.py deleted file mode 100755 index bdaeeccaf21..00000000000 --- a/systemvm/agent/noVNC/utils/json2graph.py +++ /dev/null @@ -1,206 +0,0 @@ -#!/usr/bin/env python - -''' -Use matplotlib to generate performance charts -Copyright (C) 2018 The noVNC Authors -Licensed under MPL-2.0 (see docs/LICENSE.MPL-2.0) -''' - -# a bar plot with errorbars -import sys, json -import numpy as np -import matplotlib.pyplot as plt -from matplotlib.font_manager import FontProperties - -def usage(): - print "%s json_file level1 level2 level3 [legend_height]\n\n" % sys.argv[0] - print "Description:\n" - print "level1, level2, and level3 are one each of the following:\n"; - print " select=ITEM - select only ITEM at this level"; - print " bar - each item on this level becomes a graph bar"; - print " group - items on this level become groups of bars"; - print "\n"; - print "json_file is a file containing json data in the following format:\n" - print ' {'; - print ' "conf": {'; - print ' "order_l1": ['; - print ' "level1_label1",'; - print ' "level1_label2",'; - print ' ...'; - print ' ],'; - print ' "order_l2": ['; - print ' "level2_label1",'; - print ' "level2_label2",'; - print ' ...'; - print ' ],'; - print ' "order_l3": ['; - print ' "level3_label1",'; - print ' "level3_label2",'; - print ' ...'; - print ' ]'; - print ' },'; - print ' "stats": {'; - print ' "level1_label1": {'; - print ' "level2_label1": {'; - print ' "level3_label1": [val1, val2, val3],'; - print ' "level3_label2": [val1, val2, val3],'; - print ' ...'; - print ' },'; - print ' "level2_label2": {'; - print ' ...'; - print ' },'; - print ' },'; - print ' "level1_label2": {'; - print ' ...'; - print ' },'; - print ' ...'; - print ' },'; - print ' }'; - sys.exit(2) - -def error(msg): - print msg - sys.exit(1) - - -#colors = ['#ff0000', '#0863e9', '#00f200', '#ffa100', -# '#800000', '#805100', '#013075', '#007900'] -colors = ['#ff0000', '#00ff00', '#0000ff', - '#dddd00', '#dd00dd', '#00dddd', - '#dd6622', '#dd2266', '#66dd22', - '#8844dd', '#44dd88', '#4488dd'] - -if len(sys.argv) < 5: - usage() - -filename = sys.argv[1] -L1 = sys.argv[2] -L2 = sys.argv[3] -L3 = sys.argv[4] -if len(sys.argv) > 5: - legendHeight = float(sys.argv[5]) -else: - legendHeight = 0.75 - -# Load the JSON data from the file -data = json.loads(file(filename).read()) -conf = data['conf'] -stats = data['stats'] - -# Sanity check data hierarchy -if len(conf['order_l1']) != len(stats.keys()): - error("conf.order_l1 does not match stats level 1") -for l1 in stats.keys(): - if len(conf['order_l2']) != len(stats[l1].keys()): - error("conf.order_l2 does not match stats level 2 for %s" % l1) - if conf['order_l1'].count(l1) < 1: - error("%s not found in conf.order_l1" % l1) - for l2 in stats[l1].keys(): - if len(conf['order_l3']) != len(stats[l1][l2].keys()): - error("conf.order_l3 does not match stats level 3") - if conf['order_l2'].count(l2) < 1: - error("%s not found in conf.order_l2" % l2) - for l3 in stats[l1][l2].keys(): - if conf['order_l3'].count(l3) < 1: - error("%s not found in conf.order_l3" % l3) - -# -# Generate the data based on the level specifications -# -bar_labels = None -group_labels = None -bar_vals = [] -bar_sdvs = [] -if L3.startswith("select="): - select_label = l3 = L3.split("=")[1] - bar_labels = conf['order_l1'] - group_labels = conf['order_l2'] - bar_vals = [[0]*len(group_labels) for i in bar_labels] - bar_sdvs = [[0]*len(group_labels) for i in bar_labels] - for b in range(len(bar_labels)): - l1 = bar_labels[b] - for g in range(len(group_labels)): - l2 = group_labels[g] - bar_vals[b][g] = np.mean(stats[l1][l2][l3]) - bar_sdvs[b][g] = np.std(stats[l1][l2][l3]) -elif L2.startswith("select="): - select_label = l2 = L2.split("=")[1] - bar_labels = conf['order_l1'] - group_labels = conf['order_l3'] - bar_vals = [[0]*len(group_labels) for i in bar_labels] - bar_sdvs = [[0]*len(group_labels) for i in bar_labels] - for b in range(len(bar_labels)): - l1 = bar_labels[b] - for g in range(len(group_labels)): - l3 = group_labels[g] - bar_vals[b][g] = np.mean(stats[l1][l2][l3]) - bar_sdvs[b][g] = np.std(stats[l1][l2][l3]) -elif L1.startswith("select="): - select_label = l1 = L1.split("=")[1] - bar_labels = conf['order_l2'] - group_labels = conf['order_l3'] - bar_vals = [[0]*len(group_labels) for i in bar_labels] - bar_sdvs = [[0]*len(group_labels) for i in bar_labels] - for b in range(len(bar_labels)): - l2 = bar_labels[b] - for g in range(len(group_labels)): - l3 = group_labels[g] - bar_vals[b][g] = np.mean(stats[l1][l2][l3]) - bar_sdvs[b][g] = np.std(stats[l1][l2][l3]) -else: - usage() - -# If group is before bar then flip (zip) the data -if [L1, L2, L3].index("group") < [L1, L2, L3].index("bar"): - bar_labels, group_labels = group_labels, bar_labels - bar_vals = zip(*bar_vals) - bar_sdvs = zip(*bar_sdvs) - -print "bar_vals:", bar_vals - -# -# Now render the bar graph -# -ind = np.arange(len(group_labels)) # the x locations for the groups -width = 0.8 * (1.0/len(bar_labels)) # the width of the bars - -fig = plt.figure(figsize=(10,6), dpi=80) -plot = fig.add_subplot(1, 1, 1) - -rects = [] -for i in range(len(bar_vals)): - rects.append(plot.bar(ind+width*i, bar_vals[i], width, color=colors[i], - yerr=bar_sdvs[i], align='center')) - -# add some -plot.set_ylabel('Milliseconds (less is better)') -plot.set_title("Javascript array test: %s" % select_label) -plot.set_xticks(ind+width) -plot.set_xticklabels( group_labels ) - -fontP = FontProperties() -fontP.set_size('small') -plot.legend( [r[0] for r in rects], bar_labels, prop=fontP, - loc = 'center right', bbox_to_anchor = (1.0, legendHeight)) - -def autolabel(rects): - # attach some text labels - for rect in rects: - height = rect.get_height() - if np.isnan(height): - height = 0.0 - plot.text(rect.get_x()+rect.get_width()/2., height+20, '%d'%int(height), - ha='center', va='bottom', size='7') - -for rect in rects: - autolabel(rect) - -# Adjust axis sizes -axis = list(plot.axis()) -axis[0] = -width # Make sure left side has enough for bar -#axis[1] = axis[1] * 1.20 # Add 20% to the right to make sure it fits -axis[2] = 0 # Make y-axis start at 0 -axis[3] = axis[3] * 1.10 # Add 10% to the top -plot.axis(axis) - -plt.show() diff --git a/systemvm/agent/noVNC/utils/launch.sh b/systemvm/agent/noVNC/utils/launch.sh deleted file mode 100755 index 162607eb05c..00000000000 --- a/systemvm/agent/noVNC/utils/launch.sh +++ /dev/null @@ -1,169 +0,0 @@ -#!/usr/bin/env bash - -# Copyright (C) 2018 The noVNC Authors -# Licensed under MPL 2.0 or any later version (see LICENSE.txt) - -usage() { - if [ "$*" ]; then - echo "$*" - echo - fi - echo "Usage: ${NAME} [--listen PORT] [--vnc VNC_HOST:PORT] [--cert CERT] [--ssl-only]" - echo - echo "Starts the WebSockets proxy and a mini-webserver and " - echo "provides a cut-and-paste URL to go to." - echo - echo " --listen PORT Port for proxy/webserver to listen on" - echo " Default: 6080" - echo " --vnc VNC_HOST:PORT VNC server host:port proxy target" - echo " Default: localhost:5900" - echo " --cert CERT Path to combined cert/key file" - echo " Default: self.pem" - echo " --web WEB Path to web files (e.g. vnc.html)" - echo " Default: ./" - echo " --ssl-only Disable non-https connections." - echo " " - echo " --record FILE Record traffic to FILE.session.js" - echo " " - exit 2 -} - -NAME="$(basename $0)" -REAL_NAME="$(readlink -f $0)" -HERE="$(cd "$(dirname "$REAL_NAME")" && pwd)" -PORT="6080" -VNC_DEST="localhost:5900" -CERT="" -WEB="" -proxy_pid="" -SSLONLY="" -RECORD_ARG="" - -die() { - echo "$*" - exit 1 -} - -cleanup() { - trap - TERM QUIT INT EXIT - trap "true" CHLD # Ignore cleanup messages - echo - if [ -n "${proxy_pid}" ]; then - echo "Terminating WebSockets proxy (${proxy_pid})" - kill ${proxy_pid} - fi -} - -# Process Arguments - -# Arguments that only apply to chrooter itself -while [ "$*" ]; do - param=$1; shift; OPTARG=$1 - case $param in - --listen) PORT="${OPTARG}"; shift ;; - --vnc) VNC_DEST="${OPTARG}"; shift ;; - --cert) CERT="${OPTARG}"; shift ;; - --web) WEB="${OPTARG}"; shift ;; - --ssl-only) SSLONLY="--ssl-only" ;; - --record) RECORD_ARG="--record ${OPTARG}"; shift ;; - -h|--help) usage ;; - -*) usage "Unknown chrooter option: ${param}" ;; - *) break ;; - esac -done - -# Sanity checks -if bash -c "exec 7<>/dev/tcp/localhost/${PORT}" &> /dev/null; then - exec 7<&- - exec 7>&- - die "Port ${PORT} in use. Try --listen PORT" -else - exec 7<&- - exec 7>&- -fi - -trap "cleanup" TERM QUIT INT EXIT - -# Find vnc.html -if [ -n "${WEB}" ]; then - if [ ! -e "${WEB}/vnc.html" ]; then - die "Could not find ${WEB}/vnc.html" - fi -elif [ -e "$(pwd)/vnc.html" ]; then - WEB=$(pwd) -elif [ -e "${HERE}/../vnc.html" ]; then - WEB=${HERE}/../ -elif [ -e "${HERE}/vnc.html" ]; then - WEB=${HERE} -elif [ -e "${HERE}/../share/novnc/vnc.html" ]; then - WEB=${HERE}/../share/novnc/ -else - die "Could not find vnc.html" -fi - -# Find self.pem -if [ -n "${CERT}" ]; then - if [ ! -e "${CERT}" ]; then - die "Could not find ${CERT}" - fi -elif [ -e "$(pwd)/self.pem" ]; then - CERT="$(pwd)/self.pem" -elif [ -e "${HERE}/../self.pem" ]; then - CERT="${HERE}/../self.pem" -elif [ -e "${HERE}/self.pem" ]; then - CERT="${HERE}/self.pem" -else - echo "Warning: could not find self.pem" -fi - -# try to find websockify (prefer local, try global, then download local) -if [[ -e ${HERE}/websockify ]]; then - WEBSOCKIFY=${HERE}/websockify/run - - if [[ ! -x $WEBSOCKIFY ]]; then - echo "The path ${HERE}/websockify exists, but $WEBSOCKIFY either does not exist or is not executable." - echo "If you intended to use an installed websockify package, please remove ${HERE}/websockify." - exit 1 - fi - - echo "Using local websockify at $WEBSOCKIFY" -else - WEBSOCKIFY=$(which websockify 2>/dev/null) - - if [[ $? -ne 0 ]]; then - echo "No installed websockify, attempting to clone websockify..." - WEBSOCKIFY=${HERE}/websockify/run - git clone https://github.com/novnc/websockify ${HERE}/websockify - - if [[ ! -e $WEBSOCKIFY ]]; then - echo "Unable to locate ${HERE}/websockify/run after downloading" - exit 1 - fi - - echo "Using local websockify at $WEBSOCKIFY" - else - echo "Using installed websockify at $WEBSOCKIFY" - fi -fi - -echo "Starting webserver and WebSockets proxy on port ${PORT}" -#${HERE}/websockify --web ${WEB} ${CERT:+--cert ${CERT}} ${PORT} ${VNC_DEST} & -${WEBSOCKIFY} ${SSLONLY} --web ${WEB} ${CERT:+--cert ${CERT}} ${PORT} ${VNC_DEST} ${RECORD_ARG} & -proxy_pid="$!" -sleep 1 -if ! ps -p ${proxy_pid} >/dev/null; then - proxy_pid= - echo "Failed to start WebSockets proxy" - exit 1 -fi - -echo -e "\n\nNavigate to this URL:\n" -if [ "x$SSLONLY" == "x" ]; then - echo -e " http://$(hostname):${PORT}/vnc.html?host=$(hostname)&port=${PORT}\n" -else - echo -e " https://$(hostname):${PORT}/vnc.html?host=$(hostname)&port=${PORT}\n" -fi - -echo -e "Press Ctrl-C to exit\n\n" - -wait ${proxy_pid} diff --git a/systemvm/agent/noVNC/utils/u2x11 b/systemvm/agent/noVNC/utils/u2x11 deleted file mode 100755 index fd3e4ba88a5..00000000000 --- a/systemvm/agent/noVNC/utils/u2x11 +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env bash -# -# Convert "U+..." commented entries in /usr/include/X11/keysymdef.h -# into JavaScript for use by noVNC. Note this is likely to produce -# a few duplicate properties with clashing values, that will need -# resolving manually. -# -# Colin Dean -# - -regex="^#define[ \t]+XK_[A-Za-z0-9_]+[ \t]+0x([0-9a-fA-F]+)[ \t]+\/\*[ \t]+U\+([0-9a-fA-F]+)[ \t]+[^*]+.[ \t]+\*\/[ \t]*$" -echo "unicodeTable = {" -while read line; do - if echo "${line}" | egrep -qs "${regex}"; then - - x11=$(echo "${line}" | sed -r "s/${regex}/\1/") - vnc=$(echo "${line}" | sed -r "s/${regex}/\2/") - - if echo "${vnc}" | egrep -qs "^00[2-9A-F][0-9A-F]$"; then - : # skip ISO Latin-1 (U+0020 to U+00FF) as 1-to-1 mapping - else - # note 1-to-1 is possible (e.g. for Euro symbol, U+20AC) - echo " 0x${vnc} : 0x${x11}," - fi - fi -done < /usr/include/X11/keysymdef.h | uniq -echo "};" - diff --git a/systemvm/agent/noVNC/utils/use_require.js b/systemvm/agent/noVNC/utils/use_require.js deleted file mode 100755 index 248792718c9..00000000000 --- a/systemvm/agent/noVNC/utils/use_require.js +++ /dev/null @@ -1,313 +0,0 @@ -#!/usr/bin/env node - -const path = require('path'); -const program = require('commander'); -const fs = require('fs'); -const fse = require('fs-extra'); -const babel = require('babel-core'); - -const SUPPORTED_FORMATS = new Set(['amd', 'commonjs', 'systemjs', 'umd']); - -program - .option('--as [format]', `output files using various import formats instead of ES6 import and export. Supports ${Array.from(SUPPORTED_FORMATS)}.`) - .option('-m, --with-source-maps [type]', 'output source maps when not generating a bundled app (type may be empty for external source maps, inline for inline source maps, or both) ') - .option('--with-app', 'process app files as well as core files') - .option('--only-legacy', 'only output legacy files (no ES6 modules) for the app') - .option('--clean', 'clear the lib folder before building') - .parse(process.argv); - -// the various important paths -const paths = { - main: path.resolve(__dirname, '..'), - core: path.resolve(__dirname, '..', 'core'), - app: path.resolve(__dirname, '..', 'app'), - vendor: path.resolve(__dirname, '..', 'vendor'), - out_dir_base: path.resolve(__dirname, '..', 'build'), - lib_dir_base: path.resolve(__dirname, '..', 'lib'), -}; - -const no_copy_files = new Set([ - // skip these -- they don't belong in the processed application - path.join(paths.vendor, 'sinon.js'), - path.join(paths.vendor, 'browser-es-module-loader'), - path.join(paths.vendor, 'promise.js'), - path.join(paths.app, 'images', 'icons', 'Makefile'), -]); - -const no_transform_files = new Set([ - // don't transform this -- we want it imported as-is to properly catch loading errors - path.join(paths.app, 'error-handler.js'), -]); - -no_copy_files.forEach(file => no_transform_files.add(file)); - -// util.promisify requires Node.js 8.x, so we have our own -function promisify(original) { - return function promise_wrap() { - const args = Array.prototype.slice.call(arguments); - return new Promise((resolve, reject) => { - original.apply(this, args.concat((err, value) => { - if (err) return reject(err); - resolve(value); - })); - }); - }; -} - -const readFile = promisify(fs.readFile); -const writeFile = promisify(fs.writeFile); - -const readdir = promisify(fs.readdir); -const lstat = promisify(fs.lstat); - -const copy = promisify(fse.copy); -const unlink = promisify(fse.unlink); -const ensureDir = promisify(fse.ensureDir); -const rmdir = promisify(fse.rmdir); - -const babelTransformFile = promisify(babel.transformFile); - -// walkDir *recursively* walks directories trees, -// calling the callback for all normal files found. -function walkDir(base_path, cb, filter) { - return readdir(base_path) - .then((files) => { - const paths = files.map(filename => path.join(base_path, filename)); - return Promise.all(paths.map(filepath => lstat(filepath) - .then((stats) => { - if (filter !== undefined && !filter(filepath, stats)) return; - - if (stats.isSymbolicLink()) return; - if (stats.isFile()) return cb(filepath); - if (stats.isDirectory()) return walkDir(filepath, cb, filter); - }))); - }); -} - -function transform_html(legacy_scripts, only_legacy) { - // write out the modified vnc.html file that works with the bundle - const src_html_path = path.resolve(__dirname, '..', 'vnc.html'); - const out_html_path = path.resolve(paths.out_dir_base, 'vnc.html'); - return readFile(src_html_path) - .then((contents_raw) => { - let contents = contents_raw.toString(); - - const start_marker = '\n'; - const end_marker = ''; - const start_ind = contents.indexOf(start_marker) + start_marker.length; - const end_ind = contents.indexOf(end_marker, start_ind); - - let new_script = ''; - - if (only_legacy) { - // Only legacy version, so include things directly - for (let i = 0;i < legacy_scripts.length;i++) { - new_script += ` \n`; - } - } else { - // Otherwise detect if it's a modern browser and select - // variant accordingly - new_script += `\ - \n\ - \n`; - - // Original, ES6 modules - new_script += ' \n'; - } - - contents = contents.slice(0, start_ind) + `${new_script}\n` + contents.slice(end_ind); - - return contents; - }) - .then((contents) => { - console.log(`Writing ${out_html_path}`); - return writeFile(out_html_path, contents); - }); -} - -function make_lib_files(import_format, source_maps, with_app_dir, only_legacy) { - if (!import_format) { - throw new Error("you must specify an import format to generate compiled noVNC libraries"); - } else if (!SUPPORTED_FORMATS.has(import_format)) { - throw new Error(`unsupported output format "${import_format}" for import/export -- only ${Array.from(SUPPORTED_FORMATS)} are supported`); - } - - // NB: we need to make a copy of babel_opts, since babel sets some defaults on it - const babel_opts = () => ({ - plugins: [`transform-es2015-modules-${import_format}`], - presets: ['es2015'], - ast: false, - sourceMaps: source_maps, - }); - - // No point in duplicate files without the app, so force only converted files - if (!with_app_dir) { - only_legacy = true; - } - - let in_path; - let out_path_base; - if (with_app_dir) { - out_path_base = paths.out_dir_base; - in_path = paths.main; - } else { - out_path_base = paths.lib_dir_base; - } - const legacy_path_base = only_legacy ? out_path_base : path.join(out_path_base, 'legacy'); - - fse.ensureDirSync(out_path_base); - - const helpers = require('./use_require_helpers'); - const helper = helpers[import_format]; - - const outFiles = []; - - const handleDir = (js_only, vendor_rewrite, in_path_base, filename) => Promise.resolve() - .then(() => { - if (no_copy_files.has(filename)) return; - - const out_path = path.join(out_path_base, path.relative(in_path_base, filename)); - const legacy_path = path.join(legacy_path_base, path.relative(in_path_base, filename)); - - if (path.extname(filename) !== '.js') { - if (!js_only) { - console.log(`Writing ${out_path}`); - return copy(filename, out_path); - } - return; // skip non-javascript files - } - - return Promise.resolve() - .then(() => { - if (only_legacy && !no_transform_files.has(filename)) { - return; - } - return ensureDir(path.dirname(out_path)) - .then(() => { - console.log(`Writing ${out_path}`); - return copy(filename, out_path); - }); - }) - .then(() => ensureDir(path.dirname(legacy_path))) - .then(() => { - if (no_transform_files.has(filename)) { - return; - } - - const opts = babel_opts(); - if (helper && helpers.optionsOverride) { - helper.optionsOverride(opts); - } - // Adjust for the fact that we move the core files relative - // to the vendor directory - if (vendor_rewrite) { - opts.plugins.push(["import-redirect", - {"root": legacy_path_base, - "redirect": { "vendor/(.+)": "./vendor/$1"}}]); - } - - return babelTransformFile(filename, opts) - .then((res) => { - console.log(`Writing ${legacy_path}`); - const {map} = res; - let {code} = res; - if (source_maps === true) { - // append URL for external source map - code += `\n//# sourceMappingURL=${path.basename(legacy_path)}.map\n`; - } - outFiles.push(`${legacy_path}`); - return writeFile(legacy_path, code) - .then(() => { - if (source_maps === true || source_maps === 'both') { - console.log(` and ${legacy_path}.map`); - outFiles.push(`${legacy_path}.map`); - return writeFile(`${legacy_path}.map`, JSON.stringify(map)); - } - }); - }); - }); - }); - - if (with_app_dir && helper && helper.noCopyOverride) { - helper.noCopyOverride(paths, no_copy_files); - } - - Promise.resolve() - .then(() => { - const handler = handleDir.bind(null, true, false, in_path || paths.main); - const filter = (filename, stats) => !no_copy_files.has(filename); - return walkDir(paths.vendor, handler, filter); - }) - .then(() => { - const handler = handleDir.bind(null, true, !in_path, in_path || paths.core); - const filter = (filename, stats) => !no_copy_files.has(filename); - return walkDir(paths.core, handler, filter); - }) - .then(() => { - if (!with_app_dir) return; - const handler = handleDir.bind(null, false, false, in_path); - const filter = (filename, stats) => !no_copy_files.has(filename); - return walkDir(paths.app, handler, filter); - }) - .then(() => { - if (!with_app_dir) return; - - if (!helper || !helper.appWriter) { - throw new Error(`Unable to generate app for the ${import_format} format!`); - } - - const out_app_path = path.join(legacy_path_base, 'app.js'); - console.log(`Writing ${out_app_path}`); - return helper.appWriter(out_path_base, legacy_path_base, out_app_path) - .then((extra_scripts) => { - const rel_app_path = path.relative(out_path_base, out_app_path); - const legacy_scripts = extra_scripts.concat([rel_app_path]); - transform_html(legacy_scripts, only_legacy); - }) - .then(() => { - if (!helper.removeModules) return; - console.log(`Cleaning up temporary files...`); - return Promise.all(outFiles.map((filepath) => { - unlink(filepath) - .then(() => { - // Try to clean up any empty directories if this - // was the last file in there - const rmdir_r = dir => - rmdir(dir) - .then(() => rmdir_r(path.dirname(dir))) - .catch(() => { - // Assume the error was ENOTEMPTY and ignore it - }); - return rmdir_r(path.dirname(filepath)); - }); - })); - }); - }) - .catch((err) => { - console.error(`Failure converting modules: ${err}`); - process.exit(1); - }); -} - -if (program.clean) { - console.log(`Removing ${paths.lib_dir_base}`); - fse.removeSync(paths.lib_dir_base); - - console.log(`Removing ${paths.out_dir_base}`); - fse.removeSync(paths.out_dir_base); -} - -make_lib_files(program.as, program.withSourceMaps, program.withApp, program.onlyLegacy); diff --git a/systemvm/agent/noVNC/utils/use_require_helpers.js b/systemvm/agent/noVNC/utils/use_require_helpers.js deleted file mode 100644 index a4f99c7045c..00000000000 --- a/systemvm/agent/noVNC/utils/use_require_helpers.js +++ /dev/null @@ -1,76 +0,0 @@ -// writes helpers require for vnc.html (they should output app.js) -const fs = require('fs'); -const path = require('path'); - -// util.promisify requires Node.js 8.x, so we have our own -function promisify(original) { - return function promise_wrap() { - const args = Array.prototype.slice.call(arguments); - return new Promise((resolve, reject) => { - original.apply(this, args.concat((err, value) => { - if (err) return reject(err); - resolve(value); - })); - }); - }; -} - -const writeFile = promisify(fs.writeFile); - -module.exports = { - 'amd': { - appWriter: (base_out_path, script_base_path, out_path) => { - // setup for requirejs - const ui_path = path.relative(base_out_path, - path.join(script_base_path, 'app', 'ui')); - return writeFile(out_path, `requirejs(["${ui_path}"], (ui) => {});`) - .then(() => { - console.log(`Please place RequireJS in ${path.join(script_base_path, 'require.js')}`); - const require_path = path.relative(base_out_path, - path.join(script_base_path, 'require.js')); - return [ require_path ]; - }); - }, - noCopyOverride: () => {}, - }, - 'commonjs': { - optionsOverride: (opts) => { - // CommonJS supports properly shifting the default export to work as normal - opts.plugins.unshift("add-module-exports"); - }, - appWriter: (base_out_path, script_base_path, out_path) => { - const browserify = require('browserify'); - const b = browserify(path.join(script_base_path, 'app/ui.js'), {}); - return promisify(b.bundle).call(b) - .then(buf => writeFile(out_path, buf)) - .then(() => []); - }, - noCopyOverride: () => {}, - removeModules: true, - }, - 'systemjs': { - appWriter: (base_out_path, script_base_path, out_path) => { - const ui_path = path.relative(base_out_path, - path.join(script_base_path, 'app', 'ui.js')); - return writeFile(out_path, `SystemJS.import("${ui_path}");`) - .then(() => { - console.log(`Please place SystemJS in ${path.join(script_base_path, 'system-production.js')}`); - // FIXME: Should probably be in the legacy directory - const promise_path = path.relative(base_out_path, - path.join(base_out_path, 'vendor', 'promise.js')); - const systemjs_path = path.relative(base_out_path, - path.join(script_base_path, 'system-production.js')); - return [ promise_path, systemjs_path ]; - }); - }, - noCopyOverride: (paths, no_copy_files) => { - no_copy_files.delete(path.join(paths.vendor, 'promise.js')); - }, - }, - 'umd': { - optionsOverride: (opts) => { - // umd supports properly shifting the default export to work as normal - opts.plugins.unshift("add-module-exports"); - }, - }, -}; diff --git a/systemvm/agent/noVNC/utils/validate b/systemvm/agent/noVNC/utils/validate deleted file mode 100755 index a6b5507d2ac..00000000000 --- a/systemvm/agent/noVNC/utils/validate +++ /dev/null @@ -1,45 +0,0 @@ -#!/bin/bash - -set -e - -RET=0 - -OUT=`mktemp` - -for fn in "$@"; do - echo "Validating $fn..." - echo - - case $fn in - *.html) - type="text/html" - ;; - *.css) - type="text/css" - ;; - *) - echo "Unknown format!" - echo - RET=1 - continue - ;; - esac - - curl --silent \ - --header "Content-Type: ${type}; charset=utf-8" \ - --data-binary @${fn} \ - https://validator.w3.org/nu/?out=text > $OUT - cat $OUT - echo - - # We don't fail the check for warnings as some warnings are - # not relevant for us, and we don't currently have a way to - # ignore just those - if grep -q -s -E "^Error:" $OUT; then - RET=1 - fi -done - -rm $OUT - -exit $RET diff --git a/systemvm/agent/noVNC/vendor/browser-es-module-loader/README.md b/systemvm/agent/noVNC/vendor/browser-es-module-loader/README.md index c26867f979f..a50cc37de2a 100644 --- a/systemvm/agent/noVNC/vendor/browser-es-module-loader/README.md +++ b/systemvm/agent/noVNC/vendor/browser-es-module-loader/README.md @@ -6,8 +6,8 @@ It's based heavily on https://github.com/ModuleLoader/browser-es-module-loader, but uses WebWorkers to compile the modules in the background. -To generate, run `rollup -c` in this directory, and then run `browserify -src/babel-worker.js > dist/babel-worker.js`. +To generate, run `npx rollup -c` in this directory, and then run +`./genworker.js`. LICENSE ------- diff --git a/systemvm/agent/noVNC/vendor/browser-es-module-loader/genworker.js b/systemvm/agent/noVNC/vendor/browser-es-module-loader/genworker.js new file mode 100755 index 00000000000..dbf5d2fc801 --- /dev/null +++ b/systemvm/agent/noVNC/vendor/browser-es-module-loader/genworker.js @@ -0,0 +1,13 @@ +#!/usr/bin/env node + +var fs = require("fs"); +var browserify = require("browserify"); + +browserify("src/babel-worker.js") + .transform("babelify", { + presets: [ [ "@babel/preset-env", { targets: "ie >= 11" } ] ], + global: true, + ignore: [ "../../node_modules/core-js" ] + }) + .bundle() + .pipe(fs.createWriteStream("dist/babel-worker.js")); diff --git a/systemvm/agent/noVNC/vendor/browser-es-module-loader/rollup.config.js b/systemvm/agent/noVNC/vendor/browser-es-module-loader/rollup.config.js index 4bf4a5fd18c..33a4a24aa5a 100644 --- a/systemvm/agent/noVNC/vendor/browser-es-module-loader/rollup.config.js +++ b/systemvm/agent/noVNC/vendor/browser-es-module-loader/rollup.config.js @@ -1,16 +1,15 @@ import nodeResolve from 'rollup-plugin-node-resolve'; export default { - entry: 'src/browser-es-module-loader.js', - dest: 'dist/browser-es-module-loader.js', - format: 'umd', - moduleName: 'BrowserESModuleLoader', - sourceMap: true, + input: 'src/browser-es-module-loader.js', + output: { + file: 'dist/browser-es-module-loader.js', + format: 'umd', + name: 'BrowserESModuleLoader', + sourcemap: true, + }, plugins: [ nodeResolve(), ], - - // skip rollup warnings (specifically the eval warning) - onwarn: function() {} }; diff --git a/systemvm/agent/noVNC/vendor/browser-es-module-loader/src/babel-worker.js b/systemvm/agent/noVNC/vendor/browser-es-module-loader/src/babel-worker.js index 007bd6850cb..19e23bf6c0a 100644 --- a/systemvm/agent/noVNC/vendor/browser-es-module-loader/src/babel-worker.js +++ b/systemvm/agent/noVNC/vendor/browser-es-module-loader/src/babel-worker.js @@ -1,12 +1,10 @@ -/*import { transform as babelTransform } from 'babel-core'; -import babelTransformDynamicImport from 'babel-plugin-syntax-dynamic-import'; -import babelTransformES2015ModulesSystemJS from 'babel-plugin-transform-es2015-modules-systemjs';*/ +// Polyfills needed for Babel to function +require("core-js"); -// sadly, due to how rollup works, we can't use es6 imports here -var babelTransform = require('babel-core').transform; -var babelTransformDynamicImport = require('babel-plugin-syntax-dynamic-import'); -var babelTransformES2015ModulesSystemJS = require('babel-plugin-transform-es2015-modules-systemjs'); -var babelPresetES2015 = require('babel-preset-es2015'); +var babelTransform = require('@babel/core').transform; +var babelTransformDynamicImport = require('@babel/plugin-syntax-dynamic-import'); +var babelTransformModulesSystemJS = require('@babel/plugin-transform-modules-systemjs'); +var babelPresetEnv = require('@babel/preset-env'); self.onmessage = function (evt) { // transform source with Babel @@ -17,8 +15,8 @@ self.onmessage = function (evt) { moduleIds: false, sourceMaps: 'inline', babelrc: false, - plugins: [babelTransformDynamicImport, babelTransformES2015ModulesSystemJS], - presets: [babelPresetES2015], + plugins: [babelTransformDynamicImport, babelTransformModulesSystemJS], + presets: [ [ babelPresetEnv, { targets: 'ie >= 11' } ] ], }); self.postMessage({key: evt.data.key, code: output.code, source: evt.data.source}); diff --git a/systemvm/agent/noVNC/vendor/browser-es-module-loader/src/browser-es-module-loader.js b/systemvm/agent/noVNC/vendor/browser-es-module-loader/src/browser-es-module-loader.js index efae617061f..9e50b8b5b22 100644 --- a/systemvm/agent/noVNC/vendor/browser-es-module-loader/src/browser-es-module-loader.js +++ b/systemvm/agent/noVNC/vendor/browser-es-module-loader/src/browser-es-module-loader.js @@ -1,5 +1,4 @@ import RegisterLoader from 'es-module-loader/core/register-loader.js'; -import { InternalModuleNamespace as ModuleNamespace } from 'es-module-loader/core/loader-polyfill.js'; import { baseURI, global, isBrowser } from 'es-module-loader/core/common.js'; import { resolveIfNotPlain } from 'es-module-loader/core/resolve.js'; diff --git a/systemvm/agent/noVNC/vendor/pako/lib/zlib/deflate.js b/systemvm/agent/noVNC/vendor/pako/lib/zlib/deflate.js index c51915e2d94..c3a5ba49aa7 100644 --- a/systemvm/agent/noVNC/vendor/pako/lib/zlib/deflate.js +++ b/systemvm/agent/noVNC/vendor/pako/lib/zlib/deflate.js @@ -9,51 +9,51 @@ import msg from "./messages.js"; /* Allowed flush values; see deflate() and inflate() below for details */ -var Z_NO_FLUSH = 0; -var Z_PARTIAL_FLUSH = 1; -//var Z_SYNC_FLUSH = 2; -var Z_FULL_FLUSH = 3; -var Z_FINISH = 4; -var Z_BLOCK = 5; -//var Z_TREES = 6; +export const Z_NO_FLUSH = 0; +export const Z_PARTIAL_FLUSH = 1; +//export const Z_SYNC_FLUSH = 2; +export const Z_FULL_FLUSH = 3; +export const Z_FINISH = 4; +export const Z_BLOCK = 5; +//export const Z_TREES = 6; /* Return codes for the compression/decompression functions. Negative values * are errors, positive values are used for special but normal events. */ -var Z_OK = 0; -var Z_STREAM_END = 1; -//var Z_NEED_DICT = 2; -//var Z_ERRNO = -1; -var Z_STREAM_ERROR = -2; -var Z_DATA_ERROR = -3; -//var Z_MEM_ERROR = -4; -var Z_BUF_ERROR = -5; -//var Z_VERSION_ERROR = -6; +export const Z_OK = 0; +export const Z_STREAM_END = 1; +//export const Z_NEED_DICT = 2; +//export const Z_ERRNO = -1; +export const Z_STREAM_ERROR = -2; +export const Z_DATA_ERROR = -3; +//export const Z_MEM_ERROR = -4; +export const Z_BUF_ERROR = -5; +//export const Z_VERSION_ERROR = -6; /* compression levels */ -//var Z_NO_COMPRESSION = 0; -//var Z_BEST_SPEED = 1; -//var Z_BEST_COMPRESSION = 9; -var Z_DEFAULT_COMPRESSION = -1; +//export const Z_NO_COMPRESSION = 0; +//export const Z_BEST_SPEED = 1; +//export const Z_BEST_COMPRESSION = 9; +export const Z_DEFAULT_COMPRESSION = -1; -var Z_FILTERED = 1; -var Z_HUFFMAN_ONLY = 2; -var Z_RLE = 3; -var Z_FIXED = 4; -var Z_DEFAULT_STRATEGY = 0; +export const Z_FILTERED = 1; +export const Z_HUFFMAN_ONLY = 2; +export const Z_RLE = 3; +export const Z_FIXED = 4; +export const Z_DEFAULT_STRATEGY = 0; /* Possible values of the data_type field (though see inflate()) */ -//var Z_BINARY = 0; -//var Z_TEXT = 1; -//var Z_ASCII = 1; // = Z_TEXT -var Z_UNKNOWN = 2; +//export const Z_BINARY = 0; +//export const Z_TEXT = 1; +//export const Z_ASCII = 1; // = Z_TEXT +export const Z_UNKNOWN = 2; /* The deflate compression method */ -var Z_DEFLATED = 8; +export const Z_DEFLATED = 8; /*============================================================================*/ diff --git a/systemvm/agent/noVNC/vendor/pako/lib/zlib/inflate.js b/systemvm/agent/noVNC/vendor/pako/lib/zlib/inflate.js index b79b39632cf..1d2063bc405 100644 --- a/systemvm/agent/noVNC/vendor/pako/lib/zlib/inflate.js +++ b/systemvm/agent/noVNC/vendor/pako/lib/zlib/inflate.js @@ -13,30 +13,30 @@ var DISTS = 2; /* Allowed flush values; see deflate() and inflate() below for details */ -//var Z_NO_FLUSH = 0; -//var Z_PARTIAL_FLUSH = 1; -//var Z_SYNC_FLUSH = 2; -//var Z_FULL_FLUSH = 3; -var Z_FINISH = 4; -var Z_BLOCK = 5; -var Z_TREES = 6; +//export const Z_NO_FLUSH = 0; +//export const Z_PARTIAL_FLUSH = 1; +//export const Z_SYNC_FLUSH = 2; +//export const Z_FULL_FLUSH = 3; +export const Z_FINISH = 4; +export const Z_BLOCK = 5; +export const Z_TREES = 6; /* Return codes for the compression/decompression functions. Negative values * are errors, positive values are used for special but normal events. */ -var Z_OK = 0; -var Z_STREAM_END = 1; -var Z_NEED_DICT = 2; -//var Z_ERRNO = -1; -var Z_STREAM_ERROR = -2; -var Z_DATA_ERROR = -3; -var Z_MEM_ERROR = -4; -var Z_BUF_ERROR = -5; -//var Z_VERSION_ERROR = -6; +export const Z_OK = 0; +export const Z_STREAM_END = 1; +export const Z_NEED_DICT = 2; +//export const Z_ERRNO = -1; +export const Z_STREAM_ERROR = -2; +export const Z_DATA_ERROR = -3; +export const Z_MEM_ERROR = -4; +export const Z_BUF_ERROR = -5; +//export const Z_VERSION_ERROR = -6; /* The deflate compression method */ -var Z_DEFLATED = 8; +export const Z_DEFLATED = 8; /* STATES ====================================================================*/ diff --git a/systemvm/agent/noVNC/vnc.html b/systemvm/agent/noVNC/vnc.html index 212321bd7ff..a244a7df82e 100644 --- a/systemvm/agent/noVNC/vnc.html +++ b/systemvm/agent/noVNC/vnc.html @@ -4,7 +4,7 @@ + + @@ -57,23 +61,13 @@ - - + - +
@@ -91,68 +85,54 @@
-

no
VNC

+

no
VNC

- -
- - - - - +
-
- -
-
- - - - - - -
-
+ +
+
+ + + + + + +
-
- Power + Power
@@ -161,110 +141,132 @@
-
- Clipboard + Clipboard

+
- - -
-
-
    -
  • - Settings -
  • -
  • - -
  • -
  • - -
  • -

  • -
  • - -
  • -
  • - - -
  • -

  • -
  • -
    Advanced
    -
      -
    • - - -
    • -
    • -
      WebSocket
      -
        -
      • - -
      • -
      • - - -
      • -
      • - - -
      • -
      • - - -
      • -
      -
    • -

    • -
    • - -
    • -
    • - - -
    • -

    • -
    • - -
    • -

    • - -
    • - -
    • -
    -
  • -
-
-
+ + +
+
+
    +
  • + Settings +
  • +
  • + +
  • +
  • + +
  • +

  • +
  • + +
  • +
  • + + +
  • +

  • +
  • +
    Advanced
    +
      +
    • + + +
    • +
    • + + +
    • +

    • +
    • + + +
    • +
    • +
      WebSocket
      +
        +
      • + +
      • +
      • + + +
      • +
      • + + +
      • +
      • + + +
      • +
      • + + +
      • +
      +
    • +

    • +
    • + +
    • +
    • + + +
    • +

    • +
    • + +
    • +

    • + +
    • + +
    • +
    +
  • +

  • +
  • + Version: + +
  • +
+
+
+
- @@ -279,25 +281,29 @@
-
+
-
+
    -
  • +
  • + + +
  • +
  • - +
@@ -327,4 +333,9 @@ + diff --git a/systemvm/agent/noVNC/vnc_lite.html b/systemvm/agent/noVNC/vnc_lite.html index 12ac1d53b82..0be2b53845f 100644 --- a/systemvm/agent/noVNC/vnc_lite.html +++ b/systemvm/agent/noVNC/vnc_lite.html @@ -7,7 +7,7 @@ This is a self-contained file which doesn't import WebUtil or external CSS. - Copyright (C) 2018 The noVNC Authors + Copyright (C) 2019 The noVNC Authors noVNC is licensed under the MPL 2.0 (see LICENSE.txt) This file is licensed under the 2-Clause BSD license (see LICENSE.txt). @@ -18,6 +18,10 @@ + + +