Merge remote-tracking branch 'upstream/master' into add_clipboard_support

This commit is contained in:
Seth Nickell 2021-07-12 15:11:12 -10:00
commit 03dee3bfd8
98 changed files with 7255 additions and 128090 deletions

View File

@ -25,6 +25,7 @@
"brace-style": ["error", "1tbs", { "allowSingleLine": true }], "brace-style": ["error", "1tbs", { "allowSingleLine": true }],
"indent": ["error", 4, { "SwitchCase": 1, "indent": ["error", 4, { "SwitchCase": 1,
"FunctionDeclaration": { "parameters": "first" },
"CallExpression": { "arguments": "first" }, "CallExpression": { "arguments": "first" },
"ArrayExpression": "first", "ArrayExpression": "first",
"ObjectExpression": "first", "ObjectExpression": "first",
@ -44,5 +45,6 @@
"named": "never", "named": "never",
"asyncArrow": "always" }], "asyncArrow": "always" }],
"switch-colon-spacing": ["error"], "switch-colon-spacing": ["error"],
"camelcase": ["error", { allow: ["^XK_", "^XF86XK_"] }],
} }
} }

5
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Question or discussion
url: https://groups.google.com/forum/?fromgroups#!forum/novnc
about: Ask a question or start a discussion

56
.github/workflows/deploy.yml vendored Normal file
View File

@ -0,0 +1,56 @@
name: Publish
on:
push:
pull_request:
release:
types: [published]
jobs:
npm:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
# Needs to be explicitly specified for auth to work
registry-url: 'https://registry.npmjs.org'
- run: npm install
- uses: actions/upload-artifact@v2
with:
name: npm
path: lib
- run: npm publish --access public
env:
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
if: ${{ github.event_name == 'release' && !github.event.release.prerelease }}
- run: npm publish --access public --tag beta
env:
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
if: ${{ github.event_name == 'release' && github.event.release.prerelease }}
snap:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: |
VERSION=$(grep '"version"' package.json | cut -d '"' -f 4)
echo $VERSION
sed -i "s/@VERSION@/$VERSION/g" snap/snapcraft.yaml
- uses: snapcore/action-build@v1
id: snapcraft
- uses: actions/upload-artifact@v2
with:
name: snap
path: ${{ steps.snapcraft.outputs.snap }}
- uses: snapcore/action-publish@v1
with:
store_login: ${{ secrets.SNAPCRAFT_LOGIN }}
snap: ${{ steps.build.outputs.snap }}
release: stable
if: ${{ github.event_name == 'release' && !github.event.release.prerelease }}
- uses: snapcore/action-publish@v1
with:
store_login: ${{ secrets.SNAPCRAFT_LOGIN }}
snap: ${{ steps.build.outputs.snap }}
release: beta
if: ${{ github.event_name == 'release' && github.event.release.prerelease }}

19
.github/workflows/lint.yml vendored Normal file
View File

@ -0,0 +1,19 @@
name: Lint
on: [push, pull_request]
jobs:
eslint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
- run: npm install
- run: npm run lint
html:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
- run: npm install
- run: git ls-tree --name-only -r HEAD | grep -E "[.](html|css)$" | xargs ./utils/validate

28
.github/workflows/test.yml vendored Normal file
View File

@ -0,0 +1,28 @@
name: Test
on: [push, pull_request]
jobs:
test:
strategy:
matrix:
os:
- ubuntu-latest
- windows-latest
browser:
- ChromeHeadless
- FirefoxHeadless
include:
- os: macos-latest
browser: Safari
- os: windows-latest
browser: EdgeHeadless
fail-fast: false
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
- run: npm install
- run: npm run test
env:
TEST_BROWSER_NAME: ${{ matrix.browser }}

View File

@ -1,59 +0,0 @@
language: node_js
sudo: false
cache:
directories:
- node_modules
node_js:
- lts/*
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
skip_cleanup: true
email: ossman@cendio.se
api_key:
secure: "Qq2Mi9xQawO2zlAigzshzMu2QMHvu1IaN9l0ZIivE99wHJj7eS5f4miJ9wB+/mWRRgb3E8uj9ZRV24+Oc36drlBTU9sz+lHhH0uFMfAIseceK64wZV9sLAZm472fmPp2xdUeTCCqPaRy7g1XBqiJ0LyZvEFLsRijqcLjPBF+b8w="
on:
tags: true
repo: novnc/noVNC

View File

@ -42,12 +42,6 @@ licenses (all MPL 2.0 compatible):
vendor/pako/ : MIT 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 Any other files not mentioned above are typically marked with
a copyright/license header at the top of the file. The default noVNC a copyright/license header at the top of the file. The default noVNC
license is MPL-2.0. license is MPL-2.0.

View File

@ -1,6 +1,7 @@
## noVNC: HTML VNC Client Library and Application ## noVNC: HTML VNC Client Library and Application
[![Build Status](https://travis-ci.org/novnc/noVNC.svg?branch=master)](https://travis-ci.org/novnc/noVNC) [![Test Status](https://github.com/novnc/noVNC/workflows/Test/badge.svg)](https://github.com/novnc/noVNC/actions?query=workflow%3ATest)
[![Lint Status](https://github.com/novnc/noVNC/workflows/Lint/badge.svg)](https://github.com/novnc/noVNC/actions?query=workflow%3ALint)
### Description ### Description
@ -69,6 +70,7 @@ Please tweet [@noVNC](http://www.twitter.com/noVNC) if you do.
* Local cursor rendering * Local cursor rendering
* Clipboard copy/paste * Clipboard copy/paste
* Translations * Translations
* Touch gestures for emulating common mouse actions
* Licensed mainly under the [MPL 2.0](http://www.mozilla.org/MPL/2.0/), see * Licensed mainly under the [MPL 2.0](http://www.mozilla.org/MPL/2.0/), see
[the license document](LICENSE.txt) for details [the license document](LICENSE.txt) for details
@ -89,7 +91,7 @@ noVNC uses many modern web technologies so a formal requirement list is
not available. However these are the minimum versions we are currently not available. However these are the minimum versions we are currently
aware of: aware of:
* Chrome 49, Firefox 44, Safari 11, Opera 36, IE 11, Edge 12 * Chrome 49, Firefox 44, Safari 11, Opera 36, Edge 79
### Server Requirements ### Server Requirements
@ -106,13 +108,13 @@ proxy.
### Quick Start ### Quick Start
* Use the launch script to automatically download and start websockify, which * Use the `novnc_proxy` script to automatically download and start websockify, which
includes a mini-webserver and the WebSockets proxy. The `--vnc` option is includes a mini-webserver and the WebSockets proxy. The `--vnc` option is
used to specify the location of a running VNC server: used to specify the location of a running VNC server:
`./utils/launch.sh --vnc localhost:5901` `./utils/novnc_proxy --vnc localhost:5901`
* Point your browser to the cut-and-paste URL that is output by the launch * Point your browser to the cut-and-paste URL that is output by the `novnc_procy`
script. Hit the Connect button, enter a password if the VNC server has one script. Hit the Connect button, enter a password if the VNC server has one
configured, and enjoy! configured, and enjoy!

View File

@ -1 +0,0 @@
1.1.0

View File

@ -1,92 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="25"
height="25"
viewBox="0 0 25 25"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="mouse_left.svg"
inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90">
<defs
id="defs4" />
<sodipodi:namedview
id="base"
pagecolor="#959595"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:zoom="11.313708"
inkscape:cx="15.551515"
inkscape:cy="12.205592"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
units="px"
inkscape:snap-bbox="true"
inkscape:bbox-paths="true"
inkscape:bbox-nodes="true"
inkscape:snap-bbox-edge-midpoints="true"
inkscape:object-paths="true"
showguides="true"
inkscape:window-width="1920"
inkscape:window-height="1136"
inkscape:window-x="1920"
inkscape:window-y="27"
inkscape:window-maximized="1"
inkscape:snap-smooth-nodes="true"
inkscape:object-nodes="true"
inkscape:snap-intersection-paths="true"
inkscape:snap-nodes="true"
inkscape:snap-global="true">
<inkscape:grid
type="xygrid"
id="grid4136" />
</sodipodi:namedview>
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-1027.3622)">
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#0068f6;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 8,1030.3622 c -2.1987124,0 -4,1.8013 -4,4 l 0,2 5,0 0,-2 c 0,-1.4738 1.090393,-2.7071 2.5,-2.9492 l 0,-1.0508 -3.5,0 z"
id="path6219" />
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 13.5,1030.3622 0,1.0508 c 1.409607,0.2421 2.5,1.4754 2.5,2.9492 l 0,2 5,0 0,-2 c 0,-2.1987 -1.801288,-4 -4,-4 l -3.5,0 z"
id="path6217" />
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 12,1033.3622 c -0.571311,0 -1,0.4287 -1,1 l 0,5 c 0,0.5713 0.428689,1 1,1 l 1,0 c 0.571311,0 1,-0.4287 1,-1 l 0,-5 c 0,-0.5713 -0.428689,-1 -1,-1 l -1,0 z"
id="path6215" />
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 4,1038.3622 0,3.5 c 0,4.1377 3.362302,7.5 7.5,7.5 l 2,0 c 4.137698,0 7.5,-3.3623 7.5,-7.5 l 0,-3.5 -5,0 0,1 c 0,1.6447 -1.355293,3 -3,3 l -1,0 c -1.644707,0 -3,-1.3553 -3,-3 l 0,-1 -5,0 z"
id="rect6178" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 6.8 KiB

View File

@ -1,92 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="25"
height="25"
viewBox="0 0 25 25"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="mouse_middle.svg"
inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90">
<defs
id="defs4" />
<sodipodi:namedview
id="base"
pagecolor="#959595"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:zoom="11.313708"
inkscape:cx="15.551515"
inkscape:cy="12.205592"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
units="px"
inkscape:snap-bbox="true"
inkscape:bbox-paths="true"
inkscape:bbox-nodes="true"
inkscape:snap-bbox-edge-midpoints="true"
inkscape:object-paths="true"
showguides="true"
inkscape:window-width="1920"
inkscape:window-height="1136"
inkscape:window-x="1920"
inkscape:window-y="27"
inkscape:window-maximized="1"
inkscape:snap-smooth-nodes="true"
inkscape:object-nodes="true"
inkscape:snap-intersection-paths="true"
inkscape:snap-nodes="true"
inkscape:snap-global="true">
<inkscape:grid
type="xygrid"
id="grid4136" />
</sodipodi:namedview>
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-1027.3622)">
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 8,1030.3622 c -2.1987124,0 -4,1.8013 -4,4 l 0,2 5,0 0,-2 c 0,-1.4738 1.090393,-2.7071 2.5,-2.9492 l 0,-1.0508 -3.5,0 z"
id="path6219" />
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 13.5,1030.3622 0,1.0508 c 1.409607,0.2421 2.5,1.4754 2.5,2.9492 l 0,2 5,0 0,-2 c 0,-2.1987 -1.801288,-4 -4,-4 l -3.5,0 z"
id="path6217" />
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#0068f6;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 12,1033.3622 c -0.571311,0 -1,0.4287 -1,1 l 0,5 c 0,0.5713 0.428689,1 1,1 l 1,0 c 0.571311,0 1,-0.4287 1,-1 l 0,-5 c 0,-0.5713 -0.428689,-1 -1,-1 l -1,0 z"
id="path6215" />
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 4,1038.3622 0,3.5 c 0,4.1377 3.362302,7.5 7.5,7.5 l 2,0 c 4.137698,0 7.5,-3.3623 7.5,-7.5 l 0,-3.5 -5,0 0,1 c 0,1.6447 -1.355293,3 -3,3 l -1,0 c -1.644707,0 -3,-1.3553 -3,-3 l 0,-1 -5,0 z"
id="rect6178" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 6.8 KiB

View File

@ -1,92 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="25"
height="25"
viewBox="0 0 25 25"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="mouse_none.svg"
inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90">
<defs
id="defs4" />
<sodipodi:namedview
id="base"
pagecolor="#959595"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:zoom="16"
inkscape:cx="23.160825"
inkscape:cy="13.208262"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
units="px"
inkscape:snap-bbox="true"
inkscape:bbox-paths="true"
inkscape:bbox-nodes="true"
inkscape:snap-bbox-edge-midpoints="true"
inkscape:object-paths="true"
showguides="true"
inkscape:window-width="1920"
inkscape:window-height="1136"
inkscape:window-x="1920"
inkscape:window-y="27"
inkscape:window-maximized="1"
inkscape:snap-smooth-nodes="true"
inkscape:object-nodes="true"
inkscape:snap-intersection-paths="true"
inkscape:snap-nodes="true"
inkscape:snap-global="true">
<inkscape:grid
type="xygrid"
id="grid4136" />
</sodipodi:namedview>
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-1027.3622)">
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 8,1030.3622 c -2.1987124,0 -4,1.8013 -4,4 l 0,2 5,0 0,-2 c 0,-1.4738 1.090393,-2.7071 2.5,-2.9492 l 0,-1.0508 -3.5,0 z"
id="path6219" />
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 13.5,1030.3622 0,1.0508 c 1.409607,0.2421 2.5,1.4754 2.5,2.9492 l 0,2 5,0 0,-2 c 0,-2.1987 -1.801288,-4 -4,-4 l -3.5,0 z"
id="path6217" />
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 12,1033.3622 c -0.571311,0 -1,0.4287 -1,1 l 0,5 c 0,0.5713 0.428689,1 1,1 l 1,0 c 0.571311,0 1,-0.4287 1,-1 l 0,-5 c 0,-0.5713 -0.428689,-1 -1,-1 l -1,0 z"
id="path6215" />
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 4,1038.3622 0,3.5 c 0,4.1377 3.362302,7.5 7.5,7.5 l 2,0 c 4.137698,0 7.5,-3.3623 7.5,-7.5 l 0,-3.5 -5,0 0,1 c 0,1.6447 -1.355293,3 -3,3 l -1,0 c -1.644707,0 -3,-1.3553 -3,-3 l 0,-1 -5,0 z"
id="rect6178" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 6.8 KiB

View File

@ -1,92 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="25"
height="25"
viewBox="0 0 25 25"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="mouse_right.svg"
inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90">
<defs
id="defs4" />
<sodipodi:namedview
id="base"
pagecolor="#959595"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:zoom="11.313708"
inkscape:cx="15.551515"
inkscape:cy="12.205592"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
units="px"
inkscape:snap-bbox="true"
inkscape:bbox-paths="true"
inkscape:bbox-nodes="true"
inkscape:snap-bbox-edge-midpoints="true"
inkscape:object-paths="true"
showguides="true"
inkscape:window-width="1920"
inkscape:window-height="1136"
inkscape:window-x="1920"
inkscape:window-y="27"
inkscape:window-maximized="1"
inkscape:snap-smooth-nodes="true"
inkscape:object-nodes="true"
inkscape:snap-intersection-paths="true"
inkscape:snap-nodes="true"
inkscape:snap-global="true">
<inkscape:grid
type="xygrid"
id="grid4136" />
</sodipodi:namedview>
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-1027.3622)">
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 8,1030.3622 c -2.1987124,0 -4,1.8013 -4,4 l 0,2 5,0 0,-2 c 0,-1.4738 1.090393,-2.7071 2.5,-2.9492 l 0,-1.0508 -3.5,0 z"
id="path6219" />
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#0068f6;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 13.5,1030.3622 0,1.0508 c 1.409607,0.2421 2.5,1.4754 2.5,2.9492 l 0,2 5,0 0,-2 c 0,-2.1987 -1.801288,-4 -4,-4 l -3.5,0 z"
id="path6217" />
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 12,1033.3622 c -0.571311,0 -1,0.4287 -1,1 l 0,5 c 0,0.5713 0.428689,1 1,1 l 1,0 c 0.571311,0 1,-0.4287 1,-1 l 0,-5 c 0,-0.5713 -0.428689,-1 -1,-1 l -1,0 z"
id="path6215" />
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 4,1038.3622 0,3.5 c 0,4.1377 3.362302,7.5 7.5,7.5 l 2,0 c 4.137698,0 7.5,-3.3623 7.5,-7.5 l 0,-3.5 -5,0 0,1 c 0,1.6447 -1.355293,3 -3,3 l -1,0 c -1.644707,0 -3,-1.3553 -3,-3 l 0,-1 -5,0 z"
id="rect6178" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 6.8 KiB

View File

@ -6,21 +6,16 @@
"Must set host": "ホストを設定する必要があります", "Must set host": "ホストを設定する必要があります",
"Connected (encrypted) to ": "接続しました (暗号化済み): ", "Connected (encrypted) to ": "接続しました (暗号化済み): ",
"Connected (unencrypted) to ": "接続しました (暗号化されていません): ", "Connected (unencrypted) to ": "接続しました (暗号化されていません): ",
"Something went wrong, connection is closed": "何かが問題で、接続が閉じられました", "Something went wrong, connection is closed": "何らかの問題で、接続が閉じられました",
"Failed to connect to server": "サーバーへの接続に失敗しました", "Failed to connect to server": "サーバーへの接続に失敗しました",
"Disconnected": "切断しました", "Disconnected": "切断しました",
"New connection has been rejected with reason: ": "新規接続は次の理由で拒否されました: ", "New connection has been rejected with reason: ": "新規接続は次の理由で拒否されました: ",
"New connection has been rejected": "新規接続は拒否されました", "New connection has been rejected": "新規接続は拒否されました",
"Password is required": "パスワードが必要です", "Credentials are required": "資格情報が必要です",
"noVNC encountered an error:": "noVNC でエラーが発生しました:", "noVNC encountered an error:": "noVNC でエラーが発生しました:",
"Hide/Show the control bar": "コントロールバーを隠す/表示する", "Hide/Show the control bar": "コントロールバーを隠す/表示する",
"Drag": "ドラッグ",
"Move/Drag Viewport": "ビューポートを移動/ドラッグ", "Move/Drag Viewport": "ビューポートを移動/ドラッグ",
"viewport drag": "ビューポートをドラッグ",
"Active Mouse Button": "アクティブなマウスボタン",
"No mousebutton": "マウスボタンなし",
"Left mousebutton": "左マウスボタン",
"Middle mousebutton": "中マウスボタン",
"Right mousebutton": "右マウスボタン",
"Keyboard": "キーボード", "Keyboard": "キーボード",
"Show Keyboard": "キーボードを表示", "Show Keyboard": "キーボードを表示",
"Extra keys": "追加キー", "Extra keys": "追加キー",
@ -55,6 +50,8 @@
"Local Scaling": "ローカルスケーリング", "Local Scaling": "ローカルスケーリング",
"Remote Resizing": "リモートでリサイズ", "Remote Resizing": "リモートでリサイズ",
"Advanced": "高度", "Advanced": "高度",
"Quality:": "品質:",
"Compression level:": "圧縮レベル:",
"Repeater ID:": "リピーター ID:", "Repeater ID:": "リピーター ID:",
"WebSocket": "WebSocket", "WebSocket": "WebSocket",
"Encrypt": "暗号化", "Encrypt": "暗号化",
@ -65,9 +62,11 @@
"Reconnect Delay (ms):": "再接続する遅延 (ミリ秒):", "Reconnect Delay (ms):": "再接続する遅延 (ミリ秒):",
"Show Dot when No Cursor": "カーソルがないときにドットを表示", "Show Dot when No Cursor": "カーソルがないときにドットを表示",
"Logging:": "ロギング:", "Logging:": "ロギング:",
"Version:": "バージョン:",
"Disconnect": "切断", "Disconnect": "切断",
"Connect": "接続", "Connect": "接続",
"Username:": "ユーザー名:",
"Password:": "パスワード:", "Password:": "パスワード:",
"Send Password": "パスワードを送信", "Send Credentials": "資格情報を送信",
"Cancel": "キャンセル" "Cancel": "キャンセル"
} }

72
app/locale/pt_BR.json Normal file
View File

@ -0,0 +1,72 @@
{
"Connecting...": "Conectando...",
"Disconnecting...": "Desconectando...",
"Reconnecting...": "Reconectando...",
"Internal error": "Erro interno",
"Must set host": "É necessário definir o host",
"Connected (encrypted) to ": "Conectado (com criptografia) a ",
"Connected (unencrypted) to ": "Conectado (sem criptografia) a ",
"Something went wrong, connection is closed": "Algo deu errado. A conexão foi encerrada.",
"Failed to connect to server": "Falha ao conectar-se ao servidor",
"Disconnected": "Desconectado",
"New connection has been rejected with reason: ": "A nova conexão foi rejeitada pelo motivo: ",
"New connection has been rejected": "A nova conexão foi rejeitada",
"Credentials are required": "Credenciais são obrigatórias",
"noVNC encountered an error:": "O noVNC encontrou um erro:",
"Hide/Show the control bar": "Esconder/mostrar a barra de controles",
"Drag": "Arrastar",
"Move/Drag Viewport": "Mover/arrastar a janela",
"Keyboard": "Teclado",
"Show Keyboard": "Mostrar teclado",
"Extra keys": "Teclas adicionais",
"Show Extra Keys": "Mostar teclas adicionais",
"Ctrl": "Ctrl",
"Toggle Ctrl": "Pressionar/soltar Ctrl",
"Alt": "Alt",
"Toggle Alt": "Pressionar/soltar Alt",
"Toggle Windows": "Pressionar/soltar Windows",
"Windows": "Windows",
"Send Tab": "Enviar Tab",
"Tab": "Tab",
"Esc": "Esc",
"Send Escape": "Enviar Esc",
"Ctrl+Alt+Del": "Ctrl+Alt+Del",
"Send Ctrl-Alt-Del": "Enviar Ctrl-Alt-Del",
"Shutdown/Reboot": "Desligar/reiniciar",
"Shutdown/Reboot...": "Desligar/reiniciar...",
"Power": "Ligar",
"Shutdown": "Desligar",
"Reboot": "Reiniciar",
"Reset": "Reiniciar (forçado)",
"Clipboard": "Área de transferência",
"Clear": "Limpar",
"Fullscreen": "Tela cheia",
"Settings": "Configurações",
"Shared Mode": "Modo compartilhado",
"View Only": "Apenas visualizar",
"Clip to Window": "Recortar à janela",
"Scaling Mode:": "Modo de dimensionamento:",
"None": "Nenhum",
"Local Scaling": "Local",
"Remote Resizing": "Remoto",
"Advanced": "Avançado",
"Quality:": "Qualidade:",
"Compression level:": "Nível de compressão:",
"Repeater ID:": "ID do repetidor:",
"WebSocket": "WebSocket",
"Encrypt": "Criptografar",
"Host:": "Host:",
"Port:": "Porta:",
"Path:": "Caminho:",
"Automatic Reconnect": "Reconexão automática",
"Reconnect Delay (ms):": "Atraso da reconexão (ms)",
"Show Dot when No Cursor": "Mostrar ponto quando não há cursor",
"Logging:": "Registros:",
"Version:": "Versão:",
"Disconnect": "Desconectar",
"Connect": "Conectar",
"Username:": "Nome de usuário:",
"Password:": "Senha:",
"Send Credentials": "Enviar credenciais",
"Cancel": "Cancelar"
}

View File

@ -11,16 +11,11 @@
"Disconnected": "Frånkopplad", "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 with reason: ": "Ny anslutning har blivit nekad med följande skäl: ",
"New connection has been rejected": "Ny anslutning har blivit nekad", "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:", "noVNC encountered an error:": "noVNC stötte på ett problem:",
"Hide/Show the control bar": "Göm/Visa kontrollbaren", "Hide/Show the control bar": "Göm/Visa kontrollbaren",
"Drag": "Dra",
"Move/Drag Viewport": "Flytta/Dra Vyn", "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", "Keyboard": "Tangentbord",
"Show Keyboard": "Visa Tangentbord", "Show Keyboard": "Visa Tangentbord",
"Extra keys": "Extraknappar", "Extra keys": "Extraknappar",
@ -55,6 +50,8 @@
"Local Scaling": "Lokal Skalning", "Local Scaling": "Lokal Skalning",
"Remote Resizing": "Ändra Storlek", "Remote Resizing": "Ändra Storlek",
"Advanced": "Avancerat", "Advanced": "Avancerat",
"Quality:": "Kvalitet:",
"Compression level:": "Kompressionsnivå:",
"Repeater ID:": "Repeater-ID:", "Repeater ID:": "Repeater-ID:",
"WebSocket": "WebSocket", "WebSocket": "WebSocket",
"Encrypt": "Kryptera", "Encrypt": "Kryptera",
@ -65,9 +62,11 @@
"Reconnect Delay (ms):": "Fördröjning (ms):", "Reconnect Delay (ms):": "Fördröjning (ms):",
"Show Dot when No Cursor": "Visa prick när ingen muspekare finns", "Show Dot when No Cursor": "Visa prick när ingen muspekare finns",
"Logging:": "Loggning:", "Logging:": "Loggning:",
"Version:": "Version:",
"Disconnect": "Koppla från", "Disconnect": "Koppla från",
"Connect": "Anslut", "Connect": "Anslut",
"Username:": "Användarnamn:",
"Password:": "Lösenord:", "Password:": "Lösenord:",
"Send Password": "Skicka lösenord", "Send Credentials": "Skicka Användaruppgifter",
"Cancel": "Avbryt" "Cancel": "Avbryt"
} }

View File

@ -1,19 +1,19 @@
{ {
"Connecting...": "接中...", "Connecting...": "接中...",
"Disconnecting...": "正在断连接...", "Disconnecting...": "正在连接...",
"Reconnecting...": "重新接中...", "Reconnecting...": "重新接中...",
"Internal error": "内部错误", "Internal error": "内部错误",
"Must set host": "请提供主机名", "Must set host": "请提供主机名",
"Connected (encrypted) to ": "已加密链接到", "Connected (encrypted) to ": "已连接到(加密)",
"Connected (unencrypted) to ": "未加密链接到", "Connected (unencrypted) to ": "已连接到(未加密)",
"Something went wrong, connection is closed": "发生错误,接已关闭", "Something went wrong, connection is closed": "发生错误,接已关闭",
"Failed to connect to server": "无法接到服务器", "Failed to connect to server": "无法接到服务器",
"Disconnected": "链接断", "Disconnected": "已断开连接",
"New connection has been rejected with reason: ": "接被拒绝,原因:", "New connection has been rejected with reason: ": "接被拒绝,原因:",
"New connection has been rejected": "接被拒绝", "New connection has been rejected": "接被拒绝",
"Password is required": "请提供密码", "Password is required": "请提供密码",
"noVNC encountered an error:": "noVNC 遇到一个错误:", "noVNC encountered an error:": "noVNC 遇到一个错误:",
"Hide/Show the control bar": "显示/隐藏控制", "Hide/Show the control bar": "显示/隐藏控制",
"Move/Drag Viewport": "拖放显示范围", "Move/Drag Viewport": "拖放显示范围",
"viewport drag": "显示范围拖放", "viewport drag": "显示范围拖放",
"Active Mouse Button": "启动鼠标按鍵", "Active Mouse Button": "启动鼠标按鍵",
@ -43,10 +43,10 @@
"Reset": "重置", "Reset": "重置",
"Clipboard": "剪贴板", "Clipboard": "剪贴板",
"Clear": "清除", "Clear": "清除",
"Fullscreen": "全屏", "Fullscreen": "全屏",
"Settings": "设置", "Settings": "设置",
"Shared Mode": "分享模式", "Shared Mode": "分享模式",
"View Only": "仅检视", "View Only": "仅查看",
"Clip to Window": "限制/裁切窗口大小", "Clip to Window": "限制/裁切窗口大小",
"Scaling Mode:": "缩放模式:", "Scaling Mode:": "缩放模式:",
"None": "无", "None": "无",
@ -59,11 +59,11 @@
"Host:": "主机:", "Host:": "主机:",
"Port:": "端口:", "Port:": "端口:",
"Path:": "路径:", "Path:": "路径:",
"Automatic Reconnect": "自动重新接", "Automatic Reconnect": "自动重新接",
"Reconnect Delay (ms):": "重新接间隔 (ms)", "Reconnect Delay (ms):": "重新接间隔 (ms)",
"Logging:": "日志级别:", "Logging:": "日志级别:",
"Disconnect": "终端链接", "Disconnect": "中断连接",
"Connect": "接", "Connect": "接",
"Password:": "密码:", "Password:": "密码:",
"Cancel": "取消" "Cancel": "取消"
} }

View File

@ -635,7 +635,7 @@ select:active {
} }
/* Extra manual keys */ /* Extra manual keys */
:root:not(.noVNC_connected) #noVNC_extra_keys { :root:not(.noVNC_connected) #noVNC_toggle_extra_keys_button {
display: none; display: none;
} }
@ -663,9 +663,6 @@ select:active {
:root:not(.noVNC_connected) #noVNC_clipboard_button { :root:not(.noVNC_connected) #noVNC_clipboard_button {
display: none; display: none;
} }
:root:not(.noVNC_connected) #noVNC_clipboard {
display: none;
}
#noVNC_clipboard { #noVNC_clipboard {
/* Full screen, minus padding and left and right margins */ /* Full screen, minus padding and left and right margins */
max-width: calc(100vw - 2*15px - 75px - 25px); max-width: calc(100vw - 2*15px - 75px - 25px);

112
app/ui.js
View File

@ -37,9 +37,9 @@ const UI = {
lastKeyboardinput: null, lastKeyboardinput: null,
defaultKeyboardinputLen: 100, defaultKeyboardinputLen: 100,
inhibit_reconnect: true, inhibitReconnect: true,
reconnect_callback: null, reconnectCallback: null,
reconnect_password: null, reconnectPassword: null,
prime() { prime() {
return WebUtil.initSettings().then(() => { return WebUtil.initSettings().then(() => {
@ -61,7 +61,13 @@ const UI = {
// Translate the DOM // Translate the DOM
l10n.translateDOM(); l10n.translateDOM();
WebUtil.fetchJSON('./package.json') fetch('./package.json')
.then((response) => {
if (!response.ok) {
throw Error("" + response.status + " " + response.statusText);
}
return response.json();
})
.then((packageInfo) => { .then((packageInfo) => {
Array.from(document.getElementsByClassName('noVNC_version')).forEach(el => el.innerText = packageInfo.version); Array.from(document.getElementsByClassName('noVNC_version')).forEach(el => el.innerText = packageInfo.version);
}) })
@ -234,14 +240,6 @@ const UI = {
}, },
addTouchSpecificHandlers() { 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") document.getElementById("noVNC_keyboard_button")
.addEventListener('click', UI.toggleVirtualKeyboard); .addEventListener('click', UI.toggleVirtualKeyboard);
@ -394,25 +392,25 @@ const UI = {
document.documentElement.classList.remove("noVNC_disconnecting"); document.documentElement.classList.remove("noVNC_disconnecting");
document.documentElement.classList.remove("noVNC_reconnecting"); document.documentElement.classList.remove("noVNC_reconnecting");
const transition_elem = document.getElementById("noVNC_transition_text"); const transitionElem = document.getElementById("noVNC_transition_text");
switch (state) { switch (state) {
case 'init': case 'init':
break; break;
case 'connecting': case 'connecting':
transition_elem.textContent = _("Connecting..."); transitionElem.textContent = _("Connecting...");
document.documentElement.classList.add("noVNC_connecting"); document.documentElement.classList.add("noVNC_connecting");
break; break;
case 'connected': case 'connected':
document.documentElement.classList.add("noVNC_connected"); document.documentElement.classList.add("noVNC_connected");
break; break;
case 'disconnecting': case 'disconnecting':
transition_elem.textContent = _("Disconnecting..."); transitionElem.textContent = _("Disconnecting...");
document.documentElement.classList.add("noVNC_disconnecting"); document.documentElement.classList.add("noVNC_disconnecting");
break; break;
case 'disconnected': case 'disconnected':
break; break;
case 'reconnecting': case 'reconnecting':
transition_elem.textContent = _("Reconnecting..."); transitionElem.textContent = _("Reconnecting...");
document.documentElement.classList.add("noVNC_reconnecting"); document.documentElement.classList.add("noVNC_reconnecting");
break; break;
default: default:
@ -430,7 +428,6 @@ const UI = {
UI.disableSetting('port'); UI.disableSetting('port');
UI.disableSetting('path'); UI.disableSetting('path');
UI.disableSetting('repeaterID'); UI.disableSetting('repeaterID');
UI.setMouseButton(1);
// Hide the controlbar after 2 seconds // Hide the controlbar after 2 seconds
UI.closeControlbarTimeout = setTimeout(UI.closeControlbar, 2000); UI.closeControlbarTimeout = setTimeout(UI.closeControlbar, 2000);
@ -445,16 +442,18 @@ const UI = {
UI.keepControlbar(); UI.keepControlbar();
} }
// State change closes the password dialog // State change closes dialogs as they may not be relevant
// anymore
UI.closeAllPanels();
document.getElementById('noVNC_credentials_dlg') document.getElementById('noVNC_credentials_dlg')
.classList.remove('noVNC_open'); .classList.remove('noVNC_open');
}, },
showStatus(text, status_type, time) { showStatus(text, statusType, time) {
const statusElem = document.getElementById('noVNC_status'); const statusElem = document.getElementById('noVNC_status');
if (typeof status_type === 'undefined') { if (typeof statusType === 'undefined') {
status_type = 'normal'; statusType = 'normal';
} }
// Don't overwrite more severe visible statuses and never // Don't overwrite more severe visible statuses and never
@ -464,14 +463,14 @@ const UI = {
return; return;
} }
if (statusElem.classList.contains("noVNC_status_warn") && if (statusElem.classList.contains("noVNC_status_warn") &&
status_type === 'normal') { statusType === 'normal') {
return; return;
} }
} }
clearTimeout(UI.statusTimeout); clearTimeout(UI.statusTimeout);
switch (status_type) { switch (statusType) {
case 'error': case 'error':
statusElem.classList.remove("noVNC_status_warn"); statusElem.classList.remove("noVNC_status_warn");
statusElem.classList.remove("noVNC_status_normal"); statusElem.classList.remove("noVNC_status_normal");
@ -501,7 +500,7 @@ const UI = {
} }
// Error messages do not timeout // Error messages do not timeout
if (status_type !== 'error') { if (statusType !== 'error') {
UI.statusTimeout = window.setTimeout(UI.hideStatus, time); UI.statusTimeout = window.setTimeout(UI.hideStatus, time);
} }
}, },
@ -762,11 +761,6 @@ const UI = {
} }
} }
} else { } else {
/*Weird IE9 error leads to 'null' appearring
in textboxes instead of ''.*/
if (value === null) {
value = "";
}
ctrl.value = value; ctrl.value = value;
} }
}, },
@ -1001,7 +995,7 @@ const UI = {
if (typeof password === 'undefined') { if (typeof password === 'undefined') {
password = WebUtil.getConfigVar('password'); password = WebUtil.getConfigVar('password');
UI.reconnect_password = password; UI.reconnectPassword = password;
} }
if (password === null) { if (password === null) {
@ -1016,7 +1010,6 @@ const UI = {
return; return;
} }
UI.closeAllPanels();
UI.closeConnectPanel(); UI.closeConnectPanel();
UI.updateVisualState('connecting'); UI.updateVisualState('connecting');
@ -1054,13 +1047,12 @@ const UI = {
}, },
disconnect() { disconnect() {
UI.closeAllPanels();
UI.rfb.disconnect(); UI.rfb.disconnect();
UI.connected = false; UI.connected = false;
// Disable automatic reconnecting // Disable automatic reconnecting
UI.inhibit_reconnect = true; UI.inhibitReconnect = true;
UI.updateVisualState('disconnecting'); UI.updateVisualState('disconnecting');
@ -1068,20 +1060,20 @@ const UI = {
}, },
reconnect() { reconnect() {
UI.reconnect_callback = null; UI.reconnectCallback = null;
// if reconnect has been disabled in the meantime, do nothing. // if reconnect has been disabled in the meantime, do nothing.
if (UI.inhibit_reconnect) { if (UI.inhibitReconnect) {
return; return;
} }
UI.connect(null, UI.reconnect_password); UI.connect(null, UI.reconnectPassword);
}, },
cancelReconnect() { cancelReconnect() {
if (UI.reconnect_callback !== null) { if (UI.reconnectCallback !== null) {
clearTimeout(UI.reconnect_callback); clearTimeout(UI.reconnectCallback);
UI.reconnect_callback = null; UI.reconnectCallback = null;
} }
UI.updateVisualState('disconnected'); UI.updateVisualState('disconnected');
@ -1092,7 +1084,7 @@ const UI = {
connectFinished(e) { connectFinished(e) {
UI.connected = true; UI.connected = true;
UI.inhibit_reconnect = false; UI.inhibitReconnect = false;
let msg; let msg;
if (UI.getSetting('encrypt')) { if (UI.getSetting('encrypt')) {
@ -1126,11 +1118,11 @@ const UI = {
} else { } else {
UI.showStatus(_("Failed to connect to server"), 'error'); 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'); UI.updateVisualState('reconnecting');
const delay = parseInt(UI.getSetting('reconnect_delay')); const delay = parseInt(UI.getSetting('reconnect_delay'));
UI.reconnect_callback = setTimeout(UI.reconnect, delay); UI.reconnectCallback = setTimeout(UI.reconnect, delay);
return; return;
} else { } else {
UI.updateVisualState('disconnected'); UI.updateVisualState('disconnected');
@ -1203,7 +1195,7 @@ const UI = {
inputElemPassword.value = ""; inputElemPassword.value = "";
UI.rfb.sendCredentials({ username: username, password: password }); UI.rfb.sendCredentials({ username: username, password: password });
UI.reconnect_password = password; UI.reconnectPassword = password;
document.getElementById('noVNC_credentials_dlg') document.getElementById('noVNC_credentials_dlg')
.classList.remove('noVNC_open'); .classList.remove('noVNC_open');
}, },
@ -1633,24 +1625,6 @@ const UI = {
* MISC * MISC
* ------v------*/ * ------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() { updateViewOnly() {
if (!UI.rfb) return; if (!UI.rfb) return;
UI.rfb.viewOnly = UI.getSetting('view_only'); UI.rfb.viewOnly = UI.getSetting('view_only');
@ -1661,8 +1635,6 @@ const UI = {
.classList.add('noVNC_hidden'); .classList.add('noVNC_hidden');
document.getElementById('noVNC_toggle_extra_keys_button') document.getElementById('noVNC_toggle_extra_keys_button')
.classList.add('noVNC_hidden'); .classList.add('noVNC_hidden');
document.getElementById('noVNC_mouse_button' + UI.rfb.touchButton)
.classList.add('noVNC_hidden');
document.getElementById('noVNC_clipboard_button') document.getElementById('noVNC_clipboard_button')
.classList.add('noVNC_hidden'); .classList.add('noVNC_hidden');
} else { } else {
@ -1670,8 +1642,6 @@ const UI = {
.classList.remove('noVNC_hidden'); .classList.remove('noVNC_hidden');
document.getElementById('noVNC_toggle_extra_keys_button') document.getElementById('noVNC_toggle_extra_keys_button')
.classList.remove('noVNC_hidden'); .classList.remove('noVNC_hidden');
document.getElementById('noVNC_mouse_button' + UI.rfb.touchButton)
.classList.remove('noVNC_hidden');
document.getElementById('noVNC_clipboard_button') document.getElementById('noVNC_clipboard_button')
.classList.remove('noVNC_hidden'); .classList.remove('noVNC_hidden');
} }
@ -1683,7 +1653,7 @@ const UI = {
}, },
updateLogging() { updateLogging() {
WebUtil.init_logging(UI.getSetting('logging')); WebUtil.initLogging(UI.getSetting('logging'));
}, },
updateDesktopName(e) { updateDesktopName(e) {
@ -1725,12 +1695,18 @@ const UI = {
}; };
// Set up translations // Set up translations
const LINGUAS = ["cs", "de", "el", "es", "ja", "ko", "nl", "pl", "ru", "sv", "tr", "zh_CN", "zh_TW"]; const LINGUAS = ["cs", "de", "el", "es", "fr", "ja", "ko", "nl", "pl", "pt_BR", "ru", "sv", "tr", "zh_CN", "zh_TW"];
l10n.setup(LINGUAS); l10n.setup(LINGUAS);
if (l10n.language === "en" || l10n.dictionary !== undefined) { if (l10n.language === "en" || l10n.dictionary !== undefined) {
UI.prime(); UI.prime();
} else { } else {
WebUtil.fetchJSON('app/locale/' + l10n.language + '.json') fetch('app/locale/' + l10n.language + '.json')
.then((response) => {
if (!response.ok) {
throw Error("" + response.status + " " + response.statusText);
}
return response.json();
})
.then((translations) => { l10n.dictionary = translations; }) .then((translations) => { l10n.dictionary = translations; })
.catch(err => Log.Error("Failed to load translations: " + err)) .catch(err => Log.Error("Failed to load translations: " + err))
.then(UI.prime); .then(UI.prime);

View File

@ -6,16 +6,16 @@
* See README.md for usage and integration instructions. * 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 // init log level reading the logging HTTP param
export function init_logging(level) { export function initLogging(level) {
"use strict"; "use strict";
if (typeof level !== "undefined") { if (typeof level !== "undefined") {
main_init_logging(level); mainInitLogging(level);
} else { } else {
const param = document.location.href.match(/logging=([A-Za-z0-9._-]*)/); const param = document.location.href.match(/logging=([A-Za-z0-9._-]*)/);
main_init_logging(param || undefined); mainInitLogging(param || undefined);
} }
} }
@ -175,65 +175,3 @@ export function eraseSetting(name) {
localStorage.removeItem(name); localStorage.removeItem(name);
} }
} }
export function injectParamIfMissing(path, param, value) {
// force pretend that we're dealing with a relative path
// (assume that we wanted an extra if we pass one in)
path = "/" + path;
const elem = document.createElement('a');
elem.href = path;
const param_eq = encodeURIComponent(param) + "=";
let query;
if (elem.search) {
query = elem.search.slice(1).split('&');
} else {
query = [];
}
if (!query.some(v => v.startsWith(param_eq))) {
query.push(param_eq + encodeURIComponent(value));
elem.search = "?" + query.join("&");
}
// some browsers (e.g. IE11) may occasionally omit the leading slash
// in the elem.pathname string. Handle that case gracefully.
if (elem.pathname.charAt(0) == "/") {
return elem.pathname.slice(1) + elem.search + elem.hash;
}
return elem.pathname + elem.search + elem.hash;
}
// sadly, we can't use the Fetch API until we decide to drop
// IE11 support or polyfill promises and fetch in IE11.
// resolve will receive an object on success, while reject
// will receive either an event or an error on failure.
export function fetchJSON(path) {
return new Promise((resolve, reject) => {
// NB: IE11 doesn't support JSON as a responseType
const req = new XMLHttpRequest();
req.open('GET', path);
req.onload = () => {
if (req.status === 200) {
let resObj;
try {
resObj = JSON.parse(req.responseText);
} catch (err) {
reject(err);
}
resolve(resObj);
} else {
reject(new Error("XHR got non-200 status while trying to load '" + path + "': " + req.status));
}
};
req.onerror = evt => reject(new Error("XHR encountered an error while trying to load '" + path + "': " + evt.message));
req.ontimeout = evt => reject(new Error("XHR timed out while trying to load '" + path + "'"));
req.send();
});
}

View File

@ -57,12 +57,12 @@ export default {
/* eslint-enable comma-spacing */ /* eslint-enable comma-spacing */
decode(data, offset = 0) { decode(data, offset = 0) {
let data_length = data.indexOf('=') - offset; let dataLength = data.indexOf('=') - offset;
if (data_length < 0) { data_length = data.length - offset; } if (dataLength < 0) { dataLength = data.length - offset; }
/* Every four characters is 3 resulting numbers */ /* Every four characters is 3 resulting numbers */
const result_length = (data_length >> 2) * 3 + Math.floor((data_length % 4) / 1.5); const resultLength = (dataLength >> 2) * 3 + Math.floor((dataLength % 4) / 1.5);
const result = new Array(result_length); const result = new Array(resultLength);
// Convert one by one. // Convert one by one.

View File

@ -15,6 +15,11 @@ export default class CopyRectDecoder {
let deltaX = sock.rQshift16(); let deltaX = sock.rQshift16();
let deltaY = sock.rQshift16(); let deltaY = sock.rQshift16();
if ((width === 0) || (height === 0)) {
return true;
}
display.copyImage(deltaX, deltaY, x, y, width, height); display.copyImage(deltaX, deltaY, x, y, width, height);
return true; return true;

View File

@ -13,14 +13,15 @@ export default class HextileDecoder {
constructor() { constructor() {
this._tiles = 0; this._tiles = 0;
this._lastsubencoding = 0; this._lastsubencoding = 0;
this._tileBuffer = new Uint8Array(16 * 16 * 4);
} }
decodeRect(x, y, width, height, sock, display, depth) { decodeRect(x, y, width, height, sock, display, depth) {
if (this._tiles === 0) { if (this._tiles === 0) {
this._tiles_x = Math.ceil(width / 16); this._tilesX = Math.ceil(width / 16);
this._tiles_y = Math.ceil(height / 16); this._tilesY = Math.ceil(height / 16);
this._total_tiles = this._tiles_x * this._tiles_y; this._totalTiles = this._tilesX * this._tilesY;
this._tiles = this._total_tiles; this._tiles = this._totalTiles;
} }
while (this._tiles > 0) { while (this._tiles > 0) {
@ -39,11 +40,11 @@ export default class HextileDecoder {
subencoding + ")"); subencoding + ")");
} }
const curr_tile = this._total_tiles - this._tiles; const currTile = this._totalTiles - this._tiles;
const tile_x = curr_tile % this._tiles_x; const tileX = currTile % this._tilesX;
const tile_y = Math.floor(curr_tile / this._tiles_x); const tileY = Math.floor(currTile / this._tilesX);
const tx = x + tile_x * 16; const tx = x + tileX * 16;
const ty = y + tile_y * 16; const ty = y + tileY * 16;
const tw = Math.min(16, (x + width) - tx); const tw = Math.min(16, (x + width) - tx);
const th = Math.min(16, (y + height) - ty); const th = Math.min(16, (y + height) - ty);
@ -87,6 +88,11 @@ export default class HextileDecoder {
display.fillRect(tx, ty, tw, th, this._background); display.fillRect(tx, ty, tw, th, this._background);
} }
} else if (subencoding & 0x01) { // Raw } 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); display.blitImage(tx, ty, tw, th, rQ, rQi);
rQi += bytes - 1; rQi += bytes - 1;
} else { } else {
@ -99,7 +105,7 @@ export default class HextileDecoder {
rQi += 4; rQi += 4;
} }
display.startTile(tx, ty, tw, th, this._background); this._startTile(tx, ty, tw, th, this._background);
if (subencoding & 0x08) { // AnySubrects if (subencoding & 0x08) { // AnySubrects
let subrects = rQ[rQi]; let subrects = rQ[rQi];
rQi++; rQi++;
@ -122,10 +128,10 @@ export default class HextileDecoder {
const sw = (wh >> 4) + 1; const sw = (wh >> 4) + 1;
const sh = (wh & 0x0f) + 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; sock.rQi = rQi;
this._lastsubencoding = subencoding; this._lastsubencoding = subencoding;
@ -134,4 +140,52 @@ export default class HextileDecoder {
return true; 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);
}
} }

View File

@ -13,6 +13,10 @@ export default class RawDecoder {
} }
decodeRect(x, y, width, height, sock, display, depth) { decodeRect(x, y, width, height, sock, display, depth) {
if ((width === 0) || (height === 0)) {
return true;
}
if (this._lines === 0) { if (this._lines === 0) {
this._lines = height; this._lines = height;
} }
@ -24,29 +28,35 @@ export default class RawDecoder {
return false; return false;
} }
const cur_y = y + (height - this._lines); const curY = y + (height - this._lines);
const curr_height = Math.min(this._lines, const currHeight = Math.min(this._lines,
Math.floor(sock.rQlen / bytesPerLine)); Math.floor(sock.rQlen / bytesPerLine));
const pixels = width * currHeight;
let data = sock.rQ; let data = sock.rQ;
let index = sock.rQi; let index = sock.rQi;
// Convert data if needed // Convert data if needed
if (depth == 8) { if (depth == 8) {
const pixels = width * curr_height;
const newdata = new Uint8Array(pixels * 4); const newdata = new Uint8Array(pixels * 4);
for (let i = 0; i < pixels; i++) { for (let i = 0; i < pixels; i++) {
newdata[i * 4 + 0] = ((data[index + i] >> 0) & 0x3) * 255 / 3; 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 + 1] = ((data[index + i] >> 2) & 0x3) * 255 / 3;
newdata[i * 4 + 2] = ((data[index + i] >> 4) & 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; data = newdata;
index = 0; index = 0;
} }
display.blitImage(x, cur_y, width, curr_height, data, index); // Max sure the image is fully opaque
sock.rQskipBytes(curr_height * bytesPerLine); for (let i = 0; i < pixels; i++) {
this._lines -= curr_height; data[i * 4 + 3] = 255;
}
display.blitImage(x, curY, width, currHeight, data, index);
sock.rQskipBytes(currHeight * bytesPerLine);
this._lines -= currHeight;
if (this._lines > 0) { if (this._lines > 0) {
return false; return false;
} }

View File

@ -56,7 +56,7 @@ export default class TightDecoder {
} else if (this._ctl === 0x0A) { } else if (this._ctl === 0x0A) {
ret = this._pngRect(x, y, width, height, ret = this._pngRect(x, y, width, height,
sock, display, depth); sock, display, depth);
} else if ((this._ctl & 0x80) == 0) { } else if ((this._ctl & 0x08) == 0) {
ret = this._basicRect(this._ctl, x, y, width, height, ret = this._basicRect(this._ctl, x, y, width, height,
sock, display, depth); sock, display, depth);
} else { } else {
@ -80,7 +80,7 @@ export default class TightDecoder {
const rQ = sock.rQ; const rQ = sock.rQ;
display.fillRect(x, y, width, height, 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); sock.rQskipBytes(3);
return true; return true;
@ -148,6 +148,10 @@ export default class TightDecoder {
const uncompressedSize = width * height * 3; const uncompressedSize = width * height * 3;
let data; let data;
if (uncompressedSize === 0) {
return true;
}
if (uncompressedSize < 12) { if (uncompressedSize < 12) {
if (sock.rQwait("TIGHT", uncompressedSize)) { if (sock.rQwait("TIGHT", uncompressedSize)) {
return false; return false;
@ -165,7 +169,15 @@ export default class TightDecoder {
this._zlibs[streamId].setInput(null); 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; return true;
} }
@ -195,6 +207,10 @@ export default class TightDecoder {
let data; let data;
if (uncompressedSize === 0) {
return true;
}
if (uncompressedSize < 12) { if (uncompressedSize < 12) {
if (sock.rQwait("TIGHT", uncompressedSize)) { if (sock.rQwait("TIGHT", uncompressedSize)) {
return false; return false;
@ -254,7 +270,7 @@ export default class TightDecoder {
} }
} }
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) { _paletteRect(x, y, width, height, data, palette, display) {
@ -269,7 +285,7 @@ export default class TightDecoder {
dest[i + 3] = 255; 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) { _gradientFilter(streamId, x, y, width, height, sock, display, depth) {

View File

@ -21,12 +21,14 @@ export default class Deflator {
} }
deflate(inData) { deflate(inData) {
/* eslint-disable camelcase */
this.strm.input = inData; this.strm.input = inData;
this.strm.avail_in = this.strm.input.length; this.strm.avail_in = this.strm.input.length;
this.strm.next_in = 0; this.strm.next_in = 0;
this.strm.output = this.outputBuffer; this.strm.output = this.outputBuffer;
this.strm.avail_out = this.chunkSize; this.strm.avail_out = this.chunkSize;
this.strm.next_out = 0; this.strm.next_out = 0;
/* eslint-enable camelcase */
let lastRet = deflate(this.strm, Z_FULL_FLUSH); let lastRet = deflate(this.strm, Z_FULL_FLUSH);
let outData = new Uint8Array(this.strm.output.buffer, 0, this.strm.next_out); let outData = new Uint8Array(this.strm.output.buffer, 0, this.strm.next_out);
@ -41,9 +43,11 @@ export default class Deflator {
let chunks = [outData]; let chunks = [outData];
let totalLen = outData.length; let totalLen = outData.length;
do { do {
/* eslint-disable camelcase */
this.strm.output = new Uint8Array(this.chunkSize); this.strm.output = new Uint8Array(this.chunkSize);
this.strm.next_out = 0; this.strm.next_out = 0;
this.strm.avail_out = this.chunkSize; this.strm.avail_out = this.chunkSize;
/* eslint-enable camelcase */
lastRet = deflate(this.strm, Z_FULL_FLUSH); lastRet = deflate(this.strm, Z_FULL_FLUSH);
@ -69,9 +73,11 @@ export default class Deflator {
outData = newData; outData = newData;
} }
/* eslint-disable camelcase */
this.strm.input = null; this.strm.input = null;
this.strm.avail_in = 0; this.strm.avail_in = 0;
this.strm.next_in = 0; this.strm.next_in = 0;
/* eslint-enable camelcase */
return outData; return outData;
} }

View File

@ -8,25 +8,20 @@
import * as Log from './util/logging.js'; import * as Log from './util/logging.js';
import Base64 from "./base64.js"; import Base64 from "./base64.js";
import { supportsImageMetadata } from './util/browser.js'; import { toSigned32bit } from './util/int.js';
export default class Display { export default class Display {
constructor(target) { constructor(target) {
this._drawCtx = null; this._drawCtx = null;
this._c_forceCanvas = false;
this._renderQ = []; // queue drawing actions for in-oder rendering this._renderQ = []; // queue drawing actions for in-oder rendering
this._flushing = false; this._flushing = false;
// the full frame buffer (logical canvas) size // the full frame buffer (logical canvas) size
this._fb_width = 0; this._fbWidth = 0;
this._fb_height = 0; this._fbHeight = 0;
this._prevDrawStyle = ""; this._prevDrawStyle = "";
this._tile = null;
this._tile16x16 = null;
this._tile_x = 0;
this._tile_y = 0;
Log.Debug(">> Display.constructor"); Log.Debug(">> Display.constructor");
@ -60,12 +55,6 @@ export default class Display {
Log.Debug("User Agent: " + navigator.userAgent); Log.Debug("User Agent: " + navigator.userAgent);
// 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"); Log.Debug("<< Display.constructor");
// ===== PROPERTIES ===== // ===== PROPERTIES =====
@ -95,11 +84,11 @@ export default class Display {
} }
get width() { get width() {
return this._fb_width; return this._fbWidth;
} }
get height() { get height() {
return this._fb_height; return this._fbHeight;
} }
// ===== PUBLIC METHODS ===== // ===== PUBLIC METHODS =====
@ -122,15 +111,15 @@ export default class Display {
if (deltaX < 0 && vp.x + deltaX < 0) { if (deltaX < 0 && vp.x + deltaX < 0) {
deltaX = -vp.x; deltaX = -vp.x;
} }
if (vx2 + deltaX >= this._fb_width) { if (vx2 + deltaX >= this._fbWidth) {
deltaX -= vx2 + deltaX - this._fb_width + 1; deltaX -= vx2 + deltaX - this._fbWidth + 1;
} }
if (vp.y + deltaY < 0) { if (vp.y + deltaY < 0) {
deltaY = -vp.y; deltaY = -vp.y;
} }
if (vy2 + deltaY >= this._fb_height) { if (vy2 + deltaY >= this._fbHeight) {
deltaY -= (vy2 + deltaY - this._fb_height + 1); deltaY -= (vy2 + deltaY - this._fbHeight + 1);
} }
if (deltaX === 0 && deltaY === 0) { if (deltaX === 0 && deltaY === 0) {
@ -153,18 +142,18 @@ export default class Display {
typeof(height) === "undefined") { typeof(height) === "undefined") {
Log.Debug("Setting viewport to full display region"); Log.Debug("Setting viewport to full display region");
width = this._fb_width; width = this._fbWidth;
height = this._fb_height; height = this._fbHeight;
} }
width = Math.floor(width); width = Math.floor(width);
height = Math.floor(height); height = Math.floor(height);
if (width > this._fb_width) { if (width > this._fbWidth) {
width = this._fb_width; width = this._fbWidth;
} }
if (height > this._fb_height) { if (height > this._fbHeight) {
height = this._fb_height; height = this._fbHeight;
} }
const vp = this._viewportLoc; const vp = this._viewportLoc;
@ -191,21 +180,21 @@ export default class Display {
if (this._scale === 0) { if (this._scale === 0) {
return 0; return 0;
} }
return x / this._scale + this._viewportLoc.x; return toSigned32bit(x / this._scale + this._viewportLoc.x);
} }
absY(y) { absY(y) {
if (this._scale === 0) { if (this._scale === 0) {
return 0; return 0;
} }
return y / this._scale + this._viewportLoc.y; return toSigned32bit(y / this._scale + this._viewportLoc.y);
} }
resize(width, height) { resize(width, height) {
this._prevDrawStyle = ""; this._prevDrawStyle = "";
this._fb_width = width; this._fbWidth = width;
this._fb_height = height; this._fbHeight = height;
const canvas = this._backbuffer; const canvas = this._backbuffer;
if (canvas.width !== width || canvas.height !== height) { if (canvas.width !== width || canvas.height !== height) {
@ -253,9 +242,9 @@ export default class Display {
// Update the visible canvas with the contents of the // Update the visible canvas with the contents of the
// rendering canvas // rendering canvas
flip(from_queue) { flip(fromQueue) {
if (this._renderQ.length !== 0 && !from_queue) { if (this._renderQ.length !== 0 && !fromQueue) {
this._renderQ_push({ this._renderQPush({
'type': 'flip' 'type': 'flip'
}); });
} else { } else {
@ -311,9 +300,9 @@ export default class Display {
} }
} }
fillRect(x, y, width, height, color, from_queue) { fillRect(x, y, width, height, color, fromQueue) {
if (this._renderQ.length !== 0 && !from_queue) { if (this._renderQ.length !== 0 && !fromQueue) {
this._renderQ_push({ this._renderQPush({
'type': 'fill', 'type': 'fill',
'x': x, 'x': x,
'y': y, 'y': y,
@ -328,14 +317,14 @@ export default class Display {
} }
} }
copyImage(old_x, old_y, new_x, new_y, w, h, from_queue) { copyImage(oldX, oldY, newX, newY, w, h, fromQueue) {
if (this._renderQ.length !== 0 && !from_queue) { if (this._renderQ.length !== 0 && !fromQueue) {
this._renderQ_push({ this._renderQPush({
'type': 'copy', 'type': 'copy',
'old_x': old_x, 'oldX': oldX,
'old_y': old_y, 'oldY': oldY,
'x': new_x, 'x': newX,
'y': new_y, 'y': newY,
'width': w, 'width': w,
'height': h, 'height': h,
}); });
@ -353,9 +342,9 @@ export default class Display {
this._drawCtx.imageSmoothingEnabled = false; this._drawCtx.imageSmoothingEnabled = false;
this._drawCtx.drawImage(this._backbuffer, this._drawCtx.drawImage(this._backbuffer,
old_x, old_y, w, h, oldX, oldY, w, h,
new_x, new_y, w, h); newX, newY, w, h);
this._damage(new_x, new_y, w, h); this._damage(newX, newY, w, h);
} }
} }
@ -368,7 +357,7 @@ export default class Display {
const img = new Image(); const img = new Image();
img.src = "data: " + mime + ";base64," + Base64.encode(arr); img.src = "data: " + mime + ";base64," + Base64.encode(arr);
this._renderQ_push({ this._renderQPush({
'type': 'img', 'type': 'img',
'img': img, 'img': img,
'x': x, 'x': x,
@ -378,114 +367,29 @@ export default class Display {
}); });
} }
// start updating a tile blitImage(x, y, width, height, arr, offset, fromQueue) {
startTile(x, y, width, height, color) { if (this._renderQ.length !== 0 && !fromQueue) {
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) {
// NB(directxman12): it's technically more performant here to use preallocated arrays, // 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, // 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 // this probably isn't getting called *nearly* as much
const new_arr = new Uint8Array(width * height * 4); const newArr = new Uint8Array(width * height * 4);
new_arr.set(new Uint8Array(arr.buffer, 0, new_arr.length)); newArr.set(new Uint8Array(arr.buffer, 0, newArr.length));
this._renderQ_push({ this._renderQPush({
'type': 'blit', 'type': 'blit',
'data': new_arr, 'data': newArr,
'x': x, 'x': x,
'y': y, 'y': y,
'width': width, 'width': width,
'height': height, 'height': height,
}); });
} else { } else {
this._bgrxImageData(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);
blitRgbImage(x, y, width, height, arr, offset, from_queue) { let img = new ImageData(data, width, height);
if (this._renderQ.length !== 0 && !from_queue) { this._drawCtx.putImageData(img, x, y);
// NB(directxman12): it's technically more performant here to use preallocated arrays, this._damage(x, y, width, height);
// 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);
} }
} }
@ -537,69 +441,30 @@ export default class Display {
} }
_setFillColor(color) { _setFillColor(color) {
const newStyle = 'rgb(' + color[2] + ',' + color[1] + ',' + color[0] + ')'; const newStyle = 'rgb(' + color[0] + ',' + color[1] + ',' + color[2] + ')';
if (newStyle !== this._prevDrawStyle) { if (newStyle !== this._prevDrawStyle) {
this._drawCtx.fillStyle = newStyle; this._drawCtx.fillStyle = newStyle;
this._prevDrawStyle = newStyle; this._prevDrawStyle = newStyle;
} }
} }
_rgbImageData(x, y, width, height, arr, offset) { _renderQPush(action) {
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) {
this._renderQ.push(action); this._renderQ.push(action);
if (this._renderQ.length === 1) { if (this._renderQ.length === 1) {
// If this can be rendered immediately it will be, otherwise // If this can be rendered immediately it will be, otherwise
// the scanner will wait for the relevant event // 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 // "this" is the object that is ready, not the
// display object // display object
this.removeEventListener('load', this._noVNC_display._resume_renderQ); this.removeEventListener('load', this._noVNCDisplay._resumeRenderQ);
this._noVNC_display._scan_renderQ(); this._noVNCDisplay._scanRenderQ();
} }
_scan_renderQ() { _scanRenderQ() {
let ready = true; let ready = true;
while (ready && this._renderQ.length > 0) { while (ready && this._renderQ.length > 0) {
const a = this._renderQ[0]; const a = this._renderQ[0];
@ -608,7 +473,7 @@ export default class Display {
this.flip(true); this.flip(true);
break; break;
case 'copy': 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; break;
case 'fill': case 'fill':
this.fillRect(a.x, a.y, a.width, a.height, a.color, true); this.fillRect(a.x, a.y, a.width, a.height, a.color, true);
@ -616,15 +481,8 @@ export default class Display {
case 'blit': case 'blit':
this.blitImage(a.x, a.y, a.width, a.height, a.data, 0, true); this.blitImage(a.x, a.y, a.width, a.height, a.data, 0, true);
break; 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': case 'img':
/* IE tends to set "complete" prematurely, so check dimensions */ if (a.img.complete) {
if (a.img.complete && (a.img.width !== 0) && (a.img.height !== 0)) {
if (a.img.width !== a.width || a.img.height !== a.height) { if (a.img.width !== a.width || a.img.height !== a.height) {
Log.Error("Decoded image has incorrect dimensions. Got " + Log.Error("Decoded image has incorrect dimensions. Got " +
a.img.width + "x" + a.img.height + ". Expected " + a.img.width + "x" + a.img.height + ". Expected " +
@ -633,8 +491,8 @@ export default class Display {
} }
this.drawImage(a.img, a.x, a.y); this.drawImage(a.img, a.x, a.y);
} else { } else {
a.img._noVNC_display = this; a.img._noVNCDisplay = this;
a.img.addEventListener('load', this._resume_renderQ); a.img.addEventListener('load', this._resumeRenderQ);
// We need to wait for this image to 'load' // We need to wait for this image to 'load'
// to keep things in-order // to keep things in-order
ready = false; ready = false;

View File

@ -22,6 +22,7 @@ export default class Inflate {
setInput(data) { setInput(data) {
if (!data) { if (!data) {
//FIXME: flush remaining data. //FIXME: flush remaining data.
/* eslint-disable camelcase */
this.strm.input = null; this.strm.input = null;
this.strm.avail_in = 0; this.strm.avail_in = 0;
this.strm.next_in = 0; this.strm.next_in = 0;
@ -29,6 +30,7 @@ export default class Inflate {
this.strm.input = data; this.strm.input = data;
this.strm.avail_in = this.strm.input.length; this.strm.avail_in = this.strm.input.length;
this.strm.next_in = 0; this.strm.next_in = 0;
/* eslint-enable camelcase */
} }
} }
@ -41,8 +43,10 @@ export default class Inflate {
this.strm.output = new Uint8Array(this.chunkSize); this.strm.output = new Uint8Array(this.chunkSize);
} }
/* eslint-disable camelcase */
this.strm.next_out = 0; this.strm.next_out = 0;
this.strm.avail_out = expected; this.strm.avail_out = expected;
/* eslint-enable camelcase */
let ret = inflate(this.strm, 0); // Flush argument not used. let ret = inflate(this.strm, 0); // Flush argument not used.
if (ret < 0) { if (ret < 0) {

View File

@ -35,7 +35,7 @@ function addNumpad(key, standard, numpad) {
DOMKeyTable[key] = [standard, standard, standard, numpad]; DOMKeyTable[key] = [standard, standard, standard, numpad];
} }
// 2.2. Modifier Keys // 3.2. Modifier Keys
addLeftRight("Alt", KeyTable.XK_Alt_L, KeyTable.XK_Alt_R); addLeftRight("Alt", KeyTable.XK_Alt_L, KeyTable.XK_Alt_R);
addStandard("AltGraph", KeyTable.XK_ISO_Level3_Shift); addStandard("AltGraph", KeyTable.XK_ISO_Level3_Shift);
@ -49,25 +49,27 @@ addStandard("ScrollLock", KeyTable.XK_Scroll_Lock);
addLeftRight("Shift", KeyTable.XK_Shift_L, KeyTable.XK_Shift_R); addLeftRight("Shift", KeyTable.XK_Shift_L, KeyTable.XK_Shift_R);
// - Symbol // - Symbol
// - SymbolLock // - SymbolLock
// - Hyper
// - Super
// 2.3. Whitespace Keys // 3.3. Whitespace Keys
addNumpad("Enter", KeyTable.XK_Return, KeyTable.XK_KP_Enter); addNumpad("Enter", KeyTable.XK_Return, KeyTable.XK_KP_Enter);
addStandard("Tab", KeyTable.XK_Tab); addStandard("Tab", KeyTable.XK_Tab);
addNumpad(" ", KeyTable.XK_space, KeyTable.XK_KP_Space); addNumpad(" ", KeyTable.XK_space, KeyTable.XK_KP_Space);
// 2.4. Navigation Keys // 3.4. Navigation Keys
addNumpad("ArrowDown", KeyTable.XK_Down, KeyTable.XK_KP_Down); addNumpad("ArrowDown", KeyTable.XK_Down, KeyTable.XK_KP_Down);
addNumpad("ArrowUp", KeyTable.XK_Up, KeyTable.XK_KP_Up);
addNumpad("ArrowLeft", KeyTable.XK_Left, KeyTable.XK_KP_Left); addNumpad("ArrowLeft", KeyTable.XK_Left, KeyTable.XK_KP_Left);
addNumpad("ArrowRight", KeyTable.XK_Right, KeyTable.XK_KP_Right); addNumpad("ArrowRight", KeyTable.XK_Right, KeyTable.XK_KP_Right);
addNumpad("ArrowUp", KeyTable.XK_Up, KeyTable.XK_KP_Up);
addNumpad("End", KeyTable.XK_End, KeyTable.XK_KP_End); addNumpad("End", KeyTable.XK_End, KeyTable.XK_KP_End);
addNumpad("Home", KeyTable.XK_Home, KeyTable.XK_KP_Home); addNumpad("Home", KeyTable.XK_Home, KeyTable.XK_KP_Home);
addNumpad("PageDown", KeyTable.XK_Next, KeyTable.XK_KP_Next); addNumpad("PageDown", KeyTable.XK_Next, KeyTable.XK_KP_Next);
addNumpad("PageUp", KeyTable.XK_Prior, KeyTable.XK_KP_Prior); addNumpad("PageUp", KeyTable.XK_Prior, KeyTable.XK_KP_Prior);
// 2.5. Editing Keys // 3.5. Editing Keys
addStandard("Backspace", KeyTable.XK_BackSpace); addStandard("Backspace", KeyTable.XK_BackSpace);
// Browsers send "Clear" for the numpad 5 without NumLock because // Browsers send "Clear" for the numpad 5 without NumLock because
@ -85,7 +87,7 @@ addStandard("Paste", KeyTable.XF86XK_Paste);
addStandard("Redo", KeyTable.XK_Redo); addStandard("Redo", KeyTable.XK_Redo);
addStandard("Undo", KeyTable.XK_Undo); addStandard("Undo", KeyTable.XK_Undo);
// 2.6. UI Keys // 3.6. UI Keys
// - Accept // - Accept
// - Again (could just be XK_Redo) // - Again (could just be XK_Redo)
@ -103,7 +105,7 @@ addStandard("Select", KeyTable.XK_Select);
addStandard("ZoomIn", KeyTable.XF86XK_ZoomIn); addStandard("ZoomIn", KeyTable.XF86XK_ZoomIn);
addStandard("ZoomOut", KeyTable.XF86XK_ZoomOut); addStandard("ZoomOut", KeyTable.XF86XK_ZoomOut);
// 2.7. Device Keys // 3.7. Device Keys
addStandard("BrightnessDown", KeyTable.XF86XK_MonBrightnessDown); addStandard("BrightnessDown", KeyTable.XF86XK_MonBrightnessDown);
addStandard("BrightnessUp", KeyTable.XF86XK_MonBrightnessUp); addStandard("BrightnessUp", KeyTable.XF86XK_MonBrightnessUp);
@ -116,10 +118,10 @@ addStandard("Hibernate", KeyTable.XF86XK_Hibernate);
addStandard("Standby", KeyTable.XF86XK_Standby); addStandard("Standby", KeyTable.XF86XK_Standby);
addStandard("WakeUp", KeyTable.XF86XK_WakeUp); addStandard("WakeUp", KeyTable.XF86XK_WakeUp);
// 2.8. IME and Composition Keys // 3.8. IME and Composition Keys
addStandard("AllCandidates", KeyTable.XK_MultipleCandidate); addStandard("AllCandidates", KeyTable.XK_MultipleCandidate);
addStandard("Alphanumeric", KeyTable.XK_Eisu_Shift); // could also be _Eisu_Toggle addStandard("Alphanumeric", KeyTable.XK_Eisu_toggle);
addStandard("CodeInput", KeyTable.XK_Codeinput); addStandard("CodeInput", KeyTable.XK_Codeinput);
addStandard("Compose", KeyTable.XK_Multi_key); addStandard("Compose", KeyTable.XK_Multi_key);
addStandard("Convert", KeyTable.XK_Henkan); addStandard("Convert", KeyTable.XK_Henkan);
@ -137,7 +139,7 @@ addStandard("PreviousCandidate", KeyTable.XK_PreviousCandidate);
addStandard("SingleCandidate", KeyTable.XK_SingleCandidate); addStandard("SingleCandidate", KeyTable.XK_SingleCandidate);
addStandard("HangulMode", KeyTable.XK_Hangul); addStandard("HangulMode", KeyTable.XK_Hangul);
addStandard("HanjaMode", KeyTable.XK_Hangul_Hanja); addStandard("HanjaMode", KeyTable.XK_Hangul_Hanja);
addStandard("JunjuaMode", KeyTable.XK_Hangul_Jeonja); addStandard("JunjaMode", KeyTable.XK_Hangul_Jeonja);
addStandard("Eisu", KeyTable.XK_Eisu_toggle); addStandard("Eisu", KeyTable.XK_Eisu_toggle);
addStandard("Hankaku", KeyTable.XK_Hankaku); addStandard("Hankaku", KeyTable.XK_Hankaku);
addStandard("Hiragana", KeyTable.XK_Hiragana); addStandard("Hiragana", KeyTable.XK_Hiragana);
@ -147,9 +149,9 @@ addStandard("KanjiMode", KeyTable.XK_Kanji);
addStandard("Katakana", KeyTable.XK_Katakana); addStandard("Katakana", KeyTable.XK_Katakana);
addStandard("Romaji", KeyTable.XK_Romaji); addStandard("Romaji", KeyTable.XK_Romaji);
addStandard("Zenkaku", KeyTable.XK_Zenkaku); addStandard("Zenkaku", KeyTable.XK_Zenkaku);
addStandard("ZenkakuHanaku", KeyTable.XK_Zenkaku_Hankaku); addStandard("ZenkakuHankaku", KeyTable.XK_Zenkaku_Hankaku);
// 2.9. General-Purpose Function Keys // 3.9. General-Purpose Function Keys
addStandard("F1", KeyTable.XK_F1); addStandard("F1", KeyTable.XK_F1);
addStandard("F2", KeyTable.XK_F2); addStandard("F2", KeyTable.XK_F2);
@ -188,7 +190,7 @@ addStandard("F34", KeyTable.XK_F34);
addStandard("F35", KeyTable.XK_F35); addStandard("F35", KeyTable.XK_F35);
// - Soft1... // - Soft1...
// 2.10. Multimedia Keys // 3.10. Multimedia Keys
// - ChannelDown // - ChannelDown
// - ChannelUp // - ChannelUp
@ -200,6 +202,7 @@ addStandard("MailSend", KeyTable.XF86XK_Send);
addStandard("MediaFastForward", KeyTable.XF86XK_AudioForward); addStandard("MediaFastForward", KeyTable.XF86XK_AudioForward);
addStandard("MediaPause", KeyTable.XF86XK_AudioPause); addStandard("MediaPause", KeyTable.XF86XK_AudioPause);
addStandard("MediaPlay", KeyTable.XF86XK_AudioPlay); addStandard("MediaPlay", KeyTable.XF86XK_AudioPlay);
// - MediaPlayPause
addStandard("MediaRecord", KeyTable.XF86XK_AudioRecord); addStandard("MediaRecord", KeyTable.XF86XK_AudioRecord);
addStandard("MediaRewind", KeyTable.XF86XK_AudioRewind); addStandard("MediaRewind", KeyTable.XF86XK_AudioRewind);
addStandard("MediaStop", KeyTable.XF86XK_AudioStop); addStandard("MediaStop", KeyTable.XF86XK_AudioStop);
@ -211,12 +214,12 @@ addStandard("Print", KeyTable.XK_Print);
addStandard("Save", KeyTable.XF86XK_Save); addStandard("Save", KeyTable.XF86XK_Save);
addStandard("SpellCheck", KeyTable.XF86XK_Spell); addStandard("SpellCheck", KeyTable.XF86XK_Spell);
// 2.11. Multimedia Numpad Keys // 3.11. Multimedia Numpad Keys
// - Key11 // - Key11
// - Key12 // - Key12
// 2.12. Audio Keys // 3.12. Audio Keys
// - AudioBalanceLeft // - AudioBalanceLeft
// - AudioBalanceRight // - AudioBalanceRight
@ -236,16 +239,17 @@ addStandard("AudioVolumeMute", KeyTable.XF86XK_AudioMute);
// - MicrophoneVolumeUp // - MicrophoneVolumeUp
addStandard("MicrophoneVolumeMute", KeyTable.XF86XK_AudioMicMute); addStandard("MicrophoneVolumeMute", KeyTable.XF86XK_AudioMicMute);
// 2.13. Speech Keys // 3.13. Speech Keys
// - SpeechCorrectionList // - SpeechCorrectionList
// - SpeechInputToggle // - SpeechInputToggle
// 2.14. Application Keys // 3.14. Application Keys
addStandard("LaunchApplication1", KeyTable.XF86XK_MyComputer); addStandard("LaunchApplication1", KeyTable.XF86XK_MyComputer);
addStandard("LaunchApplication2", KeyTable.XF86XK_Calculator); addStandard("LaunchApplication2", KeyTable.XF86XK_Calculator);
addStandard("LaunchCalendar", KeyTable.XF86XK_Calendar); addStandard("LaunchCalendar", KeyTable.XF86XK_Calendar);
// - LaunchContacts
addStandard("LaunchMail", KeyTable.XF86XK_Mail); addStandard("LaunchMail", KeyTable.XF86XK_Mail);
addStandard("LaunchMediaPlayer", KeyTable.XF86XK_AudioMedia); addStandard("LaunchMediaPlayer", KeyTable.XF86XK_AudioMedia);
addStandard("LaunchMusicPlayer", KeyTable.XF86XK_Music); addStandard("LaunchMusicPlayer", KeyTable.XF86XK_Music);
@ -256,7 +260,7 @@ addStandard("LaunchWebBrowser", KeyTable.XF86XK_WWW);
addStandard("LaunchWebCam", KeyTable.XF86XK_WebCam); addStandard("LaunchWebCam", KeyTable.XF86XK_WebCam);
addStandard("LaunchWordProcessor", KeyTable.XF86XK_Word); addStandard("LaunchWordProcessor", KeyTable.XF86XK_Word);
// 2.15. Browser Keys // 3.15. Browser Keys
addStandard("BrowserBack", KeyTable.XF86XK_Back); addStandard("BrowserBack", KeyTable.XF86XK_Back);
addStandard("BrowserFavorites", KeyTable.XF86XK_Favorites); addStandard("BrowserFavorites", KeyTable.XF86XK_Favorites);
@ -266,15 +270,15 @@ addStandard("BrowserRefresh", KeyTable.XF86XK_Refresh);
addStandard("BrowserSearch", KeyTable.XF86XK_Search); addStandard("BrowserSearch", KeyTable.XF86XK_Search);
addStandard("BrowserStop", KeyTable.XF86XK_Stop); addStandard("BrowserStop", KeyTable.XF86XK_Stop);
// 2.16. Mobile Phone Keys // 3.16. Mobile Phone Keys
// - A whole bunch... // - A whole bunch...
// 2.17. TV Keys // 3.17. TV Keys
// - A whole bunch... // - A whole bunch...
// 2.18. Media Controller Keys // 3.18. Media Controller Keys
// - A whole bunch... // - A whole bunch...
addStandard("Dimmer", KeyTable.XF86XK_BrightnessAdjust); addStandard("Dimmer", KeyTable.XF86XK_BrightnessAdjust);

View File

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

View File

@ -20,16 +20,13 @@ export default class Keyboard {
this._keyDownList = {}; // List of depressed keys this._keyDownList = {}; // List of depressed keys
// (even if they are happy) // (even if they are happy)
this._pendingKey = null; // Key waiting for keypress
this._altGrArmed = false; // Windows AltGr detection this._altGrArmed = false; // Windows AltGr detection
// keep these here so we can refer to them later // keep these here so we can refer to them later
this._eventHandlers = { this._eventHandlers = {
'keyup': this._handleKeyUp.bind(this), 'keyup': this._handleKeyUp.bind(this),
'keydown': this._handleKeyDown.bind(this), 'keydown': this._handleKeyDown.bind(this),
'keypress': this._handleKeyPress.bind(this),
'blur': this._allKeysUp.bind(this), 'blur': this._allKeysUp.bind(this),
'checkalt': this._checkAlt.bind(this),
}; };
// ===== EVENT HANDLERS ===== // ===== EVENT HANDLERS =====
@ -62,9 +59,7 @@ export default class Keyboard {
} }
// Unstable, but we don't have anything else to go on // Unstable, but we don't have anything else to go on
// (don't use it for 'keypress' events thought since if (e.keyCode) {
// WebKit sets it to the same as charCode)
if (e.keyCode && (e.type !== 'keypress')) {
// 229 is used for composition events // 229 is used for composition events
if (e.keyCode !== 229) { if (e.keyCode !== 229) {
return 'Platform' + e.keyCode; return 'Platform' + e.keyCode;
@ -169,20 +164,20 @@ export default class Keyboard {
return; return;
} }
// If this is a legacy browser then we'll need to wait for // Windows doesn't send proper key releases for a bunch of
// a keypress event as well // Japanese IM keys so we have to fake the release right away
// (IE and Edge has a broken KeyboardEvent.key, so we can't const jpBadKeys = [ KeyTable.XK_Zenkaku_Hankaku,
// just check for the presence of that field) KeyTable.XK_Eisu_toggle,
if (!keysym && (!e.key || browser.isIE() || browser.isEdge())) { KeyTable.XK_Katakana,
this._pendingKey = code; KeyTable.XK_Hiragana,
// However we might not get a keypress event if the key KeyTable.XK_Romaji ];
// is non-printable, which needs some special fallback if (browser.isWindows() && jpBadKeys.includes(keysym)) {
// handling this._sendKeyEvent(keysym, code, true);
setTimeout(this._handleKeyPressTimeout.bind(this), 10, e); this._sendKeyEvent(keysym, code, false);
stopEvent(e);
return; return;
} }
this._pendingKey = null;
stopEvent(e); stopEvent(e);
// Possible start of AltGr sequence? (see above) // Possible start of AltGr sequence? (see above)
@ -197,69 +192,6 @@ export default class Keyboard {
this._sendKeyEvent(keysym, code, true); this._sendKeyEvent(keysym, code, true);
} }
// Legacy event for browsers without code/key
_handleKeyPress(e) {
stopEvent(e);
// Are we expecting a keypress?
if (this._pendingKey === null) {
return;
}
let code = this._getKeyCode(e);
const keysym = KeyboardUtil.getKeysym(e);
// The key we were waiting for?
if ((code !== 'Unidentified') && (code != this._pendingKey)) {
return;
}
code = this._pendingKey;
this._pendingKey = null;
if (!keysym) {
Log.Info('keypress with no keysym:', e);
return;
}
this._sendKeyEvent(keysym, code, true);
}
_handleKeyPressTimeout(e) {
// Did someone manage to sort out the key already?
if (this._pendingKey === null) {
return;
}
let keysym;
const code = this._pendingKey;
this._pendingKey = null;
// We have no way of knowing the proper keysym with the
// information given, but the following are true for most
// layouts
if ((e.keyCode >= 0x30) && (e.keyCode <= 0x39)) {
// Digit
keysym = e.keyCode;
} else if ((e.keyCode >= 0x41) && (e.keyCode <= 0x5a)) {
// Character (A-Z)
let char = String.fromCharCode(e.keyCode);
// A feeble attempt at the correct case
if (e.shiftKey) {
char = char.toUpperCase();
} else {
char = char.toLowerCase();
}
keysym = char.charCodeAt();
} else {
// Unknown, give up
keysym = 0;
}
this._sendKeyEvent(keysym, code, true);
}
_handleKeyUp(e) { _handleKeyUp(e) {
stopEvent(e); stopEvent(e);
@ -312,30 +244,6 @@ export default class Keyboard {
Log.Debug("<< Keyboard.allKeysUp"); Log.Debug("<< Keyboard.allKeysUp");
} }
// Alt workaround for Firefox on Windows, see below
_checkAlt(e) {
if (e.skipCheckAlt) {
return;
}
if (e.altKey) {
return;
}
const target = this._target;
const downList = this._keyDownList;
['AltLeft', 'AltRight'].forEach((code) => {
if (!(code in downList)) {
return;
}
const event = new KeyboardEvent('keyup',
{ key: downList[code],
code: code });
event.skipCheckAlt = true;
target.dispatchEvent(event);
});
}
// ===== PUBLIC METHODS ===== // ===== PUBLIC METHODS =====
grab() { grab() {
@ -343,41 +251,18 @@ export default class Keyboard {
this._target.addEventListener('keydown', this._eventHandlers.keydown); this._target.addEventListener('keydown', this._eventHandlers.keydown);
this._target.addEventListener('keyup', this._eventHandlers.keyup); this._target.addEventListener('keyup', this._eventHandlers.keyup);
this._target.addEventListener('keypress', this._eventHandlers.keypress);
// Release (key up) if window loses focus // Release (key up) if window loses focus
window.addEventListener('blur', this._eventHandlers.blur); window.addEventListener('blur', this._eventHandlers.blur);
// 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',
'touchstart', 'touchend', 'touchmove',
'keydown', 'keyup'].forEach(type =>
document.addEventListener(type, handler,
{ capture: true,
passive: true }));
}
//Log.Debug("<< Keyboard.grab"); //Log.Debug("<< Keyboard.grab");
} }
ungrab() { ungrab() {
//Log.Debug(">> Keyboard.ungrab"); //Log.Debug(">> Keyboard.ungrab");
if (browser.isWindows() && browser.isFirefox()) {
const handler = this._eventHandlers.checkalt;
['mousedown', 'mouseup', 'mousemove', 'wheel',
'touchstart', 'touchend', 'touchmove',
'keydown', 'keyup'].forEach(type => document.removeEventListener(type, handler));
}
this._target.removeEventListener('keydown', this._eventHandlers.keydown); this._target.removeEventListener('keydown', this._eventHandlers.keydown);
this._target.removeEventListener('keyup', this._eventHandlers.keyup); this._target.removeEventListener('keyup', this._eventHandlers.keyup);
this._target.removeEventListener('keypress', this._eventHandlers.keypress);
window.removeEventListener('blur', this._eventHandlers.blur); window.removeEventListener('blur', this._eventHandlers.blur);
// Release (key up) all keys that are in a down state // Release (key up) all keys that are in a down state

View File

@ -1,296 +0,0 @@
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2019 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;
const MOUSE_MOVE_DELAY = 17; // Minimum wait (ms) between two mouse moves
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._oldMouseMoveTime = 0;
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 press/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);
// Limit mouse move events to one every MOUSE_MOVE_DELAY ms
clearTimeout(this.mouseMoveTimer);
const newMouseMoveTime = Date.now();
if (newMouseMoveTime < this._oldMouseMoveTime + MOUSE_MOVE_DELAY) {
this.mouseMoveTimer = setTimeout(this.onmousemove.bind(this),
MOUSE_MOVE_DELAY,
this._pos.x, this._pos.y);
} else {
this.onmousemove(this._pos.x, this._pos.y);
}
this._oldMouseMoveTime = newMouseMoveTime;
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() {
const t = this._target;
if (isTouchDevice) {
t.addEventListener('touchstart', this._eventHandlers.mousedown);
t.addEventListener('touchend', this._eventHandlers.mouseup);
t.addEventListener('touchmove', this._eventHandlers.mousemove);
}
t.addEventListener('mousedown', this._eventHandlers.mousedown);
t.addEventListener('mouseup', this._eventHandlers.mouseup);
t.addEventListener('mousemove', this._eventHandlers.mousemove);
t.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
t.addEventListener('contextmenu', this._eventHandlers.mousedisable);
}
ungrab() {
const t = this._target;
this._resetWheelStepTimers();
if (isTouchDevice) {
t.removeEventListener('touchstart', this._eventHandlers.mousedown);
t.removeEventListener('touchend', this._eventHandlers.mouseup);
t.removeEventListener('touchmove', this._eventHandlers.mousemove);
}
t.removeEventListener('mousedown', this._eventHandlers.mousedown);
t.removeEventListener('mouseup', this._eventHandlers.mouseup);
t.removeEventListener('mousemove', this._eventHandlers.mousemove);
t.removeEventListener('wheel', this._eventHandlers.mousewheel);
document.removeEventListener('click', this._eventHandlers.mousedisable);
t.removeEventListener('contextmenu', this._eventHandlers.mousedisable);
}
}

View File

@ -22,9 +22,8 @@ export function getKeycode(evt) {
} }
// The de-facto standard is to use Windows Virtual-Key codes // The de-facto standard is to use Windows Virtual-Key codes
// in the 'keyCode' field for non-printable characters. However // in the 'keyCode' field for non-printable characters
// Webkit sets it to the same as charCode in 'keypress' events. if (evt.keyCode in vkeys) {
if ((evt.type !== 'keypress') && (evt.keyCode in vkeys)) {
let code = vkeys[evt.keyCode]; let code = vkeys[evt.keyCode];
// macOS has messed up this code for some reason // macOS has messed up this code for some reason
@ -69,26 +68,6 @@ export function getKeycode(evt) {
export function getKey(evt) { export function getKey(evt) {
// Are we getting a proper key value? // Are we getting a proper key value?
if (evt.key !== undefined) { if (evt.key !== undefined) {
// IE and Edge use some ancient version of the spec
// https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/8860571/
switch (evt.key) {
case 'Spacebar': return ' ';
case 'Esc': return 'Escape';
case 'Scroll': return 'ScrollLock';
case 'Win': return 'Meta';
case 'Apps': return 'ContextMenu';
case 'Up': return 'ArrowUp';
case 'Left': return 'ArrowLeft';
case 'Right': return 'ArrowRight';
case 'Down': return 'ArrowDown';
case 'Del': return 'Delete';
case 'Divide': return '/';
case 'Multiply': return '*';
case 'Subtract': return '-';
case 'Add': return '+';
case 'Decimal': return evt.char;
}
// Mozilla isn't fully in sync with the spec yet // Mozilla isn't fully in sync with the spec yet
switch (evt.key) { switch (evt.key) {
case 'OS': return 'Meta'; case 'OS': return 'Meta';
@ -110,20 +89,9 @@ export function getKey(evt) {
return 'Delete'; 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; 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;
}
}
// Try to deduce it based on the physical key // Try to deduce it based on the physical key
const code = getKeycode(evt); const code = getKeycode(evt);
if (code in fixedkeys) { if (code in fixedkeys) {
@ -189,6 +157,21 @@ export function getKeysym(evt) {
} }
} }
// Windows sends alternating symbols for some keys when using a
// Japanese layout. We have no way of synchronising with the IM
// running on the remote system, so we send some combined keysym
// instead and hope for the best.
if (browser.isWindows()) {
switch (key) {
case 'Zenkaku':
case 'Hankaku':
return KeyTable.XK_Zenkaku_Hankaku;
case 'Romaji':
case 'KanaMode':
return KeyTable.XK_Romaji;
}
}
return DOMKeyTable[key][location]; return DOMKeyTable[key][location];
} }

View File

@ -13,7 +13,6 @@ export default {
0x08: 'Backspace', 0x08: 'Backspace',
0x09: 'Tab', 0x09: 'Tab',
0x0a: 'NumpadClear', 0x0a: 'NumpadClear',
0x0c: 'Numpad5', // IE11 sends evt.keyCode: 12 when numlock is off
0x0d: 'Enter', 0x0d: 'Enter',
0x10: 'ShiftLeft', 0x10: 'ShiftLeft',
0x11: 'ControlLeft', 0x11: 'ControlLeft',

View File

@ -1,8 +1,8 @@
/* /*
* This file is auto-generated from keymaps.csv on 2017-05-31 16:20 * This file is auto-generated from keymaps.csv
* Database checksum sha256(92fd165507f2a3b8c5b3fa56e425d45788dbcb98cf067a307527d91ce22cab94) * Database checksum sha256(76d68c10e97d37fe2ea459e210125ae41796253fb217e900bf2983ade13a7920)
* To re-generate, run: * To re-generate, run:
* keymap-gen --lang=js code-map keymaps.csv html atset1 * keymap-gen code-map --lang=js keymaps.csv html atset1
*/ */
export default { export default {
"Again": 0xe005, /* html:Again (Again) -> linux:129 (KEY_AGAIN) -> atset1:57349 */ "Again": 0xe005, /* html:Again (Again) -> linux:129 (KEY_AGAIN) -> atset1:57349 */
@ -111,6 +111,8 @@ export default {
"KeyX": 0x2d, /* html:KeyX (KeyX) -> linux:45 (KEY_X) -> atset1:45 */ "KeyX": 0x2d, /* html:KeyX (KeyX) -> linux:45 (KEY_X) -> atset1:45 */
"KeyY": 0x15, /* html:KeyY (KeyY) -> linux:21 (KEY_Y) -> atset1:21 */ "KeyY": 0x15, /* html:KeyY (KeyY) -> linux:21 (KEY_Y) -> atset1:21 */
"KeyZ": 0x2c, /* html:KeyZ (KeyZ) -> linux:44 (KEY_Z) -> atset1:44 */ "KeyZ": 0x2c, /* html:KeyZ (KeyZ) -> linux:44 (KEY_Z) -> atset1:44 */
"Lang1": 0x72, /* html:Lang1 (Lang1) -> linux:122 (KEY_HANGEUL) -> atset1:114 */
"Lang2": 0x71, /* html:Lang2 (Lang2) -> linux:123 (KEY_HANJA) -> atset1:113 */
"Lang3": 0x78, /* html:Lang3 (Lang3) -> linux:90 (KEY_KATAKANA) -> atset1:120 */ "Lang3": 0x78, /* html:Lang3 (Lang3) -> linux:90 (KEY_KATAKANA) -> atset1:120 */
"Lang4": 0x77, /* html:Lang4 (Lang4) -> linux:91 (KEY_HIRAGANA) -> atset1:119 */ "Lang4": 0x77, /* html:Lang4 (Lang4) -> linux:91 (KEY_HIRAGANA) -> atset1:119 */
"Lang5": 0x76, /* html:Lang5 (Lang5) -> linux:85 (KEY_ZENKAKUHANKAKU) -> atset1:118 */ "Lang5": 0x76, /* html:Lang5 (Lang5) -> linux:85 (KEY_ZENKAKUHANKAKU) -> atset1:118 */

File diff suppressed because it is too large Load Diff

View File

@ -45,15 +45,6 @@ try {
export const supportsCursorURIs = _supportsCursorURIs; export const supportsCursorURIs = _supportsCursorURIs;
let _supportsImageMetadata = false;
try {
new ImageData(new Uint8ClampedArray(4), 1, 1);
_supportsImageMetadata = true;
} catch (ex) {
// ignore failure
}
export const supportsImageMetadata = _supportsImageMetadata;
let _hasScrollbarGutter = true; let _hasScrollbarGutter = true;
try { try {
// Create invisible container // Create invisible container
@ -106,14 +97,6 @@ export function isSafari() {
navigator.userAgent.indexOf('Chrome') === -1); navigator.userAgent.indexOf('Chrome') === -1);
} }
export function isIE() {
return navigator && !!(/trident/i).exec(navigator.userAgent);
}
export function isEdge() {
return navigator && !!(/edge/i).exec(navigator.userAgent);
}
export function isFirefox() { export function isFirefox() {
return navigator && !!(/firefox/i).exec(navigator.userAgent); return navigator && !!(/firefox/i).exec(navigator.userAgent);
} }

View File

@ -30,9 +30,6 @@ export default class Cursor {
'mouseleave': this._handleMouseLeave.bind(this), 'mouseleave': this._handleMouseLeave.bind(this),
'mousemove': this._handleMouseMove.bind(this), 'mousemove': this._handleMouseMove.bind(this),
'mouseup': this._handleMouseUp.bind(this), 'mouseup': this._handleMouseUp.bind(this),
'touchstart': this._handleTouchStart.bind(this),
'touchmove': this._handleTouchMove.bind(this),
'touchend': this._handleTouchEnd.bind(this),
}; };
} }
@ -46,19 +43,11 @@ export default class Cursor {
if (useFallback) { if (useFallback) {
document.body.appendChild(this._canvas); 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.
const options = { capture: true, passive: true }; const options = { capture: true, passive: true };
this._target.addEventListener('mouseover', this._eventHandlers.mouseover, options); this._target.addEventListener('mouseover', this._eventHandlers.mouseover, options);
this._target.addEventListener('mouseleave', this._eventHandlers.mouseleave, options); this._target.addEventListener('mouseleave', this._eventHandlers.mouseleave, options);
this._target.addEventListener('mousemove', this._eventHandlers.mousemove, options); this._target.addEventListener('mousemove', this._eventHandlers.mousemove, options);
this._target.addEventListener('mouseup', this._eventHandlers.mouseup, 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(); this.clear();
@ -76,10 +65,6 @@ export default class Cursor {
this._target.removeEventListener('mousemove', this._eventHandlers.mousemove, options); this._target.removeEventListener('mousemove', this._eventHandlers.mousemove, options);
this._target.removeEventListener('mouseup', this._eventHandlers.mouseup, options); this._target.removeEventListener('mouseup', this._eventHandlers.mouseup, options);
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); document.body.removeChild(this._canvas);
} }
@ -102,14 +87,7 @@ export default class Cursor {
this._canvas.width = w; this._canvas.width = w;
this._canvas.height = h; this._canvas.height = h;
let img; let img = new ImageData(new Uint8ClampedArray(rgba), w, h);
try {
// IE doesn't support this
img = new ImageData(new Uint8ClampedArray(rgba), w, h);
} catch (ex) {
img = ctx.createImageData(w, h);
img.data.set(new Uint8ClampedArray(rgba));
}
ctx.clearRect(0, 0, w, h); ctx.clearRect(0, 0, w, h);
ctx.putImageData(img, 0, 0); ctx.putImageData(img, 0, 0);
@ -131,6 +109,27 @@ export default class Cursor {
this._hotSpot.y = 0; 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) { _handleMouseOver(event) {
// This event could be because we're entering the target, or // This event could be because we're entering the target, or
// moving around amongst its sub elements. Let the move handler // moving around amongst its sub elements. Let the move handler
@ -170,6 +169,10 @@ export default class Cursor {
// should be visible. // should be visible.
if (this._captureIsActive()) { if (this._captureIsActive()) {
window.setTimeout(() => { window.setTimeout(() => {
// We might have detached at this point
if (!this._target) {
return;
}
// Refresh the target from elementFromPoint since queued events // Refresh the target from elementFromPoint since queued events
// might have altered the DOM // might have altered the DOM
target = document.elementFromPoint(event.clientX, target = document.elementFromPoint(event.clientX,
@ -179,27 +182,6 @@ export default class Cursor {
} }
} }
_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);
}
_showCursor() { _showCursor() {
if (this._canvas.style.visibility === 'hidden') { if (this._canvas.style.visibility === 'hidden') {
this._canvas.style.visibility = ''; this._canvas.style.visibility = '';

32
core/util/element.js Normal file
View File

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

View File

@ -65,10 +65,6 @@ export function setCapture(target) {
target.setCapture(); target.setCapture();
document.captureElement = target; document.captureElement = target;
// IE releases capture on 'click' events which might not trigger
target.addEventListener('mouseup', releaseCapture);
} else { } else {
// Release any existing capture in case this method is // Release any existing capture in case this method is
// called multiple times without coordination // called multiple times without coordination

View File

@ -10,18 +10,18 @@
* Logging/debug routines * Logging/debug routines
*/ */
let _log_level = 'warn'; let _logLevel = 'warn';
let Debug = () => {}; let Debug = () => {};
let Info = () => {}; let Info = () => {};
let Warn = () => {}; let Warn = () => {};
let Error = () => {}; let Error = () => {};
export function init_logging(level) { export function initLogging(level) {
if (typeof level === 'undefined') { if (typeof level === 'undefined') {
level = _log_level; level = _logLevel;
} else { } else {
_log_level = level; _logLevel = level;
} }
Debug = Info = Warn = Error = () => {}; Debug = Info = Warn = Error = () => {};
@ -46,11 +46,11 @@ export function init_logging(level) {
} }
} }
export function get_logging() { export function getLogging() {
return _log_level; return _logLevel;
} }
export { Debug, Info, Warn, Error }; export { Debug, Info, Warn, Error };
// Initialize logging level // Initialize logging level
init_logging(); initLogging();

View File

@ -1,61 +0,0 @@
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2020 The noVNC Authors
* Licensed under MPL 2.0 or any later version (see LICENSE.txt)
*/
/* Polyfills to provide new APIs in old browsers */
/* Object.assign() (taken from MDN) */
if (typeof Object.assign != 'function') {
// Must be writable: true, enumerable: false, configurable: true
Object.defineProperty(Object, "assign", {
value: function assign(target, varArgs) { // .length of function is 2
'use strict';
if (target == null) { // TypeError if undefined or null
throw new TypeError('Cannot convert undefined or null to object');
}
const to = Object(target);
for (let index = 1; index < arguments.length; index++) {
const nextSource = arguments[index];
if (nextSource != null) { // Skip over if undefined or null
for (let nextKey in nextSource) {
// Avoid bugs when hasOwnProperty is shadowed
if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
to[nextKey] = nextSource[nextKey];
}
}
}
}
return to;
},
writable: true,
configurable: true
});
}
/* CustomEvent constructor (taken from MDN) */
(() => {
function CustomEvent(event, params) {
params = params || { bubbles: false, cancelable: false, detail: undefined };
const evt = document.createEvent( 'CustomEvent' );
evt.initCustomEvent( event, params.bubbles, params.cancelable, params.detail );
return evt;
}
CustomEvent.prototype = window.Event.prototype;
if (typeof window.CustomEvent !== "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;
};

View File

@ -1,10 +1,10 @@
/* /*
* Websock: high-performance binary WebSockets * Websock: high-performance buffering wrapper
* Copyright (C) 2019 The noVNC Authors * Copyright (C) 2019 The noVNC Authors
* Licensed under MPL 2.0 (see LICENSE.txt) * Licensed under MPL 2.0 (see LICENSE.txt)
* *
* Websock is similar to the standard WebSocket object but with extra * Websock is similar to the standard WebSocket / RTCDataChannel object
* buffer handling. * but with extra buffer handling.
* *
* Websock has built-in receive queue buffering; the message event * Websock has built-in receive queue buffering; the message event
* does not contain actual data but is simply a notification that * does not contain actual data but is simply a notification that
@ -17,14 +17,39 @@ import * as Log from './util/logging.js';
// this has performance issues in some versions Chromium, and // this has performance issues in some versions Chromium, and
// doesn't gain a tremendous amount of performance increase in Firefox // 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. // 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 const MAX_RQ_GROW_SIZE = 40 * 1024 * 1024; // 40 MiB
// Constants pulled from RTCDataChannelState enum
// https://developer.mozilla.org/en-US/docs/Web/API/RTCDataChannel/readyState#RTCDataChannelState_enum
const DataChannel = {
CONNECTING: "connecting",
OPEN: "open",
CLOSING: "closing",
CLOSED: "closed"
};
const ReadyStates = {
CONNECTING: [WebSocket.CONNECTING, DataChannel.CONNECTING],
OPEN: [WebSocket.OPEN, DataChannel.OPEN],
CLOSING: [WebSocket.CLOSING, DataChannel.CLOSING],
CLOSED: [WebSocket.CLOSED, DataChannel.CLOSED],
};
// Properties a raw channel must have, WebSocket and RTCDataChannel are two examples
const rawChannelProps = [
"send",
"close",
"binaryType",
"onerror",
"onmessage",
"onopen",
"protocol",
"readyState",
];
export default class Websock { export default class Websock {
constructor() { constructor() {
this._websocket = null; // WebSocket object this._websocket = null; // WebSocket or RTCDataChannel object
this._rQi = 0; // Receive queue index this._rQi = 0; // Receive queue index
this._rQlen = 0; // Next write position in the receive queue this._rQlen = 0; // Next write position in the receive queue
@ -46,6 +71,29 @@ export default class Websock {
} }
// Getters and Setters // Getters and Setters
get readyState() {
let subState;
if (this._websocket === null) {
return "unused";
}
subState = this._websocket.readyState;
if (ReadyStates.CONNECTING.includes(subState)) {
return "connecting";
} else if (ReadyStates.OPEN.includes(subState)) {
return "open";
} else if (ReadyStates.CLOSING.includes(subState)) {
return "closing";
} else if (ReadyStates.CLOSED.includes(subState)) {
return "closed";
}
return "unknown";
}
get sQ() { get sQ() {
return this._sQ; return this._sQ;
} }
@ -143,8 +191,8 @@ export default class Websock {
// Send Queue // Send Queue
flush() { flush() {
if (this._sQlen > 0 && this._websocket.readyState === WebSocket.OPEN) { if (this._sQlen > 0 && this.readyState === 'open') {
this._websocket.send(this._encode_message()); this._websocket.send(this._encodeMessage());
this._sQlen = 0; this._sQlen = 0;
} }
} }
@ -155,7 +203,7 @@ export default class Websock {
this.flush(); this.flush();
} }
send_string(str) { sendString(str) {
this.send(str.split('').map(chr => chr.charCodeAt(0))); this.send(str.split('').map(chr => chr.charCodeAt(0)));
} }
@ -168,24 +216,37 @@ export default class Websock {
this._eventHandlers[evt] = handler; this._eventHandlers[evt] = handler;
} }
_allocate_buffers() { _allocateBuffers() {
this._rQ = new Uint8Array(this._rQbufferSize); this._rQ = new Uint8Array(this._rQbufferSize);
this._sQ = new Uint8Array(this._sQbufferSize); this._sQ = new Uint8Array(this._sQbufferSize);
} }
init() { init() {
this._allocate_buffers(); this._allocateBuffers();
this._rQi = 0; this._rQi = 0;
this._websocket = null; this._websocket = null;
} }
open(uri, protocols) { open(uri, protocols) {
this.attach(new WebSocket(uri, protocols));
}
attach(rawChannel) {
this.init(); this.init();
this._websocket = new WebSocket(uri, protocols); // Must get object and class methods to be compatible with the tests.
this._websocket.binaryType = 'arraybuffer'; const channelProps = [...Object.keys(rawChannel), ...Object.getOwnPropertyNames(Object.getPrototypeOf(rawChannel))];
for (let i = 0; i < rawChannelProps.length; i++) {
const prop = rawChannelProps[i];
if (channelProps.indexOf(prop) < 0) {
throw new Error('Raw channel missing property: ' + prop);
}
}
this._websocket = rawChannel;
this._websocket.binaryType = "arraybuffer";
this._websocket.onmessage = this._recvMessage.bind(this);
this._websocket.onmessage = this._recv_message.bind(this);
this._websocket.onopen = () => { this._websocket.onopen = () => {
Log.Debug('>> WebSock.onopen'); Log.Debug('>> WebSock.onopen');
if (this._websocket.protocol) { if (this._websocket.protocol) {
@ -195,11 +256,13 @@ export default class Websock {
this._eventHandlers.open(); this._eventHandlers.open();
Log.Debug("<< WebSock.onopen"); Log.Debug("<< WebSock.onopen");
}; };
this._websocket.onclose = (e) => { this._websocket.onclose = (e) => {
Log.Debug(">> WebSock.onclose"); Log.Debug(">> WebSock.onclose");
this._eventHandlers.close(e); this._eventHandlers.close(e);
Log.Debug("<< WebSock.onclose"); Log.Debug("<< WebSock.onclose");
}; };
this._websocket.onerror = (e) => { this._websocket.onerror = (e) => {
Log.Debug(">> WebSock.onerror: " + e); Log.Debug(">> WebSock.onerror: " + e);
this._eventHandlers.error(e); this._eventHandlers.error(e);
@ -209,8 +272,8 @@ export default class Websock {
close() { close() {
if (this._websocket) { if (this._websocket) {
if ((this._websocket.readyState === WebSocket.OPEN) || if (this.readyState === 'connecting' ||
(this._websocket.readyState === WebSocket.CONNECTING)) { this.readyState === 'open') {
Log.Info("Closing WebSocket connection"); Log.Info("Closing WebSocket connection");
this._websocket.close(); this._websocket.close();
} }
@ -220,7 +283,7 @@ export default class Websock {
} }
// private methods // private methods
_encode_message() { _encodeMessage() {
// Put in a binary arraybuffer // Put in a binary arraybuffer
// according to the spec, you can send ArrayBufferViews with the send method // according to the spec, you can send ArrayBufferViews with the send method
return new Uint8Array(this._sQ.buffer, 0, this._sQlen); return new Uint8Array(this._sQ.buffer, 0, this._sQlen);
@ -231,36 +294,32 @@ export default class Websock {
// The function also expands the receive que if needed, and for // The function also expands the receive que if needed, and for
// performance reasons we combine these two actions to avoid // performance reasons we combine these two actions to avoid
// unneccessary copying. // unneccessary copying.
_expand_compact_rQ(min_fit) { _expandCompactRQ(minFit) {
// if we're using less than 1/8th of the buffer even with the incoming bytes, compact in place // if we're using less than 1/8th of the buffer even with the incoming bytes, compact in place
// instead of resizing // instead of resizing
const required_buffer_size = (this._rQlen - this._rQi + min_fit) * 8; const requiredBufferSize = (this._rQlen - this._rQi + minFit) * 8;
const resizeNeeded = this._rQbufferSize < required_buffer_size; const resizeNeeded = this._rQbufferSize < requiredBufferSize;
if (resizeNeeded) { if (resizeNeeded) {
// Make sure we always *at least* double the buffer size, and have at least space for 8x // Make sure we always *at least* double the buffer size, and have at least space for 8x
// the current amount of data // the current amount of data
this._rQbufferSize = Math.max(this._rQbufferSize * 2, required_buffer_size); this._rQbufferSize = Math.max(this._rQbufferSize * 2, requiredBufferSize);
} }
// we don't want to grow unboundedly // we don't want to grow unboundedly
if (this._rQbufferSize > MAX_RQ_GROW_SIZE) { if (this._rQbufferSize > MAX_RQ_GROW_SIZE) {
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"); throw new Error("Receive Queue buffer exceeded " + MAX_RQ_GROW_SIZE + " bytes, and the new message could not fit");
} }
} }
if (resizeNeeded) { if (resizeNeeded) {
const old_rQbuffer = this._rQ.buffer; const oldRQbuffer = this._rQ.buffer;
this._rQ = new Uint8Array(this._rQbufferSize); this._rQ = new Uint8Array(this._rQbufferSize);
this._rQ.set(new Uint8Array(old_rQbuffer, this._rQi, this._rQlen - this._rQi)); this._rQ.set(new Uint8Array(oldRQbuffer, this._rQi, this._rQlen - this._rQi));
} else { } else {
if (ENABLE_COPYWITHIN) {
this._rQ.copyWithin(0, this._rQi, this._rQlen); this._rQ.copyWithin(0, this._rQi, this._rQlen);
} else {
this._rQ.set(new Uint8Array(this._rQ.buffer, this._rQi, this._rQlen - this._rQi));
}
} }
this._rQlen = this._rQlen - this._rQi; this._rQlen = this._rQlen - this._rQi;
@ -268,17 +327,17 @@ export default class Websock {
} }
// push arraybuffer values onto the end of the receive que // push arraybuffer values onto the end of the receive que
_decode_message(data) { _DecodeMessage(data) {
const u8 = new Uint8Array(data); const u8 = new Uint8Array(data);
if (u8.length > this._rQbufferSize - this._rQlen) { if (u8.length > this._rQbufferSize - this._rQlen) {
this._expand_compact_rQ(u8.length); this._expandCompactRQ(u8.length);
} }
this._rQ.set(u8, this._rQlen); this._rQ.set(u8, this._rQlen);
this._rQlen += u8.length; this._rQlen += u8.length;
} }
_recv_message(e) { _recvMessage(e) {
this._decode_message(e.data); this._DecodeMessage(e.data);
if (this.rQlen > 0) { if (this.rQlen > 0) {
this._eventHandlers.message(); this._eventHandlers.message();
if (this._rQlen == this._rQi) { if (this._rQlen == this._rQi) {

View File

@ -11,9 +11,6 @@ official external API.
## 1.1 Module List ## 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 * __Keyboard__ (core/input/keyboard.js): Keyboard input event handler with
non-US keyboard support. Translates keyDown and keyUp events to X11 non-US keyboard support. Translates keyDown and keyUp events to X11
keysym values. keysym values.
@ -37,52 +34,29 @@ callback event name, and the callback function.
## 2. Modules ## 2. Modules
## 2.1 Mouse Module ## 2.1 Keyboard Module
### 2.1.1 Configuration Attributes ### 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 None
### 2.2.2 Methods ### 2.1.2 Methods
| name | parameters | description | name | parameters | description
| ------ | ---------- | ------------ | ------ | ---------- | ------------
| grab | () | Begin capturing keyboard events | grab | () | Begin capturing keyboard events
| ungrab | () | Stop capturing keyboard events | ungrab | () | Stop capturing keyboard events
### 2.2.3 Callbacks ### 2.1.3 Callbacks
| name | parameters | description | name | parameters | description
| ---------- | -------------------- | ------------ | ---------- | -------------------- | ------------
| onkeypress | (keysym, code, down) | Handler for key press/release | onkeypress | (keysym, code, down) | Handler for key press/release
## 2.3 Display Module ## 2.2 Display Module
### 2.3.1 Configuration Attributes ### 2.2.1 Configuration Attributes
| name | type | mode | default | description | name | type | mode | default | description
| ------------ | ----- | ---- | ------- | ------------ | ------------ | ----- | ---- | ------- | ------------
@ -91,7 +65,7 @@ None
| width | int | RO | | Display area width | width | int | RO | | Display area width
| height | int | RO | | Display area height | height | int | RO | | Display area height
### 2.3.2 Methods ### 2.2.2 Methods
| name | parameters | description | name | parameters | description
| ------------------ | ------------------------------------------------------- | ------------ | ------------------ | ------------------------------------------------------- | ------------
@ -106,16 +80,11 @@ None
| fillRect | (x, y, width, height, color, from_queue) | Draw a filled in rectangle | 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 | copyImage | (old_x, old_y, new_x, new_y, width, height, from_queue) | Copy a rectangular area
| imageRect | (x, y, width, height, mime, arr) | Draw a rectangle with an image | imageRect | (x, y, width, height, 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 | 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 | drawImage | (img, x, y) | Draw image and track damage
| autoscale | (containerWidth, containerHeight) | Scale the display | autoscale | (containerWidth, containerHeight) | Scale the display
### 2.3.3 Callbacks ### 2.2.3 Callbacks
| name | parameters | description | name | parameters | description
| ------- | ---------- | ------------ | ------- | ---------- | ------------

View File

@ -26,12 +26,6 @@ protocol stream.
moved to the remote session when a `mousedown` or `touchstart` moved to the remote session when a `mousedown` or `touchstart`
event is received. Enabled by default. event is received. Enabled by default.
`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` `clipViewport`
- Is a `boolean` indicating if the remote session should be clipped - Is a `boolean` indicating if the remote session should be clipped
to its container. When disabled scrollbars will be shown to handle to its container. When disabled scrollbars will be shown to handle
@ -171,9 +165,9 @@ connection to a specified VNC server.
existing contents of the `HTMLElement` will be untouched, but new existing contents of the `HTMLElement` will be untouched, but new
elements will be added during the lifetime of the `RFB` object. elements will be added during the lifetime of the `RFB` object.
**`url`** **`urlOrDataChannel`**
- A `DOMString` specifying the VNC server to connect to. This must be - A `DOMString` specifying the VNC server to connect to. This must be
a valid WebSocket URL. a valid WebSocket URL. This can also be a `WebSocket` or `RTCDataChannel`.
**`options`** *Optional* **`options`** *Optional*
- An `Object` specifying extra details about how the connection - An `Object` specifying extra details about how the connection
@ -388,5 +382,4 @@ to the remote server.
###### Parameters ###### Parameters
**`text`** **`text`**
- A `DOMString` specifying the clipboard data to send. Currently only - A `DOMString` specifying the clipboard data to send.
characters from ISO 8859-1 are supported.

View File

@ -71,24 +71,6 @@ query string. Currently the following options are available:
* `logging` - The console log level. Can be one of `error`, `warn`, `info` or * `logging` - The console log level. Can be one of `error`, `warn`, `info` or
`debug`. `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 ## HTTP Serving Considerations
### Browser Cache Issue ### Browser Cache Issue

View File

@ -18,18 +18,14 @@ do things.
## Conversion of Modules ## Conversion of Modules
noVNC is written using ECMAScript 6 modules. Many of the major browsers support noVNC is written using ECMAScript 6 modules. This is not supported by older
these modules natively, but not all. They are also not supported by Node.js. To versions of Node.js. To use noVNC with those older versions of Node.js the
use noVNC in these places the library must first be converted. library must first be converted.
Fortunately noVNC includes a script to handle this conversion. Please follow Fortunately noVNC includes a script to handle this conversion. Please follow
the following steps: the following steps:
1. Install Node.js 1. Install Node.js
2. Run `npm install` in the noVNC directory 2. Run `npm install` in the noVNC directory
3. Run `./utils/use_require.js --as <module format>`
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. The result of the conversion is available in the `lib/` directory.

37
docs/novnc_proxy.1 Normal file
View File

@ -0,0 +1,37 @@
.TH novnc_proxy 1 "June 25, 2020" "version 1.2.0" "USER COMMANDS"
.SH NAME
novnc_proxy - noVNC proxy server
.SH SYNOPSIS
.B novnc_proxy [--listen PORT] [--vnc VNC_HOST:PORT] [--cert CERT] [--ssl-only]
Starts the WebSockets proxy and a mini-webserver and
provides a cut-and-paste URL to go to.
--listen PORT Port for proxy/webserver to listen on
Default: 6080
--vnc VNC_HOST:PORT VNC server host:port proxy target
Default: localhost:5900
--cert CERT Path to combined cert/key file, or just
the cert file if used with --key
Default: self.pem
--key KEY Path to key file, when not combined with cert
--web WEB Path to web files (e.g. vnc.html)
Default: ./
--ssl-only Disable non-https connections.
--record FILE Record traffic to FILE.session.js
--syslog SERVER Can be local socket such as /dev/log, or a UDP host:port pair.
--heartbeat SEC send a ping to the client every SEC seconds
--timeout SEC after SEC seconds exit when not connected
--idle-timeout SEC server exits after SEC seconds if there are no
active connections
.SH AUTHOR
The noVNC Authors
https://github.com/novnc/noVNC
.SH SEE ALSO
websockify(1), nova-novncproxy(1)

View File

@ -1,48 +1,23 @@
// Karma configuration // Karma configuration
// The Safari launcher is broken, so construct our own
function SafariBrowser(id, baseBrowserDecorator, args) {
baseBrowserDecorator(this);
this._start = function(url) {
this._execCommand('/usr/bin/open', ['-W', '-n', '-a', 'Safari', url]);
}
}
SafariBrowser.prototype = {
name: 'Safari'
}
module.exports = (config) => { module.exports = (config) => {
const customLaunchers = {};
let browsers = []; let browsers = [];
let useSauce = false;
// use Sauce when running on Travis if (process.env.TEST_BROWSER_NAME) {
if (process.env.TRAVIS_JOB_NUMBER) { browsers = process.env.TEST_BROWSER_NAME.split(',');
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 = { const my_conf = {
@ -56,16 +31,13 @@ module.exports = (config) => {
// list of files / patterns to load in the browser (loaded in order) // list of files / patterns to load in the browser (loaded in order)
files: [ files: [
{ pattern: 'app/localization.js', included: false }, { pattern: 'app/localization.js', included: false, type: 'module' },
{ pattern: 'app/webutil.js', included: false }, { pattern: 'app/webutil.js', included: false, type: 'module' },
{ pattern: 'core/**/*.js', included: false }, { pattern: 'core/**/*.js', included: false, type: 'module' },
{ pattern: 'vendor/pako/**/*.js', included: false }, { pattern: 'vendor/pako/**/*.js', included: false, type: 'module' },
{ pattern: 'vendor/browser-es-module-loader/dist/*.js*', included: false }, { pattern: 'tests/test.*.js', type: 'module' },
{ pattern: 'tests/test.*.js', included: false }, { pattern: 'tests/fake.*.js', included: false, type: 'module' },
{ pattern: 'tests/fake.*.js', included: false }, { pattern: 'tests/assertions.js', type: 'module' },
{ pattern: 'tests/assertions.js', included: false },
'vendor/promise.js',
'tests/karma-test-main.js',
], ],
client: { client: {
@ -80,7 +52,11 @@ module.exports = (config) => {
exclude: [ exclude: [
], ],
customLaunchers: customLaunchers, plugins: [
'karma-*',
'@chiragrupani/karma-chromium-edge-launcher',
{ 'launcher:Safari': [ 'type', SafariBrowser ] },
],
// start these browsers // start these browsers
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
@ -92,14 +68,6 @@ module.exports = (config) => {
reporters: ['mocha'], reporters: ['mocha'],
// web server port
port: 9876,
// enable / disable colors in the output (reporters and logs)
colors: true,
// level of logging // level of logging
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
logLevel: config.LOG_INFO, logLevel: config.LOG_INFO,
@ -111,24 +79,7 @@ module.exports = (config) => {
// Continuous Integration mode // Continuous Integration mode
// if true, Karma captures browsers, runs the tests and exits // if true, Karma captures browsers, runs the tests and exits
singleRun: true, 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); config.set(my_conf);
}; };

View File

@ -1,6 +1,6 @@
{ {
"name": "@novnc/novnc", "name": "@novnc/novnc",
"version": "1.1.0", "version": "1.2.0",
"description": "An HTML5 VNC client", "description": "An HTML5 VNC client",
"browser": "lib/rfb", "browser": "lib/rfb",
"directories": { "directories": {
@ -21,7 +21,7 @@
"scripts": { "scripts": {
"lint": "eslint app core po/po2js po/xgettext-html tests utils", "lint": "eslint app core po/po2js po/xgettext-html tests utils",
"test": "karma start karma.conf.js", "test": "karma start karma.conf.js",
"prepublish": "node ./utils/use_require.js --as commonjs --clean" "prepublish": "node ./utils/use_require.js --clean"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@ -42,10 +42,7 @@
"devDependencies": { "devDependencies": {
"@babel/core": "*", "@babel/core": "*",
"@babel/plugin-syntax-dynamic-import": "*", "@babel/plugin-syntax-dynamic-import": "*",
"@babel/plugin-transform-modules-amd": "*",
"@babel/plugin-transform-modules-commonjs": "*", "@babel/plugin-transform-modules-commonjs": "*",
"@babel/plugin-transform-modules-systemjs": "*",
"@babel/plugin-transform-modules-umd": "*",
"@babel/preset-env": "*", "@babel/preset-env": "*",
"@babel/cli": "*", "@babel/cli": "*",
"babel-plugin-import-redirect": "*", "babel-plugin-import-redirect": "*",
@ -60,8 +57,13 @@
"jsdom": "*", "jsdom": "*",
"karma": "*", "karma": "*",
"karma-mocha": "*", "karma-mocha": "*",
"karma-chrome-launcher": "*",
"@chiragrupani/karma-chromium-edge-launcher": "*",
"karma-firefox-launcher": "*",
"karma-ie-launcher": "*",
"karma-mocha-reporter": "*", "karma-mocha-reporter": "*",
"karma-sauce-launcher": "*", "karma-safari-launcher": "*",
"karma-script-launcher": "*",
"karma-sinon-chai": "*", "karma-sinon-chai": "*",
"mocha": "*", "mocha": "*",
"node-getopt": "*", "node-getopt": "*",

View File

@ -1,7 +1,7 @@
all: all:
.PHONY: update-po update-js update-pot .PHONY: update-po update-js update-pot
LINGUAS := cs de el es ja ko nl pl ru sv tr zh_CN zh_TW LINGUAS := cs de el es fr ja ko nl pl pt_BR ru sv tr zh_CN zh_TW
VERSION := $(shell grep '"version"' ../package.json | cut -d '"' -f 4) VERSION := $(shell grep '"version"' ../package.json | cut -d '"' -f 4)

View File

@ -3,14 +3,15 @@
# Copyright (C) 2018 The noVNC Authors # Copyright (C) 2018 The noVNC Authors
# This file is distributed under the same license as the noVNC package. # This file is distributed under the same license as the noVNC package.
# Juanjo Diaz <juanjo.diazmo@gmail.com>, 2018. # Juanjo Diaz <juanjo.diazmo@gmail.com>, 2018.
# Adrian Scillato <ascillato@gmail.com>, 2021.
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: noVNC 1.0.0-testing.2\n" "Project-Id-Version: noVNC 1.0.0-testing.2\n"
"Report-Msgid-Bugs-To: novnc@googlegroups.com\n" "Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
"POT-Creation-Date: 2017-10-06 10:07+0200\n" "POT-Creation-Date: 2017-10-06 10:07+0200\n"
"PO-Revision-Date: 2018-01-30 19:14-0800\n" "PO-Revision-Date: 2021-04-23 12:00-0300\n"
"Last-Translator: Juanjo Diaz <juanjo.diazmo@gmail.com>\n" "Last-Translator: Adrian Scillato <ascillato@gmail.com>\n"
"Language-Team: Spanish\n" "Language-Team: Spanish\n"
"Language: es\n" "Language: es\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
@ -40,7 +41,7 @@ msgstr "Desconectado"
#: ../app/ui.js:1052 ../core/rfb.js:248 #: ../app/ui.js:1052 ../core/rfb.js:248
msgid "Must set host" msgid "Must set host"
msgstr "Debes configurar el host" msgstr "Se debe configurar el host"
#: ../app/ui.js:1101 #: ../app/ui.js:1101
msgid "Reconnecting..." msgid "Reconnecting..."
@ -48,7 +49,7 @@ msgstr "Reconectando..."
#: ../app/ui.js:1140 #: ../app/ui.js:1140
msgid "Password is required" msgid "Password is required"
msgstr "Contraseña es obligatoria" msgstr "La contraseña es obligatoria"
#: ../core/rfb.js:548 #: ../core/rfb.js:548
msgid "Disconnect timeout" msgid "Disconnect timeout"
@ -186,6 +187,10 @@ msgstr "Pantalla Completa"
msgid "Settings" msgid "Settings"
msgstr "Configuraciones" msgstr "Configuraciones"
#: ../vnc.html:200
msgid "Encrypt"
msgstr "Encriptar"
#: ../vnc.html:202 #: ../vnc.html:202
msgid "Shared Mode" msgid "Shared Mode"
msgstr "Modo Compartido" msgstr "Modo Compartido"
@ -228,7 +233,7 @@ msgstr "Cursor Local"
#: ../vnc.html:229 #: ../vnc.html:229
msgid "Repeater ID:" msgid "Repeater ID:"
msgstr "ID del Repetidor" msgstr "ID del Repetidor:"
#: ../vnc.html:233 #: ../vnc.html:233
msgid "WebSocket" msgid "WebSocket"
@ -240,15 +245,15 @@ msgstr ""
#: ../vnc.html:239 #: ../vnc.html:239
msgid "Host:" msgid "Host:"
msgstr "Host" msgstr "Host:"
#: ../vnc.html:243 #: ../vnc.html:243
msgid "Port:" msgid "Port:"
msgstr "Puesto" msgstr "Puerto:"
#: ../vnc.html:247 #: ../vnc.html:247
msgid "Path:" msgid "Path:"
msgstr "Ruta" msgstr "Ruta:"
#: ../vnc.html:254 #: ../vnc.html:254
msgid "Automatic Reconnect" msgid "Automatic Reconnect"
@ -256,11 +261,11 @@ msgstr "Reconexión automática"
#: ../vnc.html:257 #: ../vnc.html:257
msgid "Reconnect Delay (ms):" msgid "Reconnect Delay (ms):"
msgstr "Retraso en la reconexión (ms)" msgstr "Retraso en la reconexión (ms):"
#: ../vnc.html:263 #: ../vnc.html:263
msgid "Logging:" msgid "Logging:"
msgstr "Logging" msgstr "Registrando:"
#: ../vnc.html:275 #: ../vnc.html:275
msgid "Disconnect" msgid "Disconnect"
@ -272,7 +277,7 @@ msgstr "Conectar"
#: ../vnc.html:304 #: ../vnc.html:304
msgid "Password:" msgid "Password:"
msgstr "Contraseña" msgstr "Contraseña:"
#: ../vnc.html:318 #: ../vnc.html:318
msgid "Cancel" msgid "Cancel"
@ -280,4 +285,4 @@ msgstr "Cancelar"
#: ../vnc.html:334 #: ../vnc.html:334
msgid "Canvas not supported." msgid "Canvas not supported."
msgstr "Canvas no está soportado" msgstr "Canvas no soportado."

299
po/fr.po Normal file
View File

@ -0,0 +1,299 @@
# French translations for noVNC package
# Traductions françaises du paquet noVNC.
# Copyright (C) 2021 The noVNC Authors
# This file is distributed under the same license as the noVNC package.
# Jose <jose.matsuda@canada.ca>, 2021.
#
msgid ""
msgstr ""
"Project-Id-Version: noVNC 1.2.0\n"
"Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
"POT-Creation-Date: 2020-07-03 16:11+0200\n"
"PO-Revision-Date: 2021-05-05 20:19-0400\n"
"Last-Translator: Jose <jose.matsuda@canada.ca>\n"
"Language-Team: French\n"
"Language: fr\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:394
msgid "Connecting..."
msgstr "En cours de connexion..."
#: ../app/ui.js:401
msgid "Disconnecting..."
msgstr "Déconnexion en cours..."
#: ../app/ui.js:407
msgid "Reconnecting..."
msgstr "Reconnexion en cours..."
#: ../app/ui.js:412
msgid "Internal error"
msgstr "Erreur interne"
#: ../app/ui.js:1008
msgid "Must set host"
msgstr "Doit définir l'hôte"
#: ../app/ui.js:1090
msgid "Connected (encrypted) to "
msgstr "Connecté (crypté) à "
#: ../app/ui.js:1092
msgid "Connected (unencrypted) to "
msgstr "Connecté (non crypté) à "
#: ../app/ui.js:1115
msgid "Something went wrong, connection is closed"
msgstr "Quelque chose est arrivé, la connexion est fermée"
#: ../app/ui.js:1118
msgid "Failed to connect to server"
msgstr "Échec de connexion au serveur"
#: ../app/ui.js:1128
msgid "Disconnected"
msgstr "Déconnecté"
#: ../app/ui.js:1143
msgid "New connection has been rejected with reason: "
msgstr "Une nouvelle connexion a été rejetée avec raison: "
#: ../app/ui.js:1146
msgid "New connection has been rejected"
msgstr "Une nouvelle connexion a été rejetée"
#: ../app/ui.js:1181
msgid "Credentials are required"
msgstr "Les identifiants sont requis"
#: ../vnc.html:74
msgid "noVNC encountered an error:"
msgstr "noVNC a rencontré une erreur:"
#: ../vnc.html:84
msgid "Hide/Show the control bar"
msgstr "Masquer/Afficher la barre de contrôle"
#: ../vnc.html:91
msgid "Drag"
msgstr "Faire glisser"
#: ../vnc.html:91
msgid "Move/Drag Viewport"
msgstr "Déplacer/faire glisser Viewport"
#: ../vnc.html:97
msgid "Keyboard"
msgstr "Clavier"
#: ../vnc.html:97
msgid "Show Keyboard"
msgstr "Afficher le clavier"
#: ../vnc.html:102
msgid "Extra keys"
msgstr "Touches supplémentaires"
#: ../vnc.html:102
msgid "Show Extra Keys"
msgstr "Afficher les touches supplémentaires"
#: ../vnc.html:107
msgid "Ctrl"
msgstr "Ctrl"
#: ../vnc.html:107
msgid "Toggle Ctrl"
msgstr "Basculer Ctrl"
#: ../vnc.html:110
msgid "Alt"
msgstr "Alt"
#: ../vnc.html:110
msgid "Toggle Alt"
msgstr "Basculer Alt"
#: ../vnc.html:113
msgid "Toggle Windows"
msgstr "Basculer Windows"
#: ../vnc.html:113
msgid "Windows"
msgstr "Windows"
#: ../vnc.html:116
msgid "Send Tab"
msgstr "Envoyer l'onglet"
#: ../vnc.html:116
msgid "Tab"
msgstr "l'onglet"
#: ../vnc.html:119
msgid "Esc"
msgstr "Esc"
#: ../vnc.html:119
msgid "Send Escape"
msgstr "Envoyer Escape"
#: ../vnc.html:122
msgid "Ctrl+Alt+Del"
msgstr "Ctrl+Alt+Del"
#: ../vnc.html:122
msgid "Send Ctrl-Alt-Del"
msgstr "Envoyer Ctrl-Alt-Del"
#: ../vnc.html:129
msgid "Shutdown/Reboot"
msgstr "Arrêter/Redémarrer"
#: ../vnc.html:129
msgid "Shutdown/Reboot..."
msgstr "Arrêter/Redémarrer..."
#: ../vnc.html:135
msgid "Power"
msgstr "Alimentation"
#: ../vnc.html:137
msgid "Shutdown"
msgstr "Arrêter"
#: ../vnc.html:138
msgid "Reboot"
msgstr "Redémarrer"
#: ../vnc.html:139
msgid "Reset"
msgstr "Réinitialiser"
#: ../vnc.html:144 ../vnc.html:150
msgid "Clipboard"
msgstr "Presse-papiers"
#: ../vnc.html:154
msgid "Clear"
msgstr "Effacer"
#: ../vnc.html:160
msgid "Fullscreen"
msgstr "Plein écran"
#: ../vnc.html:165 ../vnc.html:172
msgid "Settings"
msgstr "Paramètres"
#: ../vnc.html:175
msgid "Shared Mode"
msgstr "Mode partagé"
#: ../vnc.html:178
msgid "View Only"
msgstr "Afficher uniquement"
#: ../vnc.html:182
msgid "Clip to Window"
msgstr "Clip à fenêtre"
#: ../vnc.html:185
msgid "Scaling Mode:"
msgstr "Mode mise à l'échelle:"
#: ../vnc.html:187
msgid "None"
msgstr "Aucun"
#: ../vnc.html:188
msgid "Local Scaling"
msgstr "Mise à l'échelle locale"
#: ../vnc.html:189
msgid "Remote Resizing"
msgstr "Redimensionnement à distance"
#: ../vnc.html:194
msgid "Advanced"
msgstr "Avancé"
#: ../vnc.html:197
msgid "Quality:"
msgstr "Qualité:"
#: ../vnc.html:201
msgid "Compression level:"
msgstr "Niveau de compression:"
#: ../vnc.html:206
msgid "Repeater ID:"
msgstr "ID Répéteur:"
#: ../vnc.html:210
msgid "WebSocket"
msgstr "WebSocket"
#: ../vnc.html:213
msgid "Encrypt"
msgstr "Crypter"
#: ../vnc.html:216
msgid "Host:"
msgstr "Hôte:"
#: ../vnc.html:220
msgid "Port:"
msgstr "Port:"
#: ../vnc.html:224
msgid "Path:"
msgstr "Chemin:"
#: ../vnc.html:231
msgid "Automatic Reconnect"
msgstr "Reconnecter automatiquemen"
#: ../vnc.html:234
msgid "Reconnect Delay (ms):"
msgstr "Délai de reconnexion (ms):"
#: ../vnc.html:239
msgid "Show Dot when No Cursor"
msgstr "Afficher le point lorsqu'il n'y a pas de curseur"
#: ../vnc.html:244
msgid "Logging:"
msgstr "Se connecter:"
#: ../vnc.html:253
msgid "Version:"
msgstr "Version:"
#: ../vnc.html:261
msgid "Disconnect"
msgstr "Déconnecter"
#: ../vnc.html:280
msgid "Connect"
msgstr "Connecter"
#: ../vnc.html:290
msgid "Username:"
msgstr "Nom d'utilisateur:"
#: ../vnc.html:294
msgid "Password:"
msgstr "Mot de passe:"
#: ../vnc.html:298
msgid "Send Credentials"
msgstr "Envoyer les identifiants"
#: ../vnc.html:308
msgid "Cancel"
msgstr "Annuler"

219
po/ja.po
View File

@ -1,303 +1,324 @@
# Japanese translations for noVNC package # Japanese translations for noVNC package
# noVNC パッケージに対する英訳. # noVNC パッケージに対する日訳
# Copyright (C) 2019 The noVNC Authors # Copyright (C) 2019 The noVNC Authors
# This file is distributed under the same license as the noVNC package. # This file is distributed under the same license as the noVNC package.
# nnn1590 <28985763+nnn1590@users.noreply.github.com>, 2019. # nnn1590 <nnn1590@nnn1590.org>, 2019-2020.
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: noVNC 1.1.0\n" "Project-Id-Version: noVNC 1.1.0\n"
"Report-Msgid-Bugs-To: novnc@googlegroups.com\n" "Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
"POT-Creation-Date: 2019-01-16 11:06+0100\n" "POT-Creation-Date: 2020-07-03 16:11+0200\n"
"PO-Revision-Date: 2019-06-12 04:06+0900\n" "PO-Revision-Date: 2021-01-15 12:37+0900\n"
"Last-Translator: nnn1590 <28985763+nnn1590@users.noreply.github.com>\n" "Last-Translator: nnn1590 <nnn1590@nnn1590.org>\n"
"Language-Team: Japanese\n" "Language-Team: Japanese\n"
"Language: ja\n" "Language: ja\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n" "Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Poedit 2.3\n"
#: ../app/ui.js:387 #: ../app/ui.js:394
msgid "Connecting..." msgid "Connecting..."
msgstr "接続しています..." msgstr "接続しています..."
#: ../app/ui.js:394 #: ../app/ui.js:401
msgid "Disconnecting..." msgid "Disconnecting..."
msgstr "切断しています..." msgstr "切断しています..."
#: ../app/ui.js:400 #: ../app/ui.js:407
msgid "Reconnecting..." msgid "Reconnecting..."
msgstr "再接続しています..." msgstr "再接続しています..."
#: ../app/ui.js:405 #: ../app/ui.js:412
msgid "Internal error" msgid "Internal error"
msgstr "内部エラー" msgstr "内部エラー"
#: ../app/ui.js:995 #: ../app/ui.js:1008
msgid "Must set host" msgid "Must set host"
msgstr "ホストを設定する必要があります" msgstr "ホストを設定する必要があります"
#: ../app/ui.js:1077 #: ../app/ui.js:1090
msgid "Connected (encrypted) to " msgid "Connected (encrypted) to "
msgstr "接続しました (暗号化済み): " msgstr "接続しました (暗号化済み): "
#: ../app/ui.js:1079 #: ../app/ui.js:1092
msgid "Connected (unencrypted) to " msgid "Connected (unencrypted) to "
msgstr "接続しました (暗号化されていません): " msgstr "接続しました (暗号化されていません): "
#: ../app/ui.js:1102 #: ../app/ui.js:1115
msgid "Something went wrong, connection is closed" msgid "Something went wrong, connection is closed"
msgstr "何かが問題で、接続が閉じられました" msgstr "何らかの問題で、接続が閉じられました"
#: ../app/ui.js:1105 #: ../app/ui.js:1118
msgid "Failed to connect to server" msgid "Failed to connect to server"
msgstr "サーバーへの接続に失敗しました" msgstr "サーバーへの接続に失敗しました"
#: ../app/ui.js:1115 #: ../app/ui.js:1128
msgid "Disconnected" msgid "Disconnected"
msgstr "切断しました" msgstr "切断しました"
#: ../app/ui.js:1128 #: ../app/ui.js:1143
msgid "New connection has been rejected with reason: " msgid "New connection has been rejected with reason: "
msgstr "新規接続は次の理由で拒否されました: " msgstr "新規接続は次の理由で拒否されました: "
#: ../app/ui.js:1131 #: ../app/ui.js:1146
msgid "New connection has been rejected" msgid "New connection has been rejected"
msgstr "新規接続は拒否されました" msgstr "新規接続は拒否されました"
#: ../app/ui.js:1151 #: ../app/ui.js:1181
msgid "Password is required" msgid "Credentials are required"
msgstr "パスワードが必要です" msgstr "資格情報が必要です"
#: ../vnc.html:84 #: ../vnc.html:74
msgid "noVNC encountered an error:" msgid "noVNC encountered an error:"
msgstr "noVNC でエラーが発生しました:" msgstr "noVNC でエラーが発生しました:"
#: ../vnc.html:94 #: ../vnc.html:84
msgid "Hide/Show the control bar" msgid "Hide/Show the control bar"
msgstr "コントロールバーを隠す/表示する" msgstr "コントロールバーを隠す/表示する"
#: ../vnc.html:101 #: ../vnc.html:91
msgid "Drag"
msgstr "ドラッグ"
#: ../vnc.html:91
msgid "Move/Drag Viewport" msgid "Move/Drag Viewport"
msgstr "ビューポートを移動/ドラッグ" msgstr "ビューポートを移動/ドラッグ"
#: ../vnc.html:101 #: ../vnc.html:97
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" msgid "Keyboard"
msgstr "キーボード" msgstr "キーボード"
#: ../vnc.html:119 #: ../vnc.html:97
msgid "Show Keyboard" msgid "Show Keyboard"
msgstr "キーボードを表示" msgstr "キーボードを表示"
#: ../vnc.html:126 #: ../vnc.html:102
msgid "Extra keys" msgid "Extra keys"
msgstr "追加キー" msgstr "追加キー"
#: ../vnc.html:126 #: ../vnc.html:102
msgid "Show Extra Keys" msgid "Show Extra Keys"
msgstr "追加キーを表示" msgstr "追加キーを表示"
#: ../vnc.html:131 #: ../vnc.html:107
msgid "Ctrl" msgid "Ctrl"
msgstr "Ctrl" msgstr "Ctrl"
#: ../vnc.html:131 #: ../vnc.html:107
msgid "Toggle Ctrl" msgid "Toggle Ctrl"
msgstr "Ctrl キーを切り替え" msgstr "Ctrl キーを切り替え"
#: ../vnc.html:134 #: ../vnc.html:110
msgid "Alt" msgid "Alt"
msgstr "Alt" msgstr "Alt"
#: ../vnc.html:134 #: ../vnc.html:110
msgid "Toggle Alt" msgid "Toggle Alt"
msgstr "Alt キーを切り替え" msgstr "Alt キーを切り替え"
#: ../vnc.html:137 #: ../vnc.html:113
msgid "Toggle Windows" msgid "Toggle Windows"
msgstr "Windows キーを切り替え" msgstr "Windows キーを切り替え"
#: ../vnc.html:137 #: ../vnc.html:113
msgid "Windows" msgid "Windows"
msgstr "Windows" msgstr "Windows"
#: ../vnc.html:140 #: ../vnc.html:116
msgid "Send Tab" msgid "Send Tab"
msgstr "Tab キーを送信" msgstr "Tab キーを送信"
#: ../vnc.html:140 #: ../vnc.html:116
msgid "Tab" msgid "Tab"
msgstr "Tab" msgstr "Tab"
#: ../vnc.html:143 #: ../vnc.html:119
msgid "Esc" msgid "Esc"
msgstr "Esc" msgstr "Esc"
#: ../vnc.html:143 #: ../vnc.html:119
msgid "Send Escape" msgid "Send Escape"
msgstr "Escape キーを送信" msgstr "Escape キーを送信"
#: ../vnc.html:146 #: ../vnc.html:122
msgid "Ctrl+Alt+Del" msgid "Ctrl+Alt+Del"
msgstr "Ctrl+Alt+Del" msgstr "Ctrl+Alt+Del"
#: ../vnc.html:146 #: ../vnc.html:122
msgid "Send Ctrl-Alt-Del" msgid "Send Ctrl-Alt-Del"
msgstr "Ctrl-Alt-Del を送信" msgstr "Ctrl-Alt-Del を送信"
#: ../vnc.html:154 #: ../vnc.html:129
msgid "Shutdown/Reboot" msgid "Shutdown/Reboot"
msgstr "シャットダウン/再起動" msgstr "シャットダウン/再起動"
#: ../vnc.html:154 #: ../vnc.html:129
msgid "Shutdown/Reboot..." msgid "Shutdown/Reboot..."
msgstr "シャットダウン/再起動..." msgstr "シャットダウン/再起動..."
#: ../vnc.html:160 #: ../vnc.html:135
msgid "Power" msgid "Power"
msgstr "電源" msgstr "電源"
#: ../vnc.html:162 #: ../vnc.html:137
msgid "Shutdown" msgid "Shutdown"
msgstr "シャットダウン" msgstr "シャットダウン"
#: ../vnc.html:163 #: ../vnc.html:138
msgid "Reboot" msgid "Reboot"
msgstr "再起動" msgstr "再起動"
#: ../vnc.html:164 #: ../vnc.html:139
msgid "Reset" msgid "Reset"
msgstr "リセット" msgstr "リセット"
#: ../vnc.html:169 ../vnc.html:175 #: ../vnc.html:144 ../vnc.html:150
msgid "Clipboard" msgid "Clipboard"
msgstr "クリップボード" msgstr "クリップボード"
#: ../vnc.html:179 #: ../vnc.html:154
msgid "Clear" msgid "Clear"
msgstr "クリア" msgstr "クリア"
#: ../vnc.html:185 #: ../vnc.html:160
msgid "Fullscreen" msgid "Fullscreen"
msgstr "全画面表示" msgstr "全画面表示"
#: ../vnc.html:190 ../vnc.html:197 #: ../vnc.html:165 ../vnc.html:172
msgid "Settings" msgid "Settings"
msgstr "設定" msgstr "設定"
#: ../vnc.html:200 #: ../vnc.html:175
msgid "Shared Mode" msgid "Shared Mode"
msgstr "共有モード" msgstr "共有モード"
#: ../vnc.html:203 #: ../vnc.html:178
msgid "View Only" msgid "View Only"
msgstr "表示のみ" msgstr "表示のみ"
#: ../vnc.html:207 #: ../vnc.html:182
msgid "Clip to Window" msgid "Clip to Window"
msgstr "ウィンドウにクリップ" msgstr "ウィンドウにクリップ"
#: ../vnc.html:210 #: ../vnc.html:185
msgid "Scaling Mode:" msgid "Scaling Mode:"
msgstr "スケーリングモード:" msgstr "スケーリングモード:"
#: ../vnc.html:212 #: ../vnc.html:187
msgid "None" msgid "None"
msgstr "なし" msgstr "なし"
#: ../vnc.html:213 #: ../vnc.html:188
msgid "Local Scaling" msgid "Local Scaling"
msgstr "ローカルスケーリング" msgstr "ローカルスケーリング"
#: ../vnc.html:214 #: ../vnc.html:189
msgid "Remote Resizing" msgid "Remote Resizing"
msgstr "リモートでリサイズ" msgstr "リモートでリサイズ"
#: ../vnc.html:219 #: ../vnc.html:194
msgid "Advanced" msgid "Advanced"
msgstr "高度" msgstr "高度"
#: ../vnc.html:222 #: ../vnc.html:197
msgid "Quality:"
msgstr "品質:"
#: ../vnc.html:201
msgid "Compression level:"
msgstr "圧縮レベル:"
#: ../vnc.html:206
msgid "Repeater ID:" msgid "Repeater ID:"
msgstr "リピーター ID:" msgstr "リピーター ID:"
#: ../vnc.html:226 #: ../vnc.html:210
msgid "WebSocket" msgid "WebSocket"
msgstr "WebSocket" msgstr "WebSocket"
#: ../vnc.html:229 #: ../vnc.html:213
msgid "Encrypt" msgid "Encrypt"
msgstr "暗号化" msgstr "暗号化"
#: ../vnc.html:232 #: ../vnc.html:216
msgid "Host:" msgid "Host:"
msgstr "ホスト:" msgstr "ホスト:"
#: ../vnc.html:236 #: ../vnc.html:220
msgid "Port:" msgid "Port:"
msgstr "ポート:" msgstr "ポート:"
#: ../vnc.html:240 #: ../vnc.html:224
msgid "Path:" msgid "Path:"
msgstr "パス:" msgstr "パス:"
#: ../vnc.html:247 #: ../vnc.html:231
msgid "Automatic Reconnect" msgid "Automatic Reconnect"
msgstr "自動再接続" msgstr "自動再接続"
#: ../vnc.html:250 #: ../vnc.html:234
msgid "Reconnect Delay (ms):" msgid "Reconnect Delay (ms):"
msgstr "再接続する遅延 (ミリ秒):" msgstr "再接続する遅延 (ミリ秒):"
#: ../vnc.html:255 #: ../vnc.html:239
msgid "Show Dot when No Cursor" msgid "Show Dot when No Cursor"
msgstr "カーソルがないときにドットを表示" msgstr "カーソルがないときにドットを表示"
#: ../vnc.html:260 #: ../vnc.html:244
msgid "Logging:" msgid "Logging:"
msgstr "ロギング:" msgstr "ロギング:"
#: ../vnc.html:272 #: ../vnc.html:253
msgid "Version:"
msgstr "バージョン:"
#: ../vnc.html:261
msgid "Disconnect" msgid "Disconnect"
msgstr "切断" msgstr "切断"
#: ../vnc.html:291 #: ../vnc.html:280
msgid "Connect" msgid "Connect"
msgstr "接続" msgstr "接続"
#: ../vnc.html:301 #: ../vnc.html:290
msgid "Username:"
msgstr "ユーザー名:"
#: ../vnc.html:294
msgid "Password:" msgid "Password:"
msgstr "パスワード:" msgstr "パスワード:"
#: ../vnc.html:305 #: ../vnc.html:298
msgid "Send Password" msgid "Send Credentials"
msgstr "パスワードを送信" msgstr "資格情報を送信"
#: ../vnc.html:315 #: ../vnc.html:308
msgid "Cancel" msgid "Cancel"
msgstr "キャンセル" msgstr "キャンセル"
#~ msgid "Password is required"
#~ msgstr "パスワードが必要です"
#~ msgid "viewport drag"
#~ msgstr "ビューポートをドラッグ"
#~ msgid "Active Mouse Button"
#~ msgstr "アクティブなマウスボタン"
#~ msgid "No mousebutton"
#~ msgstr "マウスボタンなし"
#~ msgid "Left mousebutton"
#~ msgstr "左マウスボタン"
#~ msgid "Middle mousebutton"
#~ msgstr "中マウスボタン"
#~ msgid "Right mousebutton"
#~ msgstr "右マウスボタン"
#~ msgid "Send Password"
#~ msgstr "パスワードを送信"

View File

@ -6,9 +6,9 @@
#, fuzzy #, fuzzy
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: noVNC 1.1.0\n" "Project-Id-Version: noVNC 1.2.0\n"
"Report-Msgid-Bugs-To: novnc@googlegroups.com\n" "Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
"POT-Creation-Date: 2019-01-16 11:06+0100\n" "POT-Creation-Date: 2020-07-03 16:11+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -17,286 +17,282 @@ msgstr ""
"Content-Type: text/plain; charset=CHARSET\n" "Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
#: ../app/ui.js:387 #: ../app/ui.js:394
msgid "Connecting..." msgid "Connecting..."
msgstr "" msgstr ""
#: ../app/ui.js:394 #: ../app/ui.js:401
msgid "Disconnecting..." msgid "Disconnecting..."
msgstr "" msgstr ""
#: ../app/ui.js:400 #: ../app/ui.js:407
msgid "Reconnecting..." msgid "Reconnecting..."
msgstr "" msgstr ""
#: ../app/ui.js:405 #: ../app/ui.js:412
msgid "Internal error" msgid "Internal error"
msgstr "" msgstr ""
#: ../app/ui.js:995 #: ../app/ui.js:1008
msgid "Must set host" msgid "Must set host"
msgstr "" msgstr ""
#: ../app/ui.js:1077 #: ../app/ui.js:1090
msgid "Connected (encrypted) to " msgid "Connected (encrypted) to "
msgstr "" msgstr ""
#: ../app/ui.js:1079 #: ../app/ui.js:1092
msgid "Connected (unencrypted) to " msgid "Connected (unencrypted) to "
msgstr "" msgstr ""
#: ../app/ui.js:1102 #: ../app/ui.js:1115
msgid "Something went wrong, connection is closed" msgid "Something went wrong, connection is closed"
msgstr "" msgstr ""
#: ../app/ui.js:1105 #: ../app/ui.js:1118
msgid "Failed to connect to server" msgid "Failed to connect to server"
msgstr "" msgstr ""
#: ../app/ui.js:1115 #: ../app/ui.js:1128
msgid "Disconnected" msgid "Disconnected"
msgstr "" msgstr ""
#: ../app/ui.js:1128 #: ../app/ui.js:1143
msgid "New connection has been rejected with reason: " msgid "New connection has been rejected with reason: "
msgstr "" msgstr ""
#: ../app/ui.js:1131 #: ../app/ui.js:1146
msgid "New connection has been rejected" msgid "New connection has been rejected"
msgstr "" msgstr ""
#: ../app/ui.js:1151 #: ../app/ui.js:1181
msgid "Password is required" msgid "Credentials are required"
msgstr "" msgstr ""
#: ../vnc.html:84 #: ../vnc.html:74
msgid "noVNC encountered an error:" msgid "noVNC encountered an error:"
msgstr "" msgstr ""
#: ../vnc.html:94 #: ../vnc.html:84
msgid "Hide/Show the control bar" msgid "Hide/Show the control bar"
msgstr "" msgstr ""
#: ../vnc.html:101 #: ../vnc.html:91
msgid "Drag"
msgstr ""
#: ../vnc.html:91
msgid "Move/Drag Viewport" msgid "Move/Drag Viewport"
msgstr "" msgstr ""
#: ../vnc.html:101 #: ../vnc.html:97
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" msgid "Keyboard"
msgstr "" msgstr ""
#: ../vnc.html:119 #: ../vnc.html:97
msgid "Show Keyboard" msgid "Show Keyboard"
msgstr "" msgstr ""
#: ../vnc.html:126 #: ../vnc.html:102
msgid "Extra keys" msgid "Extra keys"
msgstr "" msgstr ""
#: ../vnc.html:126 #: ../vnc.html:102
msgid "Show Extra Keys" msgid "Show Extra Keys"
msgstr "" msgstr ""
#: ../vnc.html:131 #: ../vnc.html:107
msgid "Ctrl" msgid "Ctrl"
msgstr "" msgstr ""
#: ../vnc.html:131 #: ../vnc.html:107
msgid "Toggle Ctrl" msgid "Toggle Ctrl"
msgstr "" msgstr ""
#: ../vnc.html:134 #: ../vnc.html:110
msgid "Alt" msgid "Alt"
msgstr "" msgstr ""
#: ../vnc.html:134 #: ../vnc.html:110
msgid "Toggle Alt" msgid "Toggle Alt"
msgstr "" msgstr ""
#: ../vnc.html:137 #: ../vnc.html:113
msgid "Toggle Windows" msgid "Toggle Windows"
msgstr "" msgstr ""
#: ../vnc.html:137 #: ../vnc.html:113
msgid "Windows" msgid "Windows"
msgstr "" msgstr ""
#: ../vnc.html:140 #: ../vnc.html:116
msgid "Send Tab" msgid "Send Tab"
msgstr "" msgstr ""
#: ../vnc.html:140 #: ../vnc.html:116
msgid "Tab" msgid "Tab"
msgstr "" msgstr ""
#: ../vnc.html:143 #: ../vnc.html:119
msgid "Esc" msgid "Esc"
msgstr "" msgstr ""
#: ../vnc.html:143 #: ../vnc.html:119
msgid "Send Escape" msgid "Send Escape"
msgstr "" msgstr ""
#: ../vnc.html:146 #: ../vnc.html:122
msgid "Ctrl+Alt+Del" msgid "Ctrl+Alt+Del"
msgstr "" msgstr ""
#: ../vnc.html:146 #: ../vnc.html:122
msgid "Send Ctrl-Alt-Del" msgid "Send Ctrl-Alt-Del"
msgstr "" msgstr ""
#: ../vnc.html:154 #: ../vnc.html:129
msgid "Shutdown/Reboot" msgid "Shutdown/Reboot"
msgstr "" msgstr ""
#: ../vnc.html:154 #: ../vnc.html:129
msgid "Shutdown/Reboot..." msgid "Shutdown/Reboot..."
msgstr "" msgstr ""
#: ../vnc.html:160 #: ../vnc.html:135
msgid "Power" msgid "Power"
msgstr "" msgstr ""
#: ../vnc.html:162 #: ../vnc.html:137
msgid "Shutdown" msgid "Shutdown"
msgstr "" msgstr ""
#: ../vnc.html:163 #: ../vnc.html:138
msgid "Reboot" msgid "Reboot"
msgstr "" msgstr ""
#: ../vnc.html:164 #: ../vnc.html:139
msgid "Reset" msgid "Reset"
msgstr "" msgstr ""
#: ../vnc.html:169 ../vnc.html:175 #: ../vnc.html:144 ../vnc.html:150
msgid "Clipboard" msgid "Clipboard"
msgstr "" msgstr ""
#: ../vnc.html:179 #: ../vnc.html:154
msgid "Clear" msgid "Clear"
msgstr "" msgstr ""
#: ../vnc.html:185 #: ../vnc.html:160
msgid "Fullscreen" msgid "Fullscreen"
msgstr "" msgstr ""
#: ../vnc.html:190 ../vnc.html:197 #: ../vnc.html:165 ../vnc.html:172
msgid "Settings" msgid "Settings"
msgstr "" msgstr ""
#: ../vnc.html:200 #: ../vnc.html:175
msgid "Shared Mode" msgid "Shared Mode"
msgstr "" msgstr ""
#: ../vnc.html:203 #: ../vnc.html:178
msgid "View Only" msgid "View Only"
msgstr "" msgstr ""
#: ../vnc.html:207 #: ../vnc.html:182
msgid "Clip to Window" msgid "Clip to Window"
msgstr "" msgstr ""
#: ../vnc.html:210 #: ../vnc.html:185
msgid "Scaling Mode:" msgid "Scaling Mode:"
msgstr "" msgstr ""
#: ../vnc.html:212 #: ../vnc.html:187
msgid "None" msgid "None"
msgstr "" msgstr ""
#: ../vnc.html:213 #: ../vnc.html:188
msgid "Local Scaling" msgid "Local Scaling"
msgstr "" msgstr ""
#: ../vnc.html:214 #: ../vnc.html:189
msgid "Remote Resizing" msgid "Remote Resizing"
msgstr "" msgstr ""
#: ../vnc.html:219 #: ../vnc.html:194
msgid "Advanced" msgid "Advanced"
msgstr "" msgstr ""
#: ../vnc.html:222 #: ../vnc.html:197
msgid "Quality:"
msgstr ""
#: ../vnc.html:201
msgid "Compression level:"
msgstr ""
#: ../vnc.html:206
msgid "Repeater ID:" msgid "Repeater ID:"
msgstr "" msgstr ""
#: ../vnc.html:226 #: ../vnc.html:210
msgid "WebSocket" msgid "WebSocket"
msgstr "" msgstr ""
#: ../vnc.html:229 #: ../vnc.html:213
msgid "Encrypt" msgid "Encrypt"
msgstr "" msgstr ""
#: ../vnc.html:232 #: ../vnc.html:216
msgid "Host:" msgid "Host:"
msgstr "" msgstr ""
#: ../vnc.html:236 #: ../vnc.html:220
msgid "Port:" msgid "Port:"
msgstr "" msgstr ""
#: ../vnc.html:240 #: ../vnc.html:224
msgid "Path:" msgid "Path:"
msgstr "" msgstr ""
#: ../vnc.html:247 #: ../vnc.html:231
msgid "Automatic Reconnect" msgid "Automatic Reconnect"
msgstr "" msgstr ""
#: ../vnc.html:250 #: ../vnc.html:234
msgid "Reconnect Delay (ms):" msgid "Reconnect Delay (ms):"
msgstr "" msgstr ""
#: ../vnc.html:255 #: ../vnc.html:239
msgid "Show Dot when No Cursor" msgid "Show Dot when No Cursor"
msgstr "" msgstr ""
#: ../vnc.html:260 #: ../vnc.html:244
msgid "Logging:" msgid "Logging:"
msgstr "" msgstr ""
#: ../vnc.html:272 #: ../vnc.html:253
msgid "Version:"
msgstr ""
#: ../vnc.html:261
msgid "Disconnect" msgid "Disconnect"
msgstr "" msgstr ""
#: ../vnc.html:291 #: ../vnc.html:280
msgid "Connect" msgid "Connect"
msgstr "" msgstr ""
#: ../vnc.html:301 #: ../vnc.html:290
msgid "Username:"
msgstr ""
#: ../vnc.html:294
msgid "Password:" msgid "Password:"
msgstr "" msgstr ""
#: ../vnc.html:305 #: ../vnc.html:298
msgid "Send Password" msgid "Send Credentials"
msgstr "" msgstr ""
#: ../vnc.html:315 #: ../vnc.html:308
msgid "Cancel" msgid "Cancel"
msgstr "" msgstr ""

299
po/pt_BR.po Normal file
View File

@ -0,0 +1,299 @@
# Portuguese translations for noVNC package.
# Copyright (C) 2021 The noVNC Authors
# This file is distributed under the same license as the noVNC package.
# <liddack@outlook.com>, 2021.
#
msgid ""
msgstr ""
"Project-Id-Version: noVNC 1.2.0\n"
"Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
"POT-Creation-Date: 2021-03-15 21:55-0300\n"
"PO-Revision-Date: 2021-03-15 22:09-0300\n"
"Last-Translator: <liddack@outlook.com>\n"
"Language-Team: Brazilian Portuguese\n"
"Language: pt_BR\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.4.1\n"
#: ../app/ui.js:400
msgid "Connecting..."
msgstr "Conectando..."
#: ../app/ui.js:407
msgid "Disconnecting..."
msgstr "Desconectando..."
#: ../app/ui.js:413
msgid "Reconnecting..."
msgstr "Reconectando..."
#: ../app/ui.js:418
msgid "Internal error"
msgstr "Erro interno"
#: ../app/ui.js:1009
msgid "Must set host"
msgstr "É necessário definir o host"
#: ../app/ui.js:1091
msgid "Connected (encrypted) to "
msgstr "Conectado (com criptografia) a "
#: ../app/ui.js:1093
msgid "Connected (unencrypted) to "
msgstr "Conectado (sem criptografia) a "
#: ../app/ui.js:1116
msgid "Something went wrong, connection is closed"
msgstr "Algo deu errado. A conexão foi encerrada."
#: ../app/ui.js:1119
msgid "Failed to connect to server"
msgstr "Falha ao conectar-se ao servidor"
#: ../app/ui.js:1129
msgid "Disconnected"
msgstr "Desconectado"
#: ../app/ui.js:1144
msgid "New connection has been rejected with reason: "
msgstr "A nova conexão foi rejeitada pelo motivo: "
#: ../app/ui.js:1147
msgid "New connection has been rejected"
msgstr "A nova conexão foi rejeitada"
#: ../app/ui.js:1182
msgid "Credentials are required"
msgstr "Credenciais são obrigatórias"
#: ../vnc.html:61
msgid "noVNC encountered an error:"
msgstr "O noVNC encontrou um erro:"
#: ../vnc.html:71
msgid "Hide/Show the control bar"
msgstr "Esconder/mostrar a barra de controles"
#: ../vnc.html:78
msgid "Drag"
msgstr "Arrastar"
#: ../vnc.html:78
msgid "Move/Drag Viewport"
msgstr "Mover/arrastar a janela"
#: ../vnc.html:84
msgid "Keyboard"
msgstr "Teclado"
#: ../vnc.html:84
msgid "Show Keyboard"
msgstr "Mostrar teclado"
#: ../vnc.html:89
msgid "Extra keys"
msgstr "Teclas adicionais"
#: ../vnc.html:89
msgid "Show Extra Keys"
msgstr "Mostar teclas adicionais"
#: ../vnc.html:94
msgid "Ctrl"
msgstr "Ctrl"
#: ../vnc.html:94
msgid "Toggle Ctrl"
msgstr "Pressionar/soltar Ctrl"
#: ../vnc.html:97
msgid "Alt"
msgstr "Alt"
#: ../vnc.html:97
msgid "Toggle Alt"
msgstr "Pressionar/soltar Alt"
#: ../vnc.html:100
msgid "Toggle Windows"
msgstr "Pressionar/soltar Windows"
#: ../vnc.html:100
msgid "Windows"
msgstr "Windows"
#: ../vnc.html:103
msgid "Send Tab"
msgstr "Enviar Tab"
#: ../vnc.html:103
msgid "Tab"
msgstr "Tab"
#: ../vnc.html:106
msgid "Esc"
msgstr "Esc"
#: ../vnc.html:106
msgid "Send Escape"
msgstr "Enviar Esc"
#: ../vnc.html:109
msgid "Ctrl+Alt+Del"
msgstr "Ctrl+Alt+Del"
#: ../vnc.html:109
msgid "Send Ctrl-Alt-Del"
msgstr "Enviar Ctrl-Alt-Del"
#: ../vnc.html:116
msgid "Shutdown/Reboot"
msgstr "Desligar/reiniciar"
#: ../vnc.html:116
msgid "Shutdown/Reboot..."
msgstr "Desligar/reiniciar..."
#: ../vnc.html:122
msgid "Power"
msgstr "Ligar"
#: ../vnc.html:124
msgid "Shutdown"
msgstr "Desligar"
#: ../vnc.html:125
msgid "Reboot"
msgstr "Reiniciar"
#: ../vnc.html:126
msgid "Reset"
msgstr "Reiniciar (forçado)"
#: ../vnc.html:131 ../vnc.html:137
msgid "Clipboard"
msgstr "Área de transferência"
#: ../vnc.html:141
msgid "Clear"
msgstr "Limpar"
#: ../vnc.html:147
msgid "Fullscreen"
msgstr "Tela cheia"
#: ../vnc.html:152 ../vnc.html:159
msgid "Settings"
msgstr "Configurações"
#: ../vnc.html:162
msgid "Shared Mode"
msgstr "Modo compartilhado"
#: ../vnc.html:165
msgid "View Only"
msgstr "Apenas visualizar"
#: ../vnc.html:169
msgid "Clip to Window"
msgstr "Recortar à janela"
#: ../vnc.html:172
msgid "Scaling Mode:"
msgstr "Modo de dimensionamento:"
#: ../vnc.html:174
msgid "None"
msgstr "Nenhum"
#: ../vnc.html:175
msgid "Local Scaling"
msgstr "Local"
#: ../vnc.html:176
msgid "Remote Resizing"
msgstr "Remoto"
#: ../vnc.html:181
msgid "Advanced"
msgstr "Avançado"
#: ../vnc.html:184
msgid "Quality:"
msgstr "Qualidade:"
#: ../vnc.html:188
msgid "Compression level:"
msgstr "Nível de compressão:"
#: ../vnc.html:193
msgid "Repeater ID:"
msgstr "ID do repetidor:"
#: ../vnc.html:197
msgid "WebSocket"
msgstr "WebSocket"
#: ../vnc.html:200
msgid "Encrypt"
msgstr "Criptografar"
#: ../vnc.html:203
msgid "Host:"
msgstr "Host:"
#: ../vnc.html:207
msgid "Port:"
msgstr "Porta:"
#: ../vnc.html:211
msgid "Path:"
msgstr "Caminho:"
#: ../vnc.html:218
msgid "Automatic Reconnect"
msgstr "Reconexão automática"
#: ../vnc.html:221
msgid "Reconnect Delay (ms):"
msgstr "Atraso da reconexão (ms)"
#: ../vnc.html:226
msgid "Show Dot when No Cursor"
msgstr "Mostrar ponto quando não há cursor"
#: ../vnc.html:231
msgid "Logging:"
msgstr "Registros:"
#: ../vnc.html:240
msgid "Version:"
msgstr "Versão:"
#: ../vnc.html:248
msgid "Disconnect"
msgstr "Desconectar"
#: ../vnc.html:267
msgid "Connect"
msgstr "Conectar"
#: ../vnc.html:277
msgid "Username:"
msgstr "Nome de usuário:"
#: ../vnc.html:281
msgid "Password:"
msgstr "Senha:"
#: ../vnc.html:285
msgid "Send Credentials"
msgstr "Enviar credenciais"
#: ../vnc.html:295
msgid "Cancel"
msgstr "Cancelar"

206
po/sv.po
View File

@ -1,15 +1,15 @@
# Swedish translations for noVNC package # Swedish translations for noVNC package
# Svenska översättningar för paket noVNC. # Svenska översättningar för paketet noVNC.
# Copyright (C) 2018 The noVNC Authors # Copyright (C) 2020 The noVNC Authors
# This file is distributed under the same license as the noVNC package. # This file is distributed under the same license as the noVNC package.
# Samuel Mannehed <samuel@cendio.se>, 2019. # Samuel Mannehed <samuel@cendio.se>, 2020.
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: noVNC 1.1.0\n" "Project-Id-Version: noVNC 1.2.0\n"
"Report-Msgid-Bugs-To: novnc@googlegroups.com\n" "Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
"POT-Creation-Date: 2019-01-16 11:06+0100\n" "POT-Creation-Date: 2020-07-03 16:11+0200\n"
"PO-Revision-Date: 2019-04-08 10:18+0200\n" "PO-Revision-Date: 2020-07-08 23:18+0200\n"
"Last-Translator: Samuel Mannehed <samuel@cendio.se>\n" "Last-Translator: Samuel Mannehed <samuel@cendio.se>\n"
"Language-Team: none\n" "Language-Team: none\n"
"Language: sv\n" "Language: sv\n"
@ -19,298 +19,282 @@ msgstr ""
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 2.0.3\n" "X-Generator: Poedit 2.0.3\n"
#: ../app/ui.js:387 #: ../app/ui.js:394
msgid "Connecting..." msgid "Connecting..."
msgstr "Ansluter..." msgstr "Ansluter..."
#: ../app/ui.js:394 #: ../app/ui.js:401
msgid "Disconnecting..." msgid "Disconnecting..."
msgstr "Kopplar ner..." msgstr "Kopplar ner..."
#: ../app/ui.js:400 #: ../app/ui.js:407
msgid "Reconnecting..." msgid "Reconnecting..."
msgstr "Återansluter..." msgstr "Återansluter..."
#: ../app/ui.js:405 #: ../app/ui.js:412
msgid "Internal error" msgid "Internal error"
msgstr "Internt fel" msgstr "Internt fel"
#: ../app/ui.js:995 #: ../app/ui.js:1008
msgid "Must set host" msgid "Must set host"
msgstr "Du måste specifiera en värd" msgstr "Du måste specifiera en värd"
#: ../app/ui.js:1077 #: ../app/ui.js:1090
msgid "Connected (encrypted) to " msgid "Connected (encrypted) to "
msgstr "Ansluten (krypterat) till " msgstr "Ansluten (krypterat) till "
#: ../app/ui.js:1079 #: ../app/ui.js:1092
msgid "Connected (unencrypted) to " msgid "Connected (unencrypted) to "
msgstr "Ansluten (okrypterat) till " msgstr "Ansluten (okrypterat) till "
#: ../app/ui.js:1102 #: ../app/ui.js:1115
msgid "Something went wrong, connection is closed" msgid "Something went wrong, connection is closed"
msgstr "Något gick fel, anslutningen avslutades" msgstr "Något gick fel, anslutningen avslutades"
#: ../app/ui.js:1105 #: ../app/ui.js:1118
msgid "Failed to connect to server" msgid "Failed to connect to server"
msgstr "Misslyckades att ansluta till servern" msgstr "Misslyckades att ansluta till servern"
#: ../app/ui.js:1115 #: ../app/ui.js:1128
msgid "Disconnected" msgid "Disconnected"
msgstr "Frånkopplad" msgstr "Frånkopplad"
#: ../app/ui.js:1128 #: ../app/ui.js:1143
msgid "New connection has been rejected with reason: " msgid "New connection has been rejected with reason: "
msgstr "Ny anslutning har blivit nekad med följande skäl: " msgstr "Ny anslutning har blivit nekad med följande skäl: "
#: ../app/ui.js:1131 #: ../app/ui.js:1146
msgid "New connection has been rejected" msgid "New connection has been rejected"
msgstr "Ny anslutning har blivit nekad" msgstr "Ny anslutning har blivit nekad"
#: ../app/ui.js:1151 #: ../app/ui.js:1181
msgid "Password is required" msgid "Credentials are required"
msgstr "Lösenord krävs" msgstr "Användaruppgifter krävs"
#: ../vnc.html:84 #: ../vnc.html:74
msgid "noVNC encountered an error:" msgid "noVNC encountered an error:"
msgstr "noVNC stötte på ett problem:" msgstr "noVNC stötte på ett problem:"
#: ../vnc.html:94 #: ../vnc.html:84
msgid "Hide/Show the control bar" msgid "Hide/Show the control bar"
msgstr "Göm/Visa kontrollbaren" msgstr "Göm/Visa kontrollbaren"
#: ../vnc.html:101 #: ../vnc.html:91
msgid "Drag"
msgstr "Dra"
#: ../vnc.html:91
msgid "Move/Drag Viewport" msgid "Move/Drag Viewport"
msgstr "Flytta/Dra Vyn" msgstr "Flytta/Dra Vyn"
#: ../vnc.html:101 #: ../vnc.html:97
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" msgid "Keyboard"
msgstr "Tangentbord" msgstr "Tangentbord"
#: ../vnc.html:119 #: ../vnc.html:97
msgid "Show Keyboard" msgid "Show Keyboard"
msgstr "Visa Tangentbord" msgstr "Visa Tangentbord"
#: ../vnc.html:126 #: ../vnc.html:102
msgid "Extra keys" msgid "Extra keys"
msgstr "Extraknappar" msgstr "Extraknappar"
#: ../vnc.html:126 #: ../vnc.html:102
msgid "Show Extra Keys" msgid "Show Extra Keys"
msgstr "Visa Extraknappar" msgstr "Visa Extraknappar"
#: ../vnc.html:131 #: ../vnc.html:107
msgid "Ctrl" msgid "Ctrl"
msgstr "Ctrl" msgstr "Ctrl"
#: ../vnc.html:131 #: ../vnc.html:107
msgid "Toggle Ctrl" msgid "Toggle Ctrl"
msgstr "Växla Ctrl" msgstr "Växla Ctrl"
#: ../vnc.html:134 #: ../vnc.html:110
msgid "Alt" msgid "Alt"
msgstr "Alt" msgstr "Alt"
#: ../vnc.html:134 #: ../vnc.html:110
msgid "Toggle Alt" msgid "Toggle Alt"
msgstr "Växla Alt" msgstr "Växla Alt"
#: ../vnc.html:137 #: ../vnc.html:113
msgid "Toggle Windows" msgid "Toggle Windows"
msgstr "Växla Windows" msgstr "Växla Windows"
#: ../vnc.html:137 #: ../vnc.html:113
msgid "Windows" msgid "Windows"
msgstr "Windows" msgstr "Windows"
#: ../vnc.html:140 #: ../vnc.html:116
msgid "Send Tab" msgid "Send Tab"
msgstr "Skicka Tab" msgstr "Skicka Tab"
#: ../vnc.html:140 #: ../vnc.html:116
msgid "Tab" msgid "Tab"
msgstr "Tab" msgstr "Tab"
#: ../vnc.html:143 #: ../vnc.html:119
msgid "Esc" msgid "Esc"
msgstr "Esc" msgstr "Esc"
#: ../vnc.html:143 #: ../vnc.html:119
msgid "Send Escape" msgid "Send Escape"
msgstr "Skicka Escape" msgstr "Skicka Escape"
#: ../vnc.html:146 #: ../vnc.html:122
msgid "Ctrl+Alt+Del" msgid "Ctrl+Alt+Del"
msgstr "Ctrl+Alt+Del" msgstr "Ctrl+Alt+Del"
#: ../vnc.html:146 #: ../vnc.html:122
msgid "Send Ctrl-Alt-Del" msgid "Send Ctrl-Alt-Del"
msgstr "Skicka Ctrl-Alt-Del" msgstr "Skicka Ctrl-Alt-Del"
#: ../vnc.html:154 #: ../vnc.html:129
msgid "Shutdown/Reboot" msgid "Shutdown/Reboot"
msgstr "Stäng av/Boota om" msgstr "Stäng av/Boota om"
#: ../vnc.html:154 #: ../vnc.html:129
msgid "Shutdown/Reboot..." msgid "Shutdown/Reboot..."
msgstr "Stäng av/Boota om..." msgstr "Stäng av/Boota om..."
#: ../vnc.html:160 #: ../vnc.html:135
msgid "Power" msgid "Power"
msgstr "Ström" msgstr "Ström"
#: ../vnc.html:162 #: ../vnc.html:137
msgid "Shutdown" msgid "Shutdown"
msgstr "Stäng av" msgstr "Stäng av"
#: ../vnc.html:163 #: ../vnc.html:138
msgid "Reboot" msgid "Reboot"
msgstr "Boota om" msgstr "Boota om"
#: ../vnc.html:164 #: ../vnc.html:139
msgid "Reset" msgid "Reset"
msgstr "Återställ" msgstr "Återställ"
#: ../vnc.html:169 ../vnc.html:175 #: ../vnc.html:144 ../vnc.html:150
msgid "Clipboard" msgid "Clipboard"
msgstr "Urklipp" msgstr "Urklipp"
#: ../vnc.html:179 #: ../vnc.html:154
msgid "Clear" msgid "Clear"
msgstr "Rensa" msgstr "Rensa"
#: ../vnc.html:185 #: ../vnc.html:160
msgid "Fullscreen" msgid "Fullscreen"
msgstr "Fullskärm" msgstr "Fullskärm"
#: ../vnc.html:190 ../vnc.html:197 #: ../vnc.html:165 ../vnc.html:172
msgid "Settings" msgid "Settings"
msgstr "Inställningar" msgstr "Inställningar"
#: ../vnc.html:200 #: ../vnc.html:175
msgid "Shared Mode" msgid "Shared Mode"
msgstr "Delat Läge" msgstr "Delat Läge"
#: ../vnc.html:203 #: ../vnc.html:178
msgid "View Only" msgid "View Only"
msgstr "Endast Visning" msgstr "Endast Visning"
#: ../vnc.html:207 #: ../vnc.html:182
msgid "Clip to Window" msgid "Clip to Window"
msgstr "Begränsa till Fönster" msgstr "Begränsa till Fönster"
#: ../vnc.html:210 #: ../vnc.html:185
msgid "Scaling Mode:" msgid "Scaling Mode:"
msgstr "Skalningsläge:" msgstr "Skalningsläge:"
#: ../vnc.html:212 #: ../vnc.html:187
msgid "None" msgid "None"
msgstr "Ingen" msgstr "Ingen"
#: ../vnc.html:213 #: ../vnc.html:188
msgid "Local Scaling" msgid "Local Scaling"
msgstr "Lokal Skalning" msgstr "Lokal Skalning"
#: ../vnc.html:214 #: ../vnc.html:189
msgid "Remote Resizing" msgid "Remote Resizing"
msgstr "Ändra Storlek" msgstr "Ändra Storlek"
#: ../vnc.html:219 #: ../vnc.html:194
msgid "Advanced" msgid "Advanced"
msgstr "Avancerat" msgstr "Avancerat"
#: ../vnc.html:222 #: ../vnc.html:197
msgid "Quality:"
msgstr "Kvalitet:"
#: ../vnc.html:201
msgid "Compression level:"
msgstr "Kompressionsnivå:"
#: ../vnc.html:206
msgid "Repeater ID:" msgid "Repeater ID:"
msgstr "Repeater-ID:" msgstr "Repeater-ID:"
#: ../vnc.html:226 #: ../vnc.html:210
msgid "WebSocket" msgid "WebSocket"
msgstr "WebSocket" msgstr "WebSocket"
#: ../vnc.html:229 #: ../vnc.html:213
msgid "Encrypt" msgid "Encrypt"
msgstr "Kryptera" msgstr "Kryptera"
#: ../vnc.html:232 #: ../vnc.html:216
msgid "Host:" msgid "Host:"
msgstr "Värd:" msgstr "Värd:"
#: ../vnc.html:236 #: ../vnc.html:220
msgid "Port:" msgid "Port:"
msgstr "Port:" msgstr "Port:"
#: ../vnc.html:240 #: ../vnc.html:224
msgid "Path:" msgid "Path:"
msgstr "Sökväg:" msgstr "Sökväg:"
#: ../vnc.html:247 #: ../vnc.html:231
msgid "Automatic Reconnect" msgid "Automatic Reconnect"
msgstr "Automatisk Återanslutning" msgstr "Automatisk Återanslutning"
#: ../vnc.html:250 #: ../vnc.html:234
msgid "Reconnect Delay (ms):" msgid "Reconnect Delay (ms):"
msgstr "Fördröjning (ms):" msgstr "Fördröjning (ms):"
#: ../vnc.html:255 #: ../vnc.html:239
msgid "Show Dot when No Cursor" msgid "Show Dot when No Cursor"
msgstr "Visa prick när ingen muspekare finns" msgstr "Visa prick när ingen muspekare finns"
#: ../vnc.html:260 #: ../vnc.html:244
msgid "Logging:" msgid "Logging:"
msgstr "Loggning:" msgstr "Loggning:"
#: ../vnc.html:272 #: ../vnc.html:253
msgid "Version:"
msgstr "Version:"
#: ../vnc.html:261
msgid "Disconnect" msgid "Disconnect"
msgstr "Koppla från" msgstr "Koppla från"
#: ../vnc.html:291 #: ../vnc.html:280
msgid "Connect" msgid "Connect"
msgstr "Anslut" msgstr "Anslut"
#: ../vnc.html:301 #: ../vnc.html:290
msgid "Username:"
msgstr "Användarnamn:"
#: ../vnc.html:294
msgid "Password:" msgid "Password:"
msgstr "Lösenord:" msgstr "Lösenord:"
#: ../vnc.html:305 #: ../vnc.html:298
msgid "Send Password" msgid "Send Credentials"
msgstr "Skicka lösenord" msgstr "Skicka Användaruppgifter"
#: ../vnc.html:315 #: ../vnc.html:308
msgid "Cancel" msgid "Cancel"
msgstr "Avbryt" 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"

View File

@ -24,6 +24,6 @@ snapctl get services | jq -c '.[]' | while read service; do # for each service t
echo "novnc: not starting service ${service} with listen_port ${listen_port} and vnc_host_port ${vnc_host_port}" echo "novnc: not starting service ${service} with listen_port ${listen_port} and vnc_host_port ${vnc_host_port}"
else else
# start (and fork with '&') the service using the specified listen port and VNC host:port # start (and fork with '&') the service using the specified listen port and VNC host:port
$SNAP/utils/launch.sh --listen $listen_port --vnc $vnc_host_port & $SNAP/novnc_proxy --listen $listen_port --vnc $vnc_host_port &
fi fi
done done

View File

@ -1,23 +1,44 @@
name: novnc name: novnc
base: core18 # the base snap is the execution environment for this snap base: core18 # the base snap is the execution environment for this snap
version: '1.1.0' version: '@VERSION@'
summary: Open Source VNC client using HTML5 (WebSockets, Canvas) summary: Open Source VNC client using HTML5 (WebSockets, Canvas)
description: | description: |
Open Source VNC client using HTML5 (WebSockets, Canvas). Open Source VNC client using HTML5 (WebSockets, Canvas).
noVNC is both a VNC client JavaScript library as well as an application built on top of that library. noVNC runs well in any modern browser including mobile browsers (iOS and Android). noVNC is both a VNC client JavaScript library as well as an
application built on top of that library. noVNC runs well in any
modern browser including mobile browsers (iOS and Android).
grade: stable grade: stable
confinement: strict confinement: strict
parts: parts:
novnc: novnc:
source: https://github.com/novnc/noVNC.git #https://github.com/novnc/noVNC/archive/v$SNAPCRAFT_PROJECT_VERSION.tar.gz source: .
plugin: dump plugin: dump
organize:
utils/novnc_proxy: /
stage:
- vnc.html
- app
- core/**/*.js
- vendor/**/*.js
- novnc_proxy
stage-packages:
- bash
svc-script:
source: snap/local
plugin: dump
stage:
- svc_wrapper.sh
stage-packages: stage-packages:
- websockify
- bash - bash
- jq - jq
- python-numpy
websockify:
source: https://github.com/novnc/websockify/archive/v0.9.0.tar.gz
plugin: python
stage-packages:
- python3-numpy - python3-numpy
hooks: hooks:
@ -26,9 +47,9 @@ hooks:
apps: apps:
novnc: novnc:
command: utils/launch.sh command: ./novnc_proxy
plugs: [network, network-bind] plugs: [network, network-bind]
novncsvc: novncsvc:
command: utils/svc_wrapper.sh command: ./svc_wrapper.sh
daemon: forking daemon: forking
plugs: [network, network-bind] plugs: [network, network-bind]

View File

@ -1,32 +1,33 @@
// noVNC specific assertions // noVNC specific assertions
chai.use(function (_chai, utils) { chai.use(function (_chai, utils) {
_chai.Assertion.addMethod('displayed', function (target_data) { function _equal(a, b) {
return a === b;
}
_chai.Assertion.addMethod('displayed', function (targetData, cmp=_equal) {
const obj = this._obj; const obj = this._obj;
const ctx = obj._target.getContext('2d'); const ctx = obj._target.getContext('2d');
const data_cl = ctx.getImageData(0, 0, obj._target.width, obj._target.height).data; const data = 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 len = data.length;
const data = new Uint8Array(data_cl); new chai.Assertion(len).to.be.equal(targetData.length, "unexpected display size");
const len = data_cl.length;
new chai.Assertion(len).to.be.equal(target_data.length, "unexpected display size");
let same = true; let same = true;
for (let i = 0; i < len; i++) { for (let i = 0; i < len; i++) {
if (data[i] != target_data[i]) { if (!cmp(data[i], targetData[i])) {
same = false; same = false;
break; break;
} }
} }
if (!same) { if (!same) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log("expected data: %o, actual data: %o", target_data, data); console.log("expected data: %o, actual data: %o", targetData, data);
} }
this.assert(same, this.assert(same,
"expected #{this} to have displayed the image #{exp}, but instead it displayed #{act}", "expected #{this} to have displayed the image #{exp}, but instead it displayed #{act}",
"expected #{this} not to have displayed the image #{act}", "expected #{this} not to have displayed the image #{act}",
target_data, targetData,
data); data);
}); });
_chai.Assertion.addMethod('sent', function (target_data) { _chai.Assertion.addMethod('sent', function (targetData) {
const obj = this._obj; const obj = this._obj;
obj.inspect = () => { obj.inspect = () => {
const res = { _websocket: obj._websocket, rQi: obj._rQi, _rQ: new Uint8Array(obj._rQ.buffer, 0, obj._rQlen), const res = { _websocket: obj._websocket, rQi: obj._rQi, _rQ: new Uint8Array(obj._rQ.buffer, 0, obj._rQlen),
@ -34,13 +35,13 @@ chai.use(function (_chai, utils) {
res.prototype = obj; res.prototype = obj;
return res; return res;
}; };
const data = obj._websocket._get_sent_data(); const data = obj._websocket._getSentData();
let same = true; let same = true;
if (data.length != target_data.length) { if (data.length != targetData.length) {
same = false; same = false;
} else { } else {
for (let i = 0; i < data.length; i++) { for (let i = 0; i < data.length; i++) {
if (data[i] != target_data[i]) { if (data[i] != targetData[i]) {
same = false; same = false;
break; break;
} }
@ -48,12 +49,12 @@ chai.use(function (_chai, utils) {
} }
if (!same) { if (!same) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log("expected data: %o, actual data: %o", target_data, data); console.log("expected data: %o, actual data: %o", targetData, data);
} }
this.assert(same, this.assert(same,
"expected #{this} to have sent the data #{exp}, but it actually sent #{act}", "expected #{this} to have sent the data #{exp}, but it actually sent #{act}",
"expected #{this} not to have sent the data #{act}", "expected #{this} not to have sent the data #{act}",
Array.prototype.slice.call(target_data), Array.prototype.slice.call(targetData),
Array.prototype.slice.call(data)); Array.prototype.slice.call(data));
}); });

View File

@ -1,41 +1,33 @@
import Base64 from '../core/base64.js'; 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 { export default class FakeWebSocket {
constructor(uri, protocols) { constructor(uri, protocols) {
this.url = uri; this.url = uri;
this.binaryType = "arraybuffer"; this.binaryType = "arraybuffer";
this.extensions = ""; this.extensions = "";
this.onerror = null;
this.onmessage = null;
this.onopen = null;
if (!protocols || typeof protocols === 'string') { if (!protocols || typeof protocols === 'string') {
this.protocol = protocols; this.protocol = protocols;
} else { } else {
this.protocol = protocols[0]; this.protocol = protocols[0];
} }
this._send_queue = new Uint8Array(20000); this._sendQueue = new Uint8Array(20000);
this.readyState = FakeWebSocket.CONNECTING; this.readyState = FakeWebSocket.CONNECTING;
this.bufferedAmount = 0; this.bufferedAmount = 0;
this.__is_fake = true; this._isFake = true;
} }
close(code, reason) { close(code, reason) {
this.readyState = FakeWebSocket.CLOSED; this.readyState = FakeWebSocket.CLOSED;
if (this.onclose) { if (this.onclose) {
this.onclose(make_event("close", { 'code': code, 'reason': reason, 'wasClean': true })); this.onclose(new CloseEvent("close", { 'code': code, 'reason': reason, 'wasClean': true }));
} }
} }
@ -45,12 +37,12 @@ export default class FakeWebSocket {
} else { } else {
data = new Uint8Array(data); data = new Uint8Array(data);
} }
this._send_queue.set(data, this.bufferedAmount); this._sendQueue.set(data, this.bufferedAmount);
this.bufferedAmount += data.length; this.bufferedAmount += data.length;
} }
_get_sent_data() { _getSentData() {
const res = new Uint8Array(this._send_queue.buffer, 0, this.bufferedAmount); const res = new Uint8Array(this._sendQueue.buffer, 0, this.bufferedAmount);
this.bufferedAmount = 0; this.bufferedAmount = 0;
return res; return res;
} }
@ -58,16 +50,16 @@ export default class FakeWebSocket {
_open() { _open() {
this.readyState = FakeWebSocket.OPEN; this.readyState = FakeWebSocket.OPEN;
if (this.onopen) { if (this.onopen) {
this.onopen(make_event('open')); this.onopen(new Event('open'));
} }
} }
_receive_data(data) { _receiveData(data) {
// Break apart the data to expose bugs where we assume data is // Break apart the data to expose bugs where we assume data is
// neatly packaged // neatly packaged
for (let i = 0;i < data.length;i++) { for (let i = 0;i < data.length;i++) {
let buf = data.subarray(i, i+1); let buf = data.subarray(i, i+1);
this.onmessage(make_event("message", { 'data': buf })); this.onmessage(new MessageEvent("message", { 'data': buf }));
} }
} }
} }
@ -77,20 +69,20 @@ FakeWebSocket.CONNECTING = WebSocket.CONNECTING;
FakeWebSocket.CLOSING = WebSocket.CLOSING; FakeWebSocket.CLOSING = WebSocket.CLOSING;
FakeWebSocket.CLOSED = WebSocket.CLOSED; FakeWebSocket.CLOSED = WebSocket.CLOSED;
FakeWebSocket.__is_fake = true; FakeWebSocket._isFake = true;
FakeWebSocket.replace = () => { FakeWebSocket.replace = () => {
if (!WebSocket.__is_fake) { if (!WebSocket._isFake) {
const real_version = WebSocket; const realVersion = WebSocket;
// eslint-disable-next-line no-global-assign // eslint-disable-next-line no-global-assign
WebSocket = FakeWebSocket; WebSocket = FakeWebSocket;
FakeWebSocket.__real_version = real_version; FakeWebSocket._realVersion = realVersion;
} }
}; };
FakeWebSocket.restore = () => { FakeWebSocket.restore = () => {
if (WebSocket.__is_fake) { if (WebSocket._isFake) {
// eslint-disable-next-line no-global-assign // eslint-disable-next-line no-global-assign
WebSocket = WebSocket.__real_version; WebSocket = WebSocket._realVersion;
} }
}; };

View File

@ -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);

View File

@ -41,6 +41,7 @@ function enableUI() {
document.getElementById('mode1').checked = true; document.getElementById('mode1').checked = true;
} }
/* eslint-disable-next-line camelcase */
message("Loaded " + VNC_frame_data.length + " frames"); message("Loaded " + VNC_frame_data.length + " frames");
const startButton = document.getElementById('startButton'); const startButton = document.getElementById('startButton');
@ -49,12 +50,16 @@ function enableUI() {
message("Converting..."); message("Converting...");
/* eslint-disable-next-line camelcase */
frames = VNC_frame_data; frames = VNC_frame_data;
let encoding; let encoding;
// Only present in older recordings
/* eslint-disable camelcase */
if (window.VNC_frame_encoding) { if (window.VNC_frame_encoding) {
// Only present in older recordings
encoding = VNC_frame_encoding; encoding = VNC_frame_encoding;
/* eslint-enable camelcase */
} else { } else {
let frame = frames[0]; let frame = frames[0];
let start = frame.indexOf('{', 1) + 1; let start = frame.indexOf('{', 1) + 1;
@ -102,7 +107,7 @@ class IterationPlayer {
this._iteration = undefined; this._iteration = undefined;
this._player = undefined; this._player = undefined;
this._start_time = undefined; this._startTime = undefined;
this._frames = frames; this._frames = frames;
@ -115,7 +120,7 @@ class IterationPlayer {
start(realtime) { start(realtime) {
this._iteration = 0; this._iteration = 0;
this._start_time = (new Date()).getTime(); this._startTime = (new Date()).getTime();
this._realtime = realtime; this._realtime = realtime;
@ -139,7 +144,7 @@ class IterationPlayer {
_finish() { _finish() {
const endTime = (new Date()).getTime(); const endTime = (new Date()).getTime();
const totalDuration = endTime - this._start_time; const totalDuration = endTime - this._startTime;
const evt = new CustomEvent('finish', const evt = new CustomEvent('finish',
{ detail: { detail:

View File

@ -49,10 +49,10 @@ export default class RecordingPlayer {
this._disconnected = disconnected; this._disconnected = disconnected;
this._rfb = undefined; this._rfb = undefined;
this._frame_length = this._frames.length; this._frameLength = this._frames.length;
this._frame_index = 0; this._frameIndex = 0;
this._start_time = undefined; this._startTime = undefined;
this._realtime = true; this._realtime = true;
this._trafficManagement = true; this._trafficManagement = true;
@ -72,8 +72,8 @@ export default class RecordingPlayer {
this._enablePlaybackMode(); this._enablePlaybackMode();
// reset the frame index and timer // reset the frame index and timer
this._frame_index = 0; this._frameIndex = 0;
this._start_time = (new Date()).getTime(); this._startTime = (new Date()).getTime();
this._realtime = realtime; this._realtime = realtime;
this._trafficManagement = (trafficManagement === undefined) ? !realtime : trafficManagement; this._trafficManagement = (trafficManagement === undefined) ? !realtime : trafficManagement;
@ -97,22 +97,22 @@ export default class RecordingPlayer {
_queueNextPacket() { _queueNextPacket() {
if (!this._running) { return; } if (!this._running) { return; }
let frame = this._frames[this._frame_index]; let frame = this._frames[this._frameIndex];
// skip send frames // skip send frames
while (this._frame_index < this._frame_length && frame.fromClient) { while (this._frameIndex < this._frameLength && frame.fromClient) {
this._frame_index++; this._frameIndex++;
frame = this._frames[this._frame_index]; frame = this._frames[this._frameIndex];
} }
if (this._frame_index >= this._frame_length) { if (this._frameIndex >= this._frameLength) {
Log.Debug('Finished, no more frames'); Log.Debug('Finished, no more frames');
this._finish(); this._finish();
return; return;
} }
if (this._realtime) { if (this._realtime) {
const toffset = (new Date()).getTime() - this._start_time; const toffset = (new Date()).getTime() - this._startTime;
let delay = frame.timestamp - toffset; let delay = frame.timestamp - toffset;
if (delay < 1) delay = 1; if (delay < 1) delay = 1;
@ -134,10 +134,10 @@ export default class RecordingPlayer {
return; return;
} }
const frame = this._frames[this._frame_index]; const frame = this._frames[this._frameIndex];
this._rfb._sock._recv_message({'data': frame.data}); this._rfb._sock._recvMessage({'data': frame.data});
this._frame_index++; this._frameIndex++;
this._queueNextPacket(); this._queueNextPacket();
} }
@ -155,13 +155,13 @@ export default class RecordingPlayer {
this._running = false; this._running = false;
this._rfb._sock._eventHandlers.close({code: 1000, reason: ""}); this._rfb._sock._eventHandlers.close({code: 1000, reason: ""});
delete this._rfb; delete this._rfb;
this.onfinish((new Date()).getTime() - this._start_time); this.onfinish((new Date()).getTime() - this._startTime);
} }
} }
_handleDisconnect(evt) { _handleDisconnect(evt) {
this._running = false; this._running = false;
this._disconnected(evt.detail.clean, this._frame_index); this._disconnected(evt.detail.clean, this._frameIndex);
} }
_handleCredentials(evt) { _handleCredentials(evt) {

83
tests/test.copyrect.js Normal file
View File

@ -0,0 +1,83 @@
const expect = chai.expect;
import Websock from '../core/websock.js';
import Display from '../core/display.js';
import CopyRectDecoder from '../core/decoders/copyrect.js';
import FakeWebSocket from './fake.websocket.js';
function testDecodeRect(decoder, x, y, width, height, data, display, depth) {
let sock;
sock = new Websock;
sock.open("ws://example.com");
sock.on('message', () => {
decoder.decodeRect(x, y, width, height, sock, display, depth);
});
// Empty messages are filtered at multiple layers, so we need to
// do a direct call
if (data.length === 0) {
decoder.decodeRect(x, y, width, height, sock, display, depth);
} else {
sock._websocket._receiveData(new Uint8Array(data));
}
display.flip();
}
describe('CopyRect Decoder', function () {
let decoder;
let display;
before(FakeWebSocket.replace);
after(FakeWebSocket.restore);
beforeEach(function () {
decoder = new CopyRectDecoder();
display = new Display(document.createElement('canvas'));
display.resize(4, 4);
});
it('should handle the CopyRect encoding', function () {
// seed some initial data to copy
display.fillRect(0, 0, 4, 4, [ 0x11, 0x22, 0x33 ]);
display.fillRect(0, 0, 2, 2, [ 0x00, 0x00, 0xff ]);
display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);
testDecodeRect(decoder, 0, 2, 2, 2,
[0x00, 0x02, 0x00, 0x00],
display, 24);
testDecodeRect(decoder, 2, 2, 2, 2,
[0x00, 0x00, 0x00, 0x00],
display, 24);
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]);
expect(display).to.have.displayed(targetData);
});
it('should handle empty rects', function () {
display.fillRect(0, 0, 4, 4, [ 0x00, 0x00, 0xff ]);
display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);
display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]);
testDecodeRect(decoder, 1, 2, 0, 0, [0x00, 0x00, 0x00, 0x00], display, 24);
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]);
expect(display).to.have.displayed(targetData);
});
});

View File

@ -17,12 +17,14 @@ function _inflator(compText, expected) {
strm.output = new Uint8Array(chunkSize); strm.output = new Uint8Array(chunkSize);
} }
/* eslint-disable camelcase */
strm.input = compText; strm.input = compText;
strm.avail_in = strm.input.length; strm.avail_in = strm.input.length;
strm.next_in = 0; strm.next_in = 0;
strm.next_out = 0; strm.next_out = 0;
strm.avail_out = expected.length; strm.avail_out = expected.length;
/* eslint-enable camelcase */
let ret = inflate(strm, 0); let ret = inflate(strm, 0);

View File

@ -4,28 +4,27 @@ import Base64 from '../core/base64.js';
import Display from '../core/display.js'; import Display from '../core/display.js';
describe('Display/Canvas Helper', function () { describe('Display/Canvas Helper', function () {
const checked_data = new Uint8Array([ const checkedData = new Uint8ClampedArray([
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, 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,
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]); const basicData = new Uint8ClampedArray([0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0xff, 0xff, 0xff, 255]);
function make_image_canvas(input_data) { function makeImageCanvas(inputData, width, height) {
const canvas = document.createElement('canvas'); const canvas = document.createElement('canvas');
canvas.width = 4; canvas.width = width;
canvas.height = 4; canvas.height = height;
const ctx = canvas.getContext('2d'); const ctx = canvas.getContext('2d');
const data = ctx.createImageData(4, 4); const data = new ImageData(inputData, width, height);
for (let i = 0; i < checked_data.length; i++) { data.data[i] = input_data[i]; }
ctx.putImageData(data, 0, 0); ctx.putImageData(data, 0, 0);
return canvas; return canvas;
} }
function make_image_png(input_data) { function makeImagePng(inputData, width, height) {
const canvas = make_image_canvas(input_data); const canvas = makeImageCanvas(inputData, width, height);
const url = canvas.toDataURL(); const url = canvas.toDataURL();
const data = url.split(",")[1]; const data = url.split(",")[1];
return Base64.decode(data); return Base64.decode(data);
@ -44,11 +43,11 @@ describe('Display/Canvas Helper', function () {
it('should take viewport location into consideration when drawing images', function () { it('should take viewport location into consideration when drawing images', function () {
display.resize(4, 4); display.resize(4, 4);
display.viewportChangeSize(2, 2); display.viewportChangeSize(2, 2);
display.drawImage(make_image_canvas(basic_data), 1, 1); display.drawImage(makeImageCanvas(basicData, 4, 1), 1, 1);
display.flip(); display.flip();
const expected = new Uint8Array(16); const expected = new Uint8Array(16);
for (let i = 0; i < 8; i++) { expected[i] = basic_data[i]; } for (let i = 0; i < 8; i++) { expected[i] = basicData[i]; }
for (let i = 8; i < 16; i++) { expected[i] = 0; } for (let i = 8; i < 16; i++) { expected[i] = 0; }
expect(display).to.have.displayed(expected); expect(display).to.have.displayed(expected);
}); });
@ -123,12 +122,12 @@ describe('Display/Canvas Helper', function () {
it('should change the size of the logical canvas', function () { it('should change the size of the logical canvas', function () {
display.resize(5, 7); display.resize(5, 7);
expect(display._fb_width).to.equal(5); expect(display._fbWidth).to.equal(5);
expect(display._fb_height).to.equal(7); expect(display._fbHeight).to.equal(7);
}); });
it('should keep the framebuffer data', function () { it('should keep the framebuffer data', function () {
display.fillRect(0, 0, 4, 4, [0, 0, 0xff]); display.fillRect(0, 0, 4, 4, [0xff, 0, 0]);
display.resize(2, 2); display.resize(2, 2);
display.flip(); display.flip();
const expected = []; const expected = [];
@ -271,11 +270,11 @@ describe('Display/Canvas Helper', function () {
}); });
it('should not draw directly on the target canvas', function () { it('should not draw directly on the target canvas', function () {
display.fillRect(0, 0, 4, 4, [0, 0, 0xff]); display.fillRect(0, 0, 4, 4, [0xff, 0, 0]);
display.flip(); display.flip();
display.fillRect(0, 0, 4, 4, [0, 0xff, 0]); display.fillRect(0, 0, 4, 4, [0, 0xff, 0]);
const expected = []; const expected = [];
for (let i = 0; i < 4 * display._fb_width * display._fb_height; i += 4) { for (let i = 0; i < 4 * display._fbWidth * display._fbHeight; i += 4) {
expected[i] = 0xff; expected[i] = 0xff;
expected[i+1] = expected[i+2] = 0; expected[i+1] = expected[i+2] = 0;
expected[i+3] = 0xff; expected[i+3] = 0xff;
@ -285,95 +284,41 @@ describe('Display/Canvas Helper', function () {
it('should support filling a rectangle with particular color via #fillRect', function () { 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, 4, 4, [0, 0xff, 0]);
display.fillRect(0, 0, 2, 2, [0xff, 0, 0]); display.fillRect(0, 0, 2, 2, [0, 0, 0xff]);
display.fillRect(2, 2, 2, 2, [0xff, 0, 0]); display.fillRect(2, 2, 2, 2, [0, 0, 0xff]);
display.flip(); display.flip();
expect(display).to.have.displayed(checked_data); expect(display).to.have.displayed(checkedData);
}); });
it('should support copying an portion of the canvas via #copyImage', function () { 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, 4, 4, [0, 0xff, 0]);
display.fillRect(0, 0, 2, 2, [0xff, 0, 0x00]); display.fillRect(0, 0, 2, 2, [0, 0, 0xff]);
display.copyImage(0, 0, 2, 2, 2, 2); display.copyImage(0, 0, 2, 2, 2, 2);
display.flip(); display.flip();
expect(display).to.have.displayed(checked_data); expect(display).to.have.displayed(checkedData);
}); });
it('should support drawing images via #imageRect', function (done) { it('should support drawing images via #imageRect', function (done) {
display.imageRect(0, 0, 4, 4, "image/png", make_image_png(checked_data)); display.imageRect(0, 0, 4, 4, "image/png", makeImagePng(checkedData, 4, 4));
display.flip(); display.flip();
display.onflush = () => { display.onflush = () => {
expect(display).to.have.displayed(checked_data); expect(display).to.have.displayed(checkedData);
done(); done();
}; };
display.flush(); display.flush();
}); });
it('should support drawing tile data with a background color and sub tiles', function () { it('should support blit images with true color via #blitImage', function () {
display.startTile(0, 0, 4, 4, [0, 0xff, 0]); display.blitImage(0, 0, 4, 4, checkedData, 0);
display.subTile(0, 0, 2, 2, [0xff, 0, 0]);
display.subTile(2, 2, 2, 2, [0xff, 0, 0]);
display.finishTile();
display.flip(); display.flip();
expect(display).to.have.displayed(checked_data); expect(display).to.have.displayed(checkedData);
});
// 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 () { it('should support drawing an image object via #drawImage', function () {
const img = make_image_canvas(checked_data); const img = makeImageCanvas(checkedData, 4, 4);
display.drawImage(img, 0, 0); display.drawImage(img, 0, 0);
display.flip(); display.flip();
expect(display).to.have.displayed(checked_data); expect(display).to.have.displayed(checkedData);
}); });
}); });
@ -382,22 +327,18 @@ describe('Display/Canvas Helper', function () {
beforeEach(function () { beforeEach(function () {
display = new Display(document.createElement('canvas')); display = new Display(document.createElement('canvas'));
display.resize(4, 4); display.resize(4, 4);
sinon.spy(display, '_scan_renderQ'); sinon.spy(display, '_scanRenderQ');
});
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 () { 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 display._renderQPush({ type: 'noop' }); // does nothing
expect(display._scan_renderQ).to.have.been.calledOnce; expect(display._scanRenderQ).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 () { 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.length = 2;
display._renderQ_push({ type: 'noop' }); display._renderQPush({ type: 'noop' });
expect(display._scan_renderQ).to.not.have.been.called; expect(display._scanRenderQ).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 () { it('should wait until an image is loaded to attempt to draw it and the rest of the queue', function () {
@ -407,34 +348,13 @@ describe('Display/Canvas Helper', function () {
display.drawImage = sinon.spy(); display.drawImage = sinon.spy();
display.fillRect = sinon.spy(); display.fillRect = sinon.spy();
display._scan_renderQ(); display._scanRenderQ();
expect(display.drawImage).to.not.have.been.called; expect(display.drawImage).to.not.have.been.called;
expect(display.fillRect).to.not.have.been.called; expect(display.fillRect).to.not.have.been.called;
expect(img.addEventListener).to.have.been.calledOnce; expect(img.addEventListener).to.have.been.calledOnce;
display._renderQ[0].img.complete = true; display._renderQ[0].img.complete = true;
display._scan_renderQ(); display._scanRenderQ();
expect(display.drawImage).to.have.been.calledOnce;
expect(display.fillRect).to.have.been.calledOnce;
expect(img.addEventListener).to.have.been.calledOnce;
});
it('should wait if an image is incorrectly loaded', function () {
const img = { complete: true, width: 0, height: 0, addEventListener: sinon.spy() };
display._renderQ = [{ type: 'img', x: 3, y: 4, width: 4, height: 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._renderQ[0].img.width = 4;
display._renderQ[0].img.height = 4;
display._scan_renderQ();
expect(display.drawImage).to.have.been.calledOnce; expect(display.drawImage).to.have.been.calledOnce;
expect(display.fillRect).to.have.been.calledOnce; expect(display.fillRect).to.have.been.calledOnce;
expect(img.addEventListener).to.have.been.calledOnce; expect(img.addEventListener).to.have.been.calledOnce;
@ -450,35 +370,28 @@ describe('Display/Canvas Helper', function () {
it('should draw a blit image on type "blit"', function () { it('should draw a blit image on type "blit"', function () {
display.blitImage = sinon.spy(); display.blitImage = sinon.spy();
display._renderQ_push({ type: 'blit', x: 3, y: 4, width: 5, height: 6, data: [7, 8, 9] }); display._renderQPush({ 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.calledOnce;
expect(display.blitImage).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9], 0); 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 () { it('should copy a region on type "copy"', function () {
display.copyImage = sinon.spy(); display.copyImage = sinon.spy();
display._renderQ_push({ type: 'copy', x: 3, y: 4, width: 5, height: 6, old_x: 7, old_y: 8 }); display._renderQPush({ type: 'copy', x: 3, y: 4, width: 5, height: 6, oldX: 7, oldY: 8 });
expect(display.copyImage).to.have.been.calledOnce; expect(display.copyImage).to.have.been.calledOnce;
expect(display.copyImage).to.have.been.calledWith(7, 8, 3, 4, 5, 6); 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 () { it('should fill a rect with a given color on type "fill"', function () {
display.fillRect = sinon.spy(); display.fillRect = sinon.spy();
display._renderQ_push({ type: 'fill', x: 3, y: 4, width: 5, height: 6, color: [7, 8, 9]}); display._renderQPush({ 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.calledOnce;
expect(display.fillRect).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9]); 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 () { it('should draw an image from an image object on type "img" (if complete)', function () {
display.drawImage = sinon.spy(); display.drawImage = sinon.spy();
display._renderQ_push({ type: 'img', x: 3, y: 4, img: { complete: true } }); display._renderQPush({ type: 'img', x: 3, y: 4, img: { complete: true } });
expect(display.drawImage).to.have.been.calledOnce; expect(display.drawImage).to.have.been.calledOnce;
expect(display.drawImage).to.have.been.calledWith({ complete: true }, 3, 4); expect(display.drawImage).to.have.been.calledWith({ complete: true }, 3, 4);
}); });

1026
tests/test.gesturehandler.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,6 @@
import keysyms from '../core/input/keysymdef.js'; import keysyms from '../core/input/keysymdef.js';
import * as KeyboardUtil from "../core/input/util.js"; import * as KeyboardUtil from "../core/input/util.js";
import * as browser from '../core/util/browser.js';
describe('Helpers', function () { describe('Helpers', function () {
"use strict"; "use strict";
@ -70,11 +69,6 @@ describe('Helpers', function () {
// environments, so we need to redefine it whilst running these // environments, so we need to redefine it whilst running these
// tests. // tests.
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator"); 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: {}}); Object.defineProperty(window, "navigator", {value: {}});
if (window.navigator.platform !== undefined) { if (window.navigator.platform !== undefined) {
@ -102,14 +96,10 @@ describe('Helpers', function () {
describe('getKey', function () { describe('getKey', function () {
it('should prefer key', 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'); expect(KeyboardUtil.getKey({key: 'a', charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43})).to.be.equal('a');
}); });
it('should map legacy values', function () { 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: 'OS'})).to.be.equal('Meta');
expect(KeyboardUtil.getKey({key: 'Win'})).to.be.equal('Meta');
expect(KeyboardUtil.getKey({key: 'UIKeyInputLeftArrow'})).to.be.equal('ArrowLeft'); expect(KeyboardUtil.getKey({key: 'UIKeyInputLeftArrow'})).to.be.equal('ArrowLeft');
}); });
it('should handle broken Delete', function () { it('should handle broken Delete', function () {
@ -130,60 +120,6 @@ describe('Helpers', function () {
it('should return Unidentified when it cannot map the key', function () { it('should return Unidentified when it cannot map the key', function () {
expect(KeyboardUtil.getKey({keycode: 0x42})).to.be.equal('Unidentified'); expect(KeyboardUtil.getKey({keycode: 0x42})).to.be.equal('Unidentified');
}); });
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 () {
if (origNavigator !== undefined) {
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');
});
it('should allow printable character key with charCode 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', charCode: 0x61})).to.be.equal('a');
expect(KeyboardUtil.getKey({key: 'Unidentified', charCode: 0x61})).to.be.equal('a');
});
it('should allow printable character key with charCode 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', charCode: 0x61})).to.be.equal('a');
expect(KeyboardUtil.getKey({key: 'Unidentified', charCode: 0x61})).to.be.equal('a');
});
});
}); });
describe('getKeysym', function () { describe('getKeysym', function () {
@ -236,7 +172,6 @@ describe('Helpers', function () {
describe('Numpad', function () { describe('Numpad', function () {
it('should handle Numpad numbers', 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: 'Digit5', key: '5', location: 0})).to.be.equal(0x0035);
expect(KeyboardUtil.getKeysym({code: 'Numpad5', key: '5', location: 3})).to.be.equal(0xFFB5); expect(KeyboardUtil.getKeysym({code: 'Numpad5', key: '5', location: 3})).to.be.equal(0xFFB5);
}); });
@ -247,10 +182,42 @@ describe('Helpers', function () {
expect(KeyboardUtil.getKeysym({code: 'NumpadDecimal', key: 'Delete', location: 3})).to.be.equal(0xFF9F); expect(KeyboardUtil.getKeysym({code: 'NumpadDecimal', key: 'Delete', location: 3})).to.be.equal(0xFF9F);
}); });
it('should handle Numpad Decimal key', function () { 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(0xFFAE);
expect(KeyboardUtil.getKeysym({code: 'NumpadDecimal', key: ',', location: 3})).to.be.equal(0xFFAC); expect(KeyboardUtil.getKeysym({code: 'NumpadDecimal', key: ',', location: 3})).to.be.equal(0xFFAC);
}); });
}); });
describe('Japanese IM keys 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");
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";
});
afterEach(function () {
if (origNavigator !== undefined) {
Object.defineProperty(window, "navigator", origNavigator);
}
});
const keys = { 'Zenkaku': 0xff2a, 'Hankaku': 0xff2a,
'Romaji': 0xff24, 'KanaMode': 0xff24 };
for (let [key, keysym] of Object.entries(keys)) {
it(`should fake combined key for ${key} on Windows`, function () {
expect(KeyboardUtil.getKeysym({code: 'FakeIM', key: key})).to.be.equal(keysym);
});
}
});
}); });
}); });

232
tests/test.hextile.js Normal file
View File

@ -0,0 +1,232 @@
const expect = chai.expect;
import Websock from '../core/websock.js';
import Display from '../core/display.js';
import HextileDecoder from '../core/decoders/hextile.js';
import FakeWebSocket from './fake.websocket.js';
function testDecodeRect(decoder, x, y, width, height, data, display, depth) {
let sock;
sock = new Websock;
sock.open("ws://example.com");
sock.on('message', () => {
decoder.decodeRect(x, y, width, height, sock, display, depth);
});
// Empty messages are filtered at multiple layers, so we need to
// do a direct call
if (data.length === 0) {
decoder.decodeRect(x, y, width, height, sock, display, depth);
} else {
sock._websocket._receiveData(new Uint8Array(data));
}
display.flip();
}
function push32(arr, num) {
arr.push((num >> 24) & 0xFF,
(num >> 16) & 0xFF,
(num >> 8) & 0xFF,
num & 0xFF);
}
describe('Hextile Decoder', function () {
let decoder;
let display;
before(FakeWebSocket.replace);
after(FakeWebSocket.restore);
beforeEach(function () {
decoder = new HextileDecoder();
display = new Display(document.createElement('canvas'));
display.resize(4, 4);
});
it('should handle a tile with fg, bg specified, normal subrects', function () {
let data = [];
data.push(0x02 | 0x04 | 0x08); // bg spec, fg spec, anysubrects
push32(data, 0x00ff0000); // becomes 00ff0000 --> #00FF00 bg color
data.push(0x00); // becomes 0000ff00 --> #0000FF fg color
data.push(0x00);
data.push(0xff);
data.push(0x00);
data.push(2); // 2 subrects
data.push(0); // x: 0, y: 0
data.push(1 | (1 << 4)); // width: 2, height: 2
data.push(2 | (2 << 4)); // x: 2, y: 2
data.push(1 | (1 << 4)); // width: 2, height: 2
testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]);
expect(display).to.have.displayed(targetData);
});
it('should handle a raw tile', function () {
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]);
let data = [];
data.push(0x01); // raw
for (let i = 0; i < targetData.length; i += 4) {
data.push(targetData[i]);
data.push(targetData[i + 1]);
data.push(targetData[i + 2]);
// Last byte zero to test correct alpha handling
data.push(0);
}
testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
expect(display).to.have.displayed(targetData);
});
it('should handle a tile with only bg specified (solid bg)', function () {
let data = [];
data.push(0x02);
push32(data, 0x00ff0000); // becomes 00ff0000 --> #00FF00 bg color
testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
let expected = [];
for (let i = 0; i < 16; i++) {
push32(expected, 0x00ff00ff);
}
expect(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
display.resize(8, 4);
let data = [];
// send a bg frame
data.push(0x02);
push32(data, 0x00ff0000); // becomes 00ff0000 --> #00FF00 bg color
// send an empty frame
data.push(0x00);
testDecodeRect(decoder, 0, 0, 32, 4, data, display, 24);
let expected = [];
for (let i = 0; i < 16; i++) {
push32(expected, 0x00ff00ff); // rect 1: solid
}
for (let i = 0; i < 16; i++) {
push32(expected, 0x00ff00ff); // rect 2: same bkground color
}
expect(display).to.have.displayed(new Uint8Array(expected));
});
it('should handle a tile with bg and coloured subrects', function () {
let data = [];
data.push(0x02 | 0x08 | 0x10); // bg spec, anysubrects, colouredsubrects
push32(data, 0x00ff0000); // becomes 00ff0000 --> #00FF00 bg color
data.push(2); // 2 subrects
data.push(0x00); // becomes 0000ff00 --> #0000FF fg color
data.push(0x00);
data.push(0xff);
data.push(0x00);
data.push(0); // x: 0, y: 0
data.push(1 | (1 << 4)); // width: 2, height: 2
data.push(0x00); // becomes 0000ff00 --> #0000FF fg color
data.push(0x00);
data.push(0xff);
data.push(0x00);
data.push(2 | (2 << 4)); // x: 2, y: 2
data.push(1 | (1 << 4)); // width: 2, height: 2
testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]);
expect(display).to.have.displayed(targetData);
});
it('should carry over fg and bg colors from the previous tile if not specified', function () {
display.resize(4, 17);
let data = [];
data.push(0x02 | 0x04 | 0x08); // bg spec, fg spec, anysubrects
push32(data, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
data.push(0x00); // becomes 0000ffff --> #0000FF fg color
data.push(0x00);
data.push(0xff);
data.push(0xff);
data.push(8); // 8 subrects
for (let i = 0; i < 4; i++) {
data.push((0 << 4) | (i * 4)); // x: 0, y: i*4
data.push(1 | (1 << 4)); // width: 2, height: 2
data.push((2 << 4) | (i * 4 + 2)); // x: 2, y: i * 4 + 2
data.push(1 | (1 << 4)); // width: 2, height: 2
}
data.push(0x08); // anysubrects
data.push(1); // 1 subrect
data.push(0); // x: 0, y: 0
data.push(1 | (1 << 4)); // width: 2, height: 2
testDecodeRect(decoder, 0, 0, 4, 17, data, display, 24);
let targetData = [
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 expected = [];
for (let i = 0; i < 4; i++) {
expected = expected.concat(targetData);
}
expected = expected.concat(targetData.slice(0, 16));
expect(display).to.have.displayed(new Uint8Array(expected));
});
it('should fail on an invalid subencoding', function () {
let data = [45]; // an invalid subencoding
expect(() => testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24)).to.throw();
});
it('should handle empty rects', function () {
display.fillRect(0, 0, 4, 4, [ 0x00, 0x00, 0xff ]);
display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);
display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]);
testDecodeRect(decoder, 1, 2, 0, 0, [], display, 24);
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]);
expect(display).to.have.displayed(targetData);
});
});

View File

@ -1,7 +1,6 @@
const expect = chai.expect; const expect = chai.expect;
import Keyboard from '../core/input/keyboard.js'; import Keyboard from '../core/input/keyboard.js';
import * as browser from '../core/util/browser.js';
describe('Key Event Handling', function () { describe('Key Event Handling', function () {
"use strict"; "use strict";
@ -20,7 +19,6 @@ describe('Key Event Handling', function () {
describe('Decode Keyboard Events', function () { describe('Decode Keyboard Events', function () {
it('should decode keydown events', function (done) { it('should decode keydown events', function (done) {
if (browser.isIE() || browser.isEdge()) this.skip();
const kbd = new Keyboard(document); const kbd = new Keyboard(document);
kbd.onkeyevent = (keysym, code, down) => { kbd.onkeyevent = (keysym, code, down) => {
expect(keysym).to.be.equal(0x61); expect(keysym).to.be.equal(0x61);
@ -31,7 +29,6 @@ describe('Key Event Handling', function () {
kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'})); kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));
}); });
it('should decode keyup events', function (done) { it('should decode keyup events', function (done) {
if (browser.isIE() || browser.isEdge()) this.skip();
let calls = 0; let calls = 0;
const kbd = new Keyboard(document); const kbd = new Keyboard(document);
kbd.onkeyevent = (keysym, code, down) => { kbd.onkeyevent = (keysym, code, down) => {
@ -45,118 +42,10 @@ describe('Key Event Handling', function () {
kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'})); kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));
kbd._handleKeyUp(keyevent('keyup', {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 () { describe('Fake keyup', function () {
it('should fake keyup events for virtual keyboards', function (done) { it('should fake keyup events for virtual keyboards', function (done) {
if (browser.isIE() || browser.isEdge()) this.skip();
let count = 0; let count = 0;
const kbd = new Keyboard(document); const kbd = new Keyboard(document);
kbd.onkeyevent = (keysym, code, down) => { kbd.onkeyevent = (keysym, code, down) => {
@ -178,9 +67,6 @@ describe('Key Event Handling', function () {
}); });
describe('Track Key State', function () { 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) { it('should send release using the same keysym as the press', function (done) {
const kbd = new Keyboard(document); const kbd = new Keyboard(document);
kbd.onkeyevent = (keysym, code, down) => { kbd.onkeyevent = (keysym, code, down) => {
@ -256,11 +142,6 @@ describe('Key Event Handling', function () {
// environments, so we need to redefine it whilst running these // environments, so we need to redefine it whilst running these
// tests. // tests.
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator"); 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: {}}); Object.defineProperty(window, "navigator", {value: {}});
if (window.navigator.platform !== undefined) { if (window.navigator.platform !== undefined) {
@ -323,11 +204,6 @@ describe('Key Event Handling', function () {
// environments, so we need to redefine it whilst running these // environments, so we need to redefine it whilst running these
// tests. // tests.
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator"); 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: {}}); Object.defineProperty(window, "navigator", {value: {}});
if (window.navigator.platform !== undefined) { if (window.navigator.platform !== undefined) {
@ -343,7 +219,7 @@ describe('Key Event Handling', function () {
} }
}); });
it('should toggle caps lock on key press on iOS', function (done) { it('should toggle caps lock on key press on iOS', function () {
window.navigator.platform = "iPad"; window.navigator.platform = "iPad";
const kbd = new Keyboard(document); const kbd = new Keyboard(document);
kbd.onkeyevent = sinon.spy(); kbd.onkeyevent = sinon.spy();
@ -352,10 +228,9 @@ describe('Key Event Handling', function () {
expect(kbd.onkeyevent).to.have.been.calledTwice; expect(kbd.onkeyevent).to.have.been.calledTwice;
expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0xFFE5, "CapsLock", true); expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0xFFE5, "CapsLock", true);
expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0xFFE5, "CapsLock", false); expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0xFFE5, "CapsLock", false);
done();
}); });
it('should toggle caps lock on key press on mac', function (done) { it('should toggle caps lock on key press on mac', function () {
window.navigator.platform = "Mac"; window.navigator.platform = "Mac";
const kbd = new Keyboard(document); const kbd = new Keyboard(document);
kbd.onkeyevent = sinon.spy(); kbd.onkeyevent = sinon.spy();
@ -364,10 +239,9 @@ describe('Key Event Handling', function () {
expect(kbd.onkeyevent).to.have.been.calledTwice; expect(kbd.onkeyevent).to.have.been.calledTwice;
expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0xFFE5, "CapsLock", true); expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0xFFE5, "CapsLock", true);
expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0xFFE5, "CapsLock", false); expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0xFFE5, "CapsLock", false);
done();
}); });
it('should toggle caps lock on key release on iOS', function (done) { it('should toggle caps lock on key release on iOS', function () {
window.navigator.platform = "iPad"; window.navigator.platform = "iPad";
const kbd = new Keyboard(document); const kbd = new Keyboard(document);
kbd.onkeyevent = sinon.spy(); kbd.onkeyevent = sinon.spy();
@ -376,10 +250,9 @@ describe('Key Event Handling', function () {
expect(kbd.onkeyevent).to.have.been.calledTwice; expect(kbd.onkeyevent).to.have.been.calledTwice;
expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0xFFE5, "CapsLock", true); expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0xFFE5, "CapsLock", true);
expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0xFFE5, "CapsLock", false); expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0xFFE5, "CapsLock", false);
done();
}); });
it('should toggle caps lock on key release on mac', function (done) { it('should toggle caps lock on key release on mac', function () {
window.navigator.platform = "Mac"; window.navigator.platform = "Mac";
const kbd = new Keyboard(document); const kbd = new Keyboard(document);
kbd.onkeyevent = sinon.spy(); kbd.onkeyevent = sinon.spy();
@ -388,10 +261,50 @@ describe('Key Event Handling', function () {
expect(kbd.onkeyevent).to.have.been.calledTwice; expect(kbd.onkeyevent).to.have.been.calledTwice;
expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0xFFE5, "CapsLock", true); expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0xFFE5, "CapsLock", true);
expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0xFFE5, "CapsLock", false); expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0xFFE5, "CapsLock", false);
done();
}); });
}); });
describe('Japanese IM keys 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");
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";
});
afterEach(function () {
if (origNavigator !== undefined) {
Object.defineProperty(window, "navigator", origNavigator);
}
});
const keys = { 'Zenkaku': 0xff2a, 'Hankaku': 0xff2a,
'Alphanumeric': 0xff30, 'Katakana': 0xff26,
'Hiragana': 0xff25, 'Romaji': 0xff24,
'KanaMode': 0xff24 };
for (let [key, keysym] of Object.entries(keys)) {
it(`should fake key release for ${key} on Windows`, function () {
let kbd = new Keyboard(document);
kbd.onkeyevent = sinon.spy();
kbd._handleKeyDown(keyevent('keydown', {code: 'FakeIM', key: key}));
expect(kbd.onkeyevent).to.have.been.calledTwice;
expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(keysym, "FakeIM", true);
expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(keysym, "FakeIM", false);
});
}
});
describe('Escape AltGraph on Windows', function () { describe('Escape AltGraph on Windows', function () {
let origNavigator; let origNavigator;
beforeEach(function () { beforeEach(function () {
@ -399,11 +312,6 @@ describe('Key Event Handling', function () {
// environments, so we need to redefine it whilst running these // environments, so we need to redefine it whilst running these
// tests. // tests.
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator"); 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: {}}); Object.defineProperty(window, "navigator", {value: {}});
if (window.navigator.platform !== undefined) { if (window.navigator.platform !== undefined) {
@ -549,11 +457,6 @@ describe('Key Event Handling', function () {
// environments, so we need to redefine it whilst running these // environments, so we need to redefine it whilst running these
// tests. // tests.
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator"); 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: {}}); Object.defineProperty(window, "navigator", {value: {}});
if (window.navigator.platform !== undefined) { if (window.navigator.platform !== undefined) {

View File

@ -11,11 +11,6 @@ describe('Localization', function () {
// environments, so we need to redefine it whilst running these // environments, so we need to redefine it whilst running these
// tests. // tests.
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator"); 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: {}}); Object.defineProperty(window, "navigator", {value: {}});
if (window.navigator.languages !== undefined) { if (window.navigator.languages !== undefined) {

View File

@ -1,371 +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
});
});
describe('Move events should be limited to one each 17 ms', function () {
let mouse;
beforeEach(function () {
this.clock = sinon.useFakeTimers(Date.now());
mouse = new Mouse(target);
mouse.onmousemove = sinon.spy();
});
afterEach(function () {
this.clock.restore();
});
it('should send a single move instantly', function () {
mouse._handleMouseMove(mouseevent(
'mousemove', { clientX: 1, clientY: 2 }));
expect(mouse.onmousemove).to.have.callCount(1);
});
it('should delay one if two events are too close', function () {
mouse._handleMouseMove(mouseevent(
'mousemove', { clientX: 18, clientY: 30 }));
mouse._handleMouseMove(mouseevent(
'mousemove', { clientX: 20, clientY: 50 }));
expect(mouse.onmousemove).to.have.callCount(1);
this.clock.tick(100);
expect(mouse.onmousemove).to.have.callCount(2);
});
it('should only send first and last of many close events', function () {
mouse._handleMouseMove(mouseevent(
'mousemove', { clientX: 18, clientY: 30 }));
mouse._handleMouseMove(mouseevent(
'mousemove', { clientX: 20, clientY: 50 }));
mouse._handleMouseMove(mouseevent(
'mousemove', { clientX: 21, clientY: 55 }));
// Check positions to verify that the correct calls got through.
//
// The test canvas starts 10px from top and 10 px from left,
// that means the relative coordinates are clientCoords - 10px
expect(mouse.onmousemove).to.have.been.calledWith(8, 20);
this.clock.tick(60);
expect(mouse.onmousemove).to.have.callCount(2);
expect(mouse.onmousemove).to.have.been.calledWith(11, 45);
});
it('should send events with enough time apart normally', function () {
mouse._handleMouseMove(mouseevent(
'mousemove', { clientX: 58, clientY: 60 }));
expect(mouse.onmousemove).to.have.callCount(1);
this.clock.tick(20);
mouse._handleMouseMove(mouseevent(
'mousemove', { clientX: 25, clientY: 60 }));
expect(mouse.onmousemove).to.have.callCount(2);
});
});
});

129
tests/test.raw.js Normal file
View File

@ -0,0 +1,129 @@
const expect = chai.expect;
import Websock from '../core/websock.js';
import Display from '../core/display.js';
import RawDecoder from '../core/decoders/raw.js';
import FakeWebSocket from './fake.websocket.js';
function testDecodeRect(decoder, x, y, width, height, data, display, depth) {
let sock;
sock = new Websock;
sock.open("ws://example.com");
sock.on('message', () => {
decoder.decodeRect(x, y, width, height, sock, display, depth);
});
// Empty messages are filtered at multiple layers, so we need to
// do a direct call
if (data.length === 0) {
decoder.decodeRect(x, y, width, height, sock, display, depth);
} else {
sock._websocket._receiveData(new Uint8Array(data));
}
display.flip();
}
describe('Raw Decoder', function () {
let decoder;
let display;
before(FakeWebSocket.replace);
after(FakeWebSocket.restore);
beforeEach(function () {
decoder = new RawDecoder();
display = new Display(document.createElement('canvas'));
display.resize(4, 4);
});
it('should handle the Raw encoding', function () {
testDecodeRect(decoder, 0, 0, 2, 2,
[0xff, 0x00, 0x00, 0, 0x00, 0xff, 0x00, 0,
0x00, 0xff, 0x00, 0, 0xff, 0x00, 0x00, 0],
display, 24);
testDecodeRect(decoder, 2, 0, 2, 2,
[0x00, 0x00, 0xff, 0, 0x00, 0x00, 0xff, 0,
0x00, 0x00, 0xff, 0, 0x00, 0x00, 0xff, 0],
display, 24);
testDecodeRect(decoder, 0, 2, 4, 1,
[0xee, 0x00, 0xff, 0, 0x00, 0xee, 0xff, 0,
0xaa, 0xee, 0xff, 0, 0xab, 0xee, 0xff, 0],
display, 24);
testDecodeRect(decoder, 0, 3, 4, 1,
[0xee, 0x00, 0xff, 0, 0x00, 0xee, 0xff, 0,
0xaa, 0xee, 0xff, 0, 0xab, 0xee, 0xff, 0],
display, 24);
let targetData = new Uint8Array([
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
]);
expect(display).to.have.displayed(targetData);
});
it('should handle the Raw encoding in low colour mode', function () {
testDecodeRect(decoder, 0, 0, 2, 2,
[0x30, 0x30, 0x30, 0x30],
display, 8);
testDecodeRect(decoder, 2, 0, 2, 2,
[0x0c, 0x0c, 0x0c, 0x0c],
display, 8);
testDecodeRect(decoder, 0, 2, 4, 1,
[0x0c, 0x0c, 0x30, 0x30],
display, 8);
testDecodeRect(decoder, 0, 3, 4, 1,
[0x0c, 0x0c, 0x30, 0x30],
display, 8);
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]);
expect(display).to.have.displayed(targetData);
});
it('should handle empty rects', function () {
display.fillRect(0, 0, 4, 4, [ 0x00, 0x00, 0xff ]);
display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);
display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]);
testDecodeRect(decoder, 1, 2, 0, 0, [], display, 24);
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]);
expect(display).to.have.displayed(targetData);
});
it('should handle empty rects in low colour mode', function () {
display.fillRect(0, 0, 4, 4, [ 0x00, 0x00, 0xff ]);
display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);
display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]);
testDecodeRect(decoder, 1, 2, 0, 0, [], display, 8);
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]);
expect(display).to.have.displayed(targetData);
});
});

File diff suppressed because it is too large Load Diff

107
tests/test.rre.js Normal file
View File

@ -0,0 +1,107 @@
const expect = chai.expect;
import Websock from '../core/websock.js';
import Display from '../core/display.js';
import RREDecoder from '../core/decoders/rre.js';
import FakeWebSocket from './fake.websocket.js';
function testDecodeRect(decoder, x, y, width, height, data, display, depth) {
let sock;
sock = new Websock;
sock.open("ws://example.com");
sock.on('message', () => {
decoder.decodeRect(x, y, width, height, sock, display, depth);
});
// Empty messages are filtered at multiple layers, so we need to
// do a direct call
if (data.length === 0) {
decoder.decodeRect(x, y, width, height, sock, display, depth);
} else {
sock._websocket._receiveData(new Uint8Array(data));
}
display.flip();
}
function push16(arr, num) {
arr.push((num >> 8) & 0xFF,
num & 0xFF);
}
function push32(arr, num) {
arr.push((num >> 24) & 0xFF,
(num >> 16) & 0xFF,
(num >> 8) & 0xFF,
num & 0xFF);
}
describe('RRE Decoder', function () {
let decoder;
let display;
before(FakeWebSocket.replace);
after(FakeWebSocket.restore);
beforeEach(function () {
decoder = new RREDecoder();
display = new Display(document.createElement('canvas'));
display.resize(4, 4);
});
// TODO(directxman12): test rre_chunk_sz?
it('should handle the RRE encoding', function () {
let data = [];
push32(data, 2); // 2 subrects
push32(data, 0x00ff0000); // becomes 00ff0000 --> #00FF00 bg color
data.push(0x00); // becomes 0000ff00 --> #0000FF fg color
data.push(0x00);
data.push(0xff);
data.push(0x00);
push16(data, 0); // x: 0
push16(data, 0); // y: 0
push16(data, 2); // width: 2
push16(data, 2); // height: 2
data.push(0x00); // becomes 0000ff00 --> #0000FF fg color
data.push(0x00);
data.push(0xff);
data.push(0x00);
push16(data, 2); // x: 2
push16(data, 2); // y: 2
push16(data, 2); // width: 2
push16(data, 2); // height: 2
testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]);
expect(display).to.have.displayed(targetData);
});
it('should handle empty rects', function () {
display.fillRect(0, 0, 4, 4, [ 0x00, 0x00, 0xff ]);
display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);
display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]);
testDecodeRect(decoder, 1, 2, 0, 0, [ 0x00, 0xff, 0xff, 0xff, 0xff ], display, 24);
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]);
expect(display).to.have.displayed(targetData);
});
});

394
tests/test.tight.js Normal file
View File

@ -0,0 +1,394 @@
const expect = chai.expect;
import Websock from '../core/websock.js';
import Display from '../core/display.js';
import TightDecoder from '../core/decoders/tight.js';
import FakeWebSocket from './fake.websocket.js';
function testDecodeRect(decoder, x, y, width, height, data, display, depth) {
let sock;
sock = new Websock;
sock.open("ws://example.com");
sock.on('message', () => {
decoder.decodeRect(x, y, width, height, sock, display, depth);
});
// Empty messages are filtered at multiple layers, so we need to
// do a direct call
if (data.length === 0) {
decoder.decodeRect(x, y, width, height, sock, display, depth);
} else {
sock._websocket._receiveData(new Uint8Array(data));
}
display.flip();
}
describe('Tight Decoder', function () {
let decoder;
let display;
before(FakeWebSocket.replace);
after(FakeWebSocket.restore);
beforeEach(function () {
decoder = new TightDecoder();
display = new Display(document.createElement('canvas'));
display.resize(4, 4);
});
it('should handle fill rects', function () {
testDecodeRect(decoder, 0, 0, 4, 4,
[0x80, 0xff, 0x88, 0x44],
display, 24);
let targetData = new Uint8Array([
0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255,
0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255,
0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255,
0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255,
]);
expect(display).to.have.displayed(targetData);
});
it('should handle uncompressed copy rects', function () {
let blueData = [ 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0xff ];
let greenData = [ 0x00, 0x00, 0xff, 0x00, 0x00, 0xff, 0x00 ];
testDecodeRect(decoder, 0, 0, 2, 1, blueData, display, 24);
testDecodeRect(decoder, 0, 1, 2, 1, blueData, display, 24);
testDecodeRect(decoder, 2, 0, 2, 1, greenData, display, 24);
testDecodeRect(decoder, 2, 1, 2, 1, greenData, display, 24);
testDecodeRect(decoder, 0, 2, 2, 1, greenData, display, 24);
testDecodeRect(decoder, 0, 3, 2, 1, greenData, display, 24);
testDecodeRect(decoder, 2, 2, 2, 1, blueData, display, 24);
testDecodeRect(decoder, 2, 3, 2, 1, blueData, display, 24);
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]);
expect(display).to.have.displayed(targetData);
});
it('should handle compressed copy rects', function () {
let data = [
// Control byte
0x00,
// Pixels (compressed)
0x15,
0x78, 0x9c, 0x63, 0x60, 0xf8, 0xcf, 0x00, 0x44,
0x60, 0x82, 0x01, 0x99, 0x8d, 0x29, 0x02, 0xa6,
0x00, 0x7e, 0xbf, 0x0f, 0xf1 ];
testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]);
expect(display).to.have.displayed(targetData);
});
it('should handle uncompressed mono rects', function () {
let data = [
// Control bytes
0x40, 0x01,
// Palette
0x01, 0x00, 0x00, 0xff, 0x00, 0xff, 0x00,
// Pixels
0x30, 0x30, 0xc0, 0xc0 ];
testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]);
expect(display).to.have.displayed(targetData);
});
it('should handle compressed mono rects', function () {
display.resize(4, 12);
let data = [
// Control bytes
0x40, 0x01,
// Palette
0x01, 0x00, 0x00, 0xff, 0x00, 0xff, 0x00,
// Pixels (compressed)
0x0e,
0x78, 0x9c, 0x33, 0x30, 0x38, 0x70, 0xc0, 0x00,
0x8a, 0x01, 0x21, 0x3c, 0x05, 0xa1 ];
testDecodeRect(decoder, 0, 0, 4, 12, data, display, 24);
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
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,
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]);
expect(display).to.have.displayed(targetData);
});
it('should handle uncompressed palette rects', function () {
let data1 = [
// Control bytes
0x40, 0x01,
// Palette
0x02, 0x00, 0x00, 0xff, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00,
// Pixels
0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x01 ];
let data2 = [
// Control bytes
0x40, 0x01,
// Palette
0x02, 0x00, 0x00, 0xff, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00,
// Pixels
0x01, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00 ];
testDecodeRect(decoder, 0, 0, 4, 2, data1, display, 24);
testDecodeRect(decoder, 0, 2, 4, 2, data2, display, 24);
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]);
expect(display).to.have.displayed(targetData);
});
it('should handle compressed palette rects', function () {
let data = [
// Control bytes
0x40, 0x01,
// Palette
0x02, 0x00, 0x00, 0xff, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00,
// Pixels (compressed)
0x12,
0x78, 0x9c, 0x63, 0x60, 0x60, 0x64, 0x64, 0x00,
0x62, 0x08, 0xc9, 0xc0, 0x00, 0x00, 0x00, 0x54,
0x00, 0x09 ];
testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]);
expect(display).to.have.displayed(targetData);
});
it.skip('should handle uncompressed gradient rects', function () {
// Not implemented yet
});
it.skip('should handle compressed gradient rects', function () {
// Not implemented yet
});
it('should handle empty copy rects', function () {
display.fillRect(0, 0, 4, 4, [ 0x00, 0x00, 0xff ]);
display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);
display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]);
testDecodeRect(decoder, 1, 2, 0, 0, [ 0x00 ], display, 24);
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]);
expect(display).to.have.displayed(targetData);
});
it('should handle empty palette rects', function () {
display.fillRect(0, 0, 4, 4, [ 0x00, 0x00, 0xff ]);
display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);
display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]);
testDecodeRect(decoder, 1, 2, 0, 0,
[ 0x40, 0x01, 0x01,
0xff, 0xff, 0xff,
0xff, 0xff, 0xff ], display, 24);
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]);
expect(display).to.have.displayed(targetData);
});
it('should handle empty fill rects', function () {
display.fillRect(0, 0, 4, 4, [ 0x00, 0x00, 0xff ]);
display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);
display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]);
testDecodeRect(decoder, 1, 2, 0, 0,
[ 0x80, 0xff, 0xff, 0xff ], display, 24);
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]);
expect(display).to.have.displayed(targetData);
});
it('should handle JPEG rects', function (done) {
let data = [
// Control bytes
0x90, 0xd6, 0x05,
// JPEG data
0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46,
0x49, 0x46, 0x00, 0x01, 0x01, 0x01, 0x00, 0x48,
0x00, 0x48, 0x00, 0x00, 0xff, 0xfe, 0x00, 0x13,
0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x20,
0x77, 0x69, 0x74, 0x68, 0x20, 0x47, 0x49, 0x4d,
0x50, 0xff, 0xdb, 0x00, 0x43, 0x00, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0xff, 0xdb,
0x00, 0x43, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0xff, 0xc2, 0x00, 0x11, 0x08,
0x00, 0x04, 0x00, 0x04, 0x03, 0x01, 0x11, 0x00,
0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xff, 0xc4,
0x00, 0x14, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x07, 0xff, 0xc4, 0x00, 0x14,
0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x08, 0xff, 0xda, 0x00, 0x0c, 0x03, 0x01,
0x00, 0x02, 0x10, 0x03, 0x10, 0x00, 0x00, 0x01,
0x1e, 0x0a, 0xa7, 0x7f, 0xff, 0xc4, 0x00, 0x14,
0x10, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x05, 0xff, 0xda, 0x00, 0x08, 0x01, 0x01,
0x00, 0x01, 0x05, 0x02, 0x5d, 0x74, 0x41, 0x47,
0xff, 0xc4, 0x00, 0x1f, 0x11, 0x00, 0x01, 0x04,
0x02, 0x02, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x04, 0x05,
0x07, 0x08, 0x14, 0x16, 0x03, 0x15, 0x17, 0x25,
0x26, 0xff, 0xda, 0x00, 0x08, 0x01, 0x03, 0x01,
0x01, 0x3f, 0x01, 0xad, 0x35, 0xa6, 0x13, 0xb8,
0x10, 0x98, 0x5d, 0x8a, 0xb1, 0x41, 0x7e, 0x43,
0x99, 0x24, 0x3d, 0x8f, 0x70, 0x30, 0xd8, 0xcb,
0x44, 0xbb, 0x7d, 0x48, 0xb5, 0xf8, 0x18, 0x7f,
0xe7, 0xc1, 0x9f, 0x86, 0x45, 0x9b, 0xfa, 0xf1,
0x61, 0x96, 0x46, 0xbf, 0x56, 0xc8, 0x8b, 0x2b,
0x0b, 0x35, 0x6e, 0x4b, 0x8a, 0x95, 0x6a, 0xf9,
0xff, 0x00, 0xff, 0xc4, 0x00, 0x1f, 0x11, 0x00,
0x01, 0x04, 0x02, 0x02, 0x03, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,
0x02, 0x04, 0x05, 0x12, 0x13, 0x14, 0x01, 0x06,
0x11, 0x22, 0x23, 0xff, 0xda, 0x00, 0x08, 0x01,
0x02, 0x01, 0x01, 0x3f, 0x01, 0x85, 0x85, 0x8c,
0xec, 0x31, 0x8d, 0xa6, 0x26, 0x1b, 0x6e, 0x48,
0xbc, 0xcd, 0xb0, 0xe3, 0x33, 0x86, 0xf9, 0x35,
0xdc, 0x15, 0xa8, 0xbe, 0x4d, 0x4a, 0x10, 0x22,
0x80, 0x00, 0x91, 0xe8, 0x24, 0xda, 0xb6, 0x57,
0x95, 0xf2, 0xa5, 0x73, 0xff, 0xc4, 0x00, 0x1e,
0x10, 0x00, 0x01, 0x04, 0x03, 0x00, 0x03, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x03, 0x01, 0x02, 0x04, 0x12, 0x05, 0x11,
0x13, 0x14, 0x22, 0x23, 0xff, 0xda, 0x00, 0x08,
0x01, 0x01, 0x00, 0x06, 0x3f, 0x02, 0x91, 0x89,
0xc4, 0xc8, 0xf1, 0x60, 0x45, 0xe5, 0xc0, 0x1c,
0x80, 0x7a, 0x77, 0x00, 0xe4, 0x97, 0xeb, 0x24,
0x66, 0x33, 0xac, 0x63, 0x11, 0xfe, 0xe4, 0x76,
0xad, 0x56, 0xe9, 0xa8, 0x88, 0x9f, 0xff, 0xc4,
0x00, 0x14, 0x10, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xff, 0xda, 0x00, 0x08,
0x01, 0x01, 0x00, 0x01, 0x3f, 0x21, 0x68, 0x3f,
0x92, 0x17, 0x81, 0x1f, 0x7f, 0xff, 0xda, 0x00,
0x0c, 0x03, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00,
0x00, 0x00, 0x10, 0x5f, 0xff, 0xc4, 0x00, 0x14,
0x11, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xff, 0xda, 0x00, 0x08, 0x01, 0x03,
0x01, 0x01, 0x3f, 0x10, 0x03, 0xeb, 0x11, 0xe4,
0xa7, 0xe3, 0xff, 0x00, 0xff, 0xc4, 0x00, 0x14,
0x11, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xff, 0xda, 0x00, 0x08, 0x01, 0x02,
0x01, 0x01, 0x3f, 0x10, 0x6b, 0xd3, 0x02, 0xdc,
0x9a, 0xf4, 0xff, 0x00, 0xff, 0xc4, 0x00, 0x14,
0x10, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xff, 0xda, 0x00, 0x08, 0x01, 0x01,
0x00, 0x01, 0x3f, 0x10, 0x62, 0x7b, 0x3a, 0xd0,
0x3f, 0xeb, 0xff, 0x00, 0xff, 0xd9,
];
testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
let targetData = new Uint8Array([
0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255
]);
// Browsers have rounding errors, so we need an approximate
// comparing function
function almost(a, b) {
let diff = Math.abs(a - b);
return diff < 5;
}
display.onflush = () => {
expect(display).to.have.displayed(targetData, almost);
done();
};
display.flush();
});
});

144
tests/test.tightpng.js Normal file
View File

@ -0,0 +1,144 @@
const expect = chai.expect;
import Websock from '../core/websock.js';
import Display from '../core/display.js';
import TightPngDecoder from '../core/decoders/tightpng.js';
import FakeWebSocket from './fake.websocket.js';
function testDecodeRect(decoder, x, y, width, height, data, display, depth) {
let sock;
sock = new Websock;
sock.open("ws://example.com");
sock.on('message', () => {
decoder.decodeRect(x, y, width, height, sock, display, depth);
});
// Empty messages are filtered at multiple layers, so we need to
// do a direct call
if (data.length === 0) {
decoder.decodeRect(x, y, width, height, sock, display, depth);
} else {
sock._websocket._receiveData(new Uint8Array(data));
}
display.flip();
}
describe('TightPng Decoder', function () {
let decoder;
let display;
before(FakeWebSocket.replace);
after(FakeWebSocket.restore);
beforeEach(function () {
decoder = new TightPngDecoder();
display = new Display(document.createElement('canvas'));
display.resize(4, 4);
});
it('should handle the TightPng encoding', function (done) {
let data = [
// Control bytes
0xa0, 0xb4, 0x04,
// PNG data
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a,
0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52,
0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04,
0x08, 0x02, 0x00, 0x00, 0x00, 0x26, 0x93, 0x09,
0x29, 0x00, 0x00, 0x01, 0x84, 0x69, 0x43, 0x43,
0x50, 0x49, 0x43, 0x43, 0x20, 0x70, 0x72, 0x6f,
0x66, 0x69, 0x6c, 0x65, 0x00, 0x00, 0x28, 0x91,
0x7d, 0x91, 0x3d, 0x48, 0xc3, 0x40, 0x18, 0x86,
0xdf, 0xa6, 0x6a, 0x45, 0x2a, 0x0e, 0x76, 0x10,
0x71, 0x08, 0x52, 0x9d, 0x2c, 0x88, 0x8a, 0x38,
0x6a, 0x15, 0x8a, 0x50, 0x21, 0xd4, 0x0a, 0xad,
0x3a, 0x98, 0x5c, 0xfa, 0x07, 0x4d, 0x1a, 0x92,
0x14, 0x17, 0x47, 0xc1, 0xb5, 0xe0, 0xe0, 0xcf,
0x62, 0xd5, 0xc1, 0xc5, 0x59, 0x57, 0x07, 0x57,
0x41, 0x10, 0xfc, 0x01, 0x71, 0x72, 0x74, 0x52,
0x74, 0x91, 0x12, 0xbf, 0x4b, 0x0a, 0x2d, 0x62,
0xbc, 0xe3, 0xb8, 0x87, 0xf7, 0xbe, 0xf7, 0xe5,
0xee, 0x3b, 0x40, 0xa8, 0x97, 0x99, 0x66, 0x75,
0x8c, 0x03, 0x9a, 0x6e, 0x9b, 0xa9, 0x44, 0x5c,
0xcc, 0x64, 0x57, 0xc5, 0xd0, 0x2b, 0xba, 0x68,
0x86, 0x31, 0x8c, 0x2e, 0x99, 0x59, 0xc6, 0x9c,
0x24, 0x25, 0xe1, 0x3b, 0xbe, 0xee, 0x11, 0xe0,
0xfb, 0x5d, 0x8c, 0x67, 0xf9, 0xd7, 0xfd, 0x39,
0x7a, 0xd5, 0x9c, 0xc5, 0x80, 0x80, 0x48, 0x3c,
0xcb, 0x0c, 0xd3, 0x26, 0xde, 0x20, 0x9e, 0xde,
0xb4, 0x0d, 0xce, 0xfb, 0xc4, 0x11, 0x56, 0x94,
0x55, 0xe2, 0x73, 0xe2, 0x31, 0x93, 0x2e, 0x48,
0xfc, 0xc8, 0x75, 0xc5, 0xe3, 0x37, 0xce, 0x05,
0x97, 0x05, 0x9e, 0x19, 0x31, 0xd3, 0xa9, 0x79,
0xe2, 0x08, 0xb1, 0x58, 0x68, 0x63, 0xa5, 0x8d,
0x59, 0xd1, 0xd4, 0x88, 0xa7, 0x88, 0xa3, 0xaa,
0xa6, 0x53, 0xbe, 0x90, 0xf1, 0x58, 0xe5, 0xbc,
0xc5, 0x59, 0x2b, 0x57, 0x59, 0xf3, 0x9e, 0xfc,
0x85, 0xe1, 0x9c, 0xbe, 0xb2, 0xcc, 0x75, 0x5a,
0x43, 0x48, 0x60, 0x11, 0x4b, 0x90, 0x20, 0x42,
0x41, 0x15, 0x25, 0x94, 0x61, 0x23, 0x46, 0xbb,
0x4e, 0x8a, 0x85, 0x14, 0x9d, 0xc7, 0x7d, 0xfc,
0x83, 0xae, 0x5f, 0x22, 0x97, 0x42, 0xae, 0x12,
0x18, 0x39, 0x16, 0x50, 0x81, 0x06, 0xd9, 0xf5,
0x83, 0xff, 0xc1, 0xef, 0xde, 0x5a, 0xf9, 0xc9,
0x09, 0x2f, 0x29, 0x1c, 0x07, 0x3a, 0x5f, 0x1c,
0xe7, 0x63, 0x04, 0x08, 0xed, 0x02, 0x8d, 0x9a,
0xe3, 0x7c, 0x1f, 0x3b, 0x4e, 0xe3, 0x04, 0x08,
0x3e, 0x03, 0x57, 0x7a, 0xcb, 0x5f, 0xa9, 0x03,
0x33, 0x9f, 0xa4, 0xd7, 0x5a, 0x5a, 0xf4, 0x08,
0xe8, 0xdb, 0x06, 0x2e, 0xae, 0x5b, 0x9a, 0xb2,
0x07, 0x5c, 0xee, 0x00, 0x03, 0x4f, 0x86, 0x6c,
0xca, 0xae, 0x14, 0xa4, 0x25, 0xe4, 0xf3, 0xc0,
0xfb, 0x19, 0x7d, 0x53, 0x16, 0xe8, 0xbf, 0x05,
0x7a, 0xd6, 0xbc, 0xbe, 0x35, 0xcf, 0x71, 0xfa,
0x00, 0xa4, 0xa9, 0x57, 0xc9, 0x1b, 0xe0, 0xe0,
0x10, 0x18, 0x2d, 0x50, 0xf6, 0xba, 0xcf, 0xbb,
0xbb, 0xdb, 0xfb, 0xf6, 0x6f, 0x4d, 0xb3, 0x7f,
0x3f, 0x0a, 0x27, 0x72, 0x7d, 0x49, 0x29, 0x8b,
0xbb, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59,
0x73, 0x00, 0x00, 0x2e, 0x23, 0x00, 0x00, 0x2e,
0x23, 0x01, 0x78, 0xa5, 0x3f, 0x76, 0x00, 0x00,
0x00, 0x07, 0x74, 0x49, 0x4d, 0x45, 0x07, 0xe4,
0x06, 0x06, 0x0c, 0x23, 0x1d, 0x3f, 0x9f, 0xbb,
0x94, 0x00, 0x00, 0x00, 0x19, 0x74, 0x45, 0x58,
0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74,
0x00, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64,
0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x47, 0x49,
0x4d, 0x50, 0x57, 0x81, 0x0e, 0x17, 0x00, 0x00,
0x00, 0x1e, 0x49, 0x44, 0x41, 0x54, 0x08, 0xd7,
0x65, 0xc9, 0xb1, 0x0d, 0x00, 0x00, 0x08, 0x03,
0x20, 0xea, 0xff, 0x3f, 0xd7, 0xd5, 0x44, 0x56,
0x52, 0x90, 0xc2, 0x38, 0xa2, 0xd0, 0xbc, 0x59,
0x8a, 0x9f, 0x04, 0x05, 0x6b, 0x38, 0x7b, 0xb2,
0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44,
0xae, 0x42, 0x60, 0x82,
];
testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
let targetData = new Uint8Array([
0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255
]);
// Firefox currently has some very odd rounding bug:
// https://bugzilla.mozilla.org/show_bug.cgi?id=1667747
function almost(a, b) {
let diff = Math.abs(a - b);
return diff < 30;
}
display.onflush = () => {
expect(display).to.have.displayed(targetData, almost);
done();
};
display.flush();
});
});

View File

@ -22,37 +22,37 @@ describe('Utils', function () {
console.warn.restore(); console.warn.restore();
console.error.restore(); console.error.restore();
console.info.restore(); console.info.restore();
Log.init_logging(); Log.initLogging();
}); });
it('should use noop for levels lower than the min level', function () { it('should use noop for levels lower than the min level', function () {
Log.init_logging('warn'); Log.initLogging('warn');
Log.Debug('hi'); Log.Debug('hi');
Log.Info('hello'); Log.Info('hello');
expect(console.log).to.not.have.been.called; expect(console.log).to.not.have.been.called;
}); });
it('should use console.debug for Debug', function () { it('should use console.debug for Debug', function () {
Log.init_logging('debug'); Log.initLogging('debug');
Log.Debug('dbg'); Log.Debug('dbg');
expect(console.debug).to.have.been.calledWith('dbg'); expect(console.debug).to.have.been.calledWith('dbg');
}); });
it('should use console.info for Info', function () { it('should use console.info for Info', function () {
Log.init_logging('debug'); Log.initLogging('debug');
Log.Info('inf'); Log.Info('inf');
expect(console.info).to.have.been.calledWith('inf'); expect(console.info).to.have.been.calledWith('inf');
}); });
it('should use console.warn for Warn', function () { it('should use console.warn for Warn', function () {
Log.init_logging('warn'); Log.initLogging('warn');
Log.Warn('wrn'); Log.Warn('wrn');
expect(console.warn).to.have.been.called; expect(console.warn).to.have.been.called;
expect(console.warn).to.have.been.calledWith('wrn'); expect(console.warn).to.have.been.calledWith('wrn');
}); });
it('should use console.error for Error', function () { it('should use console.error for Error', function () {
Log.init_logging('error'); Log.initLogging('error');
Log.Error('err'); Log.Error('err');
expect(console.error).to.have.been.called; expect(console.error).to.have.been.called;
expect(console.error).to.have.been.calledWith('err'); expect(console.error).to.have.been.calledWith('err');

View File

@ -13,7 +13,7 @@ describe('Websock', function () {
beforeEach(function () { beforeEach(function () {
sock = new Websock(); sock = new Websock();
// skip init // skip init
sock._allocate_buffers(); sock._allocateBuffers();
sock._rQ.set(RQ_TEMPLATE); sock._rQ.set(RQ_TEMPLATE);
sock._rQlen = RQ_TEMPLATE.length; sock._rQlen = RQ_TEMPLATE.length;
}); });
@ -33,51 +33,51 @@ describe('Websock', function () {
describe('rQpeek8', function () { describe('rQpeek8', function () {
it('should peek at the next byte without poping it off the queue', function () { it('should peek at the next byte without poping it off the queue', function () {
const bef_len = sock.rQlen; const befLen = sock.rQlen;
const peek = sock.rQpeek8(); const peek = sock.rQpeek8();
expect(sock.rQpeek8()).to.equal(peek); expect(sock.rQpeek8()).to.equal(peek);
expect(sock.rQlen).to.equal(bef_len); expect(sock.rQlen).to.equal(befLen);
}); });
}); });
describe('rQshift8()', function () { describe('rQshift8()', function () {
it('should pop a single byte from the receive queue', function () { it('should pop a single byte from the receive queue', function () {
const peek = sock.rQpeek8(); const peek = sock.rQpeek8();
const bef_len = sock.rQlen; const befLen = sock.rQlen;
expect(sock.rQshift8()).to.equal(peek); expect(sock.rQshift8()).to.equal(peek);
expect(sock.rQlen).to.equal(bef_len - 1); expect(sock.rQlen).to.equal(befLen - 1);
}); });
}); });
describe('rQshift16()', function () { describe('rQshift16()', function () {
it('should pop two bytes from the receive queue and return a single number', function () { it('should pop two bytes from the receive queue and return a single number', function () {
const bef_len = sock.rQlen; const befLen = sock.rQlen;
const expected = (RQ_TEMPLATE[0] << 8) + RQ_TEMPLATE[1]; const expected = (RQ_TEMPLATE[0] << 8) + RQ_TEMPLATE[1];
expect(sock.rQshift16()).to.equal(expected); expect(sock.rQshift16()).to.equal(expected);
expect(sock.rQlen).to.equal(bef_len - 2); expect(sock.rQlen).to.equal(befLen - 2);
}); });
}); });
describe('rQshift32()', function () { describe('rQshift32()', function () {
it('should pop four bytes from the receive queue and return a single number', function () { it('should pop four bytes from the receive queue and return a single number', function () {
const bef_len = sock.rQlen; const befLen = sock.rQlen;
const expected = (RQ_TEMPLATE[0] << 24) + const expected = (RQ_TEMPLATE[0] << 24) +
(RQ_TEMPLATE[1] << 16) + (RQ_TEMPLATE[1] << 16) +
(RQ_TEMPLATE[2] << 8) + (RQ_TEMPLATE[2] << 8) +
RQ_TEMPLATE[3]; RQ_TEMPLATE[3];
expect(sock.rQshift32()).to.equal(expected); expect(sock.rQshift32()).to.equal(expected);
expect(sock.rQlen).to.equal(bef_len - 4); expect(sock.rQlen).to.equal(befLen - 4);
}); });
}); });
describe('rQshiftStr', function () { describe('rQshiftStr', function () {
it('should shift the given number of bytes off of the receive queue and return a string', 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 befLen = sock.rQlen;
const bef_rQi = sock.rQi; const befRQi = sock.rQi;
const shifted = sock.rQshiftStr(3); const shifted = sock.rQshiftStr(3);
expect(shifted).to.be.a('string'); 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(shifted).to.equal(String.fromCharCode.apply(null, Array.prototype.slice.call(new Uint8Array(RQ_TEMPLATE.buffer, befRQi, 3))));
expect(sock.rQlen).to.equal(bef_len - 3); expect(sock.rQlen).to.equal(befLen - 3);
}); });
it('should shift the entire rest of the queue off if no length is given', function () { it('should shift the entire rest of the queue off if no length is given', function () {
@ -112,12 +112,12 @@ describe('Websock', function () {
describe('rQshiftBytes', function () { describe('rQshiftBytes', function () {
it('should shift the given number of bytes of the receive queue and return an array', function () { it('should shift the given number of bytes of the receive queue and return an array', function () {
const bef_len = sock.rQlen; const befLen = sock.rQlen;
const bef_rQi = sock.rQi; const befRQi = sock.rQi;
const shifted = sock.rQshiftBytes(3); const shifted = sock.rQshiftBytes(3);
expect(shifted).to.be.an.instanceof(Uint8Array); expect(shifted).to.be.an.instanceof(Uint8Array);
expect(shifted).to.array.equal(new Uint8Array(RQ_TEMPLATE.buffer, bef_rQi, 3)); expect(shifted).to.array.equal(new Uint8Array(RQ_TEMPLATE.buffer, befRQi, 3));
expect(sock.rQlen).to.equal(bef_len - 3); expect(sock.rQlen).to.equal(befLen - 3);
}); });
it('should shift the entire rest of the queue off if no length is given', function () { it('should shift the entire rest of the queue off if no length is given', function () {
@ -132,9 +132,9 @@ describe('Websock', function () {
}); });
it('should not modify the receive queue', function () { it('should not modify the receive queue', function () {
const bef_len = sock.rQlen; const befLen = sock.rQlen;
sock.rQslice(0, 2); sock.rQslice(0, 2);
expect(sock.rQlen).to.equal(bef_len); expect(sock.rQlen).to.equal(befLen);
}); });
it('should return an array containing the given slice of the receive queue', function () { it('should return an array containing the given slice of the receive queue', function () {
@ -198,7 +198,7 @@ describe('Websock', function () {
sock._websocket.readyState = WebSocket.OPEN; sock._websocket.readyState = WebSocket.OPEN;
sock._sQ = new Uint8Array([1, 2, 3]); sock._sQ = new Uint8Array([1, 2, 3]);
sock._sQlen = 3; sock._sQlen = 3;
const encoded = sock._encode_message(); const encoded = sock._encodeMessage();
sock.flush(); sock.flush();
expect(sock._websocket.send).to.have.been.calledOnce; expect(sock._websocket.send).to.have.been.calledOnce;
@ -232,35 +232,29 @@ describe('Websock', function () {
}); });
}); });
describe('send_string', function () { describe('sendString', function () {
beforeEach(function () { beforeEach(function () {
sock.send = sinon.spy(); sock.send = sinon.spy();
}); });
it('should call send after converting the string to an array', function () { it('should call send after converting the string to an array', function () {
sock.send_string("\x01\x02\x03"); sock.sendString("\x01\x02\x03");
expect(sock.send).to.have.been.calledWith([1, 2, 3]); expect(sock.send).to.have.been.calledWith([1, 2, 3]);
}); });
}); });
}); });
describe('lifecycle methods', function () { describe('lifecycle methods', function () {
let old_WS; let oldWS;
before(function () { before(function () {
old_WS = WebSocket; oldWS = WebSocket;
}); });
let sock; let sock;
beforeEach(function () { beforeEach(function () {
sock = new Websock(); sock = new Websock();
// eslint-disable-next-line no-global-assign // eslint-disable-next-line no-global-assign
WebSocket = sinon.spy(); WebSocket = sinon.spy(FakeWebSocket);
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 () { describe('opening', function () {
@ -276,9 +270,17 @@ describe('Websock', function () {
// it('should initialize the event handlers')? // it('should initialize the event handlers')?
}); });
describe('attaching', function () {
it('should attach to an existing websocket', function () {
let ws = new FakeWebSocket('ws://localhost:8675');
sock.attach(ws);
expect(WebSocket).to.not.have.been.called;
});
});
describe('closing', function () { describe('closing', function () {
beforeEach(function () { beforeEach(function () {
sock.open('ws://'); sock.open('ws://localhost');
sock._websocket.close = sinon.spy(); sock._websocket.close = sinon.spy();
}); });
@ -306,30 +308,30 @@ describe('Websock', function () {
expect(sock._websocket.close).not.to.have.been.called; expect(sock._websocket.close).not.to.have.been.called;
}); });
it('should reset onmessage to not call _recv_message', function () { it('should reset onmessage to not call _recvMessage', function () {
sinon.spy(sock, '_recv_message'); sinon.spy(sock, '_recvMessage');
sock.close(); sock.close();
sock._websocket.onmessage(null); sock._websocket.onmessage(null);
try { try {
expect(sock._recv_message).not.to.have.been.called; expect(sock._recvMessage).not.to.have.been.called;
} finally { } finally {
sock._recv_message.restore(); sock._recvMessage.restore();
} }
}); });
}); });
describe('event handlers', function () { describe('event handlers', function () {
beforeEach(function () { beforeEach(function () {
sock._recv_message = sinon.spy(); sock._recvMessage = sinon.spy();
sock.on('open', sinon.spy()); sock.on('open', sinon.spy());
sock.on('close', sinon.spy()); sock.on('close', sinon.spy());
sock.on('error', sinon.spy()); sock.on('error', sinon.spy());
sock.open('ws://'); sock.open('ws://localhost');
}); });
it('should call _recv_message on a message', function () { it('should call _recvMessage on a message', function () {
sock._websocket.onmessage(null); sock._websocket.onmessage(null);
expect(sock._recv_message).to.have.been.calledOnce; expect(sock._recvMessage).to.have.been.calledOnce;
}); });
it('should call the open event handler on opening', function () { it('should call the open event handler on opening', function () {
@ -348,9 +350,96 @@ describe('Websock', function () {
}); });
}); });
describe('ready state', function () {
it('should be "unused" after construction', function () {
let sock = new Websock();
expect(sock.readyState).to.equal('unused');
});
it('should be "connecting" if WebSocket is connecting', function () {
let sock = new Websock();
let ws = new FakeWebSocket();
ws.readyState = WebSocket.CONNECTING;
sock.attach(ws);
expect(sock.readyState).to.equal('connecting');
});
it('should be "open" if WebSocket is open', function () {
let sock = new Websock();
let ws = new FakeWebSocket();
ws.readyState = WebSocket.OPEN;
sock.attach(ws);
expect(sock.readyState).to.equal('open');
});
it('should be "closing" if WebSocket is closing', function () {
let sock = new Websock();
let ws = new FakeWebSocket();
ws.readyState = WebSocket.CLOSING;
sock.attach(ws);
expect(sock.readyState).to.equal('closing');
});
it('should be "closed" if WebSocket is closed', function () {
let sock = new Websock();
let ws = new FakeWebSocket();
ws.readyState = WebSocket.CLOSED;
sock.attach(ws);
expect(sock.readyState).to.equal('closed');
});
it('should be "unknown" if WebSocket state is unknown', function () {
let sock = new Websock();
let ws = new FakeWebSocket();
ws.readyState = 666;
sock.attach(ws);
expect(sock.readyState).to.equal('unknown');
});
it('should be "connecting" if RTCDataChannel is connecting', function () {
let sock = new Websock();
let ws = new FakeWebSocket();
ws.readyState = 'connecting';
sock.attach(ws);
expect(sock.readyState).to.equal('connecting');
});
it('should be "open" if RTCDataChannel is open', function () {
let sock = new Websock();
let ws = new FakeWebSocket();
ws.readyState = 'open';
sock.attach(ws);
expect(sock.readyState).to.equal('open');
});
it('should be "closing" if RTCDataChannel is closing', function () {
let sock = new Websock();
let ws = new FakeWebSocket();
ws.readyState = 'closing';
sock.attach(ws);
expect(sock.readyState).to.equal('closing');
});
it('should be "closed" if RTCDataChannel is closed', function () {
let sock = new Websock();
let ws = new FakeWebSocket();
ws.readyState = 'closed';
sock.attach(ws);
expect(sock.readyState).to.equal('closed');
});
it('should be "unknown" if RTCDataChannel state is unknown', function () {
let sock = new Websock();
let ws = new FakeWebSocket();
ws.readyState = 'foobar';
sock.attach(ws);
expect(sock.readyState).to.equal('unknown');
});
});
after(function () { after(function () {
// eslint-disable-next-line no-global-assign // eslint-disable-next-line no-global-assign
WebSocket = old_WS; WebSocket = oldWS;
}); });
}); });
@ -358,13 +447,13 @@ describe('Websock', function () {
let sock; let sock;
beforeEach(function () { beforeEach(function () {
sock = new Websock(); sock = new Websock();
sock._allocate_buffers(); sock._allocateBuffers();
}); });
it('should support adding binary Uint8Array data to the receive queue', function () { it('should support adding binary Uint8Array data to the receive queue', function () {
const msg = { data: new Uint8Array([1, 2, 3]) }; const msg = { data: new Uint8Array([1, 2, 3]) };
sock._mode = 'binary'; sock._mode = 'binary';
sock._recv_message(msg); sock._recvMessage(msg);
expect(sock.rQshiftStr(3)).to.equal('\x01\x02\x03'); expect(sock.rQshiftStr(3)).to.equal('\x01\x02\x03');
}); });
@ -372,7 +461,7 @@ describe('Websock', function () {
sock._eventHandlers.message = sinon.spy(); sock._eventHandlers.message = sinon.spy();
const msg = { data: new Uint8Array([1, 2, 3]).buffer }; const msg = { data: new Uint8Array([1, 2, 3]).buffer };
sock._mode = 'binary'; sock._mode = 'binary';
sock._recv_message(msg); sock._recvMessage(msg);
expect(sock._eventHandlers.message).to.have.been.calledOnce; expect(sock._eventHandlers.message).to.have.been.calledOnce;
}); });
@ -380,7 +469,7 @@ describe('Websock', function () {
sock._eventHandlers.message = sinon.spy(); sock._eventHandlers.message = sinon.spy();
const msg = { data: new Uint8Array([]).buffer }; const msg = { data: new Uint8Array([]).buffer };
sock._mode = 'binary'; sock._mode = 'binary';
sock._recv_message(msg); sock._recvMessage(msg);
expect(sock._eventHandlers.message).not.to.have.been.called; expect(sock._eventHandlers.message).not.to.have.been.called;
}); });
@ -391,7 +480,7 @@ describe('Websock', function () {
sock.rQi = 6; sock.rQi = 6;
const msg = { data: new Uint8Array([1, 2, 3]).buffer }; const msg = { data: new Uint8Array([1, 2, 3]).buffer };
sock._mode = 'binary'; sock._mode = 'binary';
sock._recv_message(msg); sock._recvMessage(msg);
expect(sock._rQlen).to.equal(0); expect(sock._rQlen).to.equal(0);
expect(sock.rQi).to.equal(0); expect(sock.rQi).to.equal(0);
}); });
@ -403,7 +492,7 @@ describe('Websock', function () {
sock.rQi = 10; sock.rQi = 10;
const msg = { data: new Uint8Array([1, 2]).buffer }; const msg = { data: new Uint8Array([1, 2]).buffer };
sock._mode = 'binary'; sock._mode = 'binary';
sock._recv_message(msg); sock._recvMessage(msg);
expect(sock._rQlen).to.equal(12); expect(sock._rQlen).to.equal(12);
expect(sock.rQi).to.equal(0); expect(sock.rQi).to.equal(0);
}); });
@ -415,7 +504,7 @@ describe('Websock', function () {
sock._rQbufferSize = 20; sock._rQbufferSize = 20;
const msg = { data: new Uint8Array(30).buffer }; const msg = { data: new Uint8Array(30).buffer };
sock._mode = 'binary'; sock._mode = 'binary';
sock._recv_message(msg); sock._recvMessage(msg);
expect(sock._rQlen).to.equal(30); expect(sock._rQlen).to.equal(30);
expect(sock.rQi).to.equal(0); expect(sock.rQi).to.equal(0);
expect(sock._rQ.length).to.equal(240); // keep the invariant that rQbufferSize / 8 >= rQlen expect(sock._rQ.length).to.equal(240); // keep the invariant that rQbufferSize / 8 >= rQlen
@ -428,7 +517,7 @@ describe('Websock', function () {
sock._rQbufferSize = 20; sock._rQbufferSize = 20;
const msg = { data: new Uint8Array(6).buffer }; const msg = { data: new Uint8Array(6).buffer };
sock._mode = 'binary'; sock._mode = 'binary';
sock._recv_message(msg); sock._recvMessage(msg);
expect(sock._rQlen).to.equal(6); expect(sock._rQlen).to.equal(6);
expect(sock.rQi).to.equal(0); expect(sock.rQi).to.equal(0);
expect(sock._rQ.length).to.equal(48); expect(sock._rQ.length).to.equal(48);
@ -450,13 +539,13 @@ describe('Websock', function () {
it('should only send the send queue up to the send queue length', function () { it('should only send the send queue up to the send queue length', function () {
sock._sQ = new Uint8Array([1, 2, 3, 4, 5]); sock._sQ = new Uint8Array([1, 2, 3, 4, 5]);
sock._sQlen = 3; sock._sQlen = 3;
const res = sock._encode_message(); const res = sock._encodeMessage();
expect(res).to.array.equal(new Uint8Array([1, 2, 3])); expect(res).to.array.equal(new Uint8Array([1, 2, 3]));
}); });
it('should properly pass the encoded data off to the actual WebSocket', function () { it('should properly pass the encoded data off to the actual WebSocket', function () {
sock.send([1, 2, 3]); sock.send([1, 2, 3]);
expect(sock._websocket._get_sent_data()).to.array.equal(new Uint8Array([1, 2, 3])); expect(sock._websocket._getSentData()).to.array.equal(new Uint8Array([1, 2, 3]));
}); });
}); });
}); });

View File

@ -22,11 +22,6 @@ describe('WebUtil', function () {
let origLocalStorage; let origLocalStorage;
beforeEach(function () { beforeEach(function () {
origLocalStorage = Object.getOwnPropertyDescriptor(window, "localStorage"); 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: {}}); Object.defineProperty(window, "localStorage", {value: {}});
if (window.localStorage.setItem !== undefined) { if (window.localStorage.setItem !== undefined) {

View File

@ -2,21 +2,6 @@
<html lang="en"> <html lang="en">
<head> <head>
<title>VNC Playback</title> <title>VNC Playback</title>
<!-- promise polyfills promises for IE11 -->
<script src="../vendor/promise.js"></script>
<!-- ES2015/ES6 modules polyfill -->
<script type="module">
window._noVNC_has_module_support = true;
</script>
<script>
window.addEventListener("load", function() {
if (window._noVNC_has_module_support) return;
var loader = document.createElement("script");
loader.src = "../vendor/browser-es-module-loader/dist/browser-es-module-loader.js";
document.head.appendChild(loader);
});
</script>
<!-- actual script modules -->
<script type="module" src="./playback-ui.js"></script> <script type="module" src="./playback-ui.js"></script>
</head> </head>
<body> <body>
@ -37,7 +22,5 @@
<div id="VNC_screen"> <div id="VNC_screen">
<div id="VNC_status">Loading</div> <div id="VNC_status">Loading</div>
</div> </div>
<script type="module" src="./playback-ui.js"></script>
</body> </body>
</html> </html>

View File

@ -1,6 +1,6 @@
## WebSockets Proxy/Bridge ## WebSockets Proxy/Bridge
Websockify has been forked out into its own project. `launch.sh` wil Websockify has been forked out into its own project. `novnc_proxy` will
automatically download it here if it is not already present and not automatically download it here if it is not already present and not
installed as system-wide. installed as system-wide.

View File

@ -9,14 +9,14 @@
const fs = require('fs'); const fs = require('fs');
let show_help = process.argv.length === 2; let showHelp = process.argv.length === 2;
let filename; let filename;
for (let i = 2; i < process.argv.length; ++i) { for (let i = 2; i < process.argv.length; ++i) {
switch (process.argv[i]) { switch (process.argv[i]) {
case "--help": case "--help":
case "-h": case "-h":
show_help = true; showHelp = true;
break; break;
case "--file": case "--file":
case "-f": case "-f":
@ -26,11 +26,11 @@ for (let i = 2; i < process.argv.length; ++i) {
} }
if (!filename) { if (!filename) {
show_help = true; showHelp = true;
console.log("Error: No filename specified\n"); console.log("Error: No filename specified\n");
} }
if (show_help) { if (showHelp) {
console.log("Parses a *nix keysymdef.h to generate Unicode code point mappings"); console.log("Parses a *nix keysymdef.h to generate Unicode code point mappings");
console.log("Usage: node parse.js [options] filename:"); console.log("Usage: node parse.js [options] filename:");
console.log(" -h [ --help ] Produce this help message"); console.log(" -h [ --help ] Produce this help message");

View File

@ -27,6 +27,13 @@ usage() {
echo " " echo " "
echo " --record FILE Record traffic to FILE.session.js" echo " --record FILE Record traffic to FILE.session.js"
echo " " echo " "
echo " --syslog SERVER Can be local socket such as /dev/log, or a UDP host:port pair."
echo " "
echo " --heartbeat SEC send a ping to the client every SEC seconds"
echo " --timeout SEC after SEC seconds exit when not connected"
echo " --idle-timeout SEC server exits after SEC seconds if there are no"
echo " active connections"
echo " "
exit 2 exit 2
} }
@ -41,6 +48,10 @@ WEB=""
proxy_pid="" proxy_pid=""
SSLONLY="" SSLONLY=""
RECORD_ARG="" RECORD_ARG=""
SYSLOG_ARG=""
HEARTBEAT_ARG=""
IDLETIMEOUT_ARG=""
TIMEOUT_ARG=""
die() { die() {
echo "$*" echo "$*"
@ -70,6 +81,10 @@ while [ "$*" ]; do
--web) WEB="${OPTARG}"; shift ;; --web) WEB="${OPTARG}"; shift ;;
--ssl-only) SSLONLY="--ssl-only" ;; --ssl-only) SSLONLY="--ssl-only" ;;
--record) RECORD_ARG="--record ${OPTARG}"; shift ;; --record) RECORD_ARG="--record ${OPTARG}"; shift ;;
--syslog) SYSLOG_ARG="--syslog ${OPTARG}"; shift ;;
--heartbeat) HEARTBEAT_ARG="--heartbeat ${OPTARG}"; shift ;;
--idle-timeout) IDLETIMEOUT_ARG="--idle-timeout ${OPTARG}"; shift ;;
--timeout) TIMEOUT_ARG="--timeout ${OPTARG}"; shift ;;
-h|--help) usage ;; -h|--help) usage ;;
-*) usage "Unknown chrooter option: ${param}" ;; -*) usage "Unknown chrooter option: ${param}" ;;
*) break ;; *) break ;;
@ -162,7 +177,7 @@ fi
echo "Starting webserver and WebSockets proxy on port ${PORT}" echo "Starting webserver and WebSockets proxy on port ${PORT}"
#${HERE}/websockify --web ${WEB} ${CERT:+--cert ${CERT}} ${PORT} ${VNC_DEST} & #${HERE}/websockify --web ${WEB} ${CERT:+--cert ${CERT}} ${PORT} ${VNC_DEST} &
${WEBSOCKIFY} ${SSLONLY} --web ${WEB} ${CERT:+--cert ${CERT}} ${KEY:+--key ${KEY}} ${PORT} ${VNC_DEST} ${RECORD_ARG} & ${WEBSOCKIFY} ${SYSLOG_ARG} ${SSLONLY} --web ${WEB} ${CERT:+--cert ${CERT}} ${KEY:+--key ${KEY}} ${PORT} ${VNC_DEST} ${HEARTBEAT_ARG} ${IDLETIMEOUT_ARG} ${RECORD_ARG} ${TIMEOUT_ARG} &
proxy_pid="$!" proxy_pid="$!"
sleep 1 sleep 1
if ! ps -p ${proxy_pid} >/dev/null; then if ! ps -p ${proxy_pid} >/dev/null; then

View File

@ -6,13 +6,8 @@ const fs = require('fs');
const fse = require('fs-extra'); const fse = require('fs-extra');
const babel = require('@babel/core'); const babel = require('@babel/core');
const SUPPORTED_FORMATS = new Set(['amd', 'commonjs', 'systemjs', 'umd']);
program 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('-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') .option('--clean', 'clear the lib folder before building')
.parse(process.argv); .parse(process.argv);
@ -20,33 +15,13 @@ program
const paths = { const paths = {
main: path.resolve(__dirname, '..'), main: path.resolve(__dirname, '..'),
core: path.resolve(__dirname, '..', 'core'), core: path.resolve(__dirname, '..', 'core'),
app: path.resolve(__dirname, '..', 'app'),
vendor: path.resolve(__dirname, '..', 'vendor'), vendor: path.resolve(__dirname, '..', 'vendor'),
out_dir_base: path.resolve(__dirname, '..', 'build'), libDirBase: path.resolve(__dirname, '..', 'lib'),
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.app, 'images', 'icons', 'Makefile'),
]);
const only_legacy_scripts = new Set([
path.join(paths.vendor, 'promise.js'),
]);
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 // util.promisify requires Node.js 8.x, so we have our own
function promisify(original) { function promisify(original) {
return function promise_wrap() { return function promiseWrap() {
const args = Array.prototype.slice.call(arguments); const args = Array.prototype.slice.call(arguments);
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
original.apply(this, args.concat((err, value) => { original.apply(this, args.concat((err, value) => {
@ -57,25 +32,21 @@ function promisify(original) {
}; };
} }
const readFile = promisify(fs.readFile);
const writeFile = promisify(fs.writeFile); const writeFile = promisify(fs.writeFile);
const readdir = promisify(fs.readdir); const readdir = promisify(fs.readdir);
const lstat = promisify(fs.lstat); const lstat = promisify(fs.lstat);
const copy = promisify(fse.copy);
const unlink = promisify(fse.unlink);
const ensureDir = promisify(fse.ensureDir); const ensureDir = promisify(fse.ensureDir);
const rmdir = promisify(fse.rmdir);
const babelTransformFile = promisify(babel.transformFile); const babelTransformFile = promisify(babel.transformFile);
// walkDir *recursively* walks directories trees, // walkDir *recursively* walks directories trees,
// calling the callback for all normal files found. // calling the callback for all normal files found.
function walkDir(base_path, cb, filter) { function walkDir(basePath, cb, filter) {
return readdir(base_path) return readdir(basePath)
.then((files) => { .then((files) => {
const paths = files.map(filename => path.join(base_path, filename)); const paths = files.map(filename => path.join(basePath, filename));
return Promise.all(paths.map(filepath => lstat(filepath) return Promise.all(paths.map(filepath => lstat(filepath)
.then((stats) => { .then((stats) => {
if (filter !== undefined && !filter(filepath, stats)) return; if (filter !== undefined && !filter(filepath, stats)) return;
@ -87,157 +58,57 @@ function walkDir(base_path, cb, filter) {
}); });
} }
function transform_html(legacy_scripts, only_legacy) { function makeLibFiles(sourceMaps) {
// write out the modified vnc.html file that works with the bundle // NB: we need to make a copy of babelOpts, since babel sets some defaults on it
const src_html_path = path.resolve(__dirname, '..', 'vnc.html'); const babelOpts = () => ({
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 = '<!-- begin scripts -->\n';
const end_marker = '<!-- end scripts -->';
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 += ` <script src="${legacy_scripts[i]}"></script>\n`;
}
} else {
// Otherwise include both modules and legacy fallbacks
new_script += ' <script type="module" crossorigin="anonymous" src="app/ui.js"></script>\n';
for (let i = 0;i < legacy_scripts.length;i++) {
new_script += ` <script nomodule src="${legacy_scripts[i]}"></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: [], plugins: [],
presets: [ presets: [
[ '@babel/preset-env', [ '@babel/preset-env',
{ targets: 'ie >= 11', { modules: 'commonjs' } ]
modules: import_format } ]
], ],
ast: false, ast: false,
sourceMaps: source_maps, sourceMaps: sourceMaps,
}); });
// No point in duplicate files without the app, so force only converted files fse.ensureDirSync(paths.libDirBase);
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 outFiles = [];
const legacyFiles = [];
const handleDir = (js_only, vendor_rewrite, in_path_base, filename) => Promise.resolve() const handleDir = (vendorRewrite, inPathBase, filename) => Promise.resolve()
.then(() => { .then(() => {
const out_path = path.join(out_path_base, path.relative(in_path_base, filename)); const outPath = path.join(paths.libDirBase, path.relative(inPathBase, filename));
const legacy_path = path.join(legacy_path_base, path.relative(in_path_base, filename));
if (path.extname(filename) !== '.js') { if (path.extname(filename) !== '.js') {
if (!js_only) {
console.log(`Writing ${out_path}`);
return copy(filename, out_path);
}
return; // skip non-javascript files return; // skip non-javascript files
} }
if (no_transform_files.has(filename)) {
return ensureDir(path.dirname(out_path))
.then(() => {
console.log(`Writing ${out_path}`);
return copy(filename, out_path);
});
}
if (only_legacy_scripts.has(filename)) {
legacyFiles.push(legacy_path);
return ensureDir(path.dirname(legacy_path))
.then(() => {
console.log(`Writing ${legacy_path}`);
return copy(filename, legacy_path);
});
}
return Promise.resolve() return Promise.resolve()
.then(() => ensureDir(path.dirname(outPath)))
.then(() => { .then(() => {
if (only_legacy) { const opts = babelOpts();
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(() => {
const opts = babel_opts();
if (helper && helpers.optionsOverride) {
helper.optionsOverride(opts);
}
// Adjust for the fact that we move the core files relative // Adjust for the fact that we move the core files relative
// to the vendor directory // to the vendor directory
if (vendor_rewrite) { if (vendorRewrite) {
opts.plugins.push(["import-redirect", opts.plugins.push(["import-redirect",
{"root": legacy_path_base, {"root": paths.libDirBase,
"redirect": { "vendor/(.+)": "./vendor/$1"}}]); "redirect": { "vendor/(.+)": "./vendor/$1"}}]);
} }
return babelTransformFile(filename, opts) return babelTransformFile(filename, opts)
.then((res) => { .then((res) => {
console.log(`Writing ${legacy_path}`); console.log(`Writing ${outPath}`);
const {map} = res; const {map} = res;
let {code} = res; let {code} = res;
if (source_maps === true) { if (sourceMaps === true) {
// append URL for external source map // append URL for external source map
code += `\n//# sourceMappingURL=${path.basename(legacy_path)}.map\n`; code += `\n//# sourceMappingURL=${path.basename(outPath)}.map\n`;
} }
outFiles.push(`${legacy_path}`); outFiles.push(`${outPath}`);
return writeFile(legacy_path, code) return writeFile(outPath, code)
.then(() => { .then(() => {
if (source_maps === true || source_maps === 'both') { if (sourceMaps === true || sourceMaps === 'both') {
console.log(` and ${legacy_path}.map`); console.log(` and ${outPath}.map`);
outFiles.push(`${legacy_path}.map`); outFiles.push(`${outPath}.map`);
return writeFile(`${legacy_path}.map`, JSON.stringify(map)); return writeFile(`${outPath}.map`, JSON.stringify(map));
} }
}); });
}); });
@ -246,64 +117,12 @@ function make_lib_files(import_format, source_maps, with_app_dir, only_legacy) {
Promise.resolve() Promise.resolve()
.then(() => { .then(() => {
const handler = handleDir.bind(null, true, false, in_path || paths.main); const handler = handleDir.bind(null, false, paths.main);
const filter = (filename, stats) => !no_copy_files.has(filename); return walkDir(paths.vendor, handler);
return walkDir(paths.vendor, handler, filter);
}) })
.then(() => { .then(() => {
const handler = handleDir.bind(null, true, !in_path, in_path || paths.core); const handler = handleDir.bind(null, true, paths.core);
const filter = (filename, stats) => !no_copy_files.has(filename); return walkDir(paths.core, handler);
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) => {
let legacy_scripts = [];
legacyFiles.forEach((file) => {
let rel_file_path = path.relative(out_path_base, file);
legacy_scripts.push(rel_file_path);
});
legacy_scripts = legacy_scripts.concat(extra_scripts);
let rel_app_path = path.relative(out_path_base, out_app_path);
legacy_scripts.push(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) => { .catch((err) => {
console.error(`Failure converting modules: ${err}`); console.error(`Failure converting modules: ${err}`);
@ -312,11 +131,8 @@ function make_lib_files(import_format, source_maps, with_app_dir, only_legacy) {
} }
if (program.clean) { if (program.clean) {
console.log(`Removing ${paths.lib_dir_base}`); console.log(`Removing ${paths.libDirBase}`);
fse.removeSync(paths.lib_dir_base); fse.removeSync(paths.libDirBase);
console.log(`Removing ${paths.out_dir_base}`);
fse.removeSync(paths.out_dir_base);
} }
make_lib_files(program.as, program.withSourceMaps, program.withApp, program.onlyLegacy); makeLibFiles(program.withSourceMaps);

View File

@ -1,60 +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}"], function (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 ];
});
},
},
'commonjs': {
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(() => []);
},
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')}`);
const systemjs_path = path.relative(base_out_path,
path.join(script_base_path, 'system-production.js'));
return [ systemjs_path ];
});
},
},
'umd': {
},
};

View File

@ -1,15 +0,0 @@
Custom Browser ES Module Loader
===============================
This is a module loader using babel and the ES Module Loader polyfill.
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 `npx rollup -c` in this directory, and then run
`./genworker.js`.
LICENSE
-------
MIT

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -1,13 +0,0 @@
#!/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"));

View File

@ -1,15 +0,0 @@
import nodeResolve from 'rollup-plugin-node-resolve';
export default {
input: 'src/browser-es-module-loader.js',
output: {
file: 'dist/browser-es-module-loader.js',
format: 'umd',
name: 'BrowserESModuleLoader',
sourcemap: true,
},
plugins: [
nodeResolve(),
],
};

View File

@ -1,23 +0,0 @@
// Polyfills needed for Babel to function
require("core-js");
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
var output = babelTransform(evt.data.source, {
compact: false,
filename: evt.data.key + '!transpiled',
sourceFileName: evt.data.key,
moduleIds: false,
sourceMaps: 'inline',
babelrc: false,
plugins: [babelTransformDynamicImport, babelTransformModulesSystemJS],
presets: [ [ babelPresetEnv, { targets: 'ie >= 11' } ] ],
});
self.postMessage({key: evt.data.key, code: output.code, source: evt.data.source});
};

View File

@ -1,279 +0,0 @@
import RegisterLoader from 'es-module-loader/core/register-loader.js';
import { baseURI, global, isBrowser } from 'es-module-loader/core/common.js';
import { resolveIfNotPlain } from 'es-module-loader/core/resolve.js';
var loader;
// <script type="module"> support
var anonSources = {};
if (typeof document != 'undefined' && document.getElementsByTagName) {
var handleError = function(err) {
// dispatch an error event so that we can display in errors in browsers
// that don't yet support unhandledrejection
if (window.onunhandledrejection === undefined) {
try {
var evt = new Event('error');
} catch (_eventError) {
var evt = document.createEvent('Event');
evt.initEvent('error', true, true);
}
evt.message = err.message;
if (err.fileName) {
evt.filename = err.fileName;
evt.lineno = err.lineNumber;
evt.colno = err.columnNumber;
} else if (err.sourceURL) {
evt.filename = err.sourceURL;
evt.lineno = err.line;
evt.colno = err.column;
}
evt.error = err;
window.dispatchEvent(evt);
}
// throw so it still shows up in the console
throw err;
}
var ready = function() {
document.removeEventListener('DOMContentLoaded', ready, false );
var anonCnt = 0;
var scripts = document.getElementsByTagName('script');
for (var i = 0; i < scripts.length; i++) {
var script = scripts[i];
if (script.type == 'module' && !script.loaded) {
script.loaded = true;
if (script.src) {
loader.import(script.src).catch(handleError);
}
// anonymous modules supported via a custom naming scheme and registry
else {
var uri = './<anon' + ++anonCnt + '>.js';
if (script.id !== ""){
uri = "./" + script.id;
}
var anonName = resolveIfNotPlain(uri, baseURI);
anonSources[anonName] = script.innerHTML;
loader.import(anonName).catch(handleError);
}
}
}
}
// simple DOM ready
if (document.readyState !== 'loading')
setTimeout(ready);
else
document.addEventListener('DOMContentLoaded', ready, false);
}
function BrowserESModuleLoader(baseKey) {
if (baseKey)
this.baseKey = resolveIfNotPlain(baseKey, baseURI) || resolveIfNotPlain('./' + baseKey, baseURI);
RegisterLoader.call(this);
var loader = this;
// ensure System.register is available
global.System = global.System || {};
if (typeof global.System.register == 'function')
var prevRegister = global.System.register;
global.System.register = function() {
loader.register.apply(loader, arguments);
if (prevRegister)
prevRegister.apply(this, arguments);
};
}
BrowserESModuleLoader.prototype = Object.create(RegisterLoader.prototype);
// normalize is never given a relative name like "./x", that part is already handled
BrowserESModuleLoader.prototype[RegisterLoader.resolve] = function(key, parent) {
var resolved = RegisterLoader.prototype[RegisterLoader.resolve].call(this, key, parent || this.baseKey) || key;
if (!resolved)
throw new RangeError('ES module loader does not resolve plain module names, resolving "' + key + '" to ' + parent);
return resolved;
};
function xhrFetch(url, resolve, reject) {
var xhr = new XMLHttpRequest();
var load = function(source) {
resolve(xhr.responseText);
}
var error = function() {
reject(new Error('XHR error' + (xhr.status ? ' (' + xhr.status + (xhr.statusText ? ' ' + xhr.statusText : '') + ')' : '') + ' loading ' + url));
}
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
// in Chrome on file:/// URLs, status is 0
if (xhr.status == 0) {
if (xhr.responseText) {
load();
}
else {
// when responseText is empty, wait for load or error event
// to inform if it is a 404 or empty file
xhr.addEventListener('error', error);
xhr.addEventListener('load', load);
}
}
else if (xhr.status === 200) {
load();
}
else {
error();
}
}
};
xhr.open("GET", url, true);
xhr.send(null);
}
var WorkerPool = function (script, size) {
var current = document.currentScript;
// IE doesn't support currentScript
if (!current) {
// Find an entry with out basename
var scripts = document.getElementsByTagName('script');
for (var i = 0; i < scripts.length; i++) {
if (scripts[i].src.indexOf("browser-es-module-loader.js") !== -1) {
current = scripts[i];
break;
}
}
if (!current)
throw Error("Could not find own <script> element");
}
script = current.src.substr(0, current.src.lastIndexOf("/")) + "/" + script;
this._workers = new Array(size);
this._ind = 0;
this._size = size;
this._jobs = 0;
this.onmessage = undefined;
this._stopTimeout = undefined;
for (var i = 0; i < size; i++) {
var wrkr = new Worker(script);
wrkr._count = 0;
wrkr._ind = i;
wrkr.onmessage = this._onmessage.bind(this, wrkr);
wrkr.onerror = this._onerror.bind(this);
this._workers[i] = wrkr;
}
this._checkJobs();
};
WorkerPool.prototype = {
postMessage: function (msg) {
if (this._stopTimeout !== undefined) {
clearTimeout(this._stopTimeout);
this._stopTimeout = undefined;
}
var wrkr = this._workers[this._ind % this._size];
wrkr._count++;
this._jobs++;
wrkr.postMessage(msg);
this._ind++;
},
_onmessage: function (wrkr, evt) {
wrkr._count--;
this._jobs--;
this.onmessage(evt, wrkr);
this._checkJobs();
},
_onerror: function(err) {
try {
var evt = new Event('error');
} catch (_eventError) {
var evt = document.createEvent('Event');
evt.initEvent('error', true, true);
}
evt.message = err.message;
evt.filename = err.filename;
evt.lineno = err.lineno;
evt.colno = err.colno;
evt.error = err.error;
window.dispatchEvent(evt);
},
_checkJobs: function () {
if (this._jobs === 0 && this._stopTimeout === undefined) {
// wait for 2s of inactivity before stopping (that should be enough for local loading)
this._stopTimeout = setTimeout(this._stop.bind(this), 2000);
}
},
_stop: function () {
this._workers.forEach(function(wrkr) {
wrkr.terminate();
});
}
};
var promiseMap = new Map();
var babelWorker = new WorkerPool('babel-worker.js', 3);
babelWorker.onmessage = function (evt) {
var promFuncs = promiseMap.get(evt.data.key);
promFuncs.resolve(evt.data);
promiseMap.delete(evt.data.key);
};
// instantiate just needs to run System.register
// so we fetch the source, convert into the Babel System module format, then evaluate it
BrowserESModuleLoader.prototype[RegisterLoader.instantiate] = function(key, processAnonRegister) {
var loader = this;
// load as ES with Babel converting into System.register
return new Promise(function(resolve, reject) {
// anonymous module
if (anonSources[key]) {
resolve(anonSources[key])
anonSources[key] = undefined;
}
// otherwise we fetch
else {
xhrFetch(key, resolve, reject);
}
})
.then(function(source) {
// check our cache first
var cacheEntry = localStorage.getItem(key);
if (cacheEntry) {
cacheEntry = JSON.parse(cacheEntry);
// TODO: store a hash instead
if (cacheEntry.source === source) {
return Promise.resolve({key: key, code: cacheEntry.code, source: cacheEntry.source});
}
}
return new Promise(function (resolve, reject) {
promiseMap.set(key, {resolve: resolve, reject: reject});
babelWorker.postMessage({key: key, source: source});
});
}).then(function (data) {
// evaluate without require, exports and module variables
// we leave module in for now to allow module.require access
try {
var cacheEntry = JSON.stringify({source: data.source, code: data.code});
localStorage.setItem(key, cacheEntry);
} catch (e) {
if (window.console) {
window.console.warn('Unable to cache transpiled version of ' + key + ': ' + e);
}
}
(0, eval)(data.code + '\n//# sourceURL=' + data.key + '!transpiled');
processAnonRegister();
});
};
// create a default loader instance in the browser
if (isBrowser)
loader = new BrowserESModuleLoader();
export default BrowserESModuleLoader;

255
vendor/promise.js vendored
View File

@ -1,255 +0,0 @@
/* Copyright (c) 2014 Taylor Hakes
* Copyright (c) 2014 Forbes Lindesay
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
(function (root) {
// Store setTimeout reference so promise-polyfill will be unaffected by
// other code modifying setTimeout (like sinon.useFakeTimers())
var setTimeoutFunc = setTimeout;
function noop() {}
// Polyfill for Function.prototype.bind
function bind(fn, thisArg) {
return function () {
fn.apply(thisArg, arguments);
};
}
function Promise(fn) {
if (typeof this !== 'object') throw new TypeError('Promises must be constructed via new');
if (typeof fn !== 'function') throw new TypeError('not a function');
this._state = 0;
this._handled = false;
this._value = undefined;
this._deferreds = [];
doResolve(fn, this);
}
function handle(self, deferred) {
while (self._state === 3) {
self = self._value;
}
if (self._state === 0) {
self._deferreds.push(deferred);
return;
}
self._handled = true;
Promise._immediateFn(function () {
var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected;
if (cb === null) {
(self._state === 1 ? resolve : reject)(deferred.promise, self._value);
return;
}
var ret;
try {
ret = cb(self._value);
} catch (e) {
reject(deferred.promise, e);
return;
}
resolve(deferred.promise, ret);
});
}
function resolve(self, newValue) {
try {
// Promise Resolution Procedure: https://github.com/promises-aplus/promises-spec#the-promise-resolution-procedure
if (newValue === self) throw new TypeError('A promise cannot be resolved with itself.');
if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) {
var then = newValue.then;
if (newValue instanceof Promise) {
self._state = 3;
self._value = newValue;
finale(self);
return;
} else if (typeof then === 'function') {
doResolve(bind(then, newValue), self);
return;
}
}
self._state = 1;
self._value = newValue;
finale(self);
} catch (e) {
reject(self, e);
}
}
function reject(self, newValue) {
self._state = 2;
self._value = newValue;
finale(self);
}
function finale(self) {
if (self._state === 2 && self._deferreds.length === 0) {
Promise._immediateFn(function() {
if (!self._handled) {
Promise._unhandledRejectionFn(self._value);
}
});
}
for (var i = 0, len = self._deferreds.length; i < len; i++) {
handle(self, self._deferreds[i]);
}
self._deferreds = null;
}
function Handler(onFulfilled, onRejected, promise) {
this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null;
this.onRejected = typeof onRejected === 'function' ? onRejected : null;
this.promise = promise;
}
/**
* Take a potentially misbehaving resolver function and make sure
* onFulfilled and onRejected are only called once.
*
* Makes no guarantees about asynchrony.
*/
function doResolve(fn, self) {
var done = false;
try {
fn(function (value) {
if (done) return;
done = true;
resolve(self, value);
}, function (reason) {
if (done) return;
done = true;
reject(self, reason);
});
} catch (ex) {
if (done) return;
done = true;
reject(self, ex);
}
}
Promise.prototype['catch'] = function (onRejected) {
return this.then(null, onRejected);
};
Promise.prototype.then = function (onFulfilled, onRejected) {
var prom = new (this.constructor)(noop);
handle(this, new Handler(onFulfilled, onRejected, prom));
return prom;
};
Promise.all = function (arr) {
var args = Array.prototype.slice.call(arr);
return new Promise(function (resolve, reject) {
if (args.length === 0) return resolve([]);
var remaining = args.length;
function res(i, val) {
try {
if (val && (typeof val === 'object' || typeof val === 'function')) {
var then = val.then;
if (typeof then === 'function') {
then.call(val, function (val) {
res(i, val);
}, reject);
return;
}
}
args[i] = val;
if (--remaining === 0) {
resolve(args);
}
} catch (ex) {
reject(ex);
}
}
for (var i = 0; i < args.length; i++) {
res(i, args[i]);
}
});
};
Promise.resolve = function (value) {
if (value && typeof value === 'object' && value.constructor === Promise) {
return value;
}
return new Promise(function (resolve) {
resolve(value);
});
};
Promise.reject = function (value) {
return new Promise(function (resolve, reject) {
reject(value);
});
};
Promise.race = function (values) {
return new Promise(function (resolve, reject) {
for (var i = 0, len = values.length; i < len; i++) {
values[i].then(resolve, reject);
}
});
};
// Use polyfill for setImmediate for performance gains
Promise._immediateFn = (typeof setImmediate === 'function' && function (fn) { setImmediate(fn); }) ||
function (fn) {
setTimeoutFunc(fn, 0);
};
Promise._unhandledRejectionFn = function _unhandledRejectionFn(err) {
if (typeof console !== 'undefined' && console) {
console.warn('Possible Unhandled Promise Rejection:', err); // eslint-disable-line no-console
}
};
/**
* Set the immediate function to execute callbacks
* @param fn {function} Function to execute
* @deprecated
*/
Promise._setImmediateFn = function _setImmediateFn(fn) {
Promise._immediateFn = fn;
};
/**
* Change the function to execute on unhandled rejection
* @param {function} fn Function to execute on unhandled rejection
* @deprecated
*/
Promise._setUnhandledRejectionFn = function _setUnhandledRejectionFn(fn) {
Promise._unhandledRejectionFn = fn;
};
if (typeof module !== 'undefined' && module.exports) {
module.exports = Promise;
} else if (!root.Promise) {
root.Promise = Promise;
}
})(this);

View File

@ -17,10 +17,6 @@
<meta charset="utf-8"> <meta charset="utf-8">
<!-- Always force latest IE rendering engine (even in intranet) & Chrome Frame
Remove this if you use the .htaccess -->
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<!-- Icons (see app/images/icons/Makefile for what the sizes are for) --> <!-- Icons (see app/images/icons/Makefile for what the sizes are for) -->
<link rel="icon" sizes="16x16" type="image/png" href="app/images/icons/novnc-16x16.png"> <link rel="icon" sizes="16x16" type="image/png" href="app/images/icons/novnc-16x16.png">
<link rel="icon" sizes="24x24" type="image/png" href="app/images/icons/novnc-24x24.png"> <link rel="icon" sizes="24x24" type="image/png" href="app/images/icons/novnc-24x24.png">
@ -54,17 +50,8 @@
<!-- Stylesheets --> <!-- Stylesheets -->
<link rel="stylesheet" href="app/styles/base.css"> <link rel="stylesheet" href="app/styles/base.css">
<!-- this is included as a normal file in order to catch script-loading errors as well -->
<script src="app/error-handler.js"></script> <script src="app/error-handler.js"></script>
<!-- begin scripts -->
<!-- promise polyfills promises for IE11 -->
<script src="vendor/promise.js"></script>
<!-- ES2015/ES6 modules polyfill -->
<script nomodule src="vendor/browser-es-module-loader/dist/browser-es-module-loader.js"></script>
<!-- actual script modules -->
<script type="module" crossorigin="anonymous" src="app/ui.js"></script> <script type="module" crossorigin="anonymous" src="app/ui.js"></script>
<!-- end scripts -->
</head> </head>
<body> <body>
@ -94,24 +81,11 @@
<!--noVNC Touch Device only buttons--> <!--noVNC Touch Device only buttons-->
<div id="noVNC_mobile_buttons"> <div id="noVNC_mobile_buttons">
<input type="image" alt="No mousebutton" src="app/images/mouse_none.svg"
id="noVNC_mouse_button0" class="noVNC_button"
title="Active Mouse Button">
<input type="image" alt="Left mousebutton" src="app/images/mouse_left.svg"
id="noVNC_mouse_button1" class="noVNC_button"
title="Active Mouse Button">
<input type="image" alt="Middle mousebutton" src="app/images/mouse_middle.svg"
id="noVNC_mouse_button2" class="noVNC_button"
title="Active Mouse Button">
<input type="image" alt="Right mousebutton" src="app/images/mouse_right.svg"
id="noVNC_mouse_button4" class="noVNC_button"
title="Active Mouse Button">
<input type="image" alt="Keyboard" src="app/images/keyboard.svg" <input type="image" alt="Keyboard" src="app/images/keyboard.svg"
id="noVNC_keyboard_button" class="noVNC_button" title="Show Keyboard"> id="noVNC_keyboard_button" class="noVNC_button" title="Show Keyboard">
</div> </div>
<!-- Extra manual keys --> <!-- Extra manual keys -->
<div id="noVNC_extra_keys">
<input type="image" alt="Extra keys" src="app/images/toggleextrakeys.svg" <input type="image" alt="Extra keys" src="app/images/toggleextrakeys.svg"
id="noVNC_toggle_extra_keys_button" class="noVNC_button" id="noVNC_toggle_extra_keys_button" class="noVNC_button"
title="Show Extra Keys"> title="Show Extra Keys">
@ -137,7 +111,6 @@
title="Send Ctrl-Alt-Del"> title="Send Ctrl-Alt-Del">
</div> </div>
</div> </div>
</div>
<!-- Shutdown/Reboot --> <!-- Shutdown/Reboot -->
<input type="image" alt="Shutdown/Reboot" src="app/images/power.svg" <input type="image" alt="Shutdown/Reboot" src="app/images/power.svg"

View File

@ -18,10 +18,6 @@
<meta charset="utf-8"> <meta charset="utf-8">
<!-- Always force latest IE rendering engine (even in intranet) &
Chrome Frame. Remove this if you use the .htaccess -->
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<style> <style>
body { body {
@ -61,13 +57,6 @@
</style> </style>
<!-- Promise polyfill for IE11 -->
<script src="vendor/promise.js"></script>
<!-- ES2015/ES6 modules polyfill -->
<script nomodule src="vendor/browser-es-module-loader/dist/browser-es-module-loader.js"></script>
<!-- actual script modules -->
<script type="module" crossorigin="anonymous"> <script type="module" crossorigin="anonymous">
// RFB holds the API to connect and communicate with a VNC server // RFB holds the API to connect and communicate with a VNC server
import RFB from './core/rfb.js'; import RFB from './core/rfb.js';