Compare commits
294 Commits
v1.1.0
...
v1.3.0-beta
| Author | SHA1 | Date | |
|---|---|---|---|
| 0f4a06ffcd | |||
| f69d55c02f | |||
| 7841037618 | |||
| 1afa18f09e | |||
| 375f36c575 | |||
| a9c2ff30b6 | |||
| fcb95821b7 | |||
| f796b05e42 | |||
| 0a8ced2cfe | |||
| 7a76fbb767 | |||
| cb56f35fab | |||
| 7485e82b72 | |||
| d44ddbe186 | |||
| 89e206c146 | |||
| dd20b17d49 | |||
| 8ab18125f9 | |||
| e283f08d04 | |||
| dbd519558c | |||
| de9fc9508c | |||
| 9376191fc4 | |||
| b7b7e4e26b | |||
| 2244f53774 | |||
| 42100e8233 | |||
| ae3c01f782 | |||
| f0e4908dec | |||
| ae5f3f6909 | |||
| 84f102d6a9 | |||
| ef27628c6d | |||
| 89f9ac0016 | |||
| f9a8c4ccd5 | |||
| 9ca337d3a8 | |||
| 4c96d4b7bd | |||
| 0c55c64757 | |||
| 44d384b99c | |||
| 18593154d3 | |||
| 5a0cceb815 | |||
| d56e042fee | |||
| babd665c03 | |||
| 199910e63b | |||
| 4a319c414d | |||
| adfb99e7ec | |||
| 32222304f4 | |||
| 4a8efa6bc9 | |||
| cd9f535eb3 | |||
| 23249c7263 | |||
| 76aa3d1256 | |||
| 6784bb312f | |||
| 67ac9f9c0d | |||
| 4ae9d3e75a | |||
| 0cdf2962c0 | |||
| 146258291a | |||
| 3e55d5d71a | |||
| bd1bb2ed75 | |||
| dc9da4a042 | |||
| 6a4c411976 | |||
| 27496941a0 | |||
| 5b5b747494 | |||
| 6cd9bacf8b | |||
| 273acf3e89 | |||
| 60c7518f8c | |||
| 90456dbeed | |||
| 5dbacc5e41 | |||
| 18256baad0 | |||
| 1f7e1c7572 | |||
| dccf6facdc | |||
| 890cff921d | |||
| c01eb5e74d | |||
| 499eee4d06 | |||
| c1281b136d | |||
| 20e4f1b3f8 | |||
| b91b1e8edc | |||
| 3037eb16f7 | |||
| 3762300399 | |||
| 113fa27ebc | |||
| 0630352e19 | |||
| 7ce1b071ec | |||
| ecdd075672 | |||
| 9142f8f0f7 | |||
| b053b3f86d | |||
| 2334a7a7c3 | |||
| 4fa8051949 | |||
| 2654bcd354 | |||
| e17de291b9 | |||
| 81898d7cea | |||
| dcdc17bf24 | |||
| 72ca470750 | |||
| ef5db94a89 | |||
| 90ead240c7 | |||
| 244c02c5ea | |||
| fe2ad57077 | |||
| 9b3cea950a | |||
| 794b06b2bd | |||
| 484a9551d1 | |||
| 6c6776a7a0 | |||
| ffb9dfdc0a | |||
| 2835616b75 | |||
| 643442feac | |||
| 57ba67f306 | |||
| 48f15efa69 | |||
| 35de121ac5 | |||
| bb09e766ba | |||
| 32ed7c6724 | |||
| 50cde2faab | |||
| 88589a44f7 | |||
| f84bc57bda | |||
| 4a87038080 | |||
| 77c32d164d | |||
| 07a69954b1 | |||
| 8be924c9d9 | |||
| 440ec8a0b6 | |||
| 97b86abc94 | |||
| 0a6aec3578 | |||
| 6a19390baa | |||
| f5b5767c98 | |||
| 34f52a8f41 | |||
| 18a68dfac1 | |||
| 15cfa13563 | |||
| 111225fa41 | |||
| 224f95f997 | |||
| f694c32fd5 | |||
| 0e37a3f83a | |||
| cfb824ed03 | |||
| 756af5b44c | |||
| f2fbaacc82 | |||
| 164bf50fda | |||
| a7fe079f81 | |||
| ea858bfa27 | |||
| 5d570207f7 | |||
| 95632e413d | |||
| 8b0034ee84 | |||
| 80187d158c | |||
| dff4fefa3c | |||
| 150596be83 | |||
| 11a22dbf0c | |||
| 006743857b | |||
| e7dec5270e | |||
| b5ff33a556 | |||
| 42e3b03fa8 | |||
| 776cda5dc4 | |||
| 479d8cefd1 | |||
| a672168d4d | |||
| 0f81407c64 | |||
| f477469fb5 | |||
| c9582690ac | |||
| 44eb1fe59b | |||
| e7fa686f32 | |||
| 8df281cce6 | |||
| c12e5b2b54 | |||
| 302895cdf3 | |||
| 27a6978e30 | |||
| a1015d8db5 | |||
| a040c402ed | |||
| c4633ab333 | |||
| 5243cbf611 | |||
| 71429d45d0 | |||
| efd1f8a4f2 | |||
| 9253e178fc | |||
| ceb8ef4ec1 | |||
| e4e6a9b9b4 | |||
| 384232fb56 | |||
| f73fdc3ed3 | |||
| 9a31083a8a | |||
| 13be552d60 | |||
| 2cee106eee | |||
| 3cf11004b4 | |||
| f6669ff7b2 | |||
| fe5aa6408a | |||
| 183cab0eca | |||
| 9575ded8da | |||
| f52e979082 | |||
| 3b562e8a0f | |||
| 4ab5070548 | |||
| 546edcd4a0 | |||
| 71bb3fdfa5 | |||
| eb05b45b70 | |||
| 8394462356 | |||
| 2d53a785d5 | |||
| 64fdd336a0 | |||
| 80c72e92d2 | |||
| cbf090fe70 | |||
| 274652d119 | |||
| 208e34bc34 | |||
| 11ae8f0ef4 | |||
| c32d4f3cd0 | |||
| e52a278ed7 | |||
| 8f230f45cc | |||
| 78bbf6bad2 | |||
| 3a64043f28 | |||
| 49db41ea4b | |||
| d507d1415e | |||
| c4eb4ddcfe | |||
| 4babdf33bd | |||
| b8d1a8bb57 | |||
| 2cf82a5c8e | |||
| 06a8f7d91a | |||
| dbbb676da9 | |||
| 84a8c1b0cc | |||
| ff1b10ca66 | |||
| 1c9826140a | |||
| 5b453ed4a8 | |||
| b39caa7469 | |||
| 01d4514dee | |||
| 80b078c469 | |||
| 9f557f5280 | |||
| 94c89284fc | |||
| b17f6c6929 | |||
| afa1f8a2ab | |||
| 686c8d259a | |||
| d01ecc18d5 | |||
| 66ab0d98d7 | |||
| 0dd439a874 | |||
| ae127d8a38 | |||
| b88a92afe8 | |||
| 6b20803401 | |||
| 8cfa673d94 | |||
| c6e37040de | |||
| 9653598af7 | |||
| a6304f91d0 | |||
| c15502525e | |||
| 175b843b66 | |||
| ccb511a527 | |||
| 3388c92c7f | |||
| 1096555414 | |||
| ebee9cddbf | |||
| 5736ea0bd5 | |||
| 94a01b0ae0 | |||
| 8c51e9a8a2 | |||
| 9d956e9198 | |||
| dd4341fe67 | |||
| 758399050d | |||
| 1dd1bf0306 | |||
| a5aa8e1282 | |||
| c568ad4c74 | |||
| 0c4b3e802f | |||
| 65066326c5 | |||
| ffdd0dfeef | |||
| c3a7524c9e | |||
| 7a96fc3785 | |||
| 938690375b | |||
| fcd99d04fb | |||
| 2b4c655405 | |||
| f2d42dc357 | |||
| 412d93060d | |||
| e8614e20ef | |||
| c90d53565a | |||
| 3aeaea50af | |||
| 3055307d3d | |||
| 8dc47f3c06 | |||
| c51a77c2eb | |||
| d39e0d1244 | |||
| ebb58c34da | |||
| 35b78e95d2 | |||
| 0b51419ca4 | |||
| 296ba51f49 | |||
| a1afb2a215 | |||
| c90245da25 | |||
| 8d6f686b59 | |||
| ce66b46986 | |||
| 9886d5951d | |||
| 30ff15a35a | |||
| e5255fc246 | |||
| 3855a7bee4 | |||
| 6aed0b4dd2 | |||
| 1f2bb52850 | |||
| 4222d72bfe | |||
| e24b501c47 | |||
| c47a3a3e09 | |||
| 90d463f969 | |||
| 08567b08ac | |||
| 7d755d10dc | |||
| e9f489a629 | |||
| 776024a008 | |||
| 604edf07d2 | |||
| ff7882c44c | |||
| c9765e5066 | |||
| b875486db8 | |||
| e1d50c8c10 | |||
| 897b465b87 | |||
| e14aa4d0fe | |||
| c912230309 | |||
| 21387f9c24 | |||
| 25b3d49d32 | |||
| 8f2bcfbe79 | |||
| 15c7b7a619 | |||
| 21ac6ca0f2 | |||
| df4b7515a3 | |||
| 188c9a591b | |||
| 23af6e142a | |||
| 97924ebd5d | |||
| 7ded517823 | |||
| 32e081950c | |||
| 755d6eae99 | |||
| 19cdc15aa3 | |||
| 2b2b6073dd |
@@ -25,6 +25,7 @@
|
||||
|
||||
"brace-style": ["error", "1tbs", { "allowSingleLine": true }],
|
||||
"indent": ["error", 4, { "SwitchCase": 1,
|
||||
"FunctionDeclaration": { "parameters": "first" },
|
||||
"CallExpression": { "arguments": "first" },
|
||||
"ArrayExpression": "first",
|
||||
"ObjectExpression": "first",
|
||||
@@ -44,5 +45,6 @@
|
||||
"named": "never",
|
||||
"asyncArrow": "always" }],
|
||||
"switch-colon-spacing": ["error"],
|
||||
"camelcase": ["error", { allow: ["^XK_", "^XF86XK_"] }],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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 }}
|
||||
@@ -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
|
||||
@@ -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 }}
|
||||
-58
@@ -1,58 +0,0 @@
|
||||
language: node_js
|
||||
sudo: false
|
||||
cache:
|
||||
directories:
|
||||
- node_modules
|
||||
node_js:
|
||||
- 6
|
||||
env:
|
||||
matrix:
|
||||
- TEST_BROWSER_NAME=chrome TEST_BROWSER_OS='Windows 10'
|
||||
# FIXME Skip tests in Linux since Sauce Labs browser versions are ancient.
|
||||
# - TEST_BROWSER_NAME=chrome TEST_BROWSER_OS='Linux'
|
||||
- TEST_BROWSER_NAME=chrome TEST_BROWSER_OS='OS X 10.11'
|
||||
- TEST_BROWSER_NAME=firefox TEST_BROWSER_OS='Windows 10'
|
||||
# - TEST_BROWSER_NAME=firefox TEST_BROWSER_OS='Linux'
|
||||
- TEST_BROWSER_NAME=firefox TEST_BROWSER_OS='OS X 10.11'
|
||||
- TEST_BROWSER_NAME='internet explorer' TEST_BROWSER_OS='Windows 10'
|
||||
- TEST_BROWSER_NAME='internet explorer' TEST_BROWSER_OS='Windows 7'
|
||||
- TEST_BROWSER_NAME=microsoftedge TEST_BROWSER_OS='Windows 10'
|
||||
- TEST_BROWSER_NAME=safari TEST_BROWSER_OS='OS X 10.13'
|
||||
before_script: npm install -g karma-cli
|
||||
addons:
|
||||
sauce_connect:
|
||||
username: "directxman12"
|
||||
jwt:
|
||||
secure: "d3ekMYslpn6R4f0ajtRMt9SUFmNGDiItHpqaXC5T4KI0KMEsxgvEOfJot5PiFFJWg1DSpJZH6oaW2UxGZ3duJLZrXIEd/JePY8a6NtT35BNgiDPgcp+eu2Bu3rhrSNg7/HEsD1ma+JeUTnv18Ai5oMFfCCQJx2J6osIxyl/ZVxA="
|
||||
stages:
|
||||
- lint
|
||||
- test
|
||||
- name: deploy
|
||||
if: tag is PRESENT
|
||||
jobs:
|
||||
include:
|
||||
- stage: lint
|
||||
env:
|
||||
addons:
|
||||
before_script:
|
||||
script: npm run lint
|
||||
-
|
||||
env:
|
||||
addons:
|
||||
before_script:
|
||||
script: git ls-tree --name-only -r HEAD | grep -E "[.](html|css)$" | xargs ./utils/validate
|
||||
- stage: deploy
|
||||
env:
|
||||
addons:
|
||||
script: skip
|
||||
before_script: skip
|
||||
deploy:
|
||||
provider: npm
|
||||
email: ossman@cendio.se
|
||||
api_key:
|
||||
secure: "Qq2Mi9xQawO2zlAigzshzMu2QMHvu1IaN9l0ZIivE99wHJj7eS5f4miJ9wB+/mWRRgb3E8uj9ZRV24+Oc36drlBTU9sz+lHhH0uFMfAIseceK64wZV9sLAZm472fmPp2xdUeTCCqPaRy7g1XBqiJ0LyZvEFLsRijqcLjPBF+b8w="
|
||||
on:
|
||||
tags: true
|
||||
repo: novnc/noVNC
|
||||
|
||||
|
||||
+1
-7
@@ -1,4 +1,4 @@
|
||||
noVNC is Copyright (C) 2018 The noVNC Authors
|
||||
noVNC is Copyright (C) 2019 The noVNC Authors
|
||||
(./AUTHORS)
|
||||
|
||||
The noVNC core library files are licensed under the MPL 2.0 (Mozilla
|
||||
@@ -42,12 +42,6 @@ licenses (all MPL 2.0 compatible):
|
||||
|
||||
vendor/pako/ : MIT
|
||||
|
||||
vendor/browser-es-module-loader/src/ : MIT
|
||||
|
||||
vendor/browser-es-module-loader/dist/ : Various BSD style licenses
|
||||
|
||||
vendor/promise.js : MIT
|
||||
|
||||
Any other files not mentioned above are typically marked with
|
||||
a copyright/license header at the top of the file. The default noVNC
|
||||
license is MPL-2.0.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
## noVNC: HTML VNC Client Library and Application
|
||||
|
||||
[](https://travis-ci.org/novnc/noVNC)
|
||||
[](https://github.com/novnc/noVNC/actions?query=workflow%3ATest)
|
||||
[](https://github.com/novnc/noVNC/actions?query=workflow%3ALint)
|
||||
|
||||
### Description
|
||||
|
||||
@@ -24,6 +25,7 @@ for a more complete list with additional info and links.
|
||||
- [Browser Requirements](#browser-requirements)
|
||||
- [Server Requirements](#server-requirements)
|
||||
- [Quick Start](#quick-start)
|
||||
- [Installation from Snap Package](#installation-from-snap-package)
|
||||
- [Integration and Deployment](#integration-and-deployment)
|
||||
- [Authors/Contributors](#authorscontributors)
|
||||
|
||||
@@ -68,6 +70,7 @@ Please tweet [@noVNC](http://www.twitter.com/noVNC) if you do.
|
||||
* Local cursor rendering
|
||||
* Clipboard copy/paste
|
||||
* Translations
|
||||
* Touch gestures for emulating common mouse actions
|
||||
* Licensed mainly under the [MPL 2.0](http://www.mozilla.org/MPL/2.0/), see
|
||||
[the license document](LICENSE.txt) for details
|
||||
|
||||
@@ -88,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
|
||||
aware of:
|
||||
|
||||
* Chrome 49, Firefox 44, Safari 10, Opera 36, IE 11, Edge 12
|
||||
* Chrome 64, Firefox 79, Safari 13.4, Opera 51, Edge 79
|
||||
|
||||
|
||||
### Server Requirements
|
||||
@@ -105,16 +108,76 @@ proxy.
|
||||
|
||||
### 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
|
||||
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
|
||||
configured, and enjoy!
|
||||
|
||||
### Installation from Snap Package
|
||||
Running the command below will install the latest release of noVNC from Snap:
|
||||
|
||||
`sudo snap install novnc`
|
||||
|
||||
#### Running noVNC
|
||||
|
||||
You can run the Snap-package installed novnc directly with, for example:
|
||||
|
||||
`novnc --listen 6081 --vnc localhost:5901 # /snap/bin/novnc if /snap/bin is not in your PATH`
|
||||
|
||||
#### Running as a Service (Daemon)
|
||||
The Snap package also has the capability to run a 'novnc' service which can be
|
||||
configured to listen on multiple ports connecting to multiple VNC servers
|
||||
(effectively a service runing multiple instances of novnc).
|
||||
Instructions (with example values):
|
||||
|
||||
List current services (out-of-box this will be blank):
|
||||
|
||||
```
|
||||
sudo snap get novnc services
|
||||
Key Value
|
||||
services.n6080 {...}
|
||||
services.n6081 {...}
|
||||
```
|
||||
|
||||
Create a new service that listens on port 6082 and connects to the VNC server
|
||||
running on port 5902 on localhost:
|
||||
|
||||
`sudo snap set novnc services.n6082.listen=6082 services.n6082.vnc=localhost:5902`
|
||||
|
||||
(Any services you define with 'snap set' will be automatically started)
|
||||
Note that the name of the service, 'n6082' in this example, can be anything
|
||||
as long as it doesn't start with a number or contain spaces/special characters.
|
||||
|
||||
View the configuration of the service just created:
|
||||
|
||||
```
|
||||
sudo snap get novnc services.n6082
|
||||
Key Value
|
||||
services.n6082.listen 6082
|
||||
services.n6082.vnc localhost:5902
|
||||
```
|
||||
|
||||
Disable a service (note that because of a limitation in Snap it's currently not
|
||||
possible to unset config variables, setting them to blank values is the way
|
||||
to disable a service):
|
||||
|
||||
`sudo snap set novnc services.n6082.listen='' services.n6082.vnc=''`
|
||||
|
||||
(Any services you set to blank with 'snap set' like this will be automatically stopped)
|
||||
|
||||
Verify that the service is disabled (blank values):
|
||||
|
||||
```
|
||||
sudo snap get novnc services.n6082
|
||||
Key Value
|
||||
services.n6082.listen
|
||||
services.n6082.vnc
|
||||
```
|
||||
|
||||
### Integration and Deployment
|
||||
|
||||
@@ -133,7 +196,6 @@ that list and you think you should be, feel free to send a PR to fix that.
|
||||
* Core team:
|
||||
* [Joel Martin](https://github.com/kanaka)
|
||||
* [Samuel Mannehed](https://github.com/samhed) (Cendio)
|
||||
* [Peter Åstrand](https://github.com/astrand) (Cendio)
|
||||
* [Solly Ross](https://github.com/DirectXMan12) (Red Hat / OpenStack)
|
||||
* [Pierre Ossman](https://github.com/CendioOssman) (Cendio)
|
||||
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
/*
|
||||
* noVNC: HTML5 VNC client
|
||||
* Copyright (C) 2019 The noVNC Authors
|
||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||
*
|
||||
* See README.md for usage and integration instructions.
|
||||
*/
|
||||
|
||||
// NB: this should *not* be included as a module until we have
|
||||
// native support in the browsers, so that our error handler
|
||||
// can catch script-loading errors.
|
||||
|
||||
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
+25
-45
@@ -15,18 +15,18 @@
|
||||
inkscape:export-xdpi="90"
|
||||
sodipodi:docname="windows.svg"
|
||||
inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
|
||||
inkscape:version="0.92.3 (2405546, 2018-03-11)"
|
||||
inkscape:version="0.92.4 (unknown)"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="-293 384 25 23"
|
||||
viewBox="-293 384 25 25"
|
||||
xml:space="preserve"
|
||||
width="25"
|
||||
height="23"><metadata
|
||||
height="25"><metadata
|
||||
id="metadata21"><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><defs
|
||||
id="defs19" /><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
pagecolor="#959595"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
@@ -35,51 +35,31 @@
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1017"
|
||||
inkscape:window-height="1136"
|
||||
id="namedview17"
|
||||
showgrid="false"
|
||||
inkscape:pagecheckerboard="true"
|
||||
inkscape:zoom="9.44"
|
||||
inkscape:cx="-0.84745763"
|
||||
inkscape:cy="12.5"
|
||||
inkscape:window-x="2552"
|
||||
inkscape:window-y="122"
|
||||
showgrid="true"
|
||||
inkscape:pagecheckerboard="false"
|
||||
inkscape:zoom="32"
|
||||
inkscape:cx="3.926913"
|
||||
inkscape:cy="13.255959"
|
||||
inkscape:window-x="1920"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg2" />
|
||||
inkscape:current-layer="svg2"><inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid818" /></sodipodi:namedview>
|
||||
<style
|
||||
type="text/css"
|
||||
id="style2">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style>
|
||||
<g
|
||||
id="g14"
|
||||
transform="matrix(1.2624869,0,0,1.3601695,73.614445,-144.84322)">
|
||||
<g
|
||||
id="g12">
|
||||
<path
|
||||
class="st0"
|
||||
d="m -277.4,396 c -0.7,0 -1.3,0 -2,0 -0.4,0 -0.5,-0.1 -0.5,-0.5 0,-1 0,-2 0,-3 0,-0.3 0.2,-0.5 0.5,-0.5 1.3,-0.1 2.6,-0.3 3.9,-0.4 0.4,0 0.7,0.1 0.7,0.6 0,1.1 0,2.2 0,3.3 0,0.4 -0.2,0.6 -0.6,0.6 -0.7,-0.1 -1.4,-0.1 -2,-0.1 z"
|
||||
id="path4"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#ffffff" />
|
||||
<path
|
||||
class="st0"
|
||||
d="m -274.9,399.3 c 0,0.6 0,1.1 0,1.7 0,0.4 -0.1,0.6 -0.6,0.6 -1.4,-0.1 -2.8,-0.3 -4.1,-0.4 -0.3,0 -0.4,-0.3 -0.4,-0.5 0,-1 0,-2 0,-3 0,-0.4 0.2,-0.5 0.6,-0.5 1.3,0 2.6,0 3.9,0 0.5,0 0.6,0.2 0.6,0.6 0,0.4 0,0.9 0,1.5 z"
|
||||
id="path6"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#ffffff" />
|
||||
<path
|
||||
class="st0"
|
||||
d="m -283.5,396 c -0.6,0 -1.3,0 -1.9,0 -0.4,0 -0.6,-0.1 -0.6,-0.6 0,-0.8 0,-1.5 0,-2.3 0,-0.4 0.2,-0.6 0.6,-0.7 1.3,-0.1 2.7,-0.3 4,-0.4 0.4,0 0.5,0.1 0.5,0.5 0,1 0,1.9 0,2.9 0,0.4 -0.2,0.5 -0.5,0.5 -0.8,0.1 -1.5,0.1 -2.1,0.1 z"
|
||||
id="path8"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#ffffff" />
|
||||
<path
|
||||
class="st0"
|
||||
d="m -283.5,397 c 0.6,0 1.3,0 1.9,0 0.4,0 0.6,0.1 0.6,0.5 0,1 0,1.9 0,2.9 0,0.4 -0.2,0.5 -0.5,0.5 -1.3,-0.1 -2.7,-0.3 -4,-0.4 -0.4,0 -0.6,-0.2 -0.6,-0.7 0,-0.7 0,-1.5 0,-2.2 0,-0.5 0.2,-0.7 0.7,-0.7 0.6,0.1 1.2,0.1 1.9,0.1 z"
|
||||
id="path10"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#ffffff" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
<path
|
||||
style="fill:#ffffff;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;fill-opacity:1"
|
||||
d="M 21 4 L 11 5.1757812 L 11 12 L 21 12 L 21 4 z M 10 5.2949219 L 4 6 L 4 12 L 10 12 L 10 5.2949219 z "
|
||||
transform="translate(-293,384)"
|
||||
id="path853" /><path
|
||||
id="path858"
|
||||
d="m -272,405 -10,-1.17578 V 397 h 10 z M -283,403.70508 -289,403 v -6 h 6 z"
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
inkscape:connector-curvature="0" /></svg>
|
||||
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 2.4 KiB |
@@ -0,0 +1 @@
|
||||
DO NOT MODIFY THE FILES IN THIS FOLDER, THEY ARE AUTOMATICALLY GENERATED FROM THE PO-FILES.
|
||||
@@ -0,0 +1,72 @@
|
||||
{
|
||||
"Connecting...": "接続しています...",
|
||||
"Disconnecting...": "切断しています...",
|
||||
"Reconnecting...": "再接続しています...",
|
||||
"Internal error": "内部エラー",
|
||||
"Must set host": "ホストを設定する必要があります",
|
||||
"Connected (encrypted) to ": "接続しました (暗号化済み): ",
|
||||
"Connected (unencrypted) to ": "接続しました (暗号化されていません): ",
|
||||
"Something went wrong, connection is closed": "何らかの問題で、接続が閉じられました",
|
||||
"Failed to connect to server": "サーバーへの接続に失敗しました",
|
||||
"Disconnected": "切断しました",
|
||||
"New connection has been rejected with reason: ": "新規接続は次の理由で拒否されました: ",
|
||||
"New connection has been rejected": "新規接続は拒否されました",
|
||||
"Credentials are required": "資格情報が必要です",
|
||||
"noVNC encountered an error:": "noVNC でエラーが発生しました:",
|
||||
"Hide/Show the control bar": "コントロールバーを隠す/表示する",
|
||||
"Drag": "ドラッグ",
|
||||
"Move/Drag Viewport": "ビューポートを移動/ドラッグ",
|
||||
"Keyboard": "キーボード",
|
||||
"Show Keyboard": "キーボードを表示",
|
||||
"Extra keys": "追加キー",
|
||||
"Show Extra Keys": "追加キーを表示",
|
||||
"Ctrl": "Ctrl",
|
||||
"Toggle Ctrl": "Ctrl キーを切り替え",
|
||||
"Alt": "Alt",
|
||||
"Toggle Alt": "Alt キーを切り替え",
|
||||
"Toggle Windows": "Windows キーを切り替え",
|
||||
"Windows": "Windows",
|
||||
"Send Tab": "Tab キーを送信",
|
||||
"Tab": "Tab",
|
||||
"Esc": "Esc",
|
||||
"Send Escape": "Escape キーを送信",
|
||||
"Ctrl+Alt+Del": "Ctrl+Alt+Del",
|
||||
"Send Ctrl-Alt-Del": "Ctrl-Alt-Del を送信",
|
||||
"Shutdown/Reboot": "シャットダウン/再起動",
|
||||
"Shutdown/Reboot...": "シャットダウン/再起動...",
|
||||
"Power": "電源",
|
||||
"Shutdown": "シャットダウン",
|
||||
"Reboot": "再起動",
|
||||
"Reset": "リセット",
|
||||
"Clipboard": "クリップボード",
|
||||
"Clear": "クリア",
|
||||
"Fullscreen": "全画面表示",
|
||||
"Settings": "設定",
|
||||
"Shared Mode": "共有モード",
|
||||
"View Only": "表示のみ",
|
||||
"Clip to Window": "ウィンドウにクリップ",
|
||||
"Scaling Mode:": "スケーリングモード:",
|
||||
"None": "なし",
|
||||
"Local Scaling": "ローカルスケーリング",
|
||||
"Remote Resizing": "リモートでリサイズ",
|
||||
"Advanced": "高度",
|
||||
"Quality:": "品質:",
|
||||
"Compression level:": "圧縮レベル:",
|
||||
"Repeater ID:": "リピーター ID:",
|
||||
"WebSocket": "WebSocket",
|
||||
"Encrypt": "暗号化",
|
||||
"Host:": "ホスト:",
|
||||
"Port:": "ポート:",
|
||||
"Path:": "パス:",
|
||||
"Automatic Reconnect": "自動再接続",
|
||||
"Reconnect Delay (ms):": "再接続する遅延 (ミリ秒):",
|
||||
"Show Dot when No Cursor": "カーソルがないときにドットを表示",
|
||||
"Logging:": "ロギング:",
|
||||
"Version:": "バージョン:",
|
||||
"Disconnect": "切断",
|
||||
"Connect": "接続",
|
||||
"Username:": "ユーザー名:",
|
||||
"Password:": "パスワード:",
|
||||
"Send Credentials": "資格情報を送信",
|
||||
"Cancel": "キャンセル"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
+7
-8
@@ -11,16 +11,11 @@
|
||||
"Disconnected": "Frånkopplad",
|
||||
"New connection has been rejected with reason: ": "Ny anslutning har blivit nekad med följande skäl: ",
|
||||
"New connection has been rejected": "Ny anslutning har blivit nekad",
|
||||
"Password is required": "Lösenord krävs",
|
||||
"Credentials are required": "Användaruppgifter krävs",
|
||||
"noVNC encountered an error:": "noVNC stötte på ett problem:",
|
||||
"Hide/Show the control bar": "Göm/Visa kontrollbaren",
|
||||
"Drag": "Dra",
|
||||
"Move/Drag Viewport": "Flytta/Dra Vyn",
|
||||
"viewport drag": "dra vy",
|
||||
"Active Mouse Button": "Aktiv musknapp",
|
||||
"No mousebutton": "Ingen musknapp",
|
||||
"Left mousebutton": "Vänster musknapp",
|
||||
"Middle mousebutton": "Mitten-musknapp",
|
||||
"Right mousebutton": "Höger musknapp",
|
||||
"Keyboard": "Tangentbord",
|
||||
"Show Keyboard": "Visa Tangentbord",
|
||||
"Extra keys": "Extraknappar",
|
||||
@@ -55,6 +50,8 @@
|
||||
"Local Scaling": "Lokal Skalning",
|
||||
"Remote Resizing": "Ändra Storlek",
|
||||
"Advanced": "Avancerat",
|
||||
"Quality:": "Kvalitet:",
|
||||
"Compression level:": "Kompressionsnivå:",
|
||||
"Repeater ID:": "Repeater-ID:",
|
||||
"WebSocket": "WebSocket",
|
||||
"Encrypt": "Kryptera",
|
||||
@@ -65,9 +62,11 @@
|
||||
"Reconnect Delay (ms):": "Fördröjning (ms):",
|
||||
"Show Dot when No Cursor": "Visa prick när ingen muspekare finns",
|
||||
"Logging:": "Loggning:",
|
||||
"Version:": "Version:",
|
||||
"Disconnect": "Koppla från",
|
||||
"Connect": "Anslut",
|
||||
"Username:": "Användarnamn:",
|
||||
"Password:": "Lösenord:",
|
||||
"Send Password": "Skicka lösenord",
|
||||
"Send Credentials": "Skicka Användaruppgifter",
|
||||
"Cancel": "Avbryt"
|
||||
}
|
||||
+17
-17
@@ -1,19 +1,19 @@
|
||||
{
|
||||
"Connecting...": "链接中...",
|
||||
"Disconnecting...": "正在中断连接...",
|
||||
"Reconnecting...": "重新链接中...",
|
||||
"Connecting...": "连接中...",
|
||||
"Disconnecting...": "正在断开连接...",
|
||||
"Reconnecting...": "重新连接中...",
|
||||
"Internal error": "内部错误",
|
||||
"Must set host": "请提供主机名",
|
||||
"Connected (encrypted) to ": "已加密链接到",
|
||||
"Connected (unencrypted) to ": "未加密链接到",
|
||||
"Something went wrong, connection is closed": "发生错误,链接已关闭",
|
||||
"Failed to connect to server": "无法链接到服务器",
|
||||
"Disconnected": "链接已中断",
|
||||
"New connection has been rejected with reason: ": "链接被拒绝,原因:",
|
||||
"New connection has been rejected": "链接被拒绝",
|
||||
"Connected (encrypted) to ": "已连接到(加密)",
|
||||
"Connected (unencrypted) to ": "已连接到(未加密)",
|
||||
"Something went wrong, connection is closed": "发生错误,连接已关闭",
|
||||
"Failed to connect to server": "无法连接到服务器",
|
||||
"Disconnected": "已断开连接",
|
||||
"New connection has been rejected with reason: ": "连接被拒绝,原因:",
|
||||
"New connection has been rejected": "连接被拒绝",
|
||||
"Password is required": "请提供密码",
|
||||
"noVNC encountered an error:": "noVNC 遇到一个错误:",
|
||||
"Hide/Show the control bar": "显示/隐藏控制列",
|
||||
"Hide/Show the control bar": "显示/隐藏控制栏",
|
||||
"Move/Drag Viewport": "拖放显示范围",
|
||||
"viewport drag": "显示范围拖放",
|
||||
"Active Mouse Button": "启动鼠标按鍵",
|
||||
@@ -43,10 +43,10 @@
|
||||
"Reset": "重置",
|
||||
"Clipboard": "剪贴板",
|
||||
"Clear": "清除",
|
||||
"Fullscreen": "全屏幕",
|
||||
"Fullscreen": "全屏",
|
||||
"Settings": "设置",
|
||||
"Shared Mode": "分享模式",
|
||||
"View Only": "仅检视",
|
||||
"View Only": "仅查看",
|
||||
"Clip to Window": "限制/裁切窗口大小",
|
||||
"Scaling Mode:": "缩放模式:",
|
||||
"None": "无",
|
||||
@@ -59,11 +59,11 @@
|
||||
"Host:": "主机:",
|
||||
"Port:": "端口:",
|
||||
"Path:": "路径:",
|
||||
"Automatic Reconnect": "自动重新链接",
|
||||
"Reconnect Delay (ms):": "重新链接间隔 (ms):",
|
||||
"Automatic Reconnect": "自动重新连接",
|
||||
"Reconnect Delay (ms):": "重新连接间隔 (ms):",
|
||||
"Logging:": "日志级别:",
|
||||
"Disconnect": "终端链接",
|
||||
"Connect": "链接",
|
||||
"Disconnect": "中断连接",
|
||||
"Connect": "连接",
|
||||
"Password:": "密码:",
|
||||
"Cancel": "取消"
|
||||
}
|
||||
+87
-17
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* noVNC base CSS
|
||||
* Copyright (C) 2018 The noVNC Authors
|
||||
* Copyright (C) 2019 The noVNC Authors
|
||||
* noVNC is licensed under the MPL 2.0 (see LICENSE.txt)
|
||||
* This file is licensed under the 2-Clause BSD license (see LICENSE.txt).
|
||||
*/
|
||||
@@ -83,8 +83,20 @@ html {
|
||||
* ----------------------------------------
|
||||
*/
|
||||
|
||||
input[type=input], input[type=password], input[type=number],
|
||||
input:not([type]), textarea {
|
||||
input:not([type]),
|
||||
input[type=date],
|
||||
input[type=datetime-local],
|
||||
input[type=email],
|
||||
input[type=month],
|
||||
input[type=number],
|
||||
input[type=password],
|
||||
input[type=search],
|
||||
input[type=tel],
|
||||
input[type=text],
|
||||
input[type=time],
|
||||
input[type=url],
|
||||
input[type=week],
|
||||
textarea {
|
||||
/* Disable default rendering */
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
@@ -98,7 +110,11 @@ input:not([type]), textarea {
|
||||
background: linear-gradient(to top, rgb(255, 255, 255) 80%, rgb(240, 240, 240));
|
||||
}
|
||||
|
||||
input[type=button], input[type=submit], select {
|
||||
input[type=button],
|
||||
input[type=color],
|
||||
input[type=reset],
|
||||
input[type=submit],
|
||||
select {
|
||||
/* Disable default rendering */
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
@@ -116,7 +132,10 @@ input[type=button], input[type=submit], select {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
input[type=button], input[type=submit] {
|
||||
input[type=button],
|
||||
input[type=color],
|
||||
input[type=reset],
|
||||
input[type=submit] {
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
}
|
||||
@@ -126,35 +145,72 @@ option {
|
||||
background: white;
|
||||
}
|
||||
|
||||
input[type=input]:focus, input[type=password]:focus,
|
||||
input:not([type]):focus, input[type=button]:focus,
|
||||
input:not([type]):focus,
|
||||
input[type=button]:focus,
|
||||
input[type=color]:focus,
|
||||
input[type=date]:focus,
|
||||
input[type=datetime-local]:focus,
|
||||
input[type=email]:focus,
|
||||
input[type=month]:focus,
|
||||
input[type=number]:focus,
|
||||
input[type=password]:focus,
|
||||
input[type=reset]:focus,
|
||||
input[type=search]:focus,
|
||||
input[type=submit]:focus,
|
||||
textarea:focus, select:focus {
|
||||
input[type=tel]:focus,
|
||||
input[type=text]:focus,
|
||||
input[type=time]:focus,
|
||||
input[type=url]:focus,
|
||||
input[type=week]:focus,
|
||||
select:focus,
|
||||
textarea:focus {
|
||||
box-shadow: 0px 0px 3px rgba(74, 144, 217, 0.5);
|
||||
border-color: rgb(74, 144, 217);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
input[type=button]::-moz-focus-inner,
|
||||
input[type=color]::-moz-focus-inner,
|
||||
input[type=reset]::-moz-focus-inner,
|
||||
input[type=submit]::-moz-focus-inner {
|
||||
border: none;
|
||||
}
|
||||
|
||||
input[type=input]:disabled, input[type=password]:disabled,
|
||||
input:not([type]):disabled, input[type=button]:disabled,
|
||||
input[type=submit]:disabled, input[type=number]:disabled,
|
||||
textarea:disabled, select:disabled {
|
||||
input:not([type]):disabled,
|
||||
input[type=button]:disabled,
|
||||
input[type=color]:disabled,
|
||||
input[type=date]:disabled,
|
||||
input[type=datetime-local]:disabled,
|
||||
input[type=email]:disabled,
|
||||
input[type=month]:disabled,
|
||||
input[type=number]:disabled,
|
||||
input[type=password]:disabled,
|
||||
input[type=reset]:disabled,
|
||||
input[type=search]:disabled,
|
||||
input[type=submit]:disabled,
|
||||
input[type=tel]:disabled,
|
||||
input[type=text]:disabled,
|
||||
input[type=time]:disabled,
|
||||
input[type=url]:disabled,
|
||||
input[type=week]:disabled,
|
||||
select:disabled,
|
||||
textarea:disabled {
|
||||
color: rgb(128, 128, 128);
|
||||
background: rgb(240, 240, 240);
|
||||
}
|
||||
|
||||
input[type=button]:active, input[type=submit]:active,
|
||||
input[type=button]:active,
|
||||
input[type=color]:active,
|
||||
input[type=reset]:active,
|
||||
input[type=submit]:active,
|
||||
select:active {
|
||||
border-bottom-width: 1px;
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
:root:not(.noVNC_touch) input[type=button]:hover:not(:disabled),
|
||||
:root:not(.noVNC_touch) input[type=color]:hover:not(:disabled),
|
||||
:root:not(.noVNC_touch) input[type=reset]:hover:not(:disabled),
|
||||
:root:not(.noVNC_touch) input[type=submit]:hover:not(:disabled),
|
||||
:root:not(.noVNC_touch) select:hover:not(:disabled) {
|
||||
background: linear-gradient(to top, rgb(255, 255, 255), rgb(250, 250, 250));
|
||||
@@ -579,7 +635,7 @@ select:active {
|
||||
}
|
||||
|
||||
/* Extra manual keys */
|
||||
:root:not(.noVNC_connected) #noVNC_extra_keys {
|
||||
:root:not(.noVNC_connected) #noVNC_toggle_extra_keys_button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -631,6 +687,16 @@ select:active {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
/* Version */
|
||||
|
||||
.noVNC_version_wrapper {
|
||||
font-size: small;
|
||||
}
|
||||
|
||||
.noVNC_version {
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
/* Connection Controls */
|
||||
:root:not(.noVNC_connected) #noVNC_disconnect_button {
|
||||
display: none;
|
||||
@@ -780,19 +846,23 @@ select:active {
|
||||
* ----------------------------------------
|
||||
*/
|
||||
|
||||
#noVNC_password_dlg {
|
||||
#noVNC_credentials_dlg {
|
||||
position: relative;
|
||||
|
||||
transform: translateY(-50px);
|
||||
}
|
||||
#noVNC_password_dlg.noVNC_open {
|
||||
#noVNC_credentials_dlg.noVNC_open {
|
||||
transform: translateY(0);
|
||||
}
|
||||
#noVNC_password_dlg ul {
|
||||
#noVNC_credentials_dlg ul {
|
||||
list-style: none;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
.noVNC_hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
/* ----------------------------------------
|
||||
* Main Area
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* noVNC: HTML5 VNC client
|
||||
* Copyright (C) 2018 The noVNC Authors
|
||||
* Copyright (C) 2019 The noVNC Authors
|
||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||
*
|
||||
* See README.md for usage and integration instructions.
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
import * as Log from '../core/util/logging.js';
|
||||
import _, { l10n } from './localization.js';
|
||||
import { isTouchDevice, isSafari, isIOS, isAndroid, dragThreshold }
|
||||
import { isTouchDevice, isSafari, hasScrollbarGutter, dragThreshold }
|
||||
from '../core/util/browser.js';
|
||||
import { setCapture, getPointerEvent } from '../core/util/events.js';
|
||||
import KeyTable from "../core/input/keysym.js";
|
||||
@@ -17,6 +17,8 @@ import Keyboard from "../core/input/keyboard.js";
|
||||
import RFB from "../core/rfb.js";
|
||||
import * as WebUtil from "./webutil.js";
|
||||
|
||||
const PAGE_TITLE = "noVNC";
|
||||
|
||||
const UI = {
|
||||
|
||||
connected: false,
|
||||
@@ -35,9 +37,9 @@ const UI = {
|
||||
lastKeyboardinput: null,
|
||||
defaultKeyboardinputLen: 100,
|
||||
|
||||
inhibit_reconnect: true,
|
||||
reconnect_callback: null,
|
||||
reconnect_password: null,
|
||||
inhibitReconnect: true,
|
||||
reconnectCallback: null,
|
||||
reconnectPassword: null,
|
||||
|
||||
prime() {
|
||||
return WebUtil.initSettings().then(() => {
|
||||
@@ -59,6 +61,23 @@ const UI = {
|
||||
// Translate the DOM
|
||||
l10n.translateDOM();
|
||||
|
||||
fetch('./package.json')
|
||||
.then((response) => {
|
||||
if (!response.ok) {
|
||||
throw Error("" + response.status + " " + response.statusText);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then((packageInfo) => {
|
||||
Array.from(document.getElementsByClassName('noVNC_version')).forEach(el => el.innerText = packageInfo.version);
|
||||
})
|
||||
.catch((err) => {
|
||||
Log.Error("Couldn't fetch package.json: " + err);
|
||||
Array.from(document.getElementsByClassName('noVNC_version_wrapper'))
|
||||
.concat(Array.from(document.getElementsByClassName('noVNC_version_separator')))
|
||||
.forEach(el => el.style.display = 'none');
|
||||
});
|
||||
|
||||
// Adapt the interface for touch screen devices
|
||||
if (isTouchDevice) {
|
||||
document.documentElement.classList.add("noVNC_touch");
|
||||
@@ -148,6 +167,8 @@ const UI = {
|
||||
UI.initSetting('encrypt', (window.location.protocol === "https:"));
|
||||
UI.initSetting('view_clip', false);
|
||||
UI.initSetting('resize', 'off');
|
||||
UI.initSetting('quality', 6);
|
||||
UI.initSetting('compression', 2);
|
||||
UI.initSetting('shared', true);
|
||||
UI.initSetting('view_only', false);
|
||||
UI.initSetting('show_dot', false);
|
||||
@@ -219,14 +240,6 @@ const UI = {
|
||||
},
|
||||
|
||||
addTouchSpecificHandlers() {
|
||||
document.getElementById("noVNC_mouse_button0")
|
||||
.addEventListener('click', () => UI.setMouseButton(1));
|
||||
document.getElementById("noVNC_mouse_button1")
|
||||
.addEventListener('click', () => UI.setMouseButton(2));
|
||||
document.getElementById("noVNC_mouse_button2")
|
||||
.addEventListener('click', () => UI.setMouseButton(4));
|
||||
document.getElementById("noVNC_mouse_button4")
|
||||
.addEventListener('click', () => UI.setMouseButton(0));
|
||||
document.getElementById("noVNC_keyboard_button")
|
||||
.addEventListener('click', UI.toggleVirtualKeyboard);
|
||||
|
||||
@@ -303,8 +316,8 @@ const UI = {
|
||||
document.getElementById("noVNC_cancel_reconnect_button")
|
||||
.addEventListener('click', UI.cancelReconnect);
|
||||
|
||||
document.getElementById("noVNC_password_button")
|
||||
.addEventListener('click', UI.setPassword);
|
||||
document.getElementById("noVNC_credentials_button")
|
||||
.addEventListener('click', UI.setCredentials);
|
||||
},
|
||||
|
||||
addClipboardHandlers() {
|
||||
@@ -334,6 +347,10 @@ const UI = {
|
||||
UI.addSettingChangeHandler('resize');
|
||||
UI.addSettingChangeHandler('resize', UI.applyResizeMode);
|
||||
UI.addSettingChangeHandler('resize', UI.updateViewClip);
|
||||
UI.addSettingChangeHandler('quality');
|
||||
UI.addSettingChangeHandler('quality', UI.updateQuality);
|
||||
UI.addSettingChangeHandler('compression');
|
||||
UI.addSettingChangeHandler('compression', UI.updateCompression);
|
||||
UI.addSettingChangeHandler('view_clip');
|
||||
UI.addSettingChangeHandler('view_clip', UI.updateViewClip);
|
||||
UI.addSettingChangeHandler('shared');
|
||||
@@ -375,25 +392,25 @@ const UI = {
|
||||
document.documentElement.classList.remove("noVNC_disconnecting");
|
||||
document.documentElement.classList.remove("noVNC_reconnecting");
|
||||
|
||||
const transition_elem = document.getElementById("noVNC_transition_text");
|
||||
const transitionElem = document.getElementById("noVNC_transition_text");
|
||||
switch (state) {
|
||||
case 'init':
|
||||
break;
|
||||
case 'connecting':
|
||||
transition_elem.textContent = _("Connecting...");
|
||||
transitionElem.textContent = _("Connecting...");
|
||||
document.documentElement.classList.add("noVNC_connecting");
|
||||
break;
|
||||
case 'connected':
|
||||
document.documentElement.classList.add("noVNC_connected");
|
||||
break;
|
||||
case 'disconnecting':
|
||||
transition_elem.textContent = _("Disconnecting...");
|
||||
transitionElem.textContent = _("Disconnecting...");
|
||||
document.documentElement.classList.add("noVNC_disconnecting");
|
||||
break;
|
||||
case 'disconnected':
|
||||
break;
|
||||
case 'reconnecting':
|
||||
transition_elem.textContent = _("Reconnecting...");
|
||||
transitionElem.textContent = _("Reconnecting...");
|
||||
document.documentElement.classList.add("noVNC_reconnecting");
|
||||
break;
|
||||
default:
|
||||
@@ -411,7 +428,6 @@ const UI = {
|
||||
UI.disableSetting('port');
|
||||
UI.disableSetting('path');
|
||||
UI.disableSetting('repeaterID');
|
||||
UI.setMouseButton(1);
|
||||
|
||||
// Hide the controlbar after 2 seconds
|
||||
UI.closeControlbarTimeout = setTimeout(UI.closeControlbar, 2000);
|
||||
@@ -426,38 +442,35 @@ const UI = {
|
||||
UI.keepControlbar();
|
||||
}
|
||||
|
||||
// State change closes the password dialog
|
||||
document.getElementById('noVNC_password_dlg')
|
||||
// State change closes dialogs as they may not be relevant
|
||||
// anymore
|
||||
UI.closeAllPanels();
|
||||
document.getElementById('noVNC_credentials_dlg')
|
||||
.classList.remove('noVNC_open');
|
||||
},
|
||||
|
||||
showStatus(text, status_type, time) {
|
||||
showStatus(text, statusType, time) {
|
||||
const statusElem = document.getElementById('noVNC_status');
|
||||
|
||||
clearTimeout(UI.statusTimeout);
|
||||
|
||||
if (typeof status_type === 'undefined') {
|
||||
status_type = 'normal';
|
||||
if (typeof statusType === 'undefined') {
|
||||
statusType = 'normal';
|
||||
}
|
||||
|
||||
// Don't overwrite more severe visible statuses and never
|
||||
// errors. Only shows the first error.
|
||||
let visible_status_type = 'none';
|
||||
if (statusElem.classList.contains("noVNC_open")) {
|
||||
if (statusElem.classList.contains("noVNC_status_error")) {
|
||||
visible_status_type = 'error';
|
||||
} else if (statusElem.classList.contains("noVNC_status_warn")) {
|
||||
visible_status_type = 'warn';
|
||||
} else {
|
||||
visible_status_type = 'normal';
|
||||
return;
|
||||
}
|
||||
if (statusElem.classList.contains("noVNC_status_warn") &&
|
||||
statusType === 'normal') {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (visible_status_type === 'error' ||
|
||||
(visible_status_type === 'warn' && status_type === 'normal')) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (status_type) {
|
||||
clearTimeout(UI.statusTimeout);
|
||||
|
||||
switch (statusType) {
|
||||
case 'error':
|
||||
statusElem.classList.remove("noVNC_status_warn");
|
||||
statusElem.classList.remove("noVNC_status_normal");
|
||||
@@ -487,7 +500,7 @@ const UI = {
|
||||
}
|
||||
|
||||
// Error messages do not timeout
|
||||
if (status_type !== 'error') {
|
||||
if (statusType !== 'error') {
|
||||
UI.statusTimeout = window.setTimeout(UI.hideStatus, time);
|
||||
}
|
||||
},
|
||||
@@ -507,6 +520,13 @@ const UI = {
|
||||
},
|
||||
|
||||
idleControlbar() {
|
||||
// Don't fade if a child of the control bar has focus
|
||||
if (document.getElementById('noVNC_control_bar')
|
||||
.contains(document.activeElement) && document.hasFocus()) {
|
||||
UI.activateControlbar();
|
||||
return;
|
||||
}
|
||||
|
||||
document.getElementById('noVNC_control_bar_anchor')
|
||||
.classList.add("noVNC_idle");
|
||||
},
|
||||
@@ -524,6 +544,7 @@ const UI = {
|
||||
UI.closeAllPanels();
|
||||
document.getElementById('noVNC_control_bar')
|
||||
.classList.remove("noVNC_open");
|
||||
UI.rfb.focus();
|
||||
},
|
||||
|
||||
toggleControlbar() {
|
||||
@@ -740,11 +761,6 @@ const UI = {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/*Weird IE9 error leads to 'null' appearring
|
||||
in textboxes instead of ''.*/
|
||||
if (value === null) {
|
||||
value = "";
|
||||
}
|
||||
ctrl.value = value;
|
||||
}
|
||||
},
|
||||
@@ -821,6 +837,8 @@ const UI = {
|
||||
UI.updateSetting('encrypt');
|
||||
UI.updateSetting('view_clip');
|
||||
UI.updateSetting('resize');
|
||||
UI.updateSetting('quality');
|
||||
UI.updateSetting('compression');
|
||||
UI.updateSetting('shared');
|
||||
UI.updateSetting('view_only');
|
||||
UI.updateSetting('path');
|
||||
@@ -977,7 +995,7 @@ const UI = {
|
||||
|
||||
if (typeof password === 'undefined') {
|
||||
password = WebUtil.getConfigVar('password');
|
||||
UI.reconnect_password = password;
|
||||
UI.reconnectPassword = password;
|
||||
}
|
||||
|
||||
if (password === null) {
|
||||
@@ -992,7 +1010,6 @@ const UI = {
|
||||
return;
|
||||
}
|
||||
|
||||
UI.closeAllPanels();
|
||||
UI.closeConnectPanel();
|
||||
|
||||
UI.updateVisualState('connecting');
|
||||
@@ -1009,7 +1026,6 @@ const UI = {
|
||||
|
||||
UI.rfb = new RFB(document.getElementById('noVNC_container'), url,
|
||||
{ shared: UI.getSetting('shared'),
|
||||
showDotCursor: UI.getSetting('show_dot'),
|
||||
repeaterID: UI.getSetting('repeaterID'),
|
||||
credentials: { password: password } });
|
||||
UI.rfb.addEventListener("connect", UI.connectFinished);
|
||||
@@ -1023,18 +1039,20 @@ const UI = {
|
||||
UI.rfb.clipViewport = UI.getSetting('view_clip');
|
||||
UI.rfb.scaleViewport = UI.getSetting('resize') === 'scale';
|
||||
UI.rfb.resizeSession = UI.getSetting('resize') === 'remote';
|
||||
UI.rfb.qualityLevel = parseInt(UI.getSetting('quality'));
|
||||
UI.rfb.compressionLevel = parseInt(UI.getSetting('compression'));
|
||||
UI.rfb.showDotCursor = UI.getSetting('show_dot');
|
||||
|
||||
UI.updateViewOnly(); // requires UI.rfb
|
||||
},
|
||||
|
||||
disconnect() {
|
||||
UI.closeAllPanels();
|
||||
UI.rfb.disconnect();
|
||||
|
||||
UI.connected = false;
|
||||
|
||||
// Disable automatic reconnecting
|
||||
UI.inhibit_reconnect = true;
|
||||
UI.inhibitReconnect = true;
|
||||
|
||||
UI.updateVisualState('disconnecting');
|
||||
|
||||
@@ -1042,20 +1060,20 @@ const UI = {
|
||||
},
|
||||
|
||||
reconnect() {
|
||||
UI.reconnect_callback = null;
|
||||
UI.reconnectCallback = null;
|
||||
|
||||
// if reconnect has been disabled in the meantime, do nothing.
|
||||
if (UI.inhibit_reconnect) {
|
||||
if (UI.inhibitReconnect) {
|
||||
return;
|
||||
}
|
||||
|
||||
UI.connect(null, UI.reconnect_password);
|
||||
UI.connect(null, UI.reconnectPassword);
|
||||
},
|
||||
|
||||
cancelReconnect() {
|
||||
if (UI.reconnect_callback !== null) {
|
||||
clearTimeout(UI.reconnect_callback);
|
||||
UI.reconnect_callback = null;
|
||||
if (UI.reconnectCallback !== null) {
|
||||
clearTimeout(UI.reconnectCallback);
|
||||
UI.reconnectCallback = null;
|
||||
}
|
||||
|
||||
UI.updateVisualState('disconnected');
|
||||
@@ -1066,7 +1084,7 @@ const UI = {
|
||||
|
||||
connectFinished(e) {
|
||||
UI.connected = true;
|
||||
UI.inhibit_reconnect = false;
|
||||
UI.inhibitReconnect = false;
|
||||
|
||||
let msg;
|
||||
if (UI.getSetting('encrypt')) {
|
||||
@@ -1100,17 +1118,19 @@ const UI = {
|
||||
} else {
|
||||
UI.showStatus(_("Failed to connect to server"), 'error');
|
||||
}
|
||||
} else if (UI.getSetting('reconnect', false) === true && !UI.inhibit_reconnect) {
|
||||
} else if (UI.getSetting('reconnect', false) === true && !UI.inhibitReconnect) {
|
||||
UI.updateVisualState('reconnecting');
|
||||
|
||||
const delay = parseInt(UI.getSetting('reconnect_delay'));
|
||||
UI.reconnect_callback = setTimeout(UI.reconnect, delay);
|
||||
UI.reconnectCallback = setTimeout(UI.reconnect, delay);
|
||||
return;
|
||||
} else {
|
||||
UI.updateVisualState('disconnected');
|
||||
UI.showStatus(_("Disconnected"), 'normal');
|
||||
}
|
||||
|
||||
document.title = PAGE_TITLE;
|
||||
|
||||
UI.openControlbar();
|
||||
UI.openConnectPanel();
|
||||
},
|
||||
@@ -1137,27 +1157,46 @@ const UI = {
|
||||
|
||||
credentials(e) {
|
||||
// FIXME: handle more types
|
||||
document.getElementById('noVNC_password_dlg')
|
||||
|
||||
document.getElementById("noVNC_username_block").classList.remove("noVNC_hidden");
|
||||
document.getElementById("noVNC_password_block").classList.remove("noVNC_hidden");
|
||||
|
||||
let inputFocus = "none";
|
||||
if (e.detail.types.indexOf("username") === -1) {
|
||||
document.getElementById("noVNC_username_block").classList.add("noVNC_hidden");
|
||||
} else {
|
||||
inputFocus = inputFocus === "none" ? "noVNC_username_input" : inputFocus;
|
||||
}
|
||||
if (e.detail.types.indexOf("password") === -1) {
|
||||
document.getElementById("noVNC_password_block").classList.add("noVNC_hidden");
|
||||
} else {
|
||||
inputFocus = inputFocus === "none" ? "noVNC_password_input" : inputFocus;
|
||||
}
|
||||
document.getElementById('noVNC_credentials_dlg')
|
||||
.classList.add('noVNC_open');
|
||||
|
||||
setTimeout(() => document
|
||||
.getElementById('noVNC_password_input').focus(), 100);
|
||||
.getElementById(inputFocus).focus(), 100);
|
||||
|
||||
Log.Warn("Server asked for a password");
|
||||
UI.showStatus(_("Password is required"), "warning");
|
||||
Log.Warn("Server asked for credentials");
|
||||
UI.showStatus(_("Credentials are required"), "warning");
|
||||
},
|
||||
|
||||
setPassword(e) {
|
||||
setCredentials(e) {
|
||||
// Prevent actually submitting the form
|
||||
e.preventDefault();
|
||||
|
||||
const inputElem = document.getElementById('noVNC_password_input');
|
||||
const password = inputElem.value;
|
||||
let inputElemUsername = document.getElementById('noVNC_username_input');
|
||||
const username = inputElemUsername.value;
|
||||
|
||||
let inputElemPassword = document.getElementById('noVNC_password_input');
|
||||
const password = inputElemPassword.value;
|
||||
// Clear the input after reading the password
|
||||
inputElem.value = "";
|
||||
UI.rfb.sendCredentials({ password: password });
|
||||
UI.reconnect_password = password;
|
||||
document.getElementById('noVNC_password_dlg')
|
||||
inputElemPassword.value = "";
|
||||
|
||||
UI.rfb.sendCredentials({ username: username, password: password });
|
||||
UI.reconnectPassword = password;
|
||||
document.getElementById('noVNC_credentials_dlg')
|
||||
.classList.remove('noVNC_open');
|
||||
},
|
||||
|
||||
@@ -1240,8 +1279,9 @@ const UI = {
|
||||
// Can't be clipping if viewport is scaled to fit
|
||||
UI.forceSetting('view_clip', false);
|
||||
UI.rfb.clipViewport = false;
|
||||
} else if (isIOS() || isAndroid()) {
|
||||
// iOS and Android usually have shit scrollbars
|
||||
} else if (!hasScrollbarGutter) {
|
||||
// Some platforms have scrollbars that are difficult
|
||||
// to use in our case, so we always use our own panning
|
||||
UI.forceSetting('view_clip', true);
|
||||
UI.rfb.clipViewport = true;
|
||||
} else {
|
||||
@@ -1284,30 +1324,40 @@ const UI = {
|
||||
viewDragButton.classList.remove("noVNC_selected");
|
||||
}
|
||||
|
||||
// Different behaviour for touch vs non-touch
|
||||
// The button is disabled instead of hidden on touch devices
|
||||
if (isTouchDevice) {
|
||||
if (UI.rfb.clipViewport) {
|
||||
viewDragButton.classList.remove("noVNC_hidden");
|
||||
|
||||
if (UI.rfb.clipViewport) {
|
||||
viewDragButton.disabled = false;
|
||||
} else {
|
||||
viewDragButton.disabled = true;
|
||||
}
|
||||
} else {
|
||||
viewDragButton.disabled = false;
|
||||
|
||||
if (UI.rfb.clipViewport) {
|
||||
viewDragButton.classList.remove("noVNC_hidden");
|
||||
} else {
|
||||
viewDragButton.classList.add("noVNC_hidden");
|
||||
}
|
||||
viewDragButton.classList.add("noVNC_hidden");
|
||||
}
|
||||
},
|
||||
|
||||
/* ------^-------
|
||||
* /VIEWDRAG
|
||||
* ==============
|
||||
* QUALITY
|
||||
* ------v------*/
|
||||
|
||||
updateQuality() {
|
||||
if (!UI.rfb) return;
|
||||
|
||||
UI.rfb.qualityLevel = parseInt(UI.getSetting('quality'));
|
||||
},
|
||||
|
||||
/* ------^-------
|
||||
* /QUALITY
|
||||
* ==============
|
||||
* COMPRESSION
|
||||
* ------v------*/
|
||||
|
||||
updateCompression() {
|
||||
if (!UI.rfb) return;
|
||||
|
||||
UI.rfb.compressionLevel = parseInt(UI.getSetting('compression'));
|
||||
},
|
||||
|
||||
/* ------^-------
|
||||
* /COMPRESSION
|
||||
* ==============
|
||||
* KEYBOARD
|
||||
* ------v------*/
|
||||
|
||||
@@ -1502,20 +1552,20 @@ const UI = {
|
||||
},
|
||||
|
||||
sendEsc() {
|
||||
UI.rfb.sendKey(KeyTable.XK_Escape, "Escape");
|
||||
UI.sendKey(KeyTable.XK_Escape, "Escape");
|
||||
},
|
||||
|
||||
sendTab() {
|
||||
UI.rfb.sendKey(KeyTable.XK_Tab);
|
||||
UI.sendKey(KeyTable.XK_Tab, "Tab");
|
||||
},
|
||||
|
||||
toggleCtrl() {
|
||||
const btn = document.getElementById('noVNC_toggle_ctrl_button');
|
||||
if (btn.classList.contains("noVNC_selected")) {
|
||||
UI.rfb.sendKey(KeyTable.XK_Control_L, "ControlLeft", false);
|
||||
UI.sendKey(KeyTable.XK_Control_L, "ControlLeft", false);
|
||||
btn.classList.remove("noVNC_selected");
|
||||
} else {
|
||||
UI.rfb.sendKey(KeyTable.XK_Control_L, "ControlLeft", true);
|
||||
UI.sendKey(KeyTable.XK_Control_L, "ControlLeft", true);
|
||||
btn.classList.add("noVNC_selected");
|
||||
}
|
||||
},
|
||||
@@ -1523,10 +1573,10 @@ const UI = {
|
||||
toggleWindows() {
|
||||
const btn = document.getElementById('noVNC_toggle_windows_button');
|
||||
if (btn.classList.contains("noVNC_selected")) {
|
||||
UI.rfb.sendKey(KeyTable.XK_Super_L, "MetaLeft", false);
|
||||
UI.sendKey(KeyTable.XK_Super_L, "MetaLeft", false);
|
||||
btn.classList.remove("noVNC_selected");
|
||||
} else {
|
||||
UI.rfb.sendKey(KeyTable.XK_Super_L, "MetaLeft", true);
|
||||
UI.sendKey(KeyTable.XK_Super_L, "MetaLeft", true);
|
||||
btn.classList.add("noVNC_selected");
|
||||
}
|
||||
},
|
||||
@@ -1534,16 +1584,39 @@ const UI = {
|
||||
toggleAlt() {
|
||||
const btn = document.getElementById('noVNC_toggle_alt_button');
|
||||
if (btn.classList.contains("noVNC_selected")) {
|
||||
UI.rfb.sendKey(KeyTable.XK_Alt_L, "AltLeft", false);
|
||||
UI.sendKey(KeyTable.XK_Alt_L, "AltLeft", false);
|
||||
btn.classList.remove("noVNC_selected");
|
||||
} else {
|
||||
UI.rfb.sendKey(KeyTable.XK_Alt_L, "AltLeft", true);
|
||||
UI.sendKey(KeyTable.XK_Alt_L, "AltLeft", true);
|
||||
btn.classList.add("noVNC_selected");
|
||||
}
|
||||
},
|
||||
|
||||
sendCtrlAltDel() {
|
||||
UI.rfb.sendCtrlAltDel();
|
||||
// See below
|
||||
UI.rfb.focus();
|
||||
UI.idleControlbar();
|
||||
},
|
||||
|
||||
sendKey(keysym, code, down) {
|
||||
UI.rfb.sendKey(keysym, code, down);
|
||||
|
||||
// Move focus to the screen in order to be able to use the
|
||||
// keyboard right after these extra keys.
|
||||
// The exception is when a virtual keyboard is used, because
|
||||
// if we focus the screen the virtual keyboard would be closed.
|
||||
// In this case we focus our special virtual keyboard input
|
||||
// element instead.
|
||||
if (document.getElementById('noVNC_keyboard_button')
|
||||
.classList.contains("noVNC_selected")) {
|
||||
document.getElementById('noVNC_keyboardinput').focus();
|
||||
} else {
|
||||
UI.rfb.focus();
|
||||
}
|
||||
// fade out the controlbar to highlight that
|
||||
// the focus has been moved to the screen
|
||||
UI.idleControlbar();
|
||||
},
|
||||
|
||||
/* ------^-------
|
||||
@@ -1552,24 +1625,6 @@ const UI = {
|
||||
* MISC
|
||||
* ------v------*/
|
||||
|
||||
setMouseButton(num) {
|
||||
const view_only = UI.rfb.viewOnly;
|
||||
if (UI.rfb && !view_only) {
|
||||
UI.rfb.touchButton = num;
|
||||
}
|
||||
|
||||
const blist = [0, 1, 2, 4];
|
||||
for (let b = 0; b < blist.length; b++) {
|
||||
const button = document.getElementById('noVNC_mouse_button' +
|
||||
blist[b]);
|
||||
if (blist[b] === num && !view_only) {
|
||||
button.classList.remove("noVNC_hidden");
|
||||
} else {
|
||||
button.classList.add("noVNC_hidden");
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
updateViewOnly() {
|
||||
if (!UI.rfb) return;
|
||||
UI.rfb.viewOnly = UI.getSetting('view_only');
|
||||
@@ -1580,14 +1635,14 @@ const UI = {
|
||||
.classList.add('noVNC_hidden');
|
||||
document.getElementById('noVNC_toggle_extra_keys_button')
|
||||
.classList.add('noVNC_hidden');
|
||||
document.getElementById('noVNC_mouse_button' + UI.rfb.touchButton)
|
||||
document.getElementById('noVNC_clipboard_button')
|
||||
.classList.add('noVNC_hidden');
|
||||
} else {
|
||||
document.getElementById('noVNC_keyboard_button')
|
||||
.classList.remove('noVNC_hidden');
|
||||
document.getElementById('noVNC_toggle_extra_keys_button')
|
||||
.classList.remove('noVNC_hidden');
|
||||
document.getElementById('noVNC_mouse_button' + UI.rfb.touchButton)
|
||||
document.getElementById('noVNC_clipboard_button')
|
||||
.classList.remove('noVNC_hidden');
|
||||
}
|
||||
},
|
||||
@@ -1598,13 +1653,13 @@ const UI = {
|
||||
},
|
||||
|
||||
updateLogging() {
|
||||
WebUtil.init_logging(UI.getSetting('logging'));
|
||||
WebUtil.initLogging(UI.getSetting('logging'));
|
||||
},
|
||||
|
||||
updateDesktopName(e) {
|
||||
UI.desktopName = e.detail.name;
|
||||
// Display the desktop name in the document title
|
||||
document.title = e.detail.name + " - noVNC";
|
||||
document.title = e.detail.name + " - " + PAGE_TITLE;
|
||||
},
|
||||
|
||||
bell(e) {
|
||||
@@ -1640,12 +1695,18 @@ const UI = {
|
||||
};
|
||||
|
||||
// Set up translations
|
||||
const LINGUAS = ["cs", "de", "el", "es", "ko", "nl", "pl", "ru", "sv", "tr", "zh_CN", "zh_TW"];
|
||||
const LINGUAS = ["cs", "de", "el", "es", "fr", "ja", "ko", "nl", "pl", "pt_BR", "ru", "sv", "tr", "zh_CN", "zh_TW"];
|
||||
l10n.setup(LINGUAS);
|
||||
if (l10n.language === "en" || l10n.dictionary !== undefined) {
|
||||
UI.prime();
|
||||
} 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; })
|
||||
.catch(err => Log.Error("Failed to load translations: " + err))
|
||||
.then(UI.prime);
|
||||
|
||||
+15
-68
@@ -1,29 +1,38 @@
|
||||
/*
|
||||
* noVNC: HTML5 VNC client
|
||||
* Copyright (C) 2018 The noVNC Authors
|
||||
* Copyright (C) 2019 The noVNC Authors
|
||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||
*
|
||||
* See README.md for usage and integration instructions.
|
||||
*/
|
||||
|
||||
import { init_logging as main_init_logging } from '../core/util/logging.js';
|
||||
import { initLogging as mainInitLogging } from '../core/util/logging.js';
|
||||
|
||||
// init log level reading the logging HTTP param
|
||||
export function init_logging(level) {
|
||||
export function initLogging(level) {
|
||||
"use strict";
|
||||
if (typeof level !== "undefined") {
|
||||
main_init_logging(level);
|
||||
mainInitLogging(level);
|
||||
} else {
|
||||
const param = document.location.href.match(/logging=([A-Za-z0-9._-]*)/);
|
||||
main_init_logging(param || undefined);
|
||||
mainInitLogging(param || undefined);
|
||||
}
|
||||
}
|
||||
|
||||
// Read a query string variable
|
||||
// A URL with a query parameter can look like this (But will most probably get logged on the http server):
|
||||
// https://www.example.com?myqueryparam=myvalue
|
||||
//
|
||||
// For privacy (Using a hastag #, the parameters will not be sent to the server)
|
||||
// the url can be requested in the following way:
|
||||
// https://www.example.com#myqueryparam=myvalue&password=secreatvalue
|
||||
//
|
||||
// Even Mixing public and non public parameters will work:
|
||||
// https://www.example.com?nonsecretparam=example.com#password=secreatvalue
|
||||
export function getQueryVar(name, defVal) {
|
||||
"use strict";
|
||||
const re = new RegExp('.*[?&]' + name + '=([^&#]*)'),
|
||||
match = document.location.href.match(re);
|
||||
match = ''.concat(document.location.href, window.location.hash).match(re);
|
||||
if (typeof defVal === 'undefined') { defVal = null; }
|
||||
|
||||
if (match) {
|
||||
@@ -175,65 +184,3 @@ export function eraseSetting(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();
|
||||
});
|
||||
}
|
||||
|
||||
+4
-4
@@ -57,12 +57,12 @@ export default {
|
||||
/* eslint-enable comma-spacing */
|
||||
|
||||
decode(data, offset = 0) {
|
||||
let data_length = data.indexOf('=') - offset;
|
||||
if (data_length < 0) { data_length = data.length - offset; }
|
||||
let dataLength = data.indexOf('=') - offset;
|
||||
if (dataLength < 0) { dataLength = data.length - offset; }
|
||||
|
||||
/* Every four characters is 3 resulting numbers */
|
||||
const result_length = (data_length >> 2) * 3 + Math.floor((data_length % 4) / 1.5);
|
||||
const result = new Array(result_length);
|
||||
const resultLength = (dataLength >> 2) * 3 + Math.floor((dataLength % 4) / 1.5);
|
||||
const result = new Array(resultLength);
|
||||
|
||||
// Convert one by one.
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
/*
|
||||
* noVNC: HTML5 VNC client
|
||||
* Copyright (C) 2012 Joel Martin
|
||||
* Copyright (C) 2018 Samuel Mannehed for Cendio AB
|
||||
* Copyright (C) 2018 Pierre Ossman for Cendio AB
|
||||
* Copyright (C) 2019 The noVNC Authors
|
||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||
*
|
||||
* See README.md for usage and integration instructions.
|
||||
@@ -17,6 +15,11 @@ export default class CopyRectDecoder {
|
||||
|
||||
let deltaX = sock.rQshift16();
|
||||
let deltaY = sock.rQshift16();
|
||||
|
||||
if ((width === 0) || (height === 0)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
display.copyImage(deltaX, deltaY, x, y, width, height);
|
||||
|
||||
return true;
|
||||
|
||||
+67
-15
@@ -1,8 +1,6 @@
|
||||
/*
|
||||
* noVNC: HTML5 VNC client
|
||||
* Copyright (C) 2012 Joel Martin
|
||||
* Copyright (C) 2018 Samuel Mannehed for Cendio AB
|
||||
* Copyright (C) 2018 Pierre Ossman for Cendio AB
|
||||
* Copyright (C) 2019 The noVNC Authors
|
||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||
*
|
||||
* See README.md for usage and integration instructions.
|
||||
@@ -15,14 +13,15 @@ export default class HextileDecoder {
|
||||
constructor() {
|
||||
this._tiles = 0;
|
||||
this._lastsubencoding = 0;
|
||||
this._tileBuffer = new Uint8Array(16 * 16 * 4);
|
||||
}
|
||||
|
||||
decodeRect(x, y, width, height, sock, display, depth) {
|
||||
if (this._tiles === 0) {
|
||||
this._tiles_x = Math.ceil(width / 16);
|
||||
this._tiles_y = Math.ceil(height / 16);
|
||||
this._total_tiles = this._tiles_x * this._tiles_y;
|
||||
this._tiles = this._total_tiles;
|
||||
this._tilesX = Math.ceil(width / 16);
|
||||
this._tilesY = Math.ceil(height / 16);
|
||||
this._totalTiles = this._tilesX * this._tilesY;
|
||||
this._tiles = this._totalTiles;
|
||||
}
|
||||
|
||||
while (this._tiles > 0) {
|
||||
@@ -41,11 +40,11 @@ export default class HextileDecoder {
|
||||
subencoding + ")");
|
||||
}
|
||||
|
||||
const curr_tile = this._total_tiles - this._tiles;
|
||||
const tile_x = curr_tile % this._tiles_x;
|
||||
const tile_y = Math.floor(curr_tile / this._tiles_x);
|
||||
const tx = x + tile_x * 16;
|
||||
const ty = y + tile_y * 16;
|
||||
const currTile = this._totalTiles - this._tiles;
|
||||
const tileX = currTile % this._tilesX;
|
||||
const tileY = Math.floor(currTile / this._tilesX);
|
||||
const tx = x + tileX * 16;
|
||||
const ty = y + tileY * 16;
|
||||
const tw = Math.min(16, (x + width) - tx);
|
||||
const th = Math.min(16, (y + height) - ty);
|
||||
|
||||
@@ -89,6 +88,11 @@ export default class HextileDecoder {
|
||||
display.fillRect(tx, ty, tw, th, this._background);
|
||||
}
|
||||
} else if (subencoding & 0x01) { // Raw
|
||||
let pixels = tw * th;
|
||||
// Max sure the image is fully opaque
|
||||
for (let i = 0;i < pixels;i++) {
|
||||
rQ[rQi + i * 4 + 3] = 255;
|
||||
}
|
||||
display.blitImage(tx, ty, tw, th, rQ, rQi);
|
||||
rQi += bytes - 1;
|
||||
} else {
|
||||
@@ -101,7 +105,7 @@ export default class HextileDecoder {
|
||||
rQi += 4;
|
||||
}
|
||||
|
||||
display.startTile(tx, ty, tw, th, this._background);
|
||||
this._startTile(tx, ty, tw, th, this._background);
|
||||
if (subencoding & 0x08) { // AnySubrects
|
||||
let subrects = rQ[rQi];
|
||||
rQi++;
|
||||
@@ -124,10 +128,10 @@ export default class HextileDecoder {
|
||||
const sw = (wh >> 4) + 1;
|
||||
const sh = (wh & 0x0f) + 1;
|
||||
|
||||
display.subTile(sx, sy, sw, sh, color);
|
||||
this._subTile(sx, sy, sw, sh, color);
|
||||
}
|
||||
}
|
||||
display.finishTile();
|
||||
this._finishTile(display);
|
||||
}
|
||||
sock.rQi = rQi;
|
||||
this._lastsubencoding = subencoding;
|
||||
@@ -136,4 +140,52 @@ export default class HextileDecoder {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// start updating a tile
|
||||
_startTile(x, y, width, height, color) {
|
||||
this._tileX = x;
|
||||
this._tileY = y;
|
||||
this._tileW = width;
|
||||
this._tileH = height;
|
||||
|
||||
const red = color[0];
|
||||
const green = color[1];
|
||||
const blue = color[2];
|
||||
|
||||
const data = this._tileBuffer;
|
||||
for (let i = 0; i < width * height * 4; i += 4) {
|
||||
data[i] = red;
|
||||
data[i + 1] = green;
|
||||
data[i + 2] = blue;
|
||||
data[i + 3] = 255;
|
||||
}
|
||||
}
|
||||
|
||||
// update sub-rectangle of the current tile
|
||||
_subTile(x, y, w, h, color) {
|
||||
const red = color[0];
|
||||
const green = color[1];
|
||||
const blue = color[2];
|
||||
const xend = x + w;
|
||||
const yend = y + h;
|
||||
|
||||
const data = this._tileBuffer;
|
||||
const width = this._tileW;
|
||||
for (let j = y; j < yend; j++) {
|
||||
for (let i = x; i < xend; i++) {
|
||||
const p = (i + (j * width)) * 4;
|
||||
data[p] = red;
|
||||
data[p + 1] = green;
|
||||
data[p + 2] = blue;
|
||||
data[p + 3] = 255;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// draw the current tile to the screen
|
||||
_finishTile(display) {
|
||||
display.blitImage(this._tileX, this._tileY,
|
||||
this._tileW, this._tileH,
|
||||
this._tileBuffer, 0);
|
||||
}
|
||||
}
|
||||
|
||||
+19
-11
@@ -1,8 +1,6 @@
|
||||
/*
|
||||
* noVNC: HTML5 VNC client
|
||||
* Copyright (C) 2012 Joel Martin
|
||||
* Copyright (C) 2018 Samuel Mannehed for Cendio AB
|
||||
* Copyright (C) 2018 Pierre Ossman for Cendio AB
|
||||
* Copyright (C) 2019 The noVNC Authors
|
||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||
*
|
||||
* See README.md for usage and integration instructions.
|
||||
@@ -15,6 +13,10 @@ export default class RawDecoder {
|
||||
}
|
||||
|
||||
decodeRect(x, y, width, height, sock, display, depth) {
|
||||
if ((width === 0) || (height === 0)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this._lines === 0) {
|
||||
this._lines = height;
|
||||
}
|
||||
@@ -26,29 +28,35 @@ export default class RawDecoder {
|
||||
return false;
|
||||
}
|
||||
|
||||
const cur_y = y + (height - this._lines);
|
||||
const curr_height = Math.min(this._lines,
|
||||
Math.floor(sock.rQlen / bytesPerLine));
|
||||
const curY = y + (height - this._lines);
|
||||
const currHeight = Math.min(this._lines,
|
||||
Math.floor(sock.rQlen / bytesPerLine));
|
||||
const pixels = width * currHeight;
|
||||
|
||||
let data = sock.rQ;
|
||||
let index = sock.rQi;
|
||||
|
||||
// Convert data if needed
|
||||
if (depth == 8) {
|
||||
const pixels = width * curr_height;
|
||||
const newdata = new Uint8Array(pixels * 4);
|
||||
for (let i = 0; i < pixels; i++) {
|
||||
newdata[i * 4 + 0] = ((data[index + i] >> 0) & 0x3) * 255 / 3;
|
||||
newdata[i * 4 + 1] = ((data[index + i] >> 2) & 0x3) * 255 / 3;
|
||||
newdata[i * 4 + 2] = ((data[index + i] >> 4) & 0x3) * 255 / 3;
|
||||
newdata[i * 4 + 4] = 0;
|
||||
newdata[i * 4 + 3] = 255;
|
||||
}
|
||||
data = newdata;
|
||||
index = 0;
|
||||
}
|
||||
|
||||
display.blitImage(x, cur_y, width, curr_height, data, index);
|
||||
sock.rQskipBytes(curr_height * bytesPerLine);
|
||||
this._lines -= curr_height;
|
||||
// Max sure the image is fully opaque
|
||||
for (let i = 0; i < pixels; i++) {
|
||||
data[i * 4 + 3] = 255;
|
||||
}
|
||||
|
||||
display.blitImage(x, curY, width, currHeight, data, index);
|
||||
sock.rQskipBytes(currHeight * bytesPerLine);
|
||||
this._lines -= currHeight;
|
||||
if (this._lines > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
/*
|
||||
* noVNC: HTML5 VNC client
|
||||
* Copyright (C) 2012 Joel Martin
|
||||
* Copyright (C) 2018 Samuel Mannehed for Cendio AB
|
||||
* Copyright (C) 2018 Pierre Ossman for Cendio AB
|
||||
* Copyright (C) 2019 The noVNC Authors
|
||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||
*
|
||||
* See README.md for usage and integration instructions.
|
||||
|
||||
+32
-20
@@ -1,9 +1,7 @@
|
||||
/*
|
||||
* noVNC: HTML5 VNC client
|
||||
* Copyright (C) 2012 Joel Martin
|
||||
* Copyright (C) 2019 The noVNC Authors
|
||||
* (c) 2012 Michael Tinglof, Joe Balaz, Les Piech (Mercuri.ca)
|
||||
* Copyright (C) 2018 Samuel Mannehed for Cendio AB
|
||||
* Copyright (C) 2018 Pierre Ossman for Cendio AB
|
||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||
*
|
||||
* See README.md for usage and integration instructions.
|
||||
@@ -58,7 +56,7 @@ export default class TightDecoder {
|
||||
} else if (this._ctl === 0x0A) {
|
||||
ret = this._pngRect(x, y, width, height,
|
||||
sock, display, depth);
|
||||
} else if ((this._ctl & 0x80) == 0) {
|
||||
} else if ((this._ctl & 0x08) == 0) {
|
||||
ret = this._basicRect(this._ctl, x, y, width, height,
|
||||
sock, display, depth);
|
||||
} else {
|
||||
@@ -82,7 +80,7 @@ export default class TightDecoder {
|
||||
const rQ = sock.rQ;
|
||||
|
||||
display.fillRect(x, y, width, height,
|
||||
[rQ[rQi + 2], rQ[rQi + 1], rQ[rQi]], false);
|
||||
[rQ[rQi], rQ[rQi + 1], rQ[rQi + 2]], false);
|
||||
sock.rQskipBytes(3);
|
||||
|
||||
return true;
|
||||
@@ -94,7 +92,7 @@ export default class TightDecoder {
|
||||
return false;
|
||||
}
|
||||
|
||||
display.imageRect(x, y, "image/jpeg", data);
|
||||
display.imageRect(x, y, width, height, "image/jpeg", data);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -150,6 +148,10 @@ export default class TightDecoder {
|
||||
const uncompressedSize = width * height * 3;
|
||||
let data;
|
||||
|
||||
if (uncompressedSize === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (uncompressedSize < 12) {
|
||||
if (sock.rQwait("TIGHT", uncompressedSize)) {
|
||||
return false;
|
||||
@@ -162,13 +164,20 @@ export default class TightDecoder {
|
||||
return false;
|
||||
}
|
||||
|
||||
data = this._zlibs[streamId].inflate(data, true, uncompressedSize);
|
||||
if (data.length != uncompressedSize) {
|
||||
throw new Error("Incomplete zlib block");
|
||||
}
|
||||
this._zlibs[streamId].setInput(data);
|
||||
data = this._zlibs[streamId].inflate(uncompressedSize);
|
||||
this._zlibs[streamId].setInput(null);
|
||||
}
|
||||
|
||||
display.blitRgbImage(x, y, width, height, data, 0, false);
|
||||
let rgbx = new Uint8Array(width * height * 4);
|
||||
for (let i = 0, j = 0; i < width * height * 4; i += 4, j += 3) {
|
||||
rgbx[i] = data[j];
|
||||
rgbx[i + 1] = data[j + 1];
|
||||
rgbx[i + 2] = data[j + 2];
|
||||
rgbx[i + 3] = 255; // Alpha
|
||||
}
|
||||
|
||||
display.blitImage(x, y, width, height, rgbx, 0, false);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -198,6 +207,10 @@ export default class TightDecoder {
|
||||
|
||||
let data;
|
||||
|
||||
if (uncompressedSize === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (uncompressedSize < 12) {
|
||||
if (sock.rQwait("TIGHT", uncompressedSize)) {
|
||||
return false;
|
||||
@@ -210,10 +223,9 @@ export default class TightDecoder {
|
||||
return false;
|
||||
}
|
||||
|
||||
data = this._zlibs[streamId].inflate(data, true, uncompressedSize);
|
||||
if (data.length != uncompressedSize) {
|
||||
throw new Error("Incomplete zlib block");
|
||||
}
|
||||
this._zlibs[streamId].setInput(data);
|
||||
data = this._zlibs[streamId].inflate(uncompressedSize);
|
||||
this._zlibs[streamId].setInput(null);
|
||||
}
|
||||
|
||||
// Convert indexed (palette based) image data to RGB
|
||||
@@ -241,7 +253,7 @@ export default class TightDecoder {
|
||||
for (let b = 7; b >= 0; b--) {
|
||||
dp = (y * width + x * 8 + 7 - b) * 4;
|
||||
sp = (data[y * w + x] >> b & 1) * 3;
|
||||
dest[dp] = palette[sp];
|
||||
dest[dp] = palette[sp];
|
||||
dest[dp + 1] = palette[sp + 1];
|
||||
dest[dp + 2] = palette[sp + 2];
|
||||
dest[dp + 3] = 255;
|
||||
@@ -251,14 +263,14 @@ export default class TightDecoder {
|
||||
for (let b = 7; b >= 8 - width % 8; b--) {
|
||||
dp = (y * width + x * 8 + 7 - b) * 4;
|
||||
sp = (data[y * w + x] >> b & 1) * 3;
|
||||
dest[dp] = palette[sp];
|
||||
dest[dp] = palette[sp];
|
||||
dest[dp + 1] = palette[sp + 1];
|
||||
dest[dp + 2] = palette[sp + 2];
|
||||
dest[dp + 3] = 255;
|
||||
}
|
||||
}
|
||||
|
||||
display.blitRgbxImage(x, y, width, height, dest, 0, false);
|
||||
display.blitImage(x, y, width, height, dest, 0, false);
|
||||
}
|
||||
|
||||
_paletteRect(x, y, width, height, data, palette, display) {
|
||||
@@ -267,13 +279,13 @@ export default class TightDecoder {
|
||||
const total = width * height * 4;
|
||||
for (let i = 0, j = 0; i < total; i += 4, j++) {
|
||||
const sp = data[j] * 3;
|
||||
dest[i] = palette[sp];
|
||||
dest[i] = palette[sp];
|
||||
dest[i + 1] = palette[sp + 1];
|
||||
dest[i + 2] = palette[sp + 2];
|
||||
dest[i + 3] = 255;
|
||||
}
|
||||
|
||||
display.blitRgbxImage(x, y, width, height, dest, 0, false);
|
||||
display.blitImage(x, y, width, height, dest, 0, false);
|
||||
}
|
||||
|
||||
_gradientFilter(streamId, x, y, width, height, sock, display, depth) {
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
/*
|
||||
* noVNC: HTML5 VNC client
|
||||
* Copyright (C) 2012 Joel Martin
|
||||
* Copyright (C) 2018 Samuel Mannehed for Cendio AB
|
||||
* Copyright (C) 2018 Pierre Ossman for Cendio AB
|
||||
* Copyright (C) 2019 The noVNC Authors
|
||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||
*
|
||||
* See README.md for usage and integration instructions.
|
||||
@@ -18,7 +16,7 @@ export default class TightPNGDecoder extends TightDecoder {
|
||||
return false;
|
||||
}
|
||||
|
||||
display.imageRect(x, y, "image/png", data);
|
||||
display.imageRect(x, y, width, height, "image/png", data);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* noVNC: HTML5 VNC client
|
||||
* Copyright (C) 2020 The noVNC Authors
|
||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||
*
|
||||
* See README.md for usage and integration instructions.
|
||||
*/
|
||||
|
||||
import { deflateInit, deflate } from "../vendor/pako/lib/zlib/deflate.js";
|
||||
import { Z_FULL_FLUSH } from "../vendor/pako/lib/zlib/deflate.js";
|
||||
import ZStream from "../vendor/pako/lib/zlib/zstream.js";
|
||||
|
||||
export default class Deflator {
|
||||
constructor() {
|
||||
this.strm = new ZStream();
|
||||
this.chunkSize = 1024 * 10 * 10;
|
||||
this.outputBuffer = new Uint8Array(this.chunkSize);
|
||||
this.windowBits = 5;
|
||||
|
||||
deflateInit(this.strm, this.windowBits);
|
||||
}
|
||||
|
||||
deflate(inData) {
|
||||
/* eslint-disable camelcase */
|
||||
this.strm.input = inData;
|
||||
this.strm.avail_in = this.strm.input.length;
|
||||
this.strm.next_in = 0;
|
||||
this.strm.output = this.outputBuffer;
|
||||
this.strm.avail_out = this.chunkSize;
|
||||
this.strm.next_out = 0;
|
||||
/* eslint-enable camelcase */
|
||||
|
||||
let lastRet = deflate(this.strm, Z_FULL_FLUSH);
|
||||
let outData = new Uint8Array(this.strm.output.buffer, 0, this.strm.next_out);
|
||||
|
||||
if (lastRet < 0) {
|
||||
throw new Error("zlib deflate failed");
|
||||
}
|
||||
|
||||
if (this.strm.avail_in > 0) {
|
||||
// Read chunks until done
|
||||
|
||||
let chunks = [outData];
|
||||
let totalLen = outData.length;
|
||||
do {
|
||||
/* eslint-disable camelcase */
|
||||
this.strm.output = new Uint8Array(this.chunkSize);
|
||||
this.strm.next_out = 0;
|
||||
this.strm.avail_out = this.chunkSize;
|
||||
/* eslint-enable camelcase */
|
||||
|
||||
lastRet = deflate(this.strm, Z_FULL_FLUSH);
|
||||
|
||||
if (lastRet < 0) {
|
||||
throw new Error("zlib deflate failed");
|
||||
}
|
||||
|
||||
let chunk = new Uint8Array(this.strm.output.buffer, 0, this.strm.next_out);
|
||||
totalLen += chunk.length;
|
||||
chunks.push(chunk);
|
||||
} while (this.strm.avail_in > 0);
|
||||
|
||||
// Combine chunks into a single data
|
||||
|
||||
let newData = new Uint8Array(totalLen);
|
||||
let offset = 0;
|
||||
|
||||
for (let i = 0; i < chunks.length; i++) {
|
||||
newData.set(chunks[i], offset);
|
||||
offset += chunks[i].length;
|
||||
}
|
||||
|
||||
outData = newData;
|
||||
}
|
||||
|
||||
/* eslint-disable camelcase */
|
||||
this.strm.input = null;
|
||||
this.strm.avail_in = 0;
|
||||
this.strm.next_in = 0;
|
||||
/* eslint-enable camelcase */
|
||||
|
||||
return outData;
|
||||
}
|
||||
|
||||
}
|
||||
+76
-217
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* noVNC: HTML5 VNC client
|
||||
* Copyright (C) 2018 The noVNC Authors
|
||||
* Copyright (C) 2019 The noVNC Authors
|
||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||
*
|
||||
* See README.md for usage and integration instructions.
|
||||
@@ -8,25 +8,20 @@
|
||||
|
||||
import * as Log from './util/logging.js';
|
||||
import Base64 from "./base64.js";
|
||||
import { supportsImageMetadata } from './util/browser.js';
|
||||
import { toSigned32bit } from './util/int.js';
|
||||
|
||||
export default class Display {
|
||||
constructor(target) {
|
||||
this._drawCtx = null;
|
||||
this._c_forceCanvas = false;
|
||||
|
||||
this._renderQ = []; // queue drawing actions for in-oder rendering
|
||||
this._flushing = false;
|
||||
|
||||
// the full frame buffer (logical canvas) size
|
||||
this._fb_width = 0;
|
||||
this._fb_height = 0;
|
||||
this._fbWidth = 0;
|
||||
this._fbHeight = 0;
|
||||
|
||||
this._prevDrawStyle = "";
|
||||
this._tile = null;
|
||||
this._tile16x16 = null;
|
||||
this._tile_x = 0;
|
||||
this._tile_y = 0;
|
||||
|
||||
Log.Debug(">> Display.constructor");
|
||||
|
||||
@@ -60,21 +55,12 @@ export default class Display {
|
||||
|
||||
Log.Debug("User Agent: " + navigator.userAgent);
|
||||
|
||||
this.clear();
|
||||
|
||||
// Check canvas features
|
||||
if (!('createImageData' in this._drawCtx)) {
|
||||
throw new Error("Canvas does not support createImageData");
|
||||
}
|
||||
|
||||
this._tile16x16 = this._drawCtx.createImageData(16, 16);
|
||||
Log.Debug("<< Display.constructor");
|
||||
|
||||
// ===== PROPERTIES =====
|
||||
|
||||
this._scale = 1.0;
|
||||
this._clipViewport = false;
|
||||
this.logo = null;
|
||||
|
||||
// ===== EVENT HANDLERS =====
|
||||
|
||||
@@ -98,11 +84,11 @@ export default class Display {
|
||||
}
|
||||
|
||||
get width() {
|
||||
return this._fb_width;
|
||||
return this._fbWidth;
|
||||
}
|
||||
|
||||
get height() {
|
||||
return this._fb_height;
|
||||
return this._fbHeight;
|
||||
}
|
||||
|
||||
// ===== PUBLIC METHODS =====
|
||||
@@ -125,15 +111,15 @@ export default class Display {
|
||||
if (deltaX < 0 && vp.x + deltaX < 0) {
|
||||
deltaX = -vp.x;
|
||||
}
|
||||
if (vx2 + deltaX >= this._fb_width) {
|
||||
deltaX -= vx2 + deltaX - this._fb_width + 1;
|
||||
if (vx2 + deltaX >= this._fbWidth) {
|
||||
deltaX -= vx2 + deltaX - this._fbWidth + 1;
|
||||
}
|
||||
|
||||
if (vp.y + deltaY < 0) {
|
||||
deltaY = -vp.y;
|
||||
}
|
||||
if (vy2 + deltaY >= this._fb_height) {
|
||||
deltaY -= (vy2 + deltaY - this._fb_height + 1);
|
||||
if (vy2 + deltaY >= this._fbHeight) {
|
||||
deltaY -= (vy2 + deltaY - this._fbHeight + 1);
|
||||
}
|
||||
|
||||
if (deltaX === 0 && deltaY === 0) {
|
||||
@@ -156,18 +142,18 @@ export default class Display {
|
||||
typeof(height) === "undefined") {
|
||||
|
||||
Log.Debug("Setting viewport to full display region");
|
||||
width = this._fb_width;
|
||||
height = this._fb_height;
|
||||
width = this._fbWidth;
|
||||
height = this._fbHeight;
|
||||
}
|
||||
|
||||
width = Math.floor(width);
|
||||
height = Math.floor(height);
|
||||
|
||||
if (width > this._fb_width) {
|
||||
width = this._fb_width;
|
||||
if (width > this._fbWidth) {
|
||||
width = this._fbWidth;
|
||||
}
|
||||
if (height > this._fb_height) {
|
||||
height = this._fb_height;
|
||||
if (height > this._fbHeight) {
|
||||
height = this._fbHeight;
|
||||
}
|
||||
|
||||
const vp = this._viewportLoc;
|
||||
@@ -194,21 +180,21 @@ export default class Display {
|
||||
if (this._scale === 0) {
|
||||
return 0;
|
||||
}
|
||||
return x / this._scale + this._viewportLoc.x;
|
||||
return toSigned32bit(x / this._scale + this._viewportLoc.x);
|
||||
}
|
||||
|
||||
absY(y) {
|
||||
if (this._scale === 0) {
|
||||
return 0;
|
||||
}
|
||||
return y / this._scale + this._viewportLoc.y;
|
||||
return toSigned32bit(y / this._scale + this._viewportLoc.y);
|
||||
}
|
||||
|
||||
resize(width, height) {
|
||||
this._prevDrawStyle = "";
|
||||
|
||||
this._fb_width = width;
|
||||
this._fb_height = height;
|
||||
this._fbWidth = width;
|
||||
this._fbHeight = height;
|
||||
|
||||
const canvas = this._backbuffer;
|
||||
if (canvas.width !== width || canvas.height !== height) {
|
||||
@@ -256,9 +242,9 @@ export default class Display {
|
||||
|
||||
// Update the visible canvas with the contents of the
|
||||
// rendering canvas
|
||||
flip(from_queue) {
|
||||
if (this._renderQ.length !== 0 && !from_queue) {
|
||||
this._renderQ_push({
|
||||
flip(fromQueue) {
|
||||
if (this._renderQ.length !== 0 && !fromQueue) {
|
||||
this._renderQPush({
|
||||
'type': 'flip'
|
||||
});
|
||||
} else {
|
||||
@@ -302,17 +288,6 @@ export default class Display {
|
||||
}
|
||||
}
|
||||
|
||||
clear() {
|
||||
if (this._logo) {
|
||||
this.resize(this._logo.width, this._logo.height);
|
||||
this.imageRect(0, 0, this._logo.type, this._logo.data);
|
||||
} else {
|
||||
this.resize(240, 20);
|
||||
this._drawCtx.clearRect(0, 0, this._fb_width, this._fb_height);
|
||||
}
|
||||
this.flip();
|
||||
}
|
||||
|
||||
pending() {
|
||||
return this._renderQ.length > 0;
|
||||
}
|
||||
@@ -325,9 +300,9 @@ export default class Display {
|
||||
}
|
||||
}
|
||||
|
||||
fillRect(x, y, width, height, color, from_queue) {
|
||||
if (this._renderQ.length !== 0 && !from_queue) {
|
||||
this._renderQ_push({
|
||||
fillRect(x, y, width, height, color, fromQueue) {
|
||||
if (this._renderQ.length !== 0 && !fromQueue) {
|
||||
this._renderQPush({
|
||||
'type': 'fill',
|
||||
'x': x,
|
||||
'y': y,
|
||||
@@ -342,14 +317,14 @@ export default class Display {
|
||||
}
|
||||
}
|
||||
|
||||
copyImage(old_x, old_y, new_x, new_y, w, h, from_queue) {
|
||||
if (this._renderQ.length !== 0 && !from_queue) {
|
||||
this._renderQ_push({
|
||||
copyImage(oldX, oldY, newX, newY, w, h, fromQueue) {
|
||||
if (this._renderQ.length !== 0 && !fromQueue) {
|
||||
this._renderQPush({
|
||||
'type': 'copy',
|
||||
'old_x': old_x,
|
||||
'old_y': old_y,
|
||||
'x': new_x,
|
||||
'y': new_y,
|
||||
'oldX': oldX,
|
||||
'oldY': oldY,
|
||||
'x': newX,
|
||||
'y': newY,
|
||||
'width': w,
|
||||
'height': h,
|
||||
});
|
||||
@@ -367,131 +342,54 @@ export default class Display {
|
||||
this._drawCtx.imageSmoothingEnabled = false;
|
||||
|
||||
this._drawCtx.drawImage(this._backbuffer,
|
||||
old_x, old_y, w, h,
|
||||
new_x, new_y, w, h);
|
||||
this._damage(new_x, new_y, w, h);
|
||||
oldX, oldY, w, h,
|
||||
newX, newY, w, h);
|
||||
this._damage(newX, newY, w, h);
|
||||
}
|
||||
}
|
||||
|
||||
imageRect(x, y, mime, arr) {
|
||||
imageRect(x, y, width, height, mime, arr) {
|
||||
/* The internal logic cannot handle empty images, so bail early */
|
||||
if ((width === 0) || (height === 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const img = new Image();
|
||||
img.src = "data: " + mime + ";base64," + Base64.encode(arr);
|
||||
this._renderQ_push({
|
||||
|
||||
this._renderQPush({
|
||||
'type': 'img',
|
||||
'img': img,
|
||||
'x': x,
|
||||
'y': y
|
||||
'y': y,
|
||||
'width': width,
|
||||
'height': height
|
||||
});
|
||||
}
|
||||
|
||||
// start updating a tile
|
||||
startTile(x, y, width, height, color) {
|
||||
this._tile_x = x;
|
||||
this._tile_y = y;
|
||||
if (width === 16 && height === 16) {
|
||||
this._tile = this._tile16x16;
|
||||
} else {
|
||||
this._tile = this._drawCtx.createImageData(width, height);
|
||||
}
|
||||
|
||||
const red = color[2];
|
||||
const green = color[1];
|
||||
const blue = color[0];
|
||||
|
||||
const data = this._tile.data;
|
||||
for (let i = 0; i < width * height * 4; i += 4) {
|
||||
data[i] = red;
|
||||
data[i + 1] = green;
|
||||
data[i + 2] = blue;
|
||||
data[i + 3] = 255;
|
||||
}
|
||||
}
|
||||
|
||||
// update sub-rectangle of the current tile
|
||||
subTile(x, y, w, h, color) {
|
||||
const red = color[2];
|
||||
const green = color[1];
|
||||
const blue = color[0];
|
||||
const xend = x + w;
|
||||
const yend = y + h;
|
||||
|
||||
const data = this._tile.data;
|
||||
const width = this._tile.width;
|
||||
for (let j = y; j < yend; j++) {
|
||||
for (let i = x; i < xend; i++) {
|
||||
const p = (i + (j * width)) * 4;
|
||||
data[p] = red;
|
||||
data[p + 1] = green;
|
||||
data[p + 2] = blue;
|
||||
data[p + 3] = 255;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// draw the current tile to the screen
|
||||
finishTile() {
|
||||
this._drawCtx.putImageData(this._tile, this._tile_x, this._tile_y);
|
||||
this._damage(this._tile_x, this._tile_y,
|
||||
this._tile.width, this._tile.height);
|
||||
}
|
||||
|
||||
blitImage(x, y, width, height, arr, offset, from_queue) {
|
||||
if (this._renderQ.length !== 0 && !from_queue) {
|
||||
blitImage(x, y, width, height, arr, offset, fromQueue) {
|
||||
if (this._renderQ.length !== 0 && !fromQueue) {
|
||||
// NB(directxman12): it's technically more performant here to use preallocated arrays,
|
||||
// but it's a lot of extra work for not a lot of payoff -- if we're using the render queue,
|
||||
// this probably isn't getting called *nearly* as much
|
||||
const new_arr = new Uint8Array(width * height * 4);
|
||||
new_arr.set(new Uint8Array(arr.buffer, 0, new_arr.length));
|
||||
this._renderQ_push({
|
||||
const newArr = new Uint8Array(width * height * 4);
|
||||
newArr.set(new Uint8Array(arr.buffer, 0, newArr.length));
|
||||
this._renderQPush({
|
||||
'type': 'blit',
|
||||
'data': new_arr,
|
||||
'data': newArr,
|
||||
'x': x,
|
||||
'y': y,
|
||||
'width': width,
|
||||
'height': height,
|
||||
});
|
||||
} else {
|
||||
this._bgrxImageData(x, y, width, height, arr, offset);
|
||||
}
|
||||
}
|
||||
|
||||
blitRgbImage(x, y, width, height, arr, offset, from_queue) {
|
||||
if (this._renderQ.length !== 0 && !from_queue) {
|
||||
// NB(directxman12): it's technically more performant here to use preallocated arrays,
|
||||
// but it's a lot of extra work for not a lot of payoff -- if we're using the render queue,
|
||||
// this probably isn't getting called *nearly* as much
|
||||
const new_arr = new Uint8Array(width * height * 3);
|
||||
new_arr.set(new Uint8Array(arr.buffer, 0, new_arr.length));
|
||||
this._renderQ_push({
|
||||
'type': 'blitRgb',
|
||||
'data': new_arr,
|
||||
'x': x,
|
||||
'y': y,
|
||||
'width': width,
|
||||
'height': height,
|
||||
});
|
||||
} else {
|
||||
this._rgbImageData(x, y, width, height, arr, offset);
|
||||
}
|
||||
}
|
||||
|
||||
blitRgbxImage(x, y, width, height, arr, offset, from_queue) {
|
||||
if (this._renderQ.length !== 0 && !from_queue) {
|
||||
// NB(directxman12): it's technically more performant here to use preallocated arrays,
|
||||
// but it's a lot of extra work for not a lot of payoff -- if we're using the render queue,
|
||||
// this probably isn't getting called *nearly* as much
|
||||
const new_arr = new Uint8Array(width * height * 4);
|
||||
new_arr.set(new Uint8Array(arr.buffer, 0, new_arr.length));
|
||||
this._renderQ_push({
|
||||
'type': 'blitRgbx',
|
||||
'data': new_arr,
|
||||
'x': x,
|
||||
'y': y,
|
||||
'width': width,
|
||||
'height': height,
|
||||
});
|
||||
} else {
|
||||
this._rgbxImageData(x, y, width, height, arr, offset);
|
||||
// NB(directxman12): arr must be an Type Array view
|
||||
let data = new Uint8ClampedArray(arr.buffer,
|
||||
arr.byteOffset + offset,
|
||||
width * height * 4);
|
||||
let img = new ImageData(data, width, height);
|
||||
this._drawCtx.putImageData(img, x, y);
|
||||
this._damage(x, y, width, height);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -543,69 +441,30 @@ export default class Display {
|
||||
}
|
||||
|
||||
_setFillColor(color) {
|
||||
const newStyle = 'rgb(' + color[2] + ',' + color[1] + ',' + color[0] + ')';
|
||||
const newStyle = 'rgb(' + color[0] + ',' + color[1] + ',' + color[2] + ')';
|
||||
if (newStyle !== this._prevDrawStyle) {
|
||||
this._drawCtx.fillStyle = newStyle;
|
||||
this._prevDrawStyle = newStyle;
|
||||
}
|
||||
}
|
||||
|
||||
_rgbImageData(x, y, width, height, arr, offset) {
|
||||
const img = this._drawCtx.createImageData(width, height);
|
||||
const data = img.data;
|
||||
for (let i = 0, j = offset; i < width * height * 4; i += 4, j += 3) {
|
||||
data[i] = arr[j];
|
||||
data[i + 1] = arr[j + 1];
|
||||
data[i + 2] = arr[j + 2];
|
||||
data[i + 3] = 255; // Alpha
|
||||
}
|
||||
this._drawCtx.putImageData(img, x, y);
|
||||
this._damage(x, y, img.width, img.height);
|
||||
}
|
||||
|
||||
_bgrxImageData(x, y, width, height, arr, offset) {
|
||||
const img = this._drawCtx.createImageData(width, height);
|
||||
const data = img.data;
|
||||
for (let i = 0, j = offset; i < width * height * 4; i += 4, j += 4) {
|
||||
data[i] = arr[j + 2];
|
||||
data[i + 1] = arr[j + 1];
|
||||
data[i + 2] = arr[j];
|
||||
data[i + 3] = 255; // Alpha
|
||||
}
|
||||
this._drawCtx.putImageData(img, x, y);
|
||||
this._damage(x, y, img.width, img.height);
|
||||
}
|
||||
|
||||
_rgbxImageData(x, y, width, height, arr, offset) {
|
||||
// NB(directxman12): arr must be an Type Array view
|
||||
let img;
|
||||
if (supportsImageMetadata) {
|
||||
img = new ImageData(new Uint8ClampedArray(arr.buffer, arr.byteOffset, width * height * 4), width, height);
|
||||
} else {
|
||||
img = this._drawCtx.createImageData(width, height);
|
||||
img.data.set(new Uint8ClampedArray(arr.buffer, arr.byteOffset, width * height * 4));
|
||||
}
|
||||
this._drawCtx.putImageData(img, x, y);
|
||||
this._damage(x, y, img.width, img.height);
|
||||
}
|
||||
|
||||
_renderQ_push(action) {
|
||||
_renderQPush(action) {
|
||||
this._renderQ.push(action);
|
||||
if (this._renderQ.length === 1) {
|
||||
// If this can be rendered immediately it will be, otherwise
|
||||
// the scanner will wait for the relevant event
|
||||
this._scan_renderQ();
|
||||
this._scanRenderQ();
|
||||
}
|
||||
}
|
||||
|
||||
_resume_renderQ() {
|
||||
_resumeRenderQ() {
|
||||
// "this" is the object that is ready, not the
|
||||
// display object
|
||||
this.removeEventListener('load', this._noVNC_display._resume_renderQ);
|
||||
this._noVNC_display._scan_renderQ();
|
||||
this.removeEventListener('load', this._noVNCDisplay._resumeRenderQ);
|
||||
this._noVNCDisplay._scanRenderQ();
|
||||
}
|
||||
|
||||
_scan_renderQ() {
|
||||
_scanRenderQ() {
|
||||
let ready = true;
|
||||
while (ready && this._renderQ.length > 0) {
|
||||
const a = this._renderQ[0];
|
||||
@@ -614,7 +473,7 @@ export default class Display {
|
||||
this.flip(true);
|
||||
break;
|
||||
case 'copy':
|
||||
this.copyImage(a.old_x, a.old_y, a.x, a.y, a.width, a.height, true);
|
||||
this.copyImage(a.oldX, a.oldY, a.x, a.y, a.width, a.height, true);
|
||||
break;
|
||||
case 'fill':
|
||||
this.fillRect(a.x, a.y, a.width, a.height, a.color, true);
|
||||
@@ -622,18 +481,18 @@ export default class Display {
|
||||
case 'blit':
|
||||
this.blitImage(a.x, a.y, a.width, a.height, a.data, 0, true);
|
||||
break;
|
||||
case 'blitRgb':
|
||||
this.blitRgbImage(a.x, a.y, a.width, a.height, a.data, 0, true);
|
||||
break;
|
||||
case 'blitRgbx':
|
||||
this.blitRgbxImage(a.x, a.y, a.width, a.height, a.data, 0, true);
|
||||
break;
|
||||
case 'img':
|
||||
if (a.img.complete) {
|
||||
if (a.img.width !== a.width || a.img.height !== a.height) {
|
||||
Log.Error("Decoded image has incorrect dimensions. Got " +
|
||||
a.img.width + "x" + a.img.height + ". Expected " +
|
||||
a.width + "x" + a.height + ".");
|
||||
return;
|
||||
}
|
||||
this.drawImage(a.img, a.x, a.y);
|
||||
} else {
|
||||
a.img._noVNC_display = this;
|
||||
a.img.addEventListener('load', this._resume_renderQ);
|
||||
a.img._noVNCDisplay = this;
|
||||
a.img.addEventListener('load', this._resumeRenderQ);
|
||||
// We need to wait for this image to 'load'
|
||||
// to keep things in-order
|
||||
ready = false;
|
||||
|
||||
+4
-1
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* noVNC: HTML5 VNC client
|
||||
* Copyright (C) 2018 The noVNC Authors
|
||||
* Copyright (C) 2019 The noVNC Authors
|
||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||
*
|
||||
* See README.md for usage and integration instructions.
|
||||
@@ -20,12 +20,15 @@ export const encodings = {
|
||||
pseudoEncodingLastRect: -224,
|
||||
pseudoEncodingCursor: -239,
|
||||
pseudoEncodingQEMUExtendedKeyEvent: -258,
|
||||
pseudoEncodingDesktopName: -307,
|
||||
pseudoEncodingExtendedDesktopSize: -308,
|
||||
pseudoEncodingXvp: -309,
|
||||
pseudoEncodingFence: -312,
|
||||
pseudoEncodingContinuousUpdates: -313,
|
||||
pseudoEncodingCompressLevel9: -247,
|
||||
pseudoEncodingCompressLevel0: -256,
|
||||
pseudoEncodingVMwareCursor: 0x574d5664,
|
||||
pseudoEncodingExtendedClipboard: 0xc0a1e5ce
|
||||
};
|
||||
|
||||
export function encodingName(num) {
|
||||
|
||||
+35
-7
@@ -1,3 +1,11 @@
|
||||
/*
|
||||
* noVNC: HTML5 VNC client
|
||||
* Copyright (C) 2020 The noVNC Authors
|
||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||
*
|
||||
* See README.md for usage and integration instructions.
|
||||
*/
|
||||
|
||||
import { inflateInit, inflate, inflateReset } from "../vendor/pako/lib/zlib/inflate.js";
|
||||
import ZStream from "../vendor/pako/lib/zlib/zstream.js";
|
||||
|
||||
@@ -11,12 +19,22 @@ export default class Inflate {
|
||||
inflateInit(this.strm, this.windowBits);
|
||||
}
|
||||
|
||||
inflate(data, flush, expected) {
|
||||
this.strm.input = data;
|
||||
this.strm.avail_in = this.strm.input.length;
|
||||
this.strm.next_in = 0;
|
||||
this.strm.next_out = 0;
|
||||
setInput(data) {
|
||||
if (!data) {
|
||||
//FIXME: flush remaining data.
|
||||
/* eslint-disable camelcase */
|
||||
this.strm.input = null;
|
||||
this.strm.avail_in = 0;
|
||||
this.strm.next_in = 0;
|
||||
} else {
|
||||
this.strm.input = data;
|
||||
this.strm.avail_in = this.strm.input.length;
|
||||
this.strm.next_in = 0;
|
||||
/* eslint-enable camelcase */
|
||||
}
|
||||
}
|
||||
|
||||
inflate(expected) {
|
||||
// resize our output buffer if it's too small
|
||||
// (we could just use multiple chunks, but that would cause an extra
|
||||
// allocation each time to flatten the chunks)
|
||||
@@ -25,9 +43,19 @@ export default class Inflate {
|
||||
this.strm.output = new Uint8Array(this.chunkSize);
|
||||
}
|
||||
|
||||
this.strm.avail_out = this.chunkSize;
|
||||
/* eslint-disable camelcase */
|
||||
this.strm.next_out = 0;
|
||||
this.strm.avail_out = expected;
|
||||
/* eslint-enable camelcase */
|
||||
|
||||
inflate(this.strm, flush);
|
||||
let ret = inflate(this.strm, 0); // Flush argument not used.
|
||||
if (ret < 0) {
|
||||
throw new Error("zlib inflate failed");
|
||||
}
|
||||
|
||||
if (this.strm.next_out != expected) {
|
||||
throw new Error("Incomplete zlib block");
|
||||
}
|
||||
|
||||
return new Uint8Array(this.strm.output.buffer, 0, this.strm.next_out);
|
||||
}
|
||||
|
||||
+32
-28
@@ -35,7 +35,7 @@ function addNumpad(key, 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);
|
||||
addStandard("AltGraph", KeyTable.XK_ISO_Level3_Shift);
|
||||
@@ -43,35 +43,38 @@ addStandard("CapsLock", KeyTable.XK_Caps_Lock);
|
||||
addLeftRight("Control", KeyTable.XK_Control_L, KeyTable.XK_Control_R);
|
||||
// - Fn
|
||||
// - FnLock
|
||||
addLeftRight("Hyper", KeyTable.XK_Super_L, KeyTable.XK_Super_R);
|
||||
addLeftRight("Meta", KeyTable.XK_Super_L, KeyTable.XK_Super_R);
|
||||
addStandard("NumLock", KeyTable.XK_Num_Lock);
|
||||
addStandard("ScrollLock", KeyTable.XK_Scroll_Lock);
|
||||
addLeftRight("Shift", KeyTable.XK_Shift_L, KeyTable.XK_Shift_R);
|
||||
addLeftRight("Super", KeyTable.XK_Super_L, KeyTable.XK_Super_R);
|
||||
// - Symbol
|
||||
// - SymbolLock
|
||||
// - Hyper
|
||||
// - Super
|
||||
|
||||
// 2.3. Whitespace Keys
|
||||
// 3.3. Whitespace Keys
|
||||
|
||||
addNumpad("Enter", KeyTable.XK_Return, KeyTable.XK_KP_Enter);
|
||||
addStandard("Tab", KeyTable.XK_Tab);
|
||||
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("ArrowUp", KeyTable.XK_Up, KeyTable.XK_KP_Up);
|
||||
addNumpad("ArrowLeft", KeyTable.XK_Left, KeyTable.XK_KP_Left);
|
||||
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("Home", KeyTable.XK_Home, KeyTable.XK_KP_Home);
|
||||
addNumpad("PageDown", KeyTable.XK_Next, KeyTable.XK_KP_Next);
|
||||
addNumpad("PageUp", KeyTable.XK_Prior, KeyTable.XK_KP_Prior);
|
||||
|
||||
// 2.5. Editing Keys
|
||||
// 3.5. Editing Keys
|
||||
|
||||
addStandard("Backspace", KeyTable.XK_BackSpace);
|
||||
// Browsers send "Clear" for the numpad 5 without NumLock because
|
||||
// Windows uses VK_Clear for that key. But Unix expects KP_Begin for
|
||||
// that scenario.
|
||||
addNumpad("Clear", KeyTable.XK_Clear, KeyTable.XK_KP_Begin);
|
||||
addStandard("Copy", KeyTable.XF86XK_Copy);
|
||||
// - CrSel
|
||||
@@ -84,7 +87,7 @@ addStandard("Paste", KeyTable.XF86XK_Paste);
|
||||
addStandard("Redo", KeyTable.XK_Redo);
|
||||
addStandard("Undo", KeyTable.XK_Undo);
|
||||
|
||||
// 2.6. UI Keys
|
||||
// 3.6. UI Keys
|
||||
|
||||
// - Accept
|
||||
// - Again (could just be XK_Redo)
|
||||
@@ -102,7 +105,7 @@ addStandard("Select", KeyTable.XK_Select);
|
||||
addStandard("ZoomIn", KeyTable.XF86XK_ZoomIn);
|
||||
addStandard("ZoomOut", KeyTable.XF86XK_ZoomOut);
|
||||
|
||||
// 2.7. Device Keys
|
||||
// 3.7. Device Keys
|
||||
|
||||
addStandard("BrightnessDown", KeyTable.XF86XK_MonBrightnessDown);
|
||||
addStandard("BrightnessUp", KeyTable.XF86XK_MonBrightnessUp);
|
||||
@@ -115,10 +118,10 @@ addStandard("Hibernate", KeyTable.XF86XK_Hibernate);
|
||||
addStandard("Standby", KeyTable.XF86XK_Standby);
|
||||
addStandard("WakeUp", KeyTable.XF86XK_WakeUp);
|
||||
|
||||
// 2.8. IME and Composition Keys
|
||||
// 3.8. IME and Composition Keys
|
||||
|
||||
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("Compose", KeyTable.XK_Multi_key);
|
||||
addStandard("Convert", KeyTable.XK_Henkan);
|
||||
@@ -136,7 +139,7 @@ addStandard("PreviousCandidate", KeyTable.XK_PreviousCandidate);
|
||||
addStandard("SingleCandidate", KeyTable.XK_SingleCandidate);
|
||||
addStandard("HangulMode", KeyTable.XK_Hangul);
|
||||
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("Hankaku", KeyTable.XK_Hankaku);
|
||||
addStandard("Hiragana", KeyTable.XK_Hiragana);
|
||||
@@ -146,9 +149,9 @@ addStandard("KanjiMode", KeyTable.XK_Kanji);
|
||||
addStandard("Katakana", KeyTable.XK_Katakana);
|
||||
addStandard("Romaji", KeyTable.XK_Romaji);
|
||||
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("F2", KeyTable.XK_F2);
|
||||
@@ -187,17 +190,19 @@ addStandard("F34", KeyTable.XK_F34);
|
||||
addStandard("F35", KeyTable.XK_F35);
|
||||
// - Soft1...
|
||||
|
||||
// 2.10. Multimedia Keys
|
||||
// 3.10. Multimedia Keys
|
||||
|
||||
// - ChannelDown
|
||||
// - ChannelUp
|
||||
addStandard("Close", KeyTable.XF86XK_Close);
|
||||
addStandard("MailForward", KeyTable.XF86XK_MailForward);
|
||||
addStandard("MailReply", KeyTable.XF86XK_Reply);
|
||||
addStandard("MainSend", KeyTable.XF86XK_Send);
|
||||
addStandard("MailSend", KeyTable.XF86XK_Send);
|
||||
// - MediaClose
|
||||
addStandard("MediaFastForward", KeyTable.XF86XK_AudioForward);
|
||||
addStandard("MediaPause", KeyTable.XF86XK_AudioPause);
|
||||
addStandard("MediaPlay", KeyTable.XF86XK_AudioPlay);
|
||||
// - MediaPlayPause
|
||||
addStandard("MediaRecord", KeyTable.XF86XK_AudioRecord);
|
||||
addStandard("MediaRewind", KeyTable.XF86XK_AudioRewind);
|
||||
addStandard("MediaStop", KeyTable.XF86XK_AudioStop);
|
||||
@@ -209,20 +214,18 @@ addStandard("Print", KeyTable.XK_Print);
|
||||
addStandard("Save", KeyTable.XF86XK_Save);
|
||||
addStandard("SpellCheck", KeyTable.XF86XK_Spell);
|
||||
|
||||
// 2.11. Multimedia Numpad Keys
|
||||
// 3.11. Multimedia Numpad Keys
|
||||
|
||||
// - Key11
|
||||
// - Key12
|
||||
|
||||
// 2.12. Audio Keys
|
||||
// 3.12. Audio Keys
|
||||
|
||||
// - AudioBalanceLeft
|
||||
// - AudioBalanceRight
|
||||
// - AudioBassDown
|
||||
// - AudioBassBoostDown
|
||||
// - AudioBassBoostToggle
|
||||
// - AudioBassBoostUp
|
||||
// - AudioBassUp
|
||||
// - AudioFaderFront
|
||||
// - AudioFaderRear
|
||||
// - AudioSurroundModeNext
|
||||
@@ -236,19 +239,20 @@ addStandard("AudioVolumeMute", KeyTable.XF86XK_AudioMute);
|
||||
// - MicrophoneVolumeUp
|
||||
addStandard("MicrophoneVolumeMute", KeyTable.XF86XK_AudioMicMute);
|
||||
|
||||
// 2.13. Speech Keys
|
||||
// 3.13. Speech Keys
|
||||
|
||||
// - SpeechCorrectionList
|
||||
// - SpeechInputToggle
|
||||
|
||||
// 2.14. Application Keys
|
||||
// 3.14. Application Keys
|
||||
|
||||
addStandard("LaunchCalculator", KeyTable.XF86XK_Calculator);
|
||||
addStandard("LaunchApplication1", KeyTable.XF86XK_MyComputer);
|
||||
addStandard("LaunchApplication2", KeyTable.XF86XK_Calculator);
|
||||
addStandard("LaunchCalendar", KeyTable.XF86XK_Calendar);
|
||||
// - LaunchContacts
|
||||
addStandard("LaunchMail", KeyTable.XF86XK_Mail);
|
||||
addStandard("LaunchMediaPlayer", KeyTable.XF86XK_AudioMedia);
|
||||
addStandard("LaunchMusicPlayer", KeyTable.XF86XK_Music);
|
||||
addStandard("LaunchMyComputer", KeyTable.XF86XK_MyComputer);
|
||||
addStandard("LaunchPhone", KeyTable.XF86XK_Phone);
|
||||
addStandard("LaunchScreenSaver", KeyTable.XF86XK_ScreenSaver);
|
||||
addStandard("LaunchSpreadsheet", KeyTable.XF86XK_Excel);
|
||||
@@ -256,7 +260,7 @@ addStandard("LaunchWebBrowser", KeyTable.XF86XK_WWW);
|
||||
addStandard("LaunchWebCam", KeyTable.XF86XK_WebCam);
|
||||
addStandard("LaunchWordProcessor", KeyTable.XF86XK_Word);
|
||||
|
||||
// 2.15. Browser Keys
|
||||
// 3.15. Browser Keys
|
||||
|
||||
addStandard("BrowserBack", KeyTable.XF86XK_Back);
|
||||
addStandard("BrowserFavorites", KeyTable.XF86XK_Favorites);
|
||||
@@ -266,15 +270,15 @@ addStandard("BrowserRefresh", KeyTable.XF86XK_Refresh);
|
||||
addStandard("BrowserSearch", KeyTable.XF86XK_Search);
|
||||
addStandard("BrowserStop", KeyTable.XF86XK_Stop);
|
||||
|
||||
// 2.16. Mobile Phone Keys
|
||||
// 3.16. Mobile Phone Keys
|
||||
|
||||
// - A whole bunch...
|
||||
|
||||
// 2.17. TV Keys
|
||||
// 3.17. TV Keys
|
||||
|
||||
// - A whole bunch...
|
||||
|
||||
// 2.18. Media Controller Keys
|
||||
// 3.18. Media Controller Keys
|
||||
|
||||
// - A whole bunch...
|
||||
addStandard("Dimmer", KeyTable.XF86XK_BrightnessAdjust);
|
||||
|
||||
@@ -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 } };
|
||||
}
|
||||
}
|
||||
+32
-129
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* noVNC: HTML5 VNC client
|
||||
* Copyright (C) 2018 The noVNC Authors
|
||||
* Copyright (C) 2019 The noVNC Authors
|
||||
* Licensed under MPL 2.0 or any later version (see LICENSE.txt)
|
||||
*/
|
||||
|
||||
@@ -20,16 +20,13 @@ export default class Keyboard {
|
||||
|
||||
this._keyDownList = {}; // List of depressed keys
|
||||
// (even if they are happy)
|
||||
this._pendingKey = null; // Key waiting for keypress
|
||||
this._altGrArmed = false; // Windows AltGr detection
|
||||
|
||||
// keep these here so we can refer to them later
|
||||
this._eventHandlers = {
|
||||
'keyup': this._handleKeyUp.bind(this),
|
||||
'keydown': this._handleKeyDown.bind(this),
|
||||
'keypress': this._handleKeyPress.bind(this),
|
||||
'blur': this._allKeysUp.bind(this),
|
||||
'checkalt': this._checkAlt.bind(this),
|
||||
};
|
||||
|
||||
// ===== EVENT HANDLERS =====
|
||||
@@ -62,9 +59,7 @@ export default class Keyboard {
|
||||
}
|
||||
|
||||
// Unstable, but we don't have anything else to go on
|
||||
// (don't use it for 'keypress' events thought since
|
||||
// WebKit sets it to the same as charCode)
|
||||
if (e.keyCode && (e.type !== 'keypress')) {
|
||||
if (e.keyCode) {
|
||||
// 229 is used for composition events
|
||||
if (e.keyCode !== 229) {
|
||||
return 'Platform' + e.keyCode;
|
||||
@@ -118,9 +113,7 @@ export default class Keyboard {
|
||||
|
||||
// We cannot handle keys we cannot track, but we also need
|
||||
// to deal with virtual keyboards which omit key info
|
||||
// (iOS omits tracking info on keyup events, which forces us to
|
||||
// special treat that platform here)
|
||||
if ((code === 'Unidentified') || browser.isIOS()) {
|
||||
if (code === 'Unidentified') {
|
||||
if (keysym) {
|
||||
// If it's a virtual keyboard then it should be
|
||||
// sufficient to just send press and release right
|
||||
@@ -137,7 +130,7 @@ export default class Keyboard {
|
||||
// keys around a bit to make things more sane for the remote
|
||||
// server. This method is used by RealVNC and TigerVNC (and
|
||||
// possibly others).
|
||||
if (browser.isMac()) {
|
||||
if (browser.isMac() || browser.isIOS()) {
|
||||
switch (keysym) {
|
||||
case KeyTable.XK_Super_L:
|
||||
keysym = KeyTable.XK_Alt_L;
|
||||
@@ -164,27 +157,27 @@ export default class Keyboard {
|
||||
// state change events. That gets extra confusing for CapsLock
|
||||
// which toggles on each press, but not on release. So pretend
|
||||
// it was a quick press and release of the button.
|
||||
if (browser.isMac() && (code === 'CapsLock')) {
|
||||
if ((browser.isMac() || browser.isIOS()) && (code === 'CapsLock')) {
|
||||
this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', true);
|
||||
this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', false);
|
||||
stopEvent(e);
|
||||
return;
|
||||
}
|
||||
|
||||
// If this is a legacy browser then we'll need to wait for
|
||||
// a keypress event as well
|
||||
// (IE and Edge has a broken KeyboardEvent.key, so we can't
|
||||
// just check for the presence of that field)
|
||||
if (!keysym && (!e.key || browser.isIE() || browser.isEdge())) {
|
||||
this._pendingKey = code;
|
||||
// However we might not get a keypress event if the key
|
||||
// is non-printable, which needs some special fallback
|
||||
// handling
|
||||
setTimeout(this._handleKeyPressTimeout.bind(this), 10, e);
|
||||
// Windows doesn't send proper key releases for a bunch of
|
||||
// Japanese IM keys so we have to fake the release right away
|
||||
const jpBadKeys = [ KeyTable.XK_Zenkaku_Hankaku,
|
||||
KeyTable.XK_Eisu_toggle,
|
||||
KeyTable.XK_Katakana,
|
||||
KeyTable.XK_Hiragana,
|
||||
KeyTable.XK_Romaji ];
|
||||
if (browser.isWindows() && jpBadKeys.includes(keysym)) {
|
||||
this._sendKeyEvent(keysym, code, true);
|
||||
this._sendKeyEvent(keysym, code, false);
|
||||
stopEvent(e);
|
||||
return;
|
||||
}
|
||||
|
||||
this._pendingKey = null;
|
||||
stopEvent(e);
|
||||
|
||||
// Possible start of AltGr sequence? (see above)
|
||||
@@ -199,69 +192,6 @@ export default class Keyboard {
|
||||
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) {
|
||||
stopEvent(e);
|
||||
|
||||
@@ -276,13 +206,28 @@ export default class Keyboard {
|
||||
}
|
||||
|
||||
// See comment in _handleKeyDown()
|
||||
if (browser.isMac() && (code === 'CapsLock')) {
|
||||
if ((browser.isMac() || browser.isIOS()) && (code === 'CapsLock')) {
|
||||
this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', true);
|
||||
this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', false);
|
||||
return;
|
||||
}
|
||||
|
||||
this._sendKeyEvent(this._keyDownList[code], code, false);
|
||||
|
||||
// Windows has a rather nasty bug where it won't send key
|
||||
// release events for a Shift button if the other Shift is still
|
||||
// pressed
|
||||
if (browser.isWindows() && ((code === 'ShiftLeft') ||
|
||||
(code === 'ShiftRight'))) {
|
||||
if ('ShiftRight' in this._keyDownList) {
|
||||
this._sendKeyEvent(this._keyDownList['ShiftRight'],
|
||||
'ShiftRight', false);
|
||||
}
|
||||
if ('ShiftLeft' in this._keyDownList) {
|
||||
this._sendKeyEvent(this._keyDownList['ShiftLeft'],
|
||||
'ShiftLeft', false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_handleAltGrTimeout() {
|
||||
@@ -299,26 +244,6 @@ export default class Keyboard {
|
||||
Log.Debug("<< Keyboard.allKeysUp");
|
||||
}
|
||||
|
||||
// Firefox Alt workaround, see below
|
||||
_checkAlt(e) {
|
||||
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 });
|
||||
target.dispatchEvent(event);
|
||||
});
|
||||
}
|
||||
|
||||
// ===== PUBLIC METHODS =====
|
||||
|
||||
grab() {
|
||||
@@ -326,40 +251,18 @@ export default class Keyboard {
|
||||
|
||||
this._target.addEventListener('keydown', this._eventHandlers.keydown);
|
||||
this._target.addEventListener('keyup', this._eventHandlers.keyup);
|
||||
this._target.addEventListener('keypress', this._eventHandlers.keypress);
|
||||
|
||||
// Release (key up) if window loses focus
|
||||
window.addEventListener('blur', this._eventHandlers.blur);
|
||||
|
||||
// Firefox has broken handling of Alt, so we need to poll as
|
||||
// best we can for releases (still doesn't prevent the menu
|
||||
// from popping up though as we can't call preventDefault())
|
||||
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");
|
||||
}
|
||||
|
||||
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('keyup', this._eventHandlers.keyup);
|
||||
this._target.removeEventListener('keypress', this._eventHandlers.keypress);
|
||||
window.removeEventListener('blur', this._eventHandlers.blur);
|
||||
|
||||
// Release (key up) all keys that are in a down state
|
||||
|
||||
@@ -1,276 +0,0 @@
|
||||
/*
|
||||
* noVNC: HTML5 VNC client
|
||||
* Copyright (C) 2018 The noVNC Authors
|
||||
* Licensed under MPL 2.0 or any later version (see LICENSE.txt)
|
||||
*/
|
||||
|
||||
import * as Log from '../util/logging.js';
|
||||
import { isTouchDevice } from '../util/browser.js';
|
||||
import { setCapture, stopEvent, getPointerEvent } from '../util/events.js';
|
||||
|
||||
const WHEEL_STEP = 10; // Delta threshold for a mouse wheel step
|
||||
const WHEEL_STEP_TIMEOUT = 50; // ms
|
||||
const WHEEL_LINE_HEIGHT = 19;
|
||||
|
||||
export default class Mouse {
|
||||
constructor(target) {
|
||||
this._target = target || document;
|
||||
|
||||
this._doubleClickTimer = null;
|
||||
this._lastTouchPos = null;
|
||||
|
||||
this._pos = null;
|
||||
this._wheelStepXTimer = null;
|
||||
this._wheelStepYTimer = null;
|
||||
this._accumulatedWheelDeltaX = 0;
|
||||
this._accumulatedWheelDeltaY = 0;
|
||||
|
||||
this._eventHandlers = {
|
||||
'mousedown': this._handleMouseDown.bind(this),
|
||||
'mouseup': this._handleMouseUp.bind(this),
|
||||
'mousemove': this._handleMouseMove.bind(this),
|
||||
'mousewheel': this._handleMouseWheel.bind(this),
|
||||
'mousedisable': this._handleMouseDisable.bind(this)
|
||||
};
|
||||
|
||||
// ===== PROPERTIES =====
|
||||
|
||||
this.touchButton = 1; // Button mask (1, 2, 4) for touch devices (0 means ignore clicks)
|
||||
|
||||
// ===== EVENT HANDLERS =====
|
||||
|
||||
this.onmousebutton = () => {}; // Handler for mouse button click/release
|
||||
this.onmousemove = () => {}; // Handler for mouse movement
|
||||
}
|
||||
|
||||
// ===== PRIVATE METHODS =====
|
||||
|
||||
_resetDoubleClickTimer() {
|
||||
this._doubleClickTimer = null;
|
||||
}
|
||||
|
||||
_handleMouseButton(e, down) {
|
||||
this._updateMousePosition(e);
|
||||
let pos = this._pos;
|
||||
|
||||
let bmask;
|
||||
if (e.touches || e.changedTouches) {
|
||||
// Touch device
|
||||
|
||||
// When two touches occur within 500 ms of each other and are
|
||||
// close enough together a double click is triggered.
|
||||
if (down == 1) {
|
||||
if (this._doubleClickTimer === null) {
|
||||
this._lastTouchPos = pos;
|
||||
} else {
|
||||
clearTimeout(this._doubleClickTimer);
|
||||
|
||||
// When the distance between the two touches is small enough
|
||||
// force the position of the latter touch to the position of
|
||||
// the first.
|
||||
|
||||
const xs = this._lastTouchPos.x - pos.x;
|
||||
const ys = this._lastTouchPos.y - pos.y;
|
||||
const d = Math.sqrt((xs * xs) + (ys * ys));
|
||||
|
||||
// The goal is to trigger on a certain physical width, the
|
||||
// devicePixelRatio brings us a bit closer but is not optimal.
|
||||
const threshold = 20 * (window.devicePixelRatio || 1);
|
||||
if (d < threshold) {
|
||||
pos = this._lastTouchPos;
|
||||
}
|
||||
}
|
||||
this._doubleClickTimer = setTimeout(this._resetDoubleClickTimer.bind(this), 500);
|
||||
}
|
||||
bmask = this.touchButton;
|
||||
// If bmask is set
|
||||
} else if (e.which) {
|
||||
/* everything except IE */
|
||||
bmask = 1 << e.button;
|
||||
} else {
|
||||
/* IE including 9 */
|
||||
bmask = (e.button & 0x1) + // Left
|
||||
(e.button & 0x2) * 2 + // Right
|
||||
(e.button & 0x4) / 2; // Middle
|
||||
}
|
||||
|
||||
Log.Debug("onmousebutton " + (down ? "down" : "up") +
|
||||
", x: " + pos.x + ", y: " + pos.y + ", bmask: " + bmask);
|
||||
this.onmousebutton(pos.x, pos.y, down, bmask);
|
||||
|
||||
stopEvent(e);
|
||||
}
|
||||
|
||||
_handleMouseDown(e) {
|
||||
// Touch events have implicit capture
|
||||
if (e.type === "mousedown") {
|
||||
setCapture(this._target);
|
||||
}
|
||||
|
||||
this._handleMouseButton(e, 1);
|
||||
}
|
||||
|
||||
_handleMouseUp(e) {
|
||||
this._handleMouseButton(e, 0);
|
||||
}
|
||||
|
||||
// Mouse wheel events are sent in steps over VNC. This means that the VNC
|
||||
// protocol can't handle a wheel event with specific distance or speed.
|
||||
// Therefor, if we get a lot of small mouse wheel events we combine them.
|
||||
_generateWheelStepX() {
|
||||
|
||||
if (this._accumulatedWheelDeltaX < 0) {
|
||||
this.onmousebutton(this._pos.x, this._pos.y, 1, 1 << 5);
|
||||
this.onmousebutton(this._pos.x, this._pos.y, 0, 1 << 5);
|
||||
} else if (this._accumulatedWheelDeltaX > 0) {
|
||||
this.onmousebutton(this._pos.x, this._pos.y, 1, 1 << 6);
|
||||
this.onmousebutton(this._pos.x, this._pos.y, 0, 1 << 6);
|
||||
}
|
||||
|
||||
this._accumulatedWheelDeltaX = 0;
|
||||
}
|
||||
|
||||
_generateWheelStepY() {
|
||||
|
||||
if (this._accumulatedWheelDeltaY < 0) {
|
||||
this.onmousebutton(this._pos.x, this._pos.y, 1, 1 << 3);
|
||||
this.onmousebutton(this._pos.x, this._pos.y, 0, 1 << 3);
|
||||
} else if (this._accumulatedWheelDeltaY > 0) {
|
||||
this.onmousebutton(this._pos.x, this._pos.y, 1, 1 << 4);
|
||||
this.onmousebutton(this._pos.x, this._pos.y, 0, 1 << 4);
|
||||
}
|
||||
|
||||
this._accumulatedWheelDeltaY = 0;
|
||||
}
|
||||
|
||||
_resetWheelStepTimers() {
|
||||
window.clearTimeout(this._wheelStepXTimer);
|
||||
window.clearTimeout(this._wheelStepYTimer);
|
||||
this._wheelStepXTimer = null;
|
||||
this._wheelStepYTimer = null;
|
||||
}
|
||||
|
||||
_handleMouseWheel(e) {
|
||||
this._resetWheelStepTimers();
|
||||
|
||||
this._updateMousePosition(e);
|
||||
|
||||
let dX = e.deltaX;
|
||||
let dY = e.deltaY;
|
||||
|
||||
// Pixel units unless it's non-zero.
|
||||
// Note that if deltamode is line or page won't matter since we aren't
|
||||
// sending the mouse wheel delta to the server anyway.
|
||||
// The difference between pixel and line can be important however since
|
||||
// we have a threshold that can be smaller than the line height.
|
||||
if (e.deltaMode !== 0) {
|
||||
dX *= WHEEL_LINE_HEIGHT;
|
||||
dY *= WHEEL_LINE_HEIGHT;
|
||||
}
|
||||
|
||||
this._accumulatedWheelDeltaX += dX;
|
||||
this._accumulatedWheelDeltaY += dY;
|
||||
|
||||
// Generate a mouse wheel step event when the accumulated delta
|
||||
// for one of the axes is large enough.
|
||||
// Small delta events that do not pass the threshold get sent
|
||||
// after a timeout.
|
||||
if (Math.abs(this._accumulatedWheelDeltaX) > WHEEL_STEP) {
|
||||
this._generateWheelStepX();
|
||||
} else {
|
||||
this._wheelStepXTimer =
|
||||
window.setTimeout(this._generateWheelStepX.bind(this),
|
||||
WHEEL_STEP_TIMEOUT);
|
||||
}
|
||||
if (Math.abs(this._accumulatedWheelDeltaY) > WHEEL_STEP) {
|
||||
this._generateWheelStepY();
|
||||
} else {
|
||||
this._wheelStepYTimer =
|
||||
window.setTimeout(this._generateWheelStepY.bind(this),
|
||||
WHEEL_STEP_TIMEOUT);
|
||||
}
|
||||
|
||||
stopEvent(e);
|
||||
}
|
||||
|
||||
_handleMouseMove(e) {
|
||||
this._updateMousePosition(e);
|
||||
this.onmousemove(this._pos.x, this._pos.y);
|
||||
stopEvent(e);
|
||||
}
|
||||
|
||||
_handleMouseDisable(e) {
|
||||
/*
|
||||
* Stop propagation if inside canvas area
|
||||
* Note: This is only needed for the 'click' event as it fails
|
||||
* to fire properly for the target element so we have
|
||||
* to listen on the document element instead.
|
||||
*/
|
||||
if (e.target == this._target) {
|
||||
stopEvent(e);
|
||||
}
|
||||
}
|
||||
|
||||
// Update coordinates relative to target
|
||||
_updateMousePosition(e) {
|
||||
e = getPointerEvent(e);
|
||||
const bounds = this._target.getBoundingClientRect();
|
||||
let x;
|
||||
let y;
|
||||
// Clip to target bounds
|
||||
if (e.clientX < bounds.left) {
|
||||
x = 0;
|
||||
} else if (e.clientX >= bounds.right) {
|
||||
x = bounds.width - 1;
|
||||
} else {
|
||||
x = e.clientX - bounds.left;
|
||||
}
|
||||
if (e.clientY < bounds.top) {
|
||||
y = 0;
|
||||
} else if (e.clientY >= bounds.bottom) {
|
||||
y = bounds.height - 1;
|
||||
} else {
|
||||
y = e.clientY - bounds.top;
|
||||
}
|
||||
this._pos = {x: x, y: y};
|
||||
}
|
||||
|
||||
// ===== PUBLIC METHODS =====
|
||||
|
||||
grab() {
|
||||
if (isTouchDevice) {
|
||||
this._target.addEventListener('touchstart', this._eventHandlers.mousedown);
|
||||
this._target.addEventListener('touchend', this._eventHandlers.mouseup);
|
||||
this._target.addEventListener('touchmove', this._eventHandlers.mousemove);
|
||||
}
|
||||
this._target.addEventListener('mousedown', this._eventHandlers.mousedown);
|
||||
this._target.addEventListener('mouseup', this._eventHandlers.mouseup);
|
||||
this._target.addEventListener('mousemove', this._eventHandlers.mousemove);
|
||||
this._target.addEventListener('wheel', this._eventHandlers.mousewheel);
|
||||
|
||||
/* Prevent middle-click pasting (see above for why we bind to document) */
|
||||
document.addEventListener('click', this._eventHandlers.mousedisable);
|
||||
|
||||
/* preventDefault() on mousedown doesn't stop this event for some
|
||||
reason so we have to explicitly block it */
|
||||
this._target.addEventListener('contextmenu', this._eventHandlers.mousedisable);
|
||||
}
|
||||
|
||||
ungrab() {
|
||||
this._resetWheelStepTimers();
|
||||
|
||||
if (isTouchDevice) {
|
||||
this._target.removeEventListener('touchstart', this._eventHandlers.mousedown);
|
||||
this._target.removeEventListener('touchend', this._eventHandlers.mouseup);
|
||||
this._target.removeEventListener('touchmove', this._eventHandlers.mousemove);
|
||||
}
|
||||
this._target.removeEventListener('mousedown', this._eventHandlers.mousedown);
|
||||
this._target.removeEventListener('mouseup', this._eventHandlers.mouseup);
|
||||
this._target.removeEventListener('mousemove', this._eventHandlers.mousemove);
|
||||
this._target.removeEventListener('wheel', this._eventHandlers.mousewheel);
|
||||
|
||||
document.removeEventListener('click', this._eventHandlers.mousedisable);
|
||||
|
||||
this._target.removeEventListener('contextmenu', this._eventHandlers.mousedisable);
|
||||
}
|
||||
}
|
||||
+54
-27
@@ -1,3 +1,4 @@
|
||||
import KeyTable from "./keysym.js";
|
||||
import keysyms from "./keysymdef.js";
|
||||
import vkeys from "./vkeys.js";
|
||||
import fixedkeys from "./fixedkeys.js";
|
||||
@@ -21,9 +22,8 @@ export function getKeycode(evt) {
|
||||
}
|
||||
|
||||
// The de-facto standard is to use Windows Virtual-Key codes
|
||||
// in the 'keyCode' field for non-printable characters. However
|
||||
// Webkit sets it to the same as charCode in 'keypress' events.
|
||||
if ((evt.type !== 'keypress') && (evt.keyCode in vkeys)) {
|
||||
// in the 'keyCode' field for non-printable characters
|
||||
if (evt.keyCode in vkeys) {
|
||||
let code = vkeys[evt.keyCode];
|
||||
|
||||
// macOS has messed up this code for some reason
|
||||
@@ -68,29 +68,11 @@ export function getKeycode(evt) {
|
||||
export function getKey(evt) {
|
||||
// Are we getting a proper key value?
|
||||
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
|
||||
switch (evt.key) {
|
||||
case 'OS': return 'Meta';
|
||||
case 'LaunchMyComputer': return 'LaunchApplication1';
|
||||
case 'LaunchCalculator': return 'LaunchApplication2';
|
||||
}
|
||||
|
||||
// iOS leaks some OS names
|
||||
@@ -102,11 +84,12 @@ export function getKey(evt) {
|
||||
case 'UIKeyInputEscape': return 'Escape';
|
||||
}
|
||||
|
||||
// IE and Edge have broken handling of AltGraph so we cannot
|
||||
// trust them for printable characters
|
||||
if ((evt.key.length !== 1) || (!browser.isIE() && !browser.isEdge())) {
|
||||
return evt.key;
|
||||
// Broken behaviour in Chrome
|
||||
if ((evt.key === '\x00') && (evt.code === 'NumpadDecimal')) {
|
||||
return 'Delete';
|
||||
}
|
||||
|
||||
return evt.key;
|
||||
}
|
||||
|
||||
// Try to deduce it based on the physical key
|
||||
@@ -141,10 +124,54 @@ export function getKeysym(evt) {
|
||||
location = 2;
|
||||
}
|
||||
|
||||
// And for Clear
|
||||
if ((key === 'Clear') && (location === 3)) {
|
||||
let code = getKeycode(evt);
|
||||
if (code === 'NumLock') {
|
||||
location = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if ((location === undefined) || (location > 3)) {
|
||||
location = 0;
|
||||
}
|
||||
|
||||
// The original Meta key now gets confused with the Windows key
|
||||
// https://bugs.chromium.org/p/chromium/issues/detail?id=1020141
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=1232918
|
||||
if (key === 'Meta') {
|
||||
let code = getKeycode(evt);
|
||||
if (code === 'AltLeft') {
|
||||
return KeyTable.XK_Meta_L;
|
||||
} else if (code === 'AltRight') {
|
||||
return KeyTable.XK_Meta_R;
|
||||
}
|
||||
}
|
||||
|
||||
// macOS has Clear instead of NumLock, but the remote system is
|
||||
// probably not macOS, so lying here is probably best...
|
||||
if (key === 'Clear') {
|
||||
let code = getKeycode(evt);
|
||||
if (code === 'NumLock') {
|
||||
return KeyTable.XK_Num_Lock;
|
||||
}
|
||||
}
|
||||
|
||||
// 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];
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ export default {
|
||||
0x08: 'Backspace',
|
||||
0x09: 'Tab',
|
||||
0x0a: 'NumpadClear',
|
||||
0x0c: 'Numpad5', // IE11 sends evt.keyCode: 12 when numlock is off
|
||||
0x0d: 'Enter',
|
||||
0x10: 'ShiftLeft',
|
||||
0x11: 'ControlLeft',
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*
|
||||
* This file is auto-generated from keymaps.csv on 2017-05-31 16:20
|
||||
* Database checksum sha256(92fd165507f2a3b8c5b3fa56e425d45788dbcb98cf067a307527d91ce22cab94)
|
||||
* This file is auto-generated from keymaps.csv
|
||||
* Database checksum sha256(76d68c10e97d37fe2ea459e210125ae41796253fb217e900bf2983ade13a7920)
|
||||
* 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 {
|
||||
"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 */
|
||||
"KeyY": 0x15, /* html:KeyY (KeyY) -> linux:21 (KEY_Y) -> atset1:21 */
|
||||
"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 */
|
||||
"Lang4": 0x77, /* html:Lang4 (Lang4) -> linux:91 (KEY_HIRAGANA) -> atset1:119 */
|
||||
"Lang5": 0x76, /* html:Lang5 (Lang5) -> linux:85 (KEY_ZENKAKUHANKAKU) -> atset1:118 */
|
||||
|
||||
+1314
-381
File diff suppressed because it is too large
Load Diff
+33
-20
@@ -1,9 +1,11 @@
|
||||
/*
|
||||
* noVNC: HTML5 VNC client
|
||||
* Copyright (C) 2018 The noVNC Authors
|
||||
* Copyright (C) 2019 The noVNC Authors
|
||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||
*
|
||||
* See README.md for usage and integration instructions.
|
||||
*
|
||||
* Browser feature support detection
|
||||
*/
|
||||
|
||||
import * as Log from './logging.js';
|
||||
@@ -31,7 +33,7 @@ try {
|
||||
const target = document.createElement('canvas');
|
||||
target.style.cursor = 'url("data:image/x-icon;base64,AAACAAEACAgAAAIAAgA4AQAAFgAAACgAAAAIAAAAEAAAAAEAIAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAAAAAAAAAAAAAAAA==") 2 2, default';
|
||||
|
||||
if (target.style.cursor) {
|
||||
if (target.style.cursor.indexOf("url") === 0) {
|
||||
Log.Info("Data URI scheme cursor supported");
|
||||
_supportsCursorURIs = true;
|
||||
} else {
|
||||
@@ -43,14 +45,37 @@ try {
|
||||
|
||||
export const supportsCursorURIs = _supportsCursorURIs;
|
||||
|
||||
let _supportsImageMetadata = false;
|
||||
let _hasScrollbarGutter = true;
|
||||
try {
|
||||
new ImageData(new Uint8ClampedArray(4), 1, 1);
|
||||
_supportsImageMetadata = true;
|
||||
} catch (ex) {
|
||||
// ignore failure
|
||||
// Create invisible container
|
||||
const container = document.createElement('div');
|
||||
container.style.visibility = 'hidden';
|
||||
container.style.overflow = 'scroll'; // forcing scrollbars
|
||||
document.body.appendChild(container);
|
||||
|
||||
// Create a div and place it in the container
|
||||
const child = document.createElement('div');
|
||||
container.appendChild(child);
|
||||
|
||||
// Calculate the difference between the container's full width
|
||||
// and the child's width - the difference is the scrollbars
|
||||
const scrollbarWidth = (container.offsetWidth - child.offsetWidth);
|
||||
|
||||
// Clean up
|
||||
container.parentNode.removeChild(container);
|
||||
|
||||
_hasScrollbarGutter = scrollbarWidth != 0;
|
||||
} catch (exc) {
|
||||
Log.Error("Scrollbar test exception: " + exc);
|
||||
}
|
||||
export const supportsImageMetadata = _supportsImageMetadata;
|
||||
export const hasScrollbarGutter = _hasScrollbarGutter;
|
||||
|
||||
/*
|
||||
* The functions for detection of platforms and browsers below are exported
|
||||
* but the use of these should be minimized as much as possible.
|
||||
*
|
||||
* It's better to use feature detection than platform detection.
|
||||
*/
|
||||
|
||||
export function isMac() {
|
||||
return navigator && !!(/mac/i).exec(navigator.platform);
|
||||
@@ -67,23 +92,11 @@ export function isIOS() {
|
||||
!!(/ipod/i).exec(navigator.platform));
|
||||
}
|
||||
|
||||
export function isAndroid() {
|
||||
return navigator && !!(/android/i).exec(navigator.userAgent);
|
||||
}
|
||||
|
||||
export function isSafari() {
|
||||
return navigator && (navigator.userAgent.indexOf('Safari') !== -1 &&
|
||||
navigator.userAgent.indexOf('Chrome') === -1);
|
||||
}
|
||||
|
||||
export function isIE() {
|
||||
return navigator && !!(/trident/i).exec(navigator.userAgent);
|
||||
}
|
||||
|
||||
export function isEdge() {
|
||||
return navigator && !!(/edge/i).exec(navigator.userAgent);
|
||||
}
|
||||
|
||||
export function isFirefox() {
|
||||
return navigator && !!(/firefox/i).exec(navigator.userAgent);
|
||||
}
|
||||
|
||||
+67
-45
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* noVNC: HTML5 VNC client
|
||||
* Copyright (C) 2018 The noVNC Authors
|
||||
* Copyright (C) 2019 The noVNC Authors
|
||||
* Licensed under MPL 2.0 or any later version (see LICENSE.txt)
|
||||
*/
|
||||
|
||||
@@ -20,7 +20,6 @@ export default class Cursor {
|
||||
this._canvas.style.pointerEvents = 'none';
|
||||
// Can't use "display" because of Firefox bug #1445997
|
||||
this._canvas.style.visibility = 'hidden';
|
||||
document.body.appendChild(this._canvas);
|
||||
}
|
||||
|
||||
this._position = { x: 0, y: 0 };
|
||||
@@ -31,9 +30,6 @@ export default class Cursor {
|
||||
'mouseleave': this._handleMouseLeave.bind(this),
|
||||
'mousemove': this._handleMouseMove.bind(this),
|
||||
'mouseup': this._handleMouseUp.bind(this),
|
||||
'touchstart': this._handleTouchStart.bind(this),
|
||||
'touchmove': this._handleTouchMove.bind(this),
|
||||
'touchend': this._handleTouchEnd.bind(this),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -45,25 +41,23 @@ export default class Cursor {
|
||||
this._target = target;
|
||||
|
||||
if (useFallback) {
|
||||
// FIXME: These don't fire properly except for mouse
|
||||
/// movement in IE. We want to also capture element
|
||||
// movement, size changes, visibility, etc.
|
||||
document.body.appendChild(this._canvas);
|
||||
|
||||
const options = { capture: true, passive: true };
|
||||
this._target.addEventListener('mouseover', this._eventHandlers.mouseover, options);
|
||||
this._target.addEventListener('mouseleave', this._eventHandlers.mouseleave, options);
|
||||
this._target.addEventListener('mousemove', this._eventHandlers.mousemove, options);
|
||||
this._target.addEventListener('mouseup', this._eventHandlers.mouseup, options);
|
||||
|
||||
// There is no "touchleave" so we monitor touchstart globally
|
||||
window.addEventListener('touchstart', this._eventHandlers.touchstart, options);
|
||||
this._target.addEventListener('touchmove', this._eventHandlers.touchmove, options);
|
||||
this._target.addEventListener('touchend', this._eventHandlers.touchend, options);
|
||||
}
|
||||
|
||||
this.clear();
|
||||
}
|
||||
|
||||
detach() {
|
||||
if (!this._target) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (useFallback) {
|
||||
const options = { capture: true, passive: true };
|
||||
this._target.removeEventListener('mouseover', this._eventHandlers.mouseover, options);
|
||||
@@ -71,9 +65,7 @@ export default class Cursor {
|
||||
this._target.removeEventListener('mousemove', this._eventHandlers.mousemove, options);
|
||||
this._target.removeEventListener('mouseup', this._eventHandlers.mouseup, options);
|
||||
|
||||
window.removeEventListener('touchstart', this._eventHandlers.touchstart, options);
|
||||
this._target.removeEventListener('touchmove', this._eventHandlers.touchmove, options);
|
||||
this._target.removeEventListener('touchend', this._eventHandlers.touchend, options);
|
||||
document.body.removeChild(this._canvas);
|
||||
}
|
||||
|
||||
this._target = null;
|
||||
@@ -95,14 +87,7 @@ export default class Cursor {
|
||||
this._canvas.width = w;
|
||||
this._canvas.height = h;
|
||||
|
||||
let img;
|
||||
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));
|
||||
}
|
||||
let img = new ImageData(new Uint8ClampedArray(rgba), w, h);
|
||||
ctx.clearRect(0, 0, w, h);
|
||||
ctx.putImageData(img, 0, 0);
|
||||
|
||||
@@ -124,6 +109,27 @@ export default class Cursor {
|
||||
this._hotSpot.y = 0;
|
||||
}
|
||||
|
||||
// Mouse events might be emulated, this allows
|
||||
// moving the cursor in such cases
|
||||
move(clientX, clientY) {
|
||||
if (!useFallback) {
|
||||
return;
|
||||
}
|
||||
// clientX/clientY are relative the _visual viewport_,
|
||||
// but our position is relative the _layout viewport_,
|
||||
// so try to compensate when we can
|
||||
if (window.visualViewport) {
|
||||
this._position.x = clientX + window.visualViewport.offsetLeft;
|
||||
this._position.y = clientY + window.visualViewport.offsetTop;
|
||||
} else {
|
||||
this._position.x = clientX;
|
||||
this._position.y = clientY;
|
||||
}
|
||||
this._updatePosition();
|
||||
let target = document.elementFromPoint(clientX, clientY);
|
||||
this._updateVisibility(target);
|
||||
}
|
||||
|
||||
_handleMouseOver(event) {
|
||||
// This event could be because we're entering the target, or
|
||||
// moving around amongst its sub elements. Let the move handler
|
||||
@@ -132,7 +138,8 @@ export default class Cursor {
|
||||
}
|
||||
|
||||
_handleMouseLeave(event) {
|
||||
this._hideCursor();
|
||||
// Check if we should show the cursor on the element we are leaving to
|
||||
this._updateVisibility(event.relatedTarget);
|
||||
}
|
||||
|
||||
_handleMouseMove(event) {
|
||||
@@ -150,27 +157,29 @@ export default class Cursor {
|
||||
// now and adjust visibility based on that.
|
||||
let target = document.elementFromPoint(event.clientX, event.clientY);
|
||||
this._updateVisibility(target);
|
||||
}
|
||||
|
||||
_handleTouchStart(event) {
|
||||
// Just as for mouseover, we let the move handler deal with it
|
||||
this._handleTouchMove(event);
|
||||
}
|
||||
|
||||
_handleTouchMove(event) {
|
||||
this._updateVisibility(event.target);
|
||||
|
||||
this._position.x = event.changedTouches[0].clientX - this._hotSpot.x;
|
||||
this._position.y = event.changedTouches[0].clientY - this._hotSpot.y;
|
||||
|
||||
this._updatePosition();
|
||||
}
|
||||
|
||||
_handleTouchEnd(event) {
|
||||
// Same principle as for mouseup
|
||||
let target = document.elementFromPoint(event.changedTouches[0].clientX,
|
||||
event.changedTouches[0].clientY);
|
||||
this._updateVisibility(target);
|
||||
// Captures end with a mouseup but we can't know the event order of
|
||||
// mouseup vs releaseCapture.
|
||||
//
|
||||
// In the cases when releaseCapture comes first, the code above is
|
||||
// enough.
|
||||
//
|
||||
// In the cases when the mouseup comes first, we need wait for the
|
||||
// browser to flush all events and then check again if the cursor
|
||||
// should be visible.
|
||||
if (this._captureIsActive()) {
|
||||
window.setTimeout(() => {
|
||||
// We might have detached at this point
|
||||
if (!this._target) {
|
||||
return;
|
||||
}
|
||||
// Refresh the target from elementFromPoint since queued events
|
||||
// might have altered the DOM
|
||||
target = document.elementFromPoint(event.clientX,
|
||||
event.clientY);
|
||||
this._updateVisibility(target);
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
|
||||
_showCursor() {
|
||||
@@ -189,6 +198,9 @@ export default class Cursor {
|
||||
// (i.e. are we over the target, or a child of the target without a
|
||||
// different cursor set)
|
||||
_shouldShowCursor(target) {
|
||||
if (!target) {
|
||||
return false;
|
||||
}
|
||||
// Easy case
|
||||
if (target === this._target) {
|
||||
return true;
|
||||
@@ -207,6 +219,11 @@ export default class Cursor {
|
||||
}
|
||||
|
||||
_updateVisibility(target) {
|
||||
// When the cursor target has capture we want to show the cursor.
|
||||
// So, if a capture is active - look at the captured element instead.
|
||||
if (this._captureIsActive()) {
|
||||
target = document.captureElement;
|
||||
}
|
||||
if (this._shouldShowCursor(target)) {
|
||||
this._showCursor();
|
||||
} else {
|
||||
@@ -218,4 +235,9 @@ export default class Cursor {
|
||||
this._canvas.style.left = this._position.x + "px";
|
||||
this._canvas.style.top = this._position.y + "px";
|
||||
}
|
||||
|
||||
_captureIsActive() {
|
||||
return document.captureElement &&
|
||||
document.documentElement.contains(document.captureElement);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
+46
-47
@@ -21,7 +21,8 @@ export function stopEvent(e) {
|
||||
|
||||
// Emulate Element.setCapture() when not supported
|
||||
let _captureRecursion = false;
|
||||
let _captureElem = null;
|
||||
let _elementForUnflushedEvents = null;
|
||||
document.captureElement = null;
|
||||
function _captureProxy(e) {
|
||||
// Recursion protection as we'll see our own event
|
||||
if (_captureRecursion) return;
|
||||
@@ -30,7 +31,11 @@ function _captureProxy(e) {
|
||||
const newEv = new e.constructor(e.type, e);
|
||||
|
||||
_captureRecursion = true;
|
||||
_captureElem.dispatchEvent(newEv);
|
||||
if (document.captureElement) {
|
||||
document.captureElement.dispatchEvent(newEv);
|
||||
} else {
|
||||
_elementForUnflushedEvents.dispatchEvent(newEv);
|
||||
}
|
||||
_captureRecursion = false;
|
||||
|
||||
// Avoid double events
|
||||
@@ -48,58 +53,52 @@ function _captureProxy(e) {
|
||||
}
|
||||
|
||||
// Follow cursor style of target element
|
||||
function _captureElemChanged() {
|
||||
const captureElem = document.getElementById("noVNC_mouse_capture_elem");
|
||||
captureElem.style.cursor = window.getComputedStyle(_captureElem).cursor;
|
||||
function _capturedElemChanged() {
|
||||
const proxyElem = document.getElementById("noVNC_mouse_capture_elem");
|
||||
proxyElem.style.cursor = window.getComputedStyle(document.captureElement).cursor;
|
||||
}
|
||||
|
||||
const _captureObserver = new MutationObserver(_captureElemChanged);
|
||||
const _captureObserver = new MutationObserver(_capturedElemChanged);
|
||||
|
||||
let _captureIndex = 0;
|
||||
|
||||
export function setCapture(elem) {
|
||||
if (elem.setCapture) {
|
||||
|
||||
elem.setCapture();
|
||||
|
||||
// IE releases capture on 'click' events which might not trigger
|
||||
elem.addEventListener('mouseup', releaseCapture);
|
||||
export function setCapture(target) {
|
||||
if (target.setCapture) {
|
||||
|
||||
target.setCapture();
|
||||
document.captureElement = target;
|
||||
} else {
|
||||
// Release any existing capture in case this method is
|
||||
// called multiple times without coordination
|
||||
releaseCapture();
|
||||
|
||||
let captureElem = document.getElementById("noVNC_mouse_capture_elem");
|
||||
let proxyElem = document.getElementById("noVNC_mouse_capture_elem");
|
||||
|
||||
if (captureElem === null) {
|
||||
captureElem = document.createElement("div");
|
||||
captureElem.id = "noVNC_mouse_capture_elem";
|
||||
captureElem.style.position = "fixed";
|
||||
captureElem.style.top = "0px";
|
||||
captureElem.style.left = "0px";
|
||||
captureElem.style.width = "100%";
|
||||
captureElem.style.height = "100%";
|
||||
captureElem.style.zIndex = 10000;
|
||||
captureElem.style.display = "none";
|
||||
document.body.appendChild(captureElem);
|
||||
if (proxyElem === null) {
|
||||
proxyElem = document.createElement("div");
|
||||
proxyElem.id = "noVNC_mouse_capture_elem";
|
||||
proxyElem.style.position = "fixed";
|
||||
proxyElem.style.top = "0px";
|
||||
proxyElem.style.left = "0px";
|
||||
proxyElem.style.width = "100%";
|
||||
proxyElem.style.height = "100%";
|
||||
proxyElem.style.zIndex = 10000;
|
||||
proxyElem.style.display = "none";
|
||||
document.body.appendChild(proxyElem);
|
||||
|
||||
// This is to make sure callers don't get confused by having
|
||||
// our blocking element as the target
|
||||
captureElem.addEventListener('contextmenu', _captureProxy);
|
||||
proxyElem.addEventListener('contextmenu', _captureProxy);
|
||||
|
||||
captureElem.addEventListener('mousemove', _captureProxy);
|
||||
captureElem.addEventListener('mouseup', _captureProxy);
|
||||
proxyElem.addEventListener('mousemove', _captureProxy);
|
||||
proxyElem.addEventListener('mouseup', _captureProxy);
|
||||
}
|
||||
|
||||
_captureElem = elem;
|
||||
_captureIndex++;
|
||||
document.captureElement = target;
|
||||
|
||||
// Track cursor and get initial cursor
|
||||
_captureObserver.observe(elem, {attributes: true});
|
||||
_captureElemChanged();
|
||||
_captureObserver.observe(target, {attributes: true});
|
||||
_capturedElemChanged();
|
||||
|
||||
captureElem.style.display = "";
|
||||
proxyElem.style.display = "";
|
||||
|
||||
// We listen to events on window in order to keep tracking if it
|
||||
// happens to leave the viewport
|
||||
@@ -112,26 +111,26 @@ export function releaseCapture() {
|
||||
if (document.releaseCapture) {
|
||||
|
||||
document.releaseCapture();
|
||||
document.captureElement = null;
|
||||
|
||||
} else {
|
||||
if (!_captureElem) {
|
||||
if (!document.captureElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
// There might be events already queued, so we need to wait for
|
||||
// them to flush. E.g. contextmenu in Microsoft Edge
|
||||
window.setTimeout((expected) => {
|
||||
// Only clear it if it's the expected grab (i.e. no one
|
||||
// else has initiated a new grab)
|
||||
if (_captureIndex === expected) {
|
||||
_captureElem = null;
|
||||
}
|
||||
}, 0, _captureIndex);
|
||||
// There might be events already queued. The event proxy needs
|
||||
// access to the captured element for these queued events.
|
||||
// E.g. contextmenu (right-click) in Microsoft Edge
|
||||
//
|
||||
// Before removing the capturedElem pointer we save it to a
|
||||
// temporary variable that the unflushed events can use.
|
||||
_elementForUnflushedEvents = document.captureElement;
|
||||
document.captureElement = null;
|
||||
|
||||
_captureObserver.disconnect();
|
||||
|
||||
const captureElem = document.getElementById("noVNC_mouse_capture_elem");
|
||||
captureElem.style.display = "none";
|
||||
const proxyElem = document.getElementById("noVNC_mouse_capture_elem");
|
||||
proxyElem.style.display = "none";
|
||||
|
||||
window.removeEventListener('mousemove', _captureProxy);
|
||||
window.removeEventListener('mouseup', _captureProxy);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* noVNC: HTML5 VNC client
|
||||
* Copyright (C) 2018 The noVNC Authors
|
||||
* Copyright (C) 2019 The noVNC Authors
|
||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||
*
|
||||
* See README.md for usage and integration instructions.
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* noVNC: HTML5 VNC client
|
||||
* Copyright (C) 2020 The noVNC Authors
|
||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||
*
|
||||
* See README.md for usage and integration instructions.
|
||||
*/
|
||||
|
||||
export function toUnsigned32bit(toConvert) {
|
||||
return toConvert >>> 0;
|
||||
}
|
||||
|
||||
export function toSigned32bit(toConvert) {
|
||||
return toConvert | 0;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* noVNC: HTML5 VNC client
|
||||
* Copyright (C) 2018 The noVNC Authors
|
||||
* Copyright (C) 2019 The noVNC Authors
|
||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||
*
|
||||
* See README.md for usage and integration instructions.
|
||||
@@ -10,18 +10,18 @@
|
||||
* Logging/debug routines
|
||||
*/
|
||||
|
||||
let _log_level = 'warn';
|
||||
let _logLevel = 'warn';
|
||||
|
||||
let Debug = () => {};
|
||||
let Info = () => {};
|
||||
let Warn = () => {};
|
||||
let Error = () => {};
|
||||
|
||||
export function init_logging(level) {
|
||||
export function initLogging(level) {
|
||||
if (typeof level === 'undefined') {
|
||||
level = _log_level;
|
||||
level = _logLevel;
|
||||
} else {
|
||||
_log_level = level;
|
||||
_logLevel = level;
|
||||
}
|
||||
|
||||
Debug = Info = Warn = Error = () => {};
|
||||
@@ -46,11 +46,11 @@ export function init_logging(level) {
|
||||
}
|
||||
}
|
||||
|
||||
export function get_logging() {
|
||||
return _log_level;
|
||||
export function getLogging() {
|
||||
return _logLevel;
|
||||
}
|
||||
|
||||
export { Debug, Info, Warn, Error };
|
||||
|
||||
// Initialize logging level
|
||||
init_logging();
|
||||
initLogging();
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
/*
|
||||
* noVNC: HTML5 VNC client
|
||||
* Copyright (C) 2018 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;
|
||||
}
|
||||
})();
|
||||
+20
-6
@@ -1,14 +1,28 @@
|
||||
/*
|
||||
* noVNC: HTML5 VNC client
|
||||
* Copyright (C) 2018 The noVNC Authors
|
||||
* Copyright (C) 2019 The noVNC Authors
|
||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||
*
|
||||
* See README.md for usage and integration instructions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Decode from UTF-8
|
||||
*/
|
||||
export function decodeUTF8(utf8string) {
|
||||
return decodeURIComponent(escape(utf8string));
|
||||
// Decode from UTF-8
|
||||
export function decodeUTF8(utf8string, allowLatin1=false) {
|
||||
try {
|
||||
return decodeURIComponent(escape(utf8string));
|
||||
} catch (e) {
|
||||
if (e instanceof URIError) {
|
||||
if (allowLatin1) {
|
||||
// If we allow Latin1 we can ignore any decoding fails
|
||||
// and in these cases return the original string
|
||||
return utf8string;
|
||||
}
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
// Encode to UTF-8
|
||||
export function encodeUTF8(DOMString) {
|
||||
return unescape(encodeURIComponent(DOMString));
|
||||
}
|
||||
|
||||
+107
-44
@@ -1,10 +1,10 @@
|
||||
/*
|
||||
* Websock: high-performance binary WebSockets
|
||||
* Copyright (C) 2018 The noVNC Authors
|
||||
* Websock: high-performance buffering wrapper
|
||||
* Copyright (C) 2019 The noVNC Authors
|
||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||
*
|
||||
* Websock is similar to the standard WebSocket object but with extra
|
||||
* buffer handling.
|
||||
* Websock is similar to the standard WebSocket / RTCDataChannel object
|
||||
* but with extra buffer handling.
|
||||
*
|
||||
* Websock has built-in receive queue buffering; the message event
|
||||
* does not contain actual data but is simply a notification that
|
||||
@@ -17,17 +17,43 @@ import * as Log from './util/logging.js';
|
||||
// this has performance issues in some versions Chromium, and
|
||||
// doesn't gain a tremendous amount of performance increase in Firefox
|
||||
// at the moment. It may be valuable to turn it on in the future.
|
||||
const ENABLE_COPYWITHIN = false;
|
||||
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 {
|
||||
constructor() {
|
||||
this._websocket = null; // WebSocket object
|
||||
this._websocket = null; // WebSocket or RTCDataChannel object
|
||||
|
||||
this._rQi = 0; // Receive queue index
|
||||
this._rQlen = 0; // Next write position in the receive queue
|
||||
this._rQbufferSize = 1024 * 1024 * 4; // Receive queue buffer size (4 MiB)
|
||||
this._rQmax = this._rQbufferSize / 8;
|
||||
// called in init: this._rQ = new Uint8Array(this._rQbufferSize);
|
||||
this._rQ = null; // Receive queue
|
||||
|
||||
@@ -45,6 +71,29 @@ export default class Websock {
|
||||
}
|
||||
|
||||
// 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() {
|
||||
return this._sQ;
|
||||
}
|
||||
@@ -142,8 +191,8 @@ export default class Websock {
|
||||
// Send Queue
|
||||
|
||||
flush() {
|
||||
if (this._sQlen > 0 && this._websocket.readyState === WebSocket.OPEN) {
|
||||
this._websocket.send(this._encode_message());
|
||||
if (this._sQlen > 0 && this.readyState === 'open') {
|
||||
this._websocket.send(this._encodeMessage());
|
||||
this._sQlen = 0;
|
||||
}
|
||||
}
|
||||
@@ -154,7 +203,7 @@ export default class Websock {
|
||||
this.flush();
|
||||
}
|
||||
|
||||
send_string(str) {
|
||||
sendString(str) {
|
||||
this.send(str.split('').map(chr => chr.charCodeAt(0)));
|
||||
}
|
||||
|
||||
@@ -167,24 +216,37 @@ export default class Websock {
|
||||
this._eventHandlers[evt] = handler;
|
||||
}
|
||||
|
||||
_allocate_buffers() {
|
||||
_allocateBuffers() {
|
||||
this._rQ = new Uint8Array(this._rQbufferSize);
|
||||
this._sQ = new Uint8Array(this._sQbufferSize);
|
||||
}
|
||||
|
||||
init() {
|
||||
this._allocate_buffers();
|
||||
this._allocateBuffers();
|
||||
this._rQi = 0;
|
||||
this._websocket = null;
|
||||
}
|
||||
|
||||
open(uri, protocols) {
|
||||
this.attach(new WebSocket(uri, protocols));
|
||||
}
|
||||
|
||||
attach(rawChannel) {
|
||||
this.init();
|
||||
|
||||
this._websocket = new WebSocket(uri, protocols);
|
||||
this._websocket.binaryType = 'arraybuffer';
|
||||
// Must get object and class methods to be compatible with the tests.
|
||||
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 = () => {
|
||||
Log.Debug('>> WebSock.onopen');
|
||||
if (this._websocket.protocol) {
|
||||
@@ -194,11 +256,13 @@ export default class Websock {
|
||||
this._eventHandlers.open();
|
||||
Log.Debug("<< WebSock.onopen");
|
||||
};
|
||||
|
||||
this._websocket.onclose = (e) => {
|
||||
Log.Debug(">> WebSock.onclose");
|
||||
this._eventHandlers.close(e);
|
||||
Log.Debug("<< WebSock.onclose");
|
||||
};
|
||||
|
||||
this._websocket.onerror = (e) => {
|
||||
Log.Debug(">> WebSock.onerror: " + e);
|
||||
this._eventHandlers.error(e);
|
||||
@@ -208,8 +272,8 @@ export default class Websock {
|
||||
|
||||
close() {
|
||||
if (this._websocket) {
|
||||
if ((this._websocket.readyState === WebSocket.OPEN) ||
|
||||
(this._websocket.readyState === WebSocket.CONNECTING)) {
|
||||
if (this.readyState === 'connecting' ||
|
||||
this.readyState === 'open') {
|
||||
Log.Info("Closing WebSocket connection");
|
||||
this._websocket.close();
|
||||
}
|
||||
@@ -219,69 +283,68 @@ export default class Websock {
|
||||
}
|
||||
|
||||
// private methods
|
||||
_encode_message() {
|
||||
_encodeMessage() {
|
||||
// Put in a binary arraybuffer
|
||||
// according to the spec, you can send ArrayBufferViews with the send method
|
||||
return new Uint8Array(this._sQ.buffer, 0, this._sQlen);
|
||||
}
|
||||
|
||||
_expand_compact_rQ(min_fit) {
|
||||
const resizeNeeded = min_fit || this.rQlen > this._rQbufferSize / 2;
|
||||
// We want to move all the unread data to the start of the queue,
|
||||
// e.g. compacting.
|
||||
// The function also expands the receive que if needed, and for
|
||||
// performance reasons we combine these two actions to avoid
|
||||
// unneccessary copying.
|
||||
_expandCompactRQ(minFit) {
|
||||
// if we're using less than 1/8th of the buffer even with the incoming bytes, compact in place
|
||||
// instead of resizing
|
||||
const requiredBufferSize = (this._rQlen - this._rQi + minFit) * 8;
|
||||
const resizeNeeded = this._rQbufferSize < requiredBufferSize;
|
||||
|
||||
if (resizeNeeded) {
|
||||
if (!min_fit) {
|
||||
// just double the size if we need to do compaction
|
||||
this._rQbufferSize *= 2;
|
||||
} else {
|
||||
// otherwise, make sure we satisy rQlen - rQi + min_fit < rQbufferSize / 8
|
||||
this._rQbufferSize = (this.rQlen + min_fit) * 8;
|
||||
}
|
||||
// Make sure we always *at least* double the buffer size, and have at least space for 8x
|
||||
// the current amount of data
|
||||
this._rQbufferSize = Math.max(this._rQbufferSize * 2, requiredBufferSize);
|
||||
}
|
||||
|
||||
// we don't want to grow unboundedly
|
||||
if (this._rQbufferSize > MAX_RQ_GROW_SIZE) {
|
||||
this._rQbufferSize = MAX_RQ_GROW_SIZE;
|
||||
if (this._rQbufferSize - this.rQlen < min_fit) {
|
||||
if (this._rQbufferSize - this.rQlen < minFit) {
|
||||
throw new Error("Receive Queue buffer exceeded " + MAX_RQ_GROW_SIZE + " bytes, and the new message could not fit");
|
||||
}
|
||||
}
|
||||
|
||||
if (resizeNeeded) {
|
||||
const old_rQbuffer = this._rQ.buffer;
|
||||
this._rQmax = this._rQbufferSize / 8;
|
||||
const oldRQbuffer = this._rQ.buffer;
|
||||
this._rQ = new Uint8Array(this._rQbufferSize);
|
||||
this._rQ.set(new Uint8Array(old_rQbuffer, this._rQi));
|
||||
this._rQ.set(new Uint8Array(oldRQbuffer, this._rQi, this._rQlen - this._rQi));
|
||||
} else {
|
||||
if (ENABLE_COPYWITHIN) {
|
||||
this._rQ.copyWithin(0, this._rQi);
|
||||
} else {
|
||||
this._rQ.set(new Uint8Array(this._rQ.buffer, this._rQi));
|
||||
}
|
||||
this._rQ.copyWithin(0, this._rQi, this._rQlen);
|
||||
}
|
||||
|
||||
this._rQlen = this._rQlen - this._rQi;
|
||||
this._rQi = 0;
|
||||
}
|
||||
|
||||
_decode_message(data) {
|
||||
// push arraybuffer values onto the end
|
||||
// push arraybuffer values onto the end of the receive que
|
||||
_DecodeMessage(data) {
|
||||
const u8 = new Uint8Array(data);
|
||||
if (u8.length > this._rQbufferSize - this._rQlen) {
|
||||
this._expand_compact_rQ(u8.length);
|
||||
this._expandCompactRQ(u8.length);
|
||||
}
|
||||
this._rQ.set(u8, this._rQlen);
|
||||
this._rQlen += u8.length;
|
||||
}
|
||||
|
||||
_recv_message(e) {
|
||||
this._decode_message(e.data);
|
||||
_recvMessage(e) {
|
||||
this._DecodeMessage(e.data);
|
||||
if (this.rQlen > 0) {
|
||||
this._eventHandlers.message();
|
||||
// Compact the receive queue
|
||||
if (this._rQlen == this._rQi) {
|
||||
// All data has now been processed, this means we
|
||||
// can reset the receive queue.
|
||||
this._rQlen = 0;
|
||||
this._rQi = 0;
|
||||
} else if (this._rQlen > this._rQmax) {
|
||||
this._expand_compact_rQ();
|
||||
}
|
||||
} else {
|
||||
Log.Debug("Ignoring empty message");
|
||||
|
||||
+9
-42
@@ -11,9 +11,6 @@ official external API.
|
||||
|
||||
## 1.1 Module List
|
||||
|
||||
* __Mouse__ (core/input/mouse.js): Mouse input event handler with
|
||||
limited touch support.
|
||||
|
||||
* __Keyboard__ (core/input/keyboard.js): Keyboard input event handler with
|
||||
non-US keyboard support. Translates keyDown and keyUp events to X11
|
||||
keysym values.
|
||||
@@ -23,7 +20,7 @@ layered on the HTML5 canvas element.
|
||||
|
||||
* __Websock__ (core/websock.js): Websock client from websockify
|
||||
with transparent binary data support.
|
||||
[Websock API](https://github.com/novnc/websockify/wiki/websock.js) wiki page.
|
||||
[Websock API](https://github.com/novnc/websockify-js/wiki/websock.js) wiki page.
|
||||
|
||||
|
||||
## 1.2 Callbacks
|
||||
@@ -35,62 +32,38 @@ callback event name, and the callback function.
|
||||
|
||||
## 2. Modules
|
||||
|
||||
## 2.1 Mouse Module
|
||||
## 2.1 Keyboard Module
|
||||
|
||||
### 2.1.1 Configuration Attributes
|
||||
|
||||
| name | type | mode | default | description
|
||||
| ----------- | ---- | ---- | -------- | ------------
|
||||
| touchButton | int | RW | 1 | Button mask (1, 2, 4) for which click to send on touch devices. 0 means ignore clicks.
|
||||
|
||||
### 2.1.2 Methods
|
||||
|
||||
| name | parameters | description
|
||||
| ------ | ---------- | ------------
|
||||
| grab | () | Begin capturing mouse events
|
||||
| ungrab | () | Stop capturing mouse events
|
||||
|
||||
### 2.1.2 Callbacks
|
||||
|
||||
| name | parameters | description
|
||||
| ------------- | ------------------- | ------------
|
||||
| onmousebutton | (x, y, down, bmask) | Handler for mouse button click/release
|
||||
| onmousemove | (x, y) | Handler for mouse movement
|
||||
|
||||
|
||||
## 2.2 Keyboard Module
|
||||
|
||||
### 2.2.1 Configuration Attributes
|
||||
|
||||
None
|
||||
|
||||
### 2.2.2 Methods
|
||||
### 2.1.2 Methods
|
||||
|
||||
| name | parameters | description
|
||||
| ------ | ---------- | ------------
|
||||
| grab | () | Begin capturing keyboard events
|
||||
| ungrab | () | Stop capturing keyboard events
|
||||
|
||||
### 2.2.3 Callbacks
|
||||
### 2.1.3 Callbacks
|
||||
|
||||
| name | parameters | description
|
||||
| ---------- | -------------------- | ------------
|
||||
| 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
|
||||
| ------------ | ----- | ---- | ------- | ------------
|
||||
| logo | raw | RW | | Logo to display when cleared: {"width": width, "height": height, "type": mime-type, "data": data}
|
||||
| scale | float | RW | 1.0 | Display area scale factor 0.0 - 1.0
|
||||
| clipViewport | bool | RW | false | Use viewport clipping
|
||||
| width | int | RO | | Display area width
|
||||
| height | int | RO | | Display area height
|
||||
|
||||
### 2.3.2 Methods
|
||||
### 2.2.2 Methods
|
||||
|
||||
| name | parameters | description
|
||||
| ------------------ | ------------------------------------------------------- | ------------
|
||||
@@ -100,22 +73,16 @@ None
|
||||
| absY | (y) | Return Y relative to the remote display
|
||||
| resize | (width, height) | Set width and height
|
||||
| flip | (from_queue) | Update the visible canvas with the contents of the rendering canvas
|
||||
| clear | () | Clear the display (show logo if set)
|
||||
| pending | () | Check if there are waiting items in the render queue
|
||||
| flush | () | Resume processing the render queue unless it's empty
|
||||
| fillRect | (x, y, width, height, color, from_queue) | Draw a filled in rectangle
|
||||
| copyImage | (old_x, old_y, new_x, new_y, width, height, from_queue) | Copy a rectangular area
|
||||
| imageRect | (x, y, mime, arr) | Draw a rectangle with an image
|
||||
| startTile | (x, y, width, height, color) | Begin updating a tile
|
||||
| subTile | (tile, x, y, w, h, color) | Update a sub-rectangle within the given tile
|
||||
| finishTile | () | Draw the current tile to the display
|
||||
| imageRect | (x, y, width, height, mime, arr) | Draw a rectangle with an image
|
||||
| blitImage | (x, y, width, height, arr, offset, from_queue) | Blit pixels (of R,G,B,A) to the display
|
||||
| blitRgbImage | (x, y, width, height, arr, offset, from_queue) | Blit RGB encoded image to display
|
||||
| blitRgbxImage | (x, y, width, height, arr, offset, from_queue) | Blit RGBX encoded image to display
|
||||
| drawImage | (img, x, y) | Draw image and track damage
|
||||
| autoscale | (containerWidth, containerHeight) | Scale the display
|
||||
|
||||
### 2.3.3 Callbacks
|
||||
### 2.2.3 Callbacks
|
||||
|
||||
| name | parameters | description
|
||||
| ------- | ---------- | ------------
|
||||
|
||||
+21
-11
@@ -24,13 +24,7 @@ protocol stream.
|
||||
`focusOnClick`
|
||||
- Is a `boolean` indicating if keyboard focus should automatically be
|
||||
moved to the remote session when a `mousedown` or `touchstart`
|
||||
event is received.
|
||||
|
||||
`touchButton`
|
||||
- Is a `long` controlling the button mask that should be simulated
|
||||
when a touch event is recieved. Uses the same values as
|
||||
[`MouseEvent.button`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button).
|
||||
Is set to `1` by default.
|
||||
event is received. Enabled by default.
|
||||
|
||||
`clipViewport`
|
||||
- Is a `boolean` indicating if the remote session should be clipped
|
||||
@@ -64,6 +58,19 @@ protocol stream.
|
||||
to the element containing the remote session screen. The default value is `rgb(40, 40, 40)`
|
||||
(solid gray color).
|
||||
|
||||
`qualityLevel`
|
||||
- Is an `int` in range `[0-9]` controlling the desired JPEG quality.
|
||||
Value `0` implies low quality and `9` implies high quality.
|
||||
Default value is `6`.
|
||||
|
||||
`compressionLevel`
|
||||
- Is an `int` in range `[0-9]` controlling the desired compression
|
||||
level. Value `0` means no compression. Level 1 uses a minimum of CPU
|
||||
resources and achieves weak compression ratios, while level 9 offers
|
||||
best compression but is slow in terms of CPU consumption on the server
|
||||
side. Use high levels with very slow network connections.
|
||||
Default value is `2`.
|
||||
|
||||
`capabilities` *Read only*
|
||||
- Is an `Object` indicating which optional extensions are available
|
||||
on the server. Some methods may only be called if the corresponding
|
||||
@@ -158,9 +165,9 @@ connection to a specified VNC server.
|
||||
existing contents of the `HTMLElement` will be untouched, but new
|
||||
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 valid WebSocket URL.
|
||||
a valid WebSocket URL. This can also be a `WebSocket` or `RTCDataChannel`.
|
||||
|
||||
**`options`** *Optional*
|
||||
- An `Object` specifying extra details about how the connection
|
||||
@@ -187,6 +194,10 @@ connection to a specified VNC server.
|
||||
- A `DOMString` specifying the ID to provide to any VNC repeater
|
||||
encountered.
|
||||
|
||||
`wsProtocols`
|
||||
- An `Array` of `DOMString`s specifying the sub-protocols to use
|
||||
in the WebSocket connection. Empty by default.
|
||||
|
||||
#### connect
|
||||
|
||||
The `connect` event is fired after all the handshaking with the server
|
||||
@@ -371,5 +382,4 @@ to the remote server.
|
||||
###### Parameters
|
||||
|
||||
**`text`**
|
||||
- A `DOMString` specifying the clipboard data to send. Currently only
|
||||
characters from ISO 8859-1 are supported.
|
||||
- A `DOMString` specifying the clipboard data to send.
|
||||
|
||||
+4
-18
@@ -61,30 +61,16 @@ query string. Currently the following options are available:
|
||||
* `resize` - How to resize the remote session if it is not the same size as
|
||||
the browser window. Can be one of `off`, `scale` and `remote`.
|
||||
|
||||
* `quality` - The session JPEG quality level. Can be `0` to `9`.
|
||||
|
||||
* `compression` - The session compression level. Can be `0` to `9`.
|
||||
|
||||
* `show_dot` - If a dot cursor should be shown when the remote server provides
|
||||
no local cursor, or provides a fully-transparent (invisible) cursor.
|
||||
|
||||
* `logging` - The console log level. Can be one of `error`, `warn`, `info` or
|
||||
`debug`.
|
||||
|
||||
## Pre-conversion of Modules
|
||||
|
||||
noVNC is written using ECMAScript 6 modules. Many of the major browsers support
|
||||
these modules natively, but not all. By default the noVNC application includes
|
||||
a script that can convert these modules to an older format as they are being
|
||||
loaded. However this process can be slow and severely increases the load time
|
||||
for the application.
|
||||
|
||||
It is possible to perform this conversion ahead of time, avoiding the extra
|
||||
load times. To do this please follow these steps:
|
||||
|
||||
1. Install Node.js
|
||||
2. Run `npm install` in the noVNC directory
|
||||
3. Run `./utils/use_require.js --with-app --as commonjs`
|
||||
|
||||
This will produce a `build/` directory that includes everything needed to run
|
||||
the noVNC application.
|
||||
|
||||
## HTTP Serving Considerations
|
||||
### Browser Cache Issue
|
||||
|
||||
|
||||
+3
-7
@@ -18,18 +18,14 @@ do things.
|
||||
|
||||
## Conversion of Modules
|
||||
|
||||
noVNC is written using ECMAScript 6 modules. Many of the major browsers support
|
||||
these modules natively, but not all. They are also not supported by Node.js. To
|
||||
use noVNC in these places the library must first be converted.
|
||||
noVNC is written using ECMAScript 6 modules. This is not supported by older
|
||||
versions of Node.js. To use noVNC with those older versions of Node.js the
|
||||
library must first be converted.
|
||||
|
||||
Fortunately noVNC includes a script to handle this conversion. Please follow
|
||||
the following steps:
|
||||
|
||||
1. Install Node.js
|
||||
2. Run `npm install` in the noVNC directory
|
||||
3. Run `./utils/use_require.js --as <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.
|
||||
|
||||
@@ -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)
|
||||
+27
-76
@@ -1,48 +1,23 @@
|
||||
// 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) => {
|
||||
const customLaunchers = {};
|
||||
let browsers = [];
|
||||
let useSauce = false;
|
||||
|
||||
// use Sauce when running on Travis
|
||||
if (process.env.TRAVIS_JOB_NUMBER) {
|
||||
useSauce = true;
|
||||
}
|
||||
|
||||
if (useSauce && process.env.TEST_BROWSER_NAME && process.env.TEST_BROWSER_NAME != 'PhantomJS') {
|
||||
const names = process.env.TEST_BROWSER_NAME.split(',');
|
||||
const platforms = process.env.TEST_BROWSER_OS.split(',');
|
||||
const versions = process.env.TEST_BROWSER_VERSION
|
||||
? process.env.TEST_BROWSER_VERSION.split(',')
|
||||
: [null];
|
||||
|
||||
for (let i = 0; i < names.length; i++) {
|
||||
for (let j = 0; j < platforms.length; j++) {
|
||||
for (let k = 0; k < versions.length; k++) {
|
||||
let launcher_name = 'sl_' + platforms[j].replace(/[^a-zA-Z0-9]/g, '') + '_' + names[i];
|
||||
if (versions[k]) {
|
||||
launcher_name += '_' + versions[k];
|
||||
}
|
||||
|
||||
customLaunchers[launcher_name] = {
|
||||
base: 'SauceLabs',
|
||||
browserName: names[i],
|
||||
platform: platforms[j],
|
||||
};
|
||||
|
||||
if (versions[i]) {
|
||||
customLaunchers[launcher_name].version = versions[k];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
browsers = Object.keys(customLaunchers);
|
||||
} else {
|
||||
useSauce = false;
|
||||
//browsers = ['PhantomJS'];
|
||||
browsers = [];
|
||||
if (process.env.TEST_BROWSER_NAME) {
|
||||
browsers = process.env.TEST_BROWSER_NAME.split(',');
|
||||
}
|
||||
|
||||
const my_conf = {
|
||||
@@ -56,16 +31,13 @@ module.exports = (config) => {
|
||||
|
||||
// list of files / patterns to load in the browser (loaded in order)
|
||||
files: [
|
||||
{ pattern: 'app/localization.js', included: false },
|
||||
{ pattern: 'app/webutil.js', included: false },
|
||||
{ pattern: 'core/**/*.js', included: false },
|
||||
{ pattern: 'vendor/pako/**/*.js', included: false },
|
||||
{ pattern: 'vendor/browser-es-module-loader/dist/*.js*', included: false },
|
||||
{ pattern: 'tests/test.*.js', included: false },
|
||||
{ pattern: 'tests/fake.*.js', included: false },
|
||||
{ pattern: 'tests/assertions.js', included: false },
|
||||
'vendor/promise.js',
|
||||
'tests/karma-test-main.js',
|
||||
{ pattern: 'app/localization.js', included: false, type: 'module' },
|
||||
{ pattern: 'app/webutil.js', included: false, type: 'module' },
|
||||
{ pattern: 'core/**/*.js', included: false, type: 'module' },
|
||||
{ pattern: 'vendor/pako/**/*.js', included: false, type: 'module' },
|
||||
{ pattern: 'tests/test.*.js', type: 'module' },
|
||||
{ pattern: 'tests/fake.*.js', included: false, type: 'module' },
|
||||
{ pattern: 'tests/assertions.js', type: 'module' },
|
||||
],
|
||||
|
||||
client: {
|
||||
@@ -80,7 +52,11 @@ module.exports = (config) => {
|
||||
exclude: [
|
||||
],
|
||||
|
||||
customLaunchers: customLaunchers,
|
||||
plugins: [
|
||||
'karma-*',
|
||||
'@chiragrupani/karma-chromium-edge-launcher',
|
||||
{ 'launcher:Safari': [ 'type', SafariBrowser ] },
|
||||
],
|
||||
|
||||
// start these browsers
|
||||
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
|
||||
@@ -92,14 +68,6 @@ module.exports = (config) => {
|
||||
reporters: ['mocha'],
|
||||
|
||||
|
||||
// web server port
|
||||
port: 9876,
|
||||
|
||||
|
||||
// enable / disable colors in the output (reporters and logs)
|
||||
colors: true,
|
||||
|
||||
|
||||
// level of logging
|
||||
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
|
||||
logLevel: config.LOG_INFO,
|
||||
@@ -111,24 +79,7 @@ module.exports = (config) => {
|
||||
// Continuous Integration mode
|
||||
// if true, Karma captures browsers, runs the tests and exits
|
||||
singleRun: true,
|
||||
|
||||
// Increase timeout in case connection is slow/we run more browsers than possible
|
||||
// (we currently get 3 for free, and we try to run 7, so it can take a while)
|
||||
captureTimeout: 240000,
|
||||
|
||||
// similarly to above
|
||||
browserNoActivityTimeout: 100000,
|
||||
};
|
||||
|
||||
if (useSauce) {
|
||||
my_conf.reporters.push('saucelabs');
|
||||
my_conf.captureTimeout = 0; // use SL timeout
|
||||
my_conf.sauceLabs = {
|
||||
testName: 'noVNC Tests (all)',
|
||||
startConnect: false,
|
||||
tunnelIdentifier: process.env.TRAVIS_JOB_NUMBER
|
||||
};
|
||||
}
|
||||
|
||||
config.set(my_conf);
|
||||
};
|
||||
|
||||
+32
-29
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@novnc/novnc",
|
||||
"version": "1.1.0",
|
||||
"version": "1.3.0-beta",
|
||||
"description": "An HTML5 VNC client",
|
||||
"browser": "lib/rfb",
|
||||
"directories": {
|
||||
@@ -19,9 +19,9 @@
|
||||
"vendor/pako"
|
||||
],
|
||||
"scripts": {
|
||||
"lint": "eslint app core po tests utils",
|
||||
"lint": "eslint app core po/po2js po/xgettext-html tests utils",
|
||||
"test": "karma start karma.conf.js",
|
||||
"prepublish": "node ./utils/use_require.js --as commonjs --clean"
|
||||
"prepublish": "node ./utils/use_require.js --clean"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -40,36 +40,39 @@
|
||||
},
|
||||
"homepage": "https://github.com/novnc/noVNC",
|
||||
"devDependencies": {
|
||||
"babel-core": "^6.22.1",
|
||||
"babel-plugin-add-module-exports": "^0.2.1",
|
||||
"@babel/core": "*",
|
||||
"@babel/plugin-syntax-dynamic-import": "*",
|
||||
"@babel/plugin-transform-modules-commonjs": "*",
|
||||
"@babel/preset-env": "*",
|
||||
"@babel/cli": "*",
|
||||
"babel-plugin-import-redirect": "*",
|
||||
"babel-plugin-syntax-dynamic-import": "^6.18.0",
|
||||
"babel-plugin-transform-es2015-modules-amd": "^6.22.0",
|
||||
"babel-plugin-transform-es2015-modules-commonjs": "^6.18.0",
|
||||
"babel-plugin-transform-es2015-modules-systemjs": "^6.22.0",
|
||||
"babel-plugin-transform-es2015-modules-umd": "^6.22.0",
|
||||
"babel-preset-es2015": "^6.24.1",
|
||||
"babelify": "^7.3.0",
|
||||
"browserify": "^13.1.0",
|
||||
"chai": "^3.5.0",
|
||||
"commander": "^2.9.0",
|
||||
"es-module-loader": "^2.1.0",
|
||||
"eslint": "^4.16.0",
|
||||
"fs-extra": "^1.0.0",
|
||||
"browserify": "*",
|
||||
"babelify": "*",
|
||||
"core-js": "*",
|
||||
"chai": "*",
|
||||
"commander": "*",
|
||||
"es-module-loader": "*",
|
||||
"eslint": "*",
|
||||
"fs-extra": "*",
|
||||
"jsdom": "*",
|
||||
"karma": "^1.3.0",
|
||||
"karma-mocha": "^1.3.0",
|
||||
"karma-mocha-reporter": "^2.2.0",
|
||||
"karma-sauce-launcher": "^1.0.0",
|
||||
"karma-sinon-chai": "^2.0.0",
|
||||
"mocha": "^3.1.2",
|
||||
"karma": "*",
|
||||
"karma-mocha": "*",
|
||||
"karma-chrome-launcher": "*",
|
||||
"@chiragrupani/karma-chromium-edge-launcher": "*",
|
||||
"karma-firefox-launcher": "*",
|
||||
"karma-ie-launcher": "*",
|
||||
"karma-mocha-reporter": "*",
|
||||
"karma-safari-launcher": "*",
|
||||
"karma-script-launcher": "*",
|
||||
"karma-sinon-chai": "*",
|
||||
"mocha": "*",
|
||||
"node-getopt": "*",
|
||||
"po2json": "*",
|
||||
"requirejs": "^2.3.2",
|
||||
"rollup": "^0.41.4",
|
||||
"rollup-plugin-node-resolve": "^2.0.0",
|
||||
"sinon": "^4.0.0",
|
||||
"sinon-chai": "^2.8.0"
|
||||
"requirejs": "*",
|
||||
"rollup": "*",
|
||||
"rollup-plugin-node-resolve": "*",
|
||||
"sinon": "*",
|
||||
"sinon-chai": "*"
|
||||
},
|
||||
"dependencies": {},
|
||||
"keywords": [
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"env": {
|
||||
"node": true,
|
||||
},
|
||||
}
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
all:
|
||||
.PHONY: update-po update-js update-pot
|
||||
|
||||
LINGUAS := cs de el es 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)
|
||||
|
||||
|
||||
@@ -3,14 +3,15 @@
|
||||
# Copyright (C) 2018 The noVNC Authors
|
||||
# This file is distributed under the same license as the noVNC package.
|
||||
# Juanjo Diaz <juanjo.diazmo@gmail.com>, 2018.
|
||||
# Adrian Scillato <ascillato@gmail.com>, 2021.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: noVNC 1.0.0-testing.2\n"
|
||||
"Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
|
||||
"POT-Creation-Date: 2017-10-06 10:07+0200\n"
|
||||
"PO-Revision-Date: 2018-01-30 19:14-0800\n"
|
||||
"Last-Translator: Juanjo Diaz <juanjo.diazmo@gmail.com>\n"
|
||||
"PO-Revision-Date: 2021-04-23 12:00-0300\n"
|
||||
"Last-Translator: Adrian Scillato <ascillato@gmail.com>\n"
|
||||
"Language-Team: Spanish\n"
|
||||
"Language: es\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
@@ -40,7 +41,7 @@ msgstr "Desconectado"
|
||||
|
||||
#: ../app/ui.js:1052 ../core/rfb.js:248
|
||||
msgid "Must set host"
|
||||
msgstr "Debes configurar el host"
|
||||
msgstr "Se debe configurar el host"
|
||||
|
||||
#: ../app/ui.js:1101
|
||||
msgid "Reconnecting..."
|
||||
@@ -48,7 +49,7 @@ msgstr "Reconectando..."
|
||||
|
||||
#: ../app/ui.js:1140
|
||||
msgid "Password is required"
|
||||
msgstr "Contraseña es obligatoria"
|
||||
msgstr "La contraseña es obligatoria"
|
||||
|
||||
#: ../core/rfb.js:548
|
||||
msgid "Disconnect timeout"
|
||||
@@ -186,6 +187,10 @@ msgstr "Pantalla Completa"
|
||||
msgid "Settings"
|
||||
msgstr "Configuraciones"
|
||||
|
||||
#: ../vnc.html:200
|
||||
msgid "Encrypt"
|
||||
msgstr "Encriptar"
|
||||
|
||||
#: ../vnc.html:202
|
||||
msgid "Shared Mode"
|
||||
msgstr "Modo Compartido"
|
||||
@@ -228,27 +233,23 @@ msgstr "Cursor Local"
|
||||
|
||||
#: ../vnc.html:229
|
||||
msgid "Repeater ID:"
|
||||
msgstr "ID del Repetidor"
|
||||
msgstr "ID del Repetidor:"
|
||||
|
||||
#: ../vnc.html:233
|
||||
msgid "WebSocket"
|
||||
msgstr "WebSocket"
|
||||
|
||||
#: ../vnc.html:236
|
||||
msgid "Encrypt"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:239
|
||||
msgid "Host:"
|
||||
msgstr "Host"
|
||||
msgstr "Host:"
|
||||
|
||||
#: ../vnc.html:243
|
||||
msgid "Port:"
|
||||
msgstr "Puesto"
|
||||
msgstr "Puerto:"
|
||||
|
||||
#: ../vnc.html:247
|
||||
msgid "Path:"
|
||||
msgstr "Ruta"
|
||||
msgstr "Ruta:"
|
||||
|
||||
#: ../vnc.html:254
|
||||
msgid "Automatic Reconnect"
|
||||
@@ -256,11 +257,11 @@ msgstr "Reconexión automática"
|
||||
|
||||
#: ../vnc.html:257
|
||||
msgid "Reconnect Delay (ms):"
|
||||
msgstr "Retraso en la reconexión (ms)"
|
||||
msgstr "Retraso en la reconexión (ms):"
|
||||
|
||||
#: ../vnc.html:263
|
||||
msgid "Logging:"
|
||||
msgstr "Logging"
|
||||
msgstr "Registrando:"
|
||||
|
||||
#: ../vnc.html:275
|
||||
msgid "Disconnect"
|
||||
@@ -272,7 +273,7 @@ msgstr "Conectar"
|
||||
|
||||
#: ../vnc.html:304
|
||||
msgid "Password:"
|
||||
msgstr "Contraseña"
|
||||
msgstr "Contraseña:"
|
||||
|
||||
#: ../vnc.html:318
|
||||
msgid "Cancel"
|
||||
@@ -280,4 +281,4 @@ msgstr "Cancelar"
|
||||
|
||||
#: ../vnc.html:334
|
||||
msgid "Canvas not supported."
|
||||
msgstr "Canvas no está soportado"
|
||||
msgstr "Canvas no soportado."
|
||||
|
||||
@@ -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"
|
||||
@@ -0,0 +1,324 @@
|
||||
# Japanese translations for noVNC package
|
||||
# noVNC パッケージに対する日訳
|
||||
# Copyright (C) 2019 The noVNC Authors
|
||||
# This file is distributed under the same license as the noVNC package.
|
||||
# nnn1590 <nnn1590@nnn1590.org>, 2019-2020.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: noVNC 1.1.0\n"
|
||||
"Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
|
||||
"POT-Creation-Date: 2020-07-03 16:11+0200\n"
|
||||
"PO-Revision-Date: 2021-01-15 12:37+0900\n"
|
||||
"Last-Translator: nnn1590 <nnn1590@nnn1590.org>\n"
|
||||
"Language-Team: Japanese\n"
|
||||
"Language: ja\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
"X-Generator: Poedit 2.3\n"
|
||||
|
||||
#: ../app/ui.js:394
|
||||
msgid "Connecting..."
|
||||
msgstr "接続しています..."
|
||||
|
||||
#: ../app/ui.js:401
|
||||
msgid "Disconnecting..."
|
||||
msgstr "切断しています..."
|
||||
|
||||
#: ../app/ui.js:407
|
||||
msgid "Reconnecting..."
|
||||
msgstr "再接続しています..."
|
||||
|
||||
#: ../app/ui.js:412
|
||||
msgid "Internal error"
|
||||
msgstr "内部エラー"
|
||||
|
||||
#: ../app/ui.js:1008
|
||||
msgid "Must set host"
|
||||
msgstr "ホストを設定する必要があります"
|
||||
|
||||
#: ../app/ui.js:1090
|
||||
msgid "Connected (encrypted) to "
|
||||
msgstr "接続しました (暗号化済み): "
|
||||
|
||||
#: ../app/ui.js:1092
|
||||
msgid "Connected (unencrypted) to "
|
||||
msgstr "接続しました (暗号化されていません): "
|
||||
|
||||
#: ../app/ui.js:1115
|
||||
msgid "Something went wrong, connection is closed"
|
||||
msgstr "何らかの問題で、接続が閉じられました"
|
||||
|
||||
#: ../app/ui.js:1118
|
||||
msgid "Failed to connect to server"
|
||||
msgstr "サーバーへの接続に失敗しました"
|
||||
|
||||
#: ../app/ui.js:1128
|
||||
msgid "Disconnected"
|
||||
msgstr "切断しました"
|
||||
|
||||
#: ../app/ui.js:1143
|
||||
msgid "New connection has been rejected with reason: "
|
||||
msgstr "新規接続は次の理由で拒否されました: "
|
||||
|
||||
#: ../app/ui.js:1146
|
||||
msgid "New connection has been rejected"
|
||||
msgstr "新規接続は拒否されました"
|
||||
|
||||
#: ../app/ui.js:1181
|
||||
msgid "Credentials are required"
|
||||
msgstr "資格情報が必要です"
|
||||
|
||||
#: ../vnc.html:74
|
||||
msgid "noVNC encountered an error:"
|
||||
msgstr "noVNC でエラーが発生しました:"
|
||||
|
||||
#: ../vnc.html:84
|
||||
msgid "Hide/Show the control bar"
|
||||
msgstr "コントロールバーを隠す/表示する"
|
||||
|
||||
#: ../vnc.html:91
|
||||
msgid "Drag"
|
||||
msgstr "ドラッグ"
|
||||
|
||||
#: ../vnc.html:91
|
||||
msgid "Move/Drag Viewport"
|
||||
msgstr "ビューポートを移動/ドラッグ"
|
||||
|
||||
#: ../vnc.html:97
|
||||
msgid "Keyboard"
|
||||
msgstr "キーボード"
|
||||
|
||||
#: ../vnc.html:97
|
||||
msgid "Show Keyboard"
|
||||
msgstr "キーボードを表示"
|
||||
|
||||
#: ../vnc.html:102
|
||||
msgid "Extra keys"
|
||||
msgstr "追加キー"
|
||||
|
||||
#: ../vnc.html:102
|
||||
msgid "Show Extra Keys"
|
||||
msgstr "追加キーを表示"
|
||||
|
||||
#: ../vnc.html:107
|
||||
msgid "Ctrl"
|
||||
msgstr "Ctrl"
|
||||
|
||||
#: ../vnc.html:107
|
||||
msgid "Toggle Ctrl"
|
||||
msgstr "Ctrl キーを切り替え"
|
||||
|
||||
#: ../vnc.html:110
|
||||
msgid "Alt"
|
||||
msgstr "Alt"
|
||||
|
||||
#: ../vnc.html:110
|
||||
msgid "Toggle Alt"
|
||||
msgstr "Alt キーを切り替え"
|
||||
|
||||
#: ../vnc.html:113
|
||||
msgid "Toggle Windows"
|
||||
msgstr "Windows キーを切り替え"
|
||||
|
||||
#: ../vnc.html:113
|
||||
msgid "Windows"
|
||||
msgstr "Windows"
|
||||
|
||||
#: ../vnc.html:116
|
||||
msgid "Send Tab"
|
||||
msgstr "Tab キーを送信"
|
||||
|
||||
#: ../vnc.html:116
|
||||
msgid "Tab"
|
||||
msgstr "Tab"
|
||||
|
||||
#: ../vnc.html:119
|
||||
msgid "Esc"
|
||||
msgstr "Esc"
|
||||
|
||||
#: ../vnc.html:119
|
||||
msgid "Send Escape"
|
||||
msgstr "Escape キーを送信"
|
||||
|
||||
#: ../vnc.html:122
|
||||
msgid "Ctrl+Alt+Del"
|
||||
msgstr "Ctrl+Alt+Del"
|
||||
|
||||
#: ../vnc.html:122
|
||||
msgid "Send Ctrl-Alt-Del"
|
||||
msgstr "Ctrl-Alt-Del を送信"
|
||||
|
||||
#: ../vnc.html:129
|
||||
msgid "Shutdown/Reboot"
|
||||
msgstr "シャットダウン/再起動"
|
||||
|
||||
#: ../vnc.html:129
|
||||
msgid "Shutdown/Reboot..."
|
||||
msgstr "シャットダウン/再起動..."
|
||||
|
||||
#: ../vnc.html:135
|
||||
msgid "Power"
|
||||
msgstr "電源"
|
||||
|
||||
#: ../vnc.html:137
|
||||
msgid "Shutdown"
|
||||
msgstr "シャットダウン"
|
||||
|
||||
#: ../vnc.html:138
|
||||
msgid "Reboot"
|
||||
msgstr "再起動"
|
||||
|
||||
#: ../vnc.html:139
|
||||
msgid "Reset"
|
||||
msgstr "リセット"
|
||||
|
||||
#: ../vnc.html:144 ../vnc.html:150
|
||||
msgid "Clipboard"
|
||||
msgstr "クリップボード"
|
||||
|
||||
#: ../vnc.html:154
|
||||
msgid "Clear"
|
||||
msgstr "クリア"
|
||||
|
||||
#: ../vnc.html:160
|
||||
msgid "Fullscreen"
|
||||
msgstr "全画面表示"
|
||||
|
||||
#: ../vnc.html:165 ../vnc.html:172
|
||||
msgid "Settings"
|
||||
msgstr "設定"
|
||||
|
||||
#: ../vnc.html:175
|
||||
msgid "Shared Mode"
|
||||
msgstr "共有モード"
|
||||
|
||||
#: ../vnc.html:178
|
||||
msgid "View Only"
|
||||
msgstr "表示のみ"
|
||||
|
||||
#: ../vnc.html:182
|
||||
msgid "Clip to Window"
|
||||
msgstr "ウィンドウにクリップ"
|
||||
|
||||
#: ../vnc.html:185
|
||||
msgid "Scaling Mode:"
|
||||
msgstr "スケーリングモード:"
|
||||
|
||||
#: ../vnc.html:187
|
||||
msgid "None"
|
||||
msgstr "なし"
|
||||
|
||||
#: ../vnc.html:188
|
||||
msgid "Local Scaling"
|
||||
msgstr "ローカルスケーリング"
|
||||
|
||||
#: ../vnc.html:189
|
||||
msgid "Remote Resizing"
|
||||
msgstr "リモートでリサイズ"
|
||||
|
||||
#: ../vnc.html:194
|
||||
msgid "Advanced"
|
||||
msgstr "高度"
|
||||
|
||||
#: ../vnc.html:197
|
||||
msgid "Quality:"
|
||||
msgstr "品質:"
|
||||
|
||||
#: ../vnc.html:201
|
||||
msgid "Compression level:"
|
||||
msgstr "圧縮レベル:"
|
||||
|
||||
#: ../vnc.html:206
|
||||
msgid "Repeater ID:"
|
||||
msgstr "リピーター ID:"
|
||||
|
||||
#: ../vnc.html:210
|
||||
msgid "WebSocket"
|
||||
msgstr "WebSocket"
|
||||
|
||||
#: ../vnc.html:213
|
||||
msgid "Encrypt"
|
||||
msgstr "暗号化"
|
||||
|
||||
#: ../vnc.html:216
|
||||
msgid "Host:"
|
||||
msgstr "ホスト:"
|
||||
|
||||
#: ../vnc.html:220
|
||||
msgid "Port:"
|
||||
msgstr "ポート:"
|
||||
|
||||
#: ../vnc.html:224
|
||||
msgid "Path:"
|
||||
msgstr "パス:"
|
||||
|
||||
#: ../vnc.html:231
|
||||
msgid "Automatic Reconnect"
|
||||
msgstr "自動再接続"
|
||||
|
||||
#: ../vnc.html:234
|
||||
msgid "Reconnect Delay (ms):"
|
||||
msgstr "再接続する遅延 (ミリ秒):"
|
||||
|
||||
#: ../vnc.html:239
|
||||
msgid "Show Dot when No Cursor"
|
||||
msgstr "カーソルがないときにドットを表示"
|
||||
|
||||
#: ../vnc.html:244
|
||||
msgid "Logging:"
|
||||
msgstr "ロギング:"
|
||||
|
||||
#: ../vnc.html:253
|
||||
msgid "Version:"
|
||||
msgstr "バージョン:"
|
||||
|
||||
#: ../vnc.html:261
|
||||
msgid "Disconnect"
|
||||
msgstr "切断"
|
||||
|
||||
#: ../vnc.html:280
|
||||
msgid "Connect"
|
||||
msgstr "接続"
|
||||
|
||||
#: ../vnc.html:290
|
||||
msgid "Username:"
|
||||
msgstr "ユーザー名:"
|
||||
|
||||
#: ../vnc.html:294
|
||||
msgid "Password:"
|
||||
msgstr "パスワード:"
|
||||
|
||||
#: ../vnc.html:298
|
||||
msgid "Send Credentials"
|
||||
msgstr "資格情報を送信"
|
||||
|
||||
#: ../vnc.html:308
|
||||
msgid "Cancel"
|
||||
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 "パスワードを送信"
|
||||
+89
-93
@@ -6,9 +6,9 @@
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: noVNC 1.1.0\n"
|
||||
"Project-Id-Version: noVNC 1.3.0\n"
|
||||
"Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
|
||||
"POT-Creation-Date: 2019-01-16 11:06+0100\n"
|
||||
"POT-Creation-Date: 2021-08-27 16:03+0200\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@@ -17,286 +17,282 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=CHARSET\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: ../app/ui.js:387
|
||||
#: ../app/ui.js:400
|
||||
msgid "Connecting..."
|
||||
msgstr ""
|
||||
|
||||
#: ../app/ui.js:394
|
||||
#: ../app/ui.js:407
|
||||
msgid "Disconnecting..."
|
||||
msgstr ""
|
||||
|
||||
#: ../app/ui.js:400
|
||||
#: ../app/ui.js:413
|
||||
msgid "Reconnecting..."
|
||||
msgstr ""
|
||||
|
||||
#: ../app/ui.js:405
|
||||
#: ../app/ui.js:418
|
||||
msgid "Internal error"
|
||||
msgstr ""
|
||||
|
||||
#: ../app/ui.js:995
|
||||
#: ../app/ui.js:1009
|
||||
msgid "Must set host"
|
||||
msgstr ""
|
||||
|
||||
#: ../app/ui.js:1077
|
||||
#: ../app/ui.js:1091
|
||||
msgid "Connected (encrypted) to "
|
||||
msgstr ""
|
||||
|
||||
#: ../app/ui.js:1079
|
||||
#: ../app/ui.js:1093
|
||||
msgid "Connected (unencrypted) to "
|
||||
msgstr ""
|
||||
|
||||
#: ../app/ui.js:1102
|
||||
#: ../app/ui.js:1116
|
||||
msgid "Something went wrong, connection is closed"
|
||||
msgstr ""
|
||||
|
||||
#: ../app/ui.js:1105
|
||||
#: ../app/ui.js:1119
|
||||
msgid "Failed to connect to server"
|
||||
msgstr ""
|
||||
|
||||
#: ../app/ui.js:1115
|
||||
#: ../app/ui.js:1129
|
||||
msgid "Disconnected"
|
||||
msgstr ""
|
||||
|
||||
#: ../app/ui.js:1128
|
||||
#: ../app/ui.js:1144
|
||||
msgid "New connection has been rejected with reason: "
|
||||
msgstr ""
|
||||
|
||||
#: ../app/ui.js:1131
|
||||
#: ../app/ui.js:1147
|
||||
msgid "New connection has been rejected"
|
||||
msgstr ""
|
||||
|
||||
#: ../app/ui.js:1151
|
||||
msgid "Password is required"
|
||||
#: ../app/ui.js:1182
|
||||
msgid "Credentials are required"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:84
|
||||
#: ../vnc.html:61
|
||||
msgid "noVNC encountered an error:"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:94
|
||||
#: ../vnc.html:71
|
||||
msgid "Hide/Show the control bar"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:101
|
||||
#: ../vnc.html:78
|
||||
msgid "Drag"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:78
|
||||
msgid "Move/Drag Viewport"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:101
|
||||
msgid "viewport drag"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:107 ../vnc.html:110 ../vnc.html:113 ../vnc.html:116
|
||||
msgid "Active Mouse Button"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:107
|
||||
msgid "No mousebutton"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:110
|
||||
msgid "Left mousebutton"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:113
|
||||
msgid "Middle mousebutton"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:116
|
||||
msgid "Right mousebutton"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:119
|
||||
#: ../vnc.html:84
|
||||
msgid "Keyboard"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:119
|
||||
#: ../vnc.html:84
|
||||
msgid "Show Keyboard"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:126
|
||||
#: ../vnc.html:89
|
||||
msgid "Extra keys"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:126
|
||||
#: ../vnc.html:89
|
||||
msgid "Show Extra Keys"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:131
|
||||
#: ../vnc.html:94
|
||||
msgid "Ctrl"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:131
|
||||
#: ../vnc.html:94
|
||||
msgid "Toggle Ctrl"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:134
|
||||
#: ../vnc.html:97
|
||||
msgid "Alt"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:134
|
||||
#: ../vnc.html:97
|
||||
msgid "Toggle Alt"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:137
|
||||
#: ../vnc.html:100
|
||||
msgid "Toggle Windows"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:137
|
||||
#: ../vnc.html:100
|
||||
msgid "Windows"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:140
|
||||
#: ../vnc.html:103
|
||||
msgid "Send Tab"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:140
|
||||
#: ../vnc.html:103
|
||||
msgid "Tab"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:143
|
||||
#: ../vnc.html:106
|
||||
msgid "Esc"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:143
|
||||
#: ../vnc.html:106
|
||||
msgid "Send Escape"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:146
|
||||
#: ../vnc.html:109
|
||||
msgid "Ctrl+Alt+Del"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:146
|
||||
#: ../vnc.html:109
|
||||
msgid "Send Ctrl-Alt-Del"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:154
|
||||
#: ../vnc.html:116
|
||||
msgid "Shutdown/Reboot"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:154
|
||||
#: ../vnc.html:116
|
||||
msgid "Shutdown/Reboot..."
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:160
|
||||
#: ../vnc.html:122
|
||||
msgid "Power"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:162
|
||||
#: ../vnc.html:124
|
||||
msgid "Shutdown"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:163
|
||||
#: ../vnc.html:125
|
||||
msgid "Reboot"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:164
|
||||
#: ../vnc.html:126
|
||||
msgid "Reset"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:169 ../vnc.html:175
|
||||
#: ../vnc.html:131 ../vnc.html:137
|
||||
msgid "Clipboard"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:179
|
||||
#: ../vnc.html:141
|
||||
msgid "Clear"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:185
|
||||
#: ../vnc.html:147
|
||||
msgid "Fullscreen"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:190 ../vnc.html:197
|
||||
#: ../vnc.html:152 ../vnc.html:159
|
||||
msgid "Settings"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:200
|
||||
#: ../vnc.html:162
|
||||
msgid "Shared Mode"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:203
|
||||
#: ../vnc.html:165
|
||||
msgid "View Only"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:207
|
||||
#: ../vnc.html:169
|
||||
msgid "Clip to Window"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:210
|
||||
#: ../vnc.html:172
|
||||
msgid "Scaling Mode:"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:212
|
||||
#: ../vnc.html:174
|
||||
msgid "None"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:213
|
||||
#: ../vnc.html:175
|
||||
msgid "Local Scaling"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:214
|
||||
#: ../vnc.html:176
|
||||
msgid "Remote Resizing"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:219
|
||||
#: ../vnc.html:181
|
||||
msgid "Advanced"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:222
|
||||
#: ../vnc.html:184
|
||||
msgid "Quality:"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:188
|
||||
msgid "Compression level:"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:193
|
||||
msgid "Repeater ID:"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:226
|
||||
#: ../vnc.html:197
|
||||
msgid "WebSocket"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:229
|
||||
#: ../vnc.html:200
|
||||
msgid "Encrypt"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:232
|
||||
#: ../vnc.html:203
|
||||
msgid "Host:"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:236
|
||||
#: ../vnc.html:207
|
||||
msgid "Port:"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:240
|
||||
#: ../vnc.html:211
|
||||
msgid "Path:"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:247
|
||||
#: ../vnc.html:218
|
||||
msgid "Automatic Reconnect"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:250
|
||||
#: ../vnc.html:221
|
||||
msgid "Reconnect Delay (ms):"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:255
|
||||
#: ../vnc.html:226
|
||||
msgid "Show Dot when No Cursor"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:260
|
||||
#: ../vnc.html:231
|
||||
msgid "Logging:"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:272
|
||||
#: ../vnc.html:240
|
||||
msgid "Version:"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:248
|
||||
msgid "Disconnect"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:291
|
||||
#: ../vnc.html:267
|
||||
msgid "Connect"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:301
|
||||
#: ../vnc.html:277
|
||||
msgid "Username:"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:281
|
||||
msgid "Password:"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:305
|
||||
msgid "Send Password"
|
||||
#: ../vnc.html:285
|
||||
msgid "Send Credentials"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:315
|
||||
#: ../vnc.html:295
|
||||
msgid "Cancel"
|
||||
msgstr ""
|
||||
|
||||
@@ -22,17 +22,17 @@ const fs = require('fs');
|
||||
const po2json = require("po2json");
|
||||
|
||||
const opt = getopt.create([
|
||||
['h' , 'help' , 'display this help'],
|
||||
['h', 'help', 'display this help'],
|
||||
]).bindHelp().parseSystem();
|
||||
|
||||
if (opt.argv.length != 2) {
|
||||
console.error("Incorrect number of arguments given");
|
||||
process.exit(1);
|
||||
console.error("Incorrect number of arguments given");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const data = po2json.parseFileSync(opt.argv[0]);
|
||||
|
||||
const bodyPart = Object.keys(data).filter((msgid) => msgid !== "").map((msgid) => {
|
||||
const bodyPart = Object.keys(data).filter(msgid => msgid !== "").map((msgid) => {
|
||||
if (msgid === "") return;
|
||||
const msgstr = data[msgid][1];
|
||||
return " " + JSON.stringify(msgid) + ": " + JSON.stringify(msgstr);
|
||||
|
||||
+299
@@ -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"
|
||||
@@ -1,15 +1,15 @@
|
||||
# Swedish translations for noVNC package
|
||||
# Svenska översättningar för paket noVNC.
|
||||
# Copyright (C) 2018 The noVNC Authors
|
||||
# Svenska översättningar för paketet noVNC.
|
||||
# Copyright (C) 2020 The noVNC Authors
|
||||
# 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 ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: noVNC 1.1.0\n"
|
||||
"Project-Id-Version: noVNC 1.3.0\n"
|
||||
"Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
|
||||
"POT-Creation-Date: 2019-01-16 11:06+0100\n"
|
||||
"PO-Revision-Date: 2019-04-08 10:18+0200\n"
|
||||
"POT-Creation-Date: 2021-08-27 16:03+0200\n"
|
||||
"PO-Revision-Date: 2021-08-27 16:18+0200\n"
|
||||
"Last-Translator: Samuel Mannehed <samuel@cendio.se>\n"
|
||||
"Language-Team: none\n"
|
||||
"Language: sv\n"
|
||||
@@ -19,298 +19,282 @@ msgstr ""
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Generator: Poedit 2.0.3\n"
|
||||
|
||||
#: ../app/ui.js:387
|
||||
#: ../app/ui.js:400
|
||||
msgid "Connecting..."
|
||||
msgstr "Ansluter..."
|
||||
|
||||
#: ../app/ui.js:394
|
||||
#: ../app/ui.js:407
|
||||
msgid "Disconnecting..."
|
||||
msgstr "Kopplar ner..."
|
||||
|
||||
#: ../app/ui.js:400
|
||||
#: ../app/ui.js:413
|
||||
msgid "Reconnecting..."
|
||||
msgstr "Återansluter..."
|
||||
|
||||
#: ../app/ui.js:405
|
||||
#: ../app/ui.js:418
|
||||
msgid "Internal error"
|
||||
msgstr "Internt fel"
|
||||
|
||||
#: ../app/ui.js:995
|
||||
#: ../app/ui.js:1009
|
||||
msgid "Must set host"
|
||||
msgstr "Du måste specifiera en värd"
|
||||
|
||||
#: ../app/ui.js:1077
|
||||
#: ../app/ui.js:1091
|
||||
msgid "Connected (encrypted) to "
|
||||
msgstr "Ansluten (krypterat) till "
|
||||
|
||||
#: ../app/ui.js:1079
|
||||
#: ../app/ui.js:1093
|
||||
msgid "Connected (unencrypted) to "
|
||||
msgstr "Ansluten (okrypterat) till "
|
||||
|
||||
#: ../app/ui.js:1102
|
||||
#: ../app/ui.js:1116
|
||||
msgid "Something went wrong, connection is closed"
|
||||
msgstr "Något gick fel, anslutningen avslutades"
|
||||
|
||||
#: ../app/ui.js:1105
|
||||
#: ../app/ui.js:1119
|
||||
msgid "Failed to connect to server"
|
||||
msgstr "Misslyckades att ansluta till servern"
|
||||
|
||||
#: ../app/ui.js:1115
|
||||
#: ../app/ui.js:1129
|
||||
msgid "Disconnected"
|
||||
msgstr "Frånkopplad"
|
||||
|
||||
#: ../app/ui.js:1128
|
||||
#: ../app/ui.js:1144
|
||||
msgid "New connection has been rejected with reason: "
|
||||
msgstr "Ny anslutning har blivit nekad med följande skäl: "
|
||||
|
||||
#: ../app/ui.js:1131
|
||||
#: ../app/ui.js:1147
|
||||
msgid "New connection has been rejected"
|
||||
msgstr "Ny anslutning har blivit nekad"
|
||||
|
||||
#: ../app/ui.js:1151
|
||||
msgid "Password is required"
|
||||
msgstr "Lösenord krävs"
|
||||
#: ../app/ui.js:1182
|
||||
msgid "Credentials are required"
|
||||
msgstr "Användaruppgifter krävs"
|
||||
|
||||
#: ../vnc.html:84
|
||||
#: ../vnc.html:61
|
||||
msgid "noVNC encountered an error:"
|
||||
msgstr "noVNC stötte på ett problem:"
|
||||
|
||||
#: ../vnc.html:94
|
||||
#: ../vnc.html:71
|
||||
msgid "Hide/Show the control bar"
|
||||
msgstr "Göm/Visa kontrollbaren"
|
||||
|
||||
#: ../vnc.html:101
|
||||
#: ../vnc.html:78
|
||||
msgid "Drag"
|
||||
msgstr "Dra"
|
||||
|
||||
#: ../vnc.html:78
|
||||
msgid "Move/Drag Viewport"
|
||||
msgstr "Flytta/Dra Vyn"
|
||||
|
||||
#: ../vnc.html:101
|
||||
msgid "viewport drag"
|
||||
msgstr "dra vy"
|
||||
|
||||
#: ../vnc.html:107 ../vnc.html:110 ../vnc.html:113 ../vnc.html:116
|
||||
msgid "Active Mouse Button"
|
||||
msgstr "Aktiv musknapp"
|
||||
|
||||
#: ../vnc.html:107
|
||||
msgid "No mousebutton"
|
||||
msgstr "Ingen musknapp"
|
||||
|
||||
#: ../vnc.html:110
|
||||
msgid "Left mousebutton"
|
||||
msgstr "Vänster musknapp"
|
||||
|
||||
#: ../vnc.html:113
|
||||
msgid "Middle mousebutton"
|
||||
msgstr "Mitten-musknapp"
|
||||
|
||||
#: ../vnc.html:116
|
||||
msgid "Right mousebutton"
|
||||
msgstr "Höger musknapp"
|
||||
|
||||
#: ../vnc.html:119
|
||||
#: ../vnc.html:84
|
||||
msgid "Keyboard"
|
||||
msgstr "Tangentbord"
|
||||
|
||||
#: ../vnc.html:119
|
||||
#: ../vnc.html:84
|
||||
msgid "Show Keyboard"
|
||||
msgstr "Visa Tangentbord"
|
||||
|
||||
#: ../vnc.html:126
|
||||
#: ../vnc.html:89
|
||||
msgid "Extra keys"
|
||||
msgstr "Extraknappar"
|
||||
|
||||
#: ../vnc.html:126
|
||||
#: ../vnc.html:89
|
||||
msgid "Show Extra Keys"
|
||||
msgstr "Visa Extraknappar"
|
||||
|
||||
#: ../vnc.html:131
|
||||
#: ../vnc.html:94
|
||||
msgid "Ctrl"
|
||||
msgstr "Ctrl"
|
||||
|
||||
#: ../vnc.html:131
|
||||
#: ../vnc.html:94
|
||||
msgid "Toggle Ctrl"
|
||||
msgstr "Växla Ctrl"
|
||||
|
||||
#: ../vnc.html:134
|
||||
#: ../vnc.html:97
|
||||
msgid "Alt"
|
||||
msgstr "Alt"
|
||||
|
||||
#: ../vnc.html:134
|
||||
#: ../vnc.html:97
|
||||
msgid "Toggle Alt"
|
||||
msgstr "Växla Alt"
|
||||
|
||||
#: ../vnc.html:137
|
||||
#: ../vnc.html:100
|
||||
msgid "Toggle Windows"
|
||||
msgstr "Växla Windows"
|
||||
|
||||
#: ../vnc.html:137
|
||||
#: ../vnc.html:100
|
||||
msgid "Windows"
|
||||
msgstr "Windows"
|
||||
|
||||
#: ../vnc.html:140
|
||||
#: ../vnc.html:103
|
||||
msgid "Send Tab"
|
||||
msgstr "Skicka Tab"
|
||||
|
||||
#: ../vnc.html:140
|
||||
#: ../vnc.html:103
|
||||
msgid "Tab"
|
||||
msgstr "Tab"
|
||||
|
||||
#: ../vnc.html:143
|
||||
#: ../vnc.html:106
|
||||
msgid "Esc"
|
||||
msgstr "Esc"
|
||||
|
||||
#: ../vnc.html:143
|
||||
#: ../vnc.html:106
|
||||
msgid "Send Escape"
|
||||
msgstr "Skicka Escape"
|
||||
|
||||
#: ../vnc.html:146
|
||||
#: ../vnc.html:109
|
||||
msgid "Ctrl+Alt+Del"
|
||||
msgstr "Ctrl+Alt+Del"
|
||||
|
||||
#: ../vnc.html:146
|
||||
#: ../vnc.html:109
|
||||
msgid "Send Ctrl-Alt-Del"
|
||||
msgstr "Skicka Ctrl-Alt-Del"
|
||||
|
||||
#: ../vnc.html:154
|
||||
#: ../vnc.html:116
|
||||
msgid "Shutdown/Reboot"
|
||||
msgstr "Stäng av/Boota om"
|
||||
|
||||
#: ../vnc.html:154
|
||||
#: ../vnc.html:116
|
||||
msgid "Shutdown/Reboot..."
|
||||
msgstr "Stäng av/Boota om..."
|
||||
|
||||
#: ../vnc.html:160
|
||||
#: ../vnc.html:122
|
||||
msgid "Power"
|
||||
msgstr "Ström"
|
||||
|
||||
#: ../vnc.html:162
|
||||
#: ../vnc.html:124
|
||||
msgid "Shutdown"
|
||||
msgstr "Stäng av"
|
||||
|
||||
#: ../vnc.html:163
|
||||
#: ../vnc.html:125
|
||||
msgid "Reboot"
|
||||
msgstr "Boota om"
|
||||
|
||||
#: ../vnc.html:164
|
||||
#: ../vnc.html:126
|
||||
msgid "Reset"
|
||||
msgstr "Återställ"
|
||||
|
||||
#: ../vnc.html:169 ../vnc.html:175
|
||||
#: ../vnc.html:131 ../vnc.html:137
|
||||
msgid "Clipboard"
|
||||
msgstr "Urklipp"
|
||||
|
||||
#: ../vnc.html:179
|
||||
#: ../vnc.html:141
|
||||
msgid "Clear"
|
||||
msgstr "Rensa"
|
||||
|
||||
#: ../vnc.html:185
|
||||
#: ../vnc.html:147
|
||||
msgid "Fullscreen"
|
||||
msgstr "Fullskärm"
|
||||
|
||||
#: ../vnc.html:190 ../vnc.html:197
|
||||
#: ../vnc.html:152 ../vnc.html:159
|
||||
msgid "Settings"
|
||||
msgstr "Inställningar"
|
||||
|
||||
#: ../vnc.html:200
|
||||
#: ../vnc.html:162
|
||||
msgid "Shared Mode"
|
||||
msgstr "Delat Läge"
|
||||
|
||||
#: ../vnc.html:203
|
||||
#: ../vnc.html:165
|
||||
msgid "View Only"
|
||||
msgstr "Endast Visning"
|
||||
|
||||
#: ../vnc.html:207
|
||||
#: ../vnc.html:169
|
||||
msgid "Clip to Window"
|
||||
msgstr "Begränsa till Fönster"
|
||||
|
||||
#: ../vnc.html:210
|
||||
#: ../vnc.html:172
|
||||
msgid "Scaling Mode:"
|
||||
msgstr "Skalningsläge:"
|
||||
|
||||
#: ../vnc.html:212
|
||||
#: ../vnc.html:174
|
||||
msgid "None"
|
||||
msgstr "Ingen"
|
||||
|
||||
#: ../vnc.html:213
|
||||
#: ../vnc.html:175
|
||||
msgid "Local Scaling"
|
||||
msgstr "Lokal Skalning"
|
||||
|
||||
#: ../vnc.html:214
|
||||
#: ../vnc.html:176
|
||||
msgid "Remote Resizing"
|
||||
msgstr "Ändra Storlek"
|
||||
|
||||
#: ../vnc.html:219
|
||||
#: ../vnc.html:181
|
||||
msgid "Advanced"
|
||||
msgstr "Avancerat"
|
||||
|
||||
#: ../vnc.html:222
|
||||
#: ../vnc.html:184
|
||||
msgid "Quality:"
|
||||
msgstr "Kvalitet:"
|
||||
|
||||
#: ../vnc.html:188
|
||||
msgid "Compression level:"
|
||||
msgstr "Kompressionsnivå:"
|
||||
|
||||
#: ../vnc.html:193
|
||||
msgid "Repeater ID:"
|
||||
msgstr "Repeater-ID:"
|
||||
|
||||
#: ../vnc.html:226
|
||||
#: ../vnc.html:197
|
||||
msgid "WebSocket"
|
||||
msgstr "WebSocket"
|
||||
|
||||
#: ../vnc.html:229
|
||||
#: ../vnc.html:200
|
||||
msgid "Encrypt"
|
||||
msgstr "Kryptera"
|
||||
|
||||
#: ../vnc.html:232
|
||||
#: ../vnc.html:203
|
||||
msgid "Host:"
|
||||
msgstr "Värd:"
|
||||
|
||||
#: ../vnc.html:236
|
||||
#: ../vnc.html:207
|
||||
msgid "Port:"
|
||||
msgstr "Port:"
|
||||
|
||||
#: ../vnc.html:240
|
||||
#: ../vnc.html:211
|
||||
msgid "Path:"
|
||||
msgstr "Sökväg:"
|
||||
|
||||
#: ../vnc.html:247
|
||||
#: ../vnc.html:218
|
||||
msgid "Automatic Reconnect"
|
||||
msgstr "Automatisk Återanslutning"
|
||||
|
||||
#: ../vnc.html:250
|
||||
#: ../vnc.html:221
|
||||
msgid "Reconnect Delay (ms):"
|
||||
msgstr "Fördröjning (ms):"
|
||||
|
||||
#: ../vnc.html:255
|
||||
#: ../vnc.html:226
|
||||
msgid "Show Dot when No Cursor"
|
||||
msgstr "Visa prick när ingen muspekare finns"
|
||||
|
||||
#: ../vnc.html:260
|
||||
#: ../vnc.html:231
|
||||
msgid "Logging:"
|
||||
msgstr "Loggning:"
|
||||
|
||||
#: ../vnc.html:272
|
||||
#: ../vnc.html:240
|
||||
msgid "Version:"
|
||||
msgstr "Version:"
|
||||
|
||||
#: ../vnc.html:248
|
||||
msgid "Disconnect"
|
||||
msgstr "Koppla från"
|
||||
|
||||
#: ../vnc.html:291
|
||||
#: ../vnc.html:267
|
||||
msgid "Connect"
|
||||
msgstr "Anslut"
|
||||
|
||||
#: ../vnc.html:301
|
||||
#: ../vnc.html:277
|
||||
msgid "Username:"
|
||||
msgstr "Användarnamn:"
|
||||
|
||||
#: ../vnc.html:281
|
||||
msgid "Password:"
|
||||
msgstr "Lösenord:"
|
||||
|
||||
#: ../vnc.html:305
|
||||
msgid "Send Password"
|
||||
msgstr "Skicka lösenord"
|
||||
#: ../vnc.html:285
|
||||
msgid "Send Credentials"
|
||||
msgstr "Skicka Användaruppgifter"
|
||||
|
||||
#: ../vnc.html:315
|
||||
#: ../vnc.html:295
|
||||
msgid "Cancel"
|
||||
msgstr "Avbryt"
|
||||
|
||||
#~ msgid "Disconnect timeout"
|
||||
#~ msgstr "Det tog för lång tid att koppla ner"
|
||||
|
||||
#~ msgid "Local Downscaling"
|
||||
#~ msgstr "Lokal Nedskalning"
|
||||
|
||||
#~ msgid "Local Cursor"
|
||||
#~ msgstr "Lokal Muspekare"
|
||||
|
||||
#~ msgid "Canvas not supported."
|
||||
#~ msgstr "Canvas stöds ej"
|
||||
|
||||
+7
-7
@@ -10,8 +10,8 @@ const jsdom = require("jsdom");
|
||||
const fs = require("fs");
|
||||
|
||||
const opt = getopt.create([
|
||||
['o' , 'output=FILE' , 'write output to specified file'],
|
||||
['h' , 'help' , 'display this help'],
|
||||
['o', 'output=FILE', 'write output to specified file'],
|
||||
['h', 'help', 'display this help'],
|
||||
]).bindHelp().parseSystem();
|
||||
|
||||
const strings = {};
|
||||
@@ -22,7 +22,7 @@ function addString(str, location) {
|
||||
}
|
||||
|
||||
if (strings[str] === undefined) {
|
||||
strings[str] = {}
|
||||
strings[str] = {};
|
||||
}
|
||||
strings[str][location] = null;
|
||||
}
|
||||
@@ -74,7 +74,7 @@ function process(elem, locator, enabled) {
|
||||
}
|
||||
|
||||
for (let i = 0; i < elem.childNodes.length; i++) {
|
||||
node = elem.childNodes[i];
|
||||
let node = elem.childNodes[i];
|
||||
if (node.nodeType === node.ELEMENT_NODE) {
|
||||
process(node, locator, enabled);
|
||||
} else if (node.nodeType === node.TEXT_NODE && enabled) {
|
||||
@@ -89,18 +89,18 @@ for (let i = 0; i < opt.argv.length; i++) {
|
||||
const dom = new jsdom.JSDOM(file, { includeNodeLocations: true });
|
||||
const body = dom.window.document.body;
|
||||
|
||||
function locator(elem) {
|
||||
let locator = (elem) => {
|
||||
const offset = dom.nodeLocation(elem).startOffset;
|
||||
const line = file.slice(0, offset).split("\n").length;
|
||||
return fn + ":" + line;
|
||||
}
|
||||
};
|
||||
|
||||
process(body, locator, true);
|
||||
}
|
||||
|
||||
let output = "";
|
||||
|
||||
for (str in strings) {
|
||||
for (let str in strings) {
|
||||
output += "#:";
|
||||
for (location in strings[str]) {
|
||||
output += " " + location;
|
||||
|
||||
+20
-20
@@ -1,14 +1,14 @@
|
||||
# Simplified Chinese translations for noVNC package.
|
||||
# Copyright (C) 2018 The noVNC Authors
|
||||
# Copyright (C) 2020 The noVNC Authors
|
||||
# This file is distributed under the same license as the noVNC package.
|
||||
# Peter Dave Hello <hsu@peterdavehello.org>, 2018.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: noVNC 1.0.0-testing.2\n"
|
||||
"Project-Id-Version: noVNC 1.1.0\n"
|
||||
"Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
|
||||
"POT-Creation-Date: 2018-01-10 00:53+0800\n"
|
||||
"PO-Revision-Date: 2018-04-06 21:33+0800\n"
|
||||
"PO-Revision-Date: 2020-01-02 13:19+0800\n"
|
||||
"Last-Translator: CUI Wei <ghostplant@qq.com>\n"
|
||||
"Language: zh_CN\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
@@ -17,15 +17,15 @@ msgstr ""
|
||||
|
||||
#: ../app/ui.js:395
|
||||
msgid "Connecting..."
|
||||
msgstr "链接中..."
|
||||
msgstr "连接中..."
|
||||
|
||||
#: ../app/ui.js:402
|
||||
msgid "Disconnecting..."
|
||||
msgstr "正在中断连接..."
|
||||
msgstr "正在断开连接..."
|
||||
|
||||
#: ../app/ui.js:408
|
||||
msgid "Reconnecting..."
|
||||
msgstr "重新链接中..."
|
||||
msgstr "重新连接中..."
|
||||
|
||||
#: ../app/ui.js:413
|
||||
msgid "Internal error"
|
||||
@@ -37,31 +37,31 @@ msgstr "请提供主机名"
|
||||
|
||||
#: ../app/ui.js:1097
|
||||
msgid "Connected (encrypted) to "
|
||||
msgstr "已加密链接到"
|
||||
msgstr "已连接到(加密)"
|
||||
|
||||
#: ../app/ui.js:1099
|
||||
msgid "Connected (unencrypted) to "
|
||||
msgstr "未加密链接到"
|
||||
msgstr "已连接到(未加密)"
|
||||
|
||||
#: ../app/ui.js:1120
|
||||
msgid "Something went wrong, connection is closed"
|
||||
msgstr "发生错误,链接已关闭"
|
||||
msgstr "发生错误,连接已关闭"
|
||||
|
||||
#: ../app/ui.js:1123
|
||||
msgid "Failed to connect to server"
|
||||
msgstr "无法链接到服务器"
|
||||
msgstr "无法连接到服务器"
|
||||
|
||||
#: ../app/ui.js:1133
|
||||
msgid "Disconnected"
|
||||
msgstr "链接已中断"
|
||||
msgstr "已断开连接"
|
||||
|
||||
#: ../app/ui.js:1146
|
||||
msgid "New connection has been rejected with reason: "
|
||||
msgstr "链接被拒绝,原因:"
|
||||
msgstr "连接被拒绝,原因:"
|
||||
|
||||
#: ../app/ui.js:1149
|
||||
msgid "New connection has been rejected"
|
||||
msgstr "链接被拒绝"
|
||||
msgstr "连接被拒绝"
|
||||
|
||||
#: ../app/ui.js:1170
|
||||
msgid "Password is required"
|
||||
@@ -73,7 +73,7 @@ msgstr "noVNC 遇到一个错误:"
|
||||
|
||||
#: ../vnc.html:99
|
||||
msgid "Hide/Show the control bar"
|
||||
msgstr "显示/隐藏控制列"
|
||||
msgstr "显示/隐藏控制栏"
|
||||
|
||||
#: ../vnc.html:106
|
||||
msgid "Move/Drag Viewport"
|
||||
@@ -193,7 +193,7 @@ msgstr "清除"
|
||||
|
||||
#: ../vnc.html:187
|
||||
msgid "Fullscreen"
|
||||
msgstr "全屏幕"
|
||||
msgstr "全屏"
|
||||
|
||||
#: ../vnc.html:192 ../vnc.html:199
|
||||
msgid "Settings"
|
||||
@@ -205,7 +205,7 @@ msgstr "分享模式"
|
||||
|
||||
#: ../vnc.html:205
|
||||
msgid "View Only"
|
||||
msgstr "仅检视"
|
||||
msgstr "仅查看"
|
||||
|
||||
#: ../vnc.html:209
|
||||
msgid "Clip to Window"
|
||||
@@ -257,11 +257,11 @@ msgstr "路径:"
|
||||
|
||||
#: ../vnc.html:249
|
||||
msgid "Automatic Reconnect"
|
||||
msgstr "自动重新链接"
|
||||
msgstr "自动重新连接"
|
||||
|
||||
#: ../vnc.html:252
|
||||
msgid "Reconnect Delay (ms):"
|
||||
msgstr "重新链接间隔 (ms):"
|
||||
msgstr "重新连接间隔 (ms):"
|
||||
|
||||
#: ../vnc.html:258
|
||||
msgid "Logging:"
|
||||
@@ -269,11 +269,11 @@ msgstr "日志级别:"
|
||||
|
||||
#: ../vnc.html:270
|
||||
msgid "Disconnect"
|
||||
msgstr "终端链接"
|
||||
msgstr "中断连接"
|
||||
|
||||
#: ../vnc.html:289
|
||||
msgid "Connect"
|
||||
msgstr "链接"
|
||||
msgstr "连接"
|
||||
|
||||
#: ../vnc.html:299
|
||||
msgid "Password:"
|
||||
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh -e
|
||||
|
||||
snapctl restart novnc.novncsvc
|
||||
Executable
+29
@@ -0,0 +1,29 @@
|
||||
#!/bin/bash
|
||||
|
||||
# `snapctl get services` returns a JSON array, example:
|
||||
#{
|
||||
#"n6801": {
|
||||
# "listen": 6801,
|
||||
# "vnc": "localhost:5901"
|
||||
#},
|
||||
#"n6802": {
|
||||
# "listen": 6802,
|
||||
# "vnc": "localhost:5902"
|
||||
#}
|
||||
#}
|
||||
snapctl get services | jq -c '.[]' | while read service; do # for each service the user sepcified..
|
||||
# get the important data for the service (listen port, VNC host:port)
|
||||
listen_port="$(echo $service | jq --raw-output '.listen')"
|
||||
vnc_host_port="$(echo $service | jq --raw-output '.vnc')" # --raw-output removes any quotation marks from the output
|
||||
|
||||
# check whether those values are valid
|
||||
expr "$listen_port" : '^[0-9]\+$' > /dev/null
|
||||
listen_port_valid=$?
|
||||
if [ ! $listen_port_valid ] || [ -z "$vnc_host_port" ]; then
|
||||
# invalid values mean the service is disabled, do nothing except for printing a message (logged in /var/log/system or systemd journal)
|
||||
echo "novnc: not starting service ${service} with listen_port ${listen_port} and vnc_host_port ${vnc_host_port}"
|
||||
else
|
||||
# start (and fork with '&') the service using the specified listen port and VNC host:port
|
||||
$SNAP/novnc_proxy --listen $listen_port --vnc $vnc_host_port &
|
||||
fi
|
||||
done
|
||||
@@ -0,0 +1,55 @@
|
||||
name: novnc
|
||||
base: core18 # the base snap is the execution environment for this snap
|
||||
version: '@VERSION@'
|
||||
summary: Open Source VNC client using HTML5 (WebSockets, Canvas)
|
||||
description: |
|
||||
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).
|
||||
|
||||
grade: stable
|
||||
confinement: strict
|
||||
|
||||
parts:
|
||||
novnc:
|
||||
source: .
|
||||
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:
|
||||
- bash
|
||||
- jq
|
||||
|
||||
websockify:
|
||||
source: https://github.com/novnc/websockify/archive/v0.9.0.tar.gz
|
||||
plugin: python
|
||||
stage-packages:
|
||||
- python3-numpy
|
||||
|
||||
hooks:
|
||||
configure:
|
||||
plugs: [network, network-bind]
|
||||
|
||||
apps:
|
||||
novnc:
|
||||
command: ./novnc_proxy
|
||||
plugs: [network, network-bind]
|
||||
novncsvc:
|
||||
command: ./svc_wrapper.sh
|
||||
daemon: forking
|
||||
plugs: [network, network-bind]
|
||||
+16
-15
@@ -1,32 +1,33 @@
|
||||
// noVNC specific assertions
|
||||
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 ctx = obj._target.getContext('2d');
|
||||
const data_cl = ctx.getImageData(0, 0, obj._target.width, obj._target.height).data;
|
||||
// NB(directxman12): PhantomJS 1.x doesn't implement Uint8ClampedArray, so work around that
|
||||
const data = new Uint8Array(data_cl);
|
||||
const len = data_cl.length;
|
||||
new chai.Assertion(len).to.be.equal(target_data.length, "unexpected display size");
|
||||
const data = ctx.getImageData(0, 0, obj._target.width, obj._target.height).data;
|
||||
const len = data.length;
|
||||
new chai.Assertion(len).to.be.equal(targetData.length, "unexpected display size");
|
||||
let same = true;
|
||||
for (let i = 0; i < len; i++) {
|
||||
if (data[i] != target_data[i]) {
|
||||
if (!cmp(data[i], targetData[i])) {
|
||||
same = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!same) {
|
||||
// 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,
|
||||
"expected #{this} to have displayed the image #{exp}, but instead it displayed #{act}",
|
||||
"expected #{this} not to have displayed the image #{act}",
|
||||
target_data,
|
||||
targetData,
|
||||
data);
|
||||
});
|
||||
|
||||
_chai.Assertion.addMethod('sent', function (target_data) {
|
||||
_chai.Assertion.addMethod('sent', function (targetData) {
|
||||
const obj = this._obj;
|
||||
obj.inspect = () => {
|
||||
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;
|
||||
return res;
|
||||
};
|
||||
const data = obj._websocket._get_sent_data();
|
||||
const data = obj._websocket._getSentData();
|
||||
let same = true;
|
||||
if (data.length != target_data.length) {
|
||||
if (data.length != targetData.length) {
|
||||
same = false;
|
||||
} else {
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
if (data[i] != target_data[i]) {
|
||||
if (data[i] != targetData[i]) {
|
||||
same = false;
|
||||
break;
|
||||
}
|
||||
@@ -48,12 +49,12 @@ chai.use(function (_chai, utils) {
|
||||
}
|
||||
if (!same) {
|
||||
// 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,
|
||||
"expected #{this} to have sent the data #{exp}, but it actually sent #{act}",
|
||||
"expected #{this} not to have sent the data #{act}",
|
||||
Array.prototype.slice.call(target_data),
|
||||
Array.prototype.slice.call(targetData),
|
||||
Array.prototype.slice.call(data));
|
||||
});
|
||||
|
||||
|
||||
+19
-27
@@ -1,41 +1,33 @@
|
||||
import Base64 from '../core/base64.js';
|
||||
|
||||
// PhantomJS can't create Event objects directly, so we need to use this
|
||||
function make_event(name, props) {
|
||||
const evt = document.createEvent('Event');
|
||||
evt.initEvent(name, true, true);
|
||||
if (props) {
|
||||
for (let prop in props) {
|
||||
evt[prop] = props[prop];
|
||||
}
|
||||
}
|
||||
return evt;
|
||||
}
|
||||
|
||||
export default class FakeWebSocket {
|
||||
constructor(uri, protocols) {
|
||||
this.url = uri;
|
||||
this.binaryType = "arraybuffer";
|
||||
this.extensions = "";
|
||||
|
||||
this.onerror = null;
|
||||
this.onmessage = null;
|
||||
this.onopen = null;
|
||||
|
||||
if (!protocols || typeof protocols === 'string') {
|
||||
this.protocol = protocols;
|
||||
} else {
|
||||
this.protocol = protocols[0];
|
||||
}
|
||||
|
||||
this._send_queue = new Uint8Array(20000);
|
||||
this._sendQueue = new Uint8Array(20000);
|
||||
|
||||
this.readyState = FakeWebSocket.CONNECTING;
|
||||
this.bufferedAmount = 0;
|
||||
|
||||
this.__is_fake = true;
|
||||
this._isFake = true;
|
||||
}
|
||||
|
||||
close(code, reason) {
|
||||
this.readyState = FakeWebSocket.CLOSED;
|
||||
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 {
|
||||
data = new Uint8Array(data);
|
||||
}
|
||||
this._send_queue.set(data, this.bufferedAmount);
|
||||
this._sendQueue.set(data, this.bufferedAmount);
|
||||
this.bufferedAmount += data.length;
|
||||
}
|
||||
|
||||
_get_sent_data() {
|
||||
const res = new Uint8Array(this._send_queue.buffer, 0, this.bufferedAmount);
|
||||
_getSentData() {
|
||||
const res = new Uint8Array(this._sendQueue.buffer, 0, this.bufferedAmount);
|
||||
this.bufferedAmount = 0;
|
||||
return res;
|
||||
}
|
||||
@@ -58,16 +50,16 @@ export default class FakeWebSocket {
|
||||
_open() {
|
||||
this.readyState = FakeWebSocket.OPEN;
|
||||
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
|
||||
// neatly packaged
|
||||
for (let i = 0;i < data.length;i++) {
|
||||
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.CLOSED = WebSocket.CLOSED;
|
||||
|
||||
FakeWebSocket.__is_fake = true;
|
||||
FakeWebSocket._isFake = true;
|
||||
|
||||
FakeWebSocket.replace = () => {
|
||||
if (!WebSocket.__is_fake) {
|
||||
const real_version = WebSocket;
|
||||
if (!WebSocket._isFake) {
|
||||
const realVersion = WebSocket;
|
||||
// eslint-disable-next-line no-global-assign
|
||||
WebSocket = FakeWebSocket;
|
||||
FakeWebSocket.__real_version = real_version;
|
||||
FakeWebSocket._realVersion = realVersion;
|
||||
}
|
||||
};
|
||||
|
||||
FakeWebSocket.restore = () => {
|
||||
if (WebSocket.__is_fake) {
|
||||
if (WebSocket._isFake) {
|
||||
// eslint-disable-next-line no-global-assign
|
||||
WebSocket = WebSocket.__real_version;
|
||||
WebSocket = WebSocket._realVersion;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
@@ -41,6 +41,7 @@ function enableUI() {
|
||||
document.getElementById('mode1').checked = true;
|
||||
}
|
||||
|
||||
/* eslint-disable-next-line camelcase */
|
||||
message("Loaded " + VNC_frame_data.length + " frames");
|
||||
|
||||
const startButton = document.getElementById('startButton');
|
||||
@@ -49,12 +50,16 @@ function enableUI() {
|
||||
|
||||
message("Converting...");
|
||||
|
||||
/* eslint-disable-next-line camelcase */
|
||||
frames = VNC_frame_data;
|
||||
|
||||
let encoding;
|
||||
// Only present in older recordings
|
||||
|
||||
/* eslint-disable camelcase */
|
||||
if (window.VNC_frame_encoding) {
|
||||
// Only present in older recordings
|
||||
encoding = VNC_frame_encoding;
|
||||
/* eslint-enable camelcase */
|
||||
} else {
|
||||
let frame = frames[0];
|
||||
let start = frame.indexOf('{', 1) + 1;
|
||||
@@ -102,7 +107,7 @@ class IterationPlayer {
|
||||
this._iteration = undefined;
|
||||
this._player = undefined;
|
||||
|
||||
this._start_time = undefined;
|
||||
this._startTime = undefined;
|
||||
|
||||
this._frames = frames;
|
||||
|
||||
@@ -115,7 +120,7 @@ class IterationPlayer {
|
||||
|
||||
start(realtime) {
|
||||
this._iteration = 0;
|
||||
this._start_time = (new Date()).getTime();
|
||||
this._startTime = (new Date()).getTime();
|
||||
|
||||
this._realtime = realtime;
|
||||
|
||||
@@ -139,7 +144,7 @@ class IterationPlayer {
|
||||
|
||||
_finish() {
|
||||
const endTime = (new Date()).getTime();
|
||||
const totalDuration = endTime - this._start_time;
|
||||
const totalDuration = endTime - this._startTime;
|
||||
|
||||
const evt = new CustomEvent('finish',
|
||||
{ detail:
|
||||
|
||||
+38
-32
@@ -42,6 +42,24 @@ if (window.setImmediate === undefined) {
|
||||
});
|
||||
}
|
||||
|
||||
class FakeWebSocket {
|
||||
constructor() {
|
||||
this.binaryType = "arraybuffer";
|
||||
this.protocol = "";
|
||||
this.readyState = "open";
|
||||
|
||||
this.onerror = () => {};
|
||||
this.onmessage = () => {};
|
||||
this.onopen = () => {};
|
||||
}
|
||||
|
||||
send() {
|
||||
}
|
||||
|
||||
close() {
|
||||
}
|
||||
}
|
||||
|
||||
export default class RecordingPlayer {
|
||||
constructor(frames, disconnected) {
|
||||
this._frames = frames;
|
||||
@@ -49,10 +67,10 @@ export default class RecordingPlayer {
|
||||
this._disconnected = disconnected;
|
||||
|
||||
this._rfb = undefined;
|
||||
this._frame_length = this._frames.length;
|
||||
this._frameLength = this._frames.length;
|
||||
|
||||
this._frame_index = 0;
|
||||
this._start_time = undefined;
|
||||
this._frameIndex = 0;
|
||||
this._startTime = undefined;
|
||||
this._realtime = true;
|
||||
this._trafficManagement = true;
|
||||
|
||||
@@ -63,56 +81,44 @@ export default class RecordingPlayer {
|
||||
|
||||
run(realtime, trafficManagement) {
|
||||
// initialize a new RFB
|
||||
this._rfb = new RFB(document.getElementById('VNC_screen'), 'wss://test');
|
||||
this._ws = new FakeWebSocket();
|
||||
this._rfb = new RFB(document.getElementById('VNC_screen'), this._ws);
|
||||
this._rfb.viewOnly = true;
|
||||
this._rfb.addEventListener("disconnect",
|
||||
this._handleDisconnect.bind(this));
|
||||
this._rfb.addEventListener("credentialsrequired",
|
||||
this._handleCredentials.bind(this));
|
||||
this._enablePlaybackMode();
|
||||
|
||||
// reset the frame index and timer
|
||||
this._frame_index = 0;
|
||||
this._start_time = (new Date()).getTime();
|
||||
this._frameIndex = 0;
|
||||
this._startTime = (new Date()).getTime();
|
||||
|
||||
this._realtime = realtime;
|
||||
this._trafficManagement = (trafficManagement === undefined) ? !realtime : trafficManagement;
|
||||
|
||||
this._running = true;
|
||||
}
|
||||
|
||||
// _enablePlaybackMode mocks out things not required for running playback
|
||||
_enablePlaybackMode() {
|
||||
const self = this;
|
||||
this._rfb._sock.send = () => {};
|
||||
this._rfb._sock.close = () => {};
|
||||
this._rfb._sock.flush = () => {};
|
||||
this._rfb._sock.open = function () {
|
||||
this.init();
|
||||
this._eventHandlers.open();
|
||||
self._queueNextPacket();
|
||||
};
|
||||
this._queueNextPacket();
|
||||
}
|
||||
|
||||
_queueNextPacket() {
|
||||
if (!this._running) { return; }
|
||||
|
||||
let frame = this._frames[this._frame_index];
|
||||
let frame = this._frames[this._frameIndex];
|
||||
|
||||
// skip send frames
|
||||
while (this._frame_index < this._frame_length && frame.fromClient) {
|
||||
this._frame_index++;
|
||||
frame = this._frames[this._frame_index];
|
||||
while (this._frameIndex < this._frameLength && frame.fromClient) {
|
||||
this._frameIndex++;
|
||||
frame = this._frames[this._frameIndex];
|
||||
}
|
||||
|
||||
if (this._frame_index >= this._frame_length) {
|
||||
if (this._frameIndex >= this._frameLength) {
|
||||
Log.Debug('Finished, no more frames');
|
||||
this._finish();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._realtime) {
|
||||
const toffset = (new Date()).getTime() - this._start_time;
|
||||
const toffset = (new Date()).getTime() - this._startTime;
|
||||
let delay = frame.timestamp - toffset;
|
||||
if (delay < 1) delay = 1;
|
||||
|
||||
@@ -134,10 +140,10 @@ export default class RecordingPlayer {
|
||||
return;
|
||||
}
|
||||
|
||||
const frame = this._frames[this._frame_index];
|
||||
const frame = this._frames[this._frameIndex];
|
||||
|
||||
this._rfb._sock._recv_message({'data': frame.data});
|
||||
this._frame_index++;
|
||||
this._ws.onmessage({'data': frame.data});
|
||||
this._frameIndex++;
|
||||
|
||||
this._queueNextPacket();
|
||||
}
|
||||
@@ -153,15 +159,15 @@ export default class RecordingPlayer {
|
||||
this._rfb._display.flush();
|
||||
} else {
|
||||
this._running = false;
|
||||
this._rfb._sock._eventHandlers.close({code: 1000, reason: ""});
|
||||
this._ws.onclose({code: 1000, reason: ""});
|
||||
delete this._rfb;
|
||||
this.onfinish((new Date()).getTime() - this._start_time);
|
||||
this.onfinish((new Date()).getTime() - this._startTime);
|
||||
}
|
||||
}
|
||||
|
||||
_handleDisconnect(evt) {
|
||||
this._running = false;
|
||||
this._disconnected(evt.detail.clean, this._frame_index);
|
||||
this._disconnected(evt.detail.clean, this._frameIndex);
|
||||
}
|
||||
|
||||
_handleCredentials(evt) {
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,82 @@
|
||||
/* eslint-disable no-console */
|
||||
const expect = chai.expect;
|
||||
|
||||
import { inflateInit, inflate } from "../vendor/pako/lib/zlib/inflate.js";
|
||||
import ZStream from "../vendor/pako/lib/zlib/zstream.js";
|
||||
import Deflator from "../core/deflator.js";
|
||||
|
||||
function _inflator(compText, expected) {
|
||||
let strm = new ZStream();
|
||||
let chunkSize = 1024 * 10 * 10;
|
||||
strm.output = new Uint8Array(chunkSize);
|
||||
|
||||
inflateInit(strm, 5);
|
||||
|
||||
if (expected > chunkSize) {
|
||||
chunkSize = expected;
|
||||
strm.output = new Uint8Array(chunkSize);
|
||||
}
|
||||
|
||||
/* eslint-disable camelcase */
|
||||
strm.input = compText;
|
||||
strm.avail_in = strm.input.length;
|
||||
strm.next_in = 0;
|
||||
|
||||
strm.next_out = 0;
|
||||
strm.avail_out = expected.length;
|
||||
/* eslint-enable camelcase */
|
||||
|
||||
let ret = inflate(strm, 0);
|
||||
|
||||
// Check that return code is not an error
|
||||
expect(ret).to.be.greaterThan(-1);
|
||||
|
||||
return new Uint8Array(strm.output.buffer, 0, strm.next_out);
|
||||
}
|
||||
|
||||
describe('Deflate data', function () {
|
||||
|
||||
it('should be able to deflate messages', function () {
|
||||
let deflator = new Deflator();
|
||||
|
||||
let text = "123asdf";
|
||||
let preText = new Uint8Array(text.length);
|
||||
for (let i = 0; i < preText.length; i++) {
|
||||
preText[i] = text.charCodeAt(i);
|
||||
}
|
||||
|
||||
let compText = deflator.deflate(preText);
|
||||
|
||||
let inflatedText = _inflator(compText, text.length);
|
||||
expect(inflatedText).to.array.equal(preText);
|
||||
|
||||
});
|
||||
|
||||
it('should be able to deflate large messages', function () {
|
||||
let deflator = new Deflator();
|
||||
|
||||
/* Generate a big string with random characters. Used because
|
||||
repetition of letters might be deflated more effectively than
|
||||
random ones. */
|
||||
let text = "";
|
||||
let characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
for (let i = 0; i < 300000; i++) {
|
||||
text += characters.charAt(Math.floor(Math.random() * characters.length));
|
||||
}
|
||||
|
||||
let preText = new Uint8Array(text.length);
|
||||
for (let i = 0; i < preText.length; i++) {
|
||||
preText[i] = text.charCodeAt(i);
|
||||
}
|
||||
|
||||
let compText = deflator.deflate(preText);
|
||||
|
||||
//Check that the compressed size is expected size
|
||||
expect(compText.length).to.be.greaterThan((1024 * 10 * 10) * 2);
|
||||
|
||||
let inflatedText = _inflator(compText, text.length);
|
||||
|
||||
expect(inflatedText).to.array.equal(preText);
|
||||
|
||||
});
|
||||
});
|
||||
+41
-128
@@ -4,28 +4,27 @@ import Base64 from '../core/base64.js';
|
||||
import Display from '../core/display.js';
|
||||
|
||||
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, 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');
|
||||
canvas.width = 4;
|
||||
canvas.height = 4;
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
const ctx = canvas.getContext('2d');
|
||||
const data = ctx.createImageData(4, 4);
|
||||
for (let i = 0; i < checked_data.length; i++) { data.data[i] = input_data[i]; }
|
||||
const data = new ImageData(inputData, width, height);
|
||||
ctx.putImageData(data, 0, 0);
|
||||
return canvas;
|
||||
}
|
||||
|
||||
function make_image_png(input_data) {
|
||||
const canvas = make_image_canvas(input_data);
|
||||
function makeImagePng(inputData, width, height) {
|
||||
const canvas = makeImageCanvas(inputData, width, height);
|
||||
const url = canvas.toDataURL();
|
||||
const data = url.split(",")[1];
|
||||
return Base64.decode(data);
|
||||
@@ -44,11 +43,11 @@ describe('Display/Canvas Helper', function () {
|
||||
it('should take viewport location into consideration when drawing images', function () {
|
||||
display.resize(4, 4);
|
||||
display.viewportChangeSize(2, 2);
|
||||
display.drawImage(make_image_canvas(basic_data), 1, 1);
|
||||
display.drawImage(makeImageCanvas(basicData, 4, 1), 1, 1);
|
||||
display.flip();
|
||||
|
||||
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; }
|
||||
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 () {
|
||||
display.resize(5, 7);
|
||||
expect(display._fb_width).to.equal(5);
|
||||
expect(display._fb_height).to.equal(7);
|
||||
expect(display._fbWidth).to.equal(5);
|
||||
expect(display._fbHeight).to.equal(7);
|
||||
});
|
||||
|
||||
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.flip();
|
||||
const expected = [];
|
||||
@@ -223,6 +222,7 @@ describe('Display/Canvas Helper', function () {
|
||||
display = new Display(canvas);
|
||||
display.clipViewport = true;
|
||||
display.resize(4, 3);
|
||||
display.viewportChangeSize(4, 3);
|
||||
document.body.appendChild(canvas);
|
||||
});
|
||||
|
||||
@@ -269,34 +269,12 @@ describe('Display/Canvas Helper', function () {
|
||||
display.resize(4, 4);
|
||||
});
|
||||
|
||||
it('should clear the screen on #clear without a logo set', function () {
|
||||
display.fillRect(0, 0, 4, 4, [0x00, 0x00, 0xff]);
|
||||
display._logo = null;
|
||||
display.clear();
|
||||
display.resize(4, 4);
|
||||
const empty = [];
|
||||
for (let i = 0; i < 4 * display._fb_width * display._fb_height; i++) { empty[i] = 0; }
|
||||
expect(display).to.have.displayed(new Uint8Array(empty));
|
||||
});
|
||||
|
||||
it('should draw the logo on #clear with a logo set', function (done) {
|
||||
display._logo = { width: 4, height: 4, type: "image/png", data: make_image_png(checked_data) };
|
||||
display.clear();
|
||||
display.onflush = () => {
|
||||
expect(display).to.have.displayed(checked_data);
|
||||
expect(display._fb_width).to.equal(4);
|
||||
expect(display._fb_height).to.equal(4);
|
||||
done();
|
||||
};
|
||||
display.flush();
|
||||
});
|
||||
|
||||
it('should not draw directly on the target canvas', function () {
|
||||
display.fillRect(0, 0, 4, 4, [0, 0, 0xff]);
|
||||
display.fillRect(0, 0, 4, 4, [0xff, 0, 0]);
|
||||
display.flip();
|
||||
display.fillRect(0, 0, 4, 4, [0, 0xff, 0]);
|
||||
const expected = [];
|
||||
for (let i = 0; i < 4 * display._fb_width * display._fb_height; i += 4) {
|
||||
for (let i = 0; i < 4 * display._fbWidth * display._fbHeight; i += 4) {
|
||||
expected[i] = 0xff;
|
||||
expected[i+1] = expected[i+2] = 0;
|
||||
expected[i+3] = 0xff;
|
||||
@@ -306,95 +284,41 @@ describe('Display/Canvas Helper', 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, 2, 2, [0xff, 0, 0]);
|
||||
display.fillRect(2, 2, 2, 2, [0xff, 0, 0]);
|
||||
display.fillRect(0, 0, 2, 2, [0, 0, 0xff]);
|
||||
display.fillRect(2, 2, 2, 2, [0, 0, 0xff]);
|
||||
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 () {
|
||||
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.flip();
|
||||
expect(display).to.have.displayed(checked_data);
|
||||
expect(display).to.have.displayed(checkedData);
|
||||
});
|
||||
|
||||
it('should support drawing images via #imageRect', function (done) {
|
||||
display.imageRect(0, 0, "image/png", make_image_png(checked_data));
|
||||
display.imageRect(0, 0, 4, 4, "image/png", makeImagePng(checkedData, 4, 4));
|
||||
display.flip();
|
||||
display.onflush = () => {
|
||||
expect(display).to.have.displayed(checked_data);
|
||||
expect(display).to.have.displayed(checkedData);
|
||||
done();
|
||||
};
|
||||
display.flush();
|
||||
});
|
||||
|
||||
it('should support drawing tile data with a background color and sub tiles', function () {
|
||||
display.startTile(0, 0, 4, 4, [0, 0xff, 0]);
|
||||
display.subTile(0, 0, 2, 2, [0xff, 0, 0]);
|
||||
display.subTile(2, 2, 2, 2, [0xff, 0, 0]);
|
||||
display.finishTile();
|
||||
it('should support blit images with true color via #blitImage', function () {
|
||||
display.blitImage(0, 0, 4, 4, checkedData, 0);
|
||||
display.flip();
|
||||
expect(display).to.have.displayed(checked_data);
|
||||
});
|
||||
|
||||
// We have a special cache for 16x16 tiles that we need to test
|
||||
it('should support drawing a 16x16 tile', function () {
|
||||
const large_checked_data = new Uint8Array(16*16*4);
|
||||
display.resize(16, 16);
|
||||
|
||||
for (let y = 0;y < 16;y++) {
|
||||
for (let x = 0;x < 16;x++) {
|
||||
let pixel;
|
||||
if ((x < 4) && (y < 4)) {
|
||||
// NB: of course IE11 doesn't support #slice on ArrayBufferViews...
|
||||
pixel = Array.prototype.slice.call(checked_data, (y*4+x)*4, (y*4+x+1)*4);
|
||||
} else {
|
||||
pixel = [0, 0xff, 0, 255];
|
||||
}
|
||||
large_checked_data.set(pixel, (y*16+x)*4);
|
||||
}
|
||||
}
|
||||
|
||||
display.startTile(0, 0, 16, 16, [0, 0xff, 0]);
|
||||
display.subTile(0, 0, 2, 2, [0xff, 0, 0]);
|
||||
display.subTile(2, 2, 2, 2, [0xff, 0, 0]);
|
||||
display.finishTile();
|
||||
display.flip();
|
||||
expect(display).to.have.displayed(large_checked_data);
|
||||
});
|
||||
|
||||
it('should support drawing BGRX blit images with true color via #blitImage', function () {
|
||||
const data = [];
|
||||
for (let i = 0; i < 16; i++) {
|
||||
data[i * 4] = checked_data[i * 4 + 2];
|
||||
data[i * 4 + 1] = checked_data[i * 4 + 1];
|
||||
data[i * 4 + 2] = checked_data[i * 4];
|
||||
data[i * 4 + 3] = checked_data[i * 4 + 3];
|
||||
}
|
||||
display.blitImage(0, 0, 4, 4, data, 0);
|
||||
display.flip();
|
||||
expect(display).to.have.displayed(checked_data);
|
||||
});
|
||||
|
||||
it('should support drawing RGB blit images with true color via #blitRgbImage', function () {
|
||||
const data = [];
|
||||
for (let i = 0; i < 16; i++) {
|
||||
data[i * 3] = checked_data[i * 4];
|
||||
data[i * 3 + 1] = checked_data[i * 4 + 1];
|
||||
data[i * 3 + 2] = checked_data[i * 4 + 2];
|
||||
}
|
||||
display.blitRgbImage(0, 0, 4, 4, data, 0);
|
||||
display.flip();
|
||||
expect(display).to.have.displayed(checked_data);
|
||||
expect(display).to.have.displayed(checkedData);
|
||||
});
|
||||
|
||||
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.flip();
|
||||
expect(display).to.have.displayed(checked_data);
|
||||
expect(display).to.have.displayed(checkedData);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -403,38 +327,34 @@ describe('Display/Canvas Helper', function () {
|
||||
beforeEach(function () {
|
||||
display = new Display(document.createElement('canvas'));
|
||||
display.resize(4, 4);
|
||||
sinon.spy(display, '_scan_renderQ');
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
window.requestAnimationFrame = this.old_requestAnimationFrame;
|
||||
sinon.spy(display, '_scanRenderQ');
|
||||
});
|
||||
|
||||
it('should try to process an item when it is pushed on, if nothing else is on the queue', function () {
|
||||
display._renderQ_push({ type: 'noop' }); // does nothing
|
||||
expect(display._scan_renderQ).to.have.been.calledOnce;
|
||||
display._renderQPush({ type: 'noop' }); // does nothing
|
||||
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 () {
|
||||
display._renderQ.length = 2;
|
||||
display._renderQ_push({ type: 'noop' });
|
||||
expect(display._scan_renderQ).to.not.have.been.called;
|
||||
display._renderQPush({ type: 'noop' });
|
||||
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 () {
|
||||
const img = { complete: false, addEventListener: sinon.spy() };
|
||||
display._renderQ = [{ type: 'img', x: 3, y: 4, img: img },
|
||||
const img = { complete: false, width: 4, height: 4, 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();
|
||||
display._scanRenderQ();
|
||||
expect(display.drawImage).to.not.have.been.called;
|
||||
expect(display.fillRect).to.not.have.been.called;
|
||||
expect(img.addEventListener).to.have.been.calledOnce;
|
||||
|
||||
display._renderQ[0].img.complete = true;
|
||||
display._scan_renderQ();
|
||||
display._scanRenderQ();
|
||||
expect(display.drawImage).to.have.been.calledOnce;
|
||||
expect(display.fillRect).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 () {
|
||||
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.calledWith(3, 4, 5, 6, [7, 8, 9], 0);
|
||||
});
|
||||
|
||||
it('should draw a blit RGB image on type "blitRgb"', function () {
|
||||
display.blitRgbImage = sinon.spy();
|
||||
display._renderQ_push({ type: 'blitRgb', x: 3, y: 4, width: 5, height: 6, data: [7, 8, 9] });
|
||||
expect(display.blitRgbImage).to.have.been.calledOnce;
|
||||
expect(display.blitRgbImage).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9], 0);
|
||||
});
|
||||
|
||||
it('should copy a region on type "copy"', function () {
|
||||
display.copyImage = sinon.spy();
|
||||
display._renderQ_push({ type: 'copy', x: 3, y: 4, width: 5, height: 6, old_x: 7, old_y: 8 });
|
||||
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.calledWith(7, 8, 3, 4, 5, 6);
|
||||
});
|
||||
|
||||
it('should fill a rect with a given color on type "fill"', function () {
|
||||
display.fillRect = sinon.spy();
|
||||
display._renderQ_push({ type: 'fill', x: 3, y: 4, width: 5, height: 6, color: [7, 8, 9]});
|
||||
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.calledWith(3, 4, 5, 6, [7, 8, 9]);
|
||||
});
|
||||
|
||||
it('should draw an image from an image object on type "img" (if complete)', function () {
|
||||
display.drawImage = sinon.spy();
|
||||
display._renderQ_push({ type: 'img', x: 3, y: 4, img: { complete: true } });
|
||||
display._renderQPush({ type: 'img', x: 3, y: 4, img: { complete: true } });
|
||||
expect(display.drawImage).to.have.been.calledOnce;
|
||||
expect(display.drawImage).to.have.been.calledWith({ complete: true }, 3, 4);
|
||||
});
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+55
-55
@@ -2,7 +2,6 @@
|
||||
|
||||
import keysyms from '../core/input/keysymdef.js';
|
||||
import * as KeyboardUtil from "../core/input/util.js";
|
||||
import * as browser from '../core/util/browser.js';
|
||||
|
||||
describe('Helpers', function () {
|
||||
"use strict";
|
||||
@@ -70,11 +69,6 @@ describe('Helpers', function () {
|
||||
// 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) {
|
||||
@@ -86,7 +80,9 @@ describe('Helpers', function () {
|
||||
window.navigator.platform = "Mac x86_64";
|
||||
});
|
||||
afterEach(function () {
|
||||
Object.defineProperty(window, "navigator", origNavigator);
|
||||
if (origNavigator !== undefined) {
|
||||
Object.defineProperty(window, "navigator", origNavigator);
|
||||
}
|
||||
});
|
||||
|
||||
it('should respect ContextMenu on modern browser', function () {
|
||||
@@ -100,16 +96,15 @@ describe('Helpers', function () {
|
||||
|
||||
describe('getKey', function () {
|
||||
it('should prefer key', function () {
|
||||
if (browser.isIE() || browser.isEdge()) this.skip();
|
||||
expect(KeyboardUtil.getKey({key: 'a', charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43})).to.be.equal('a');
|
||||
});
|
||||
it('should map legacy values', function () {
|
||||
expect(KeyboardUtil.getKey({key: 'Spacebar'})).to.be.equal(' ');
|
||||
expect(KeyboardUtil.getKey({key: 'Left'})).to.be.equal('ArrowLeft');
|
||||
expect(KeyboardUtil.getKey({key: 'OS'})).to.be.equal('Meta');
|
||||
expect(KeyboardUtil.getKey({key: 'Win'})).to.be.equal('Meta');
|
||||
expect(KeyboardUtil.getKey({key: 'UIKeyInputLeftArrow'})).to.be.equal('ArrowLeft');
|
||||
});
|
||||
it('should handle broken Delete', function () {
|
||||
expect(KeyboardUtil.getKey({key: '\x00', code: 'NumpadDecimal'})).to.be.equal('Delete');
|
||||
});
|
||||
it('should use code if no key', function () {
|
||||
expect(KeyboardUtil.getKey({code: 'NumpadBackspace'})).to.be.equal('Backspace');
|
||||
});
|
||||
@@ -125,48 +120,6 @@ describe('Helpers', function () {
|
||||
it('should return Unidentified when it cannot map the key', function () {
|
||||
expect(KeyboardUtil.getKey({keycode: 0x42})).to.be.equal('Unidentified');
|
||||
});
|
||||
|
||||
describe('Broken key AltGraph on IE/Edge', function () {
|
||||
let origNavigator;
|
||||
beforeEach(function () {
|
||||
// window.navigator is a protected read-only property in many
|
||||
// environments, so we need to redefine it whilst running these
|
||||
// tests.
|
||||
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
|
||||
if (origNavigator === undefined) {
|
||||
// Object.getOwnPropertyDescriptor() doesn't work
|
||||
// properly in any version of IE
|
||||
this.skip();
|
||||
}
|
||||
|
||||
Object.defineProperty(window, "navigator", {value: {}});
|
||||
if (window.navigator.platform !== undefined) {
|
||||
// Object.defineProperty() doesn't work properly in old
|
||||
// versions of Chrome
|
||||
this.skip();
|
||||
}
|
||||
});
|
||||
afterEach(function () {
|
||||
Object.defineProperty(window, "navigator", origNavigator);
|
||||
});
|
||||
|
||||
it('should ignore printable character key on IE', function () {
|
||||
window.navigator.userAgent = "Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko";
|
||||
expect(KeyboardUtil.getKey({key: 'a'})).to.be.equal('Unidentified');
|
||||
});
|
||||
it('should ignore printable character key on Edge', function () {
|
||||
window.navigator.userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36 Edge/14.14393";
|
||||
expect(KeyboardUtil.getKey({key: 'a'})).to.be.equal('Unidentified');
|
||||
});
|
||||
it('should allow non-printable character key on IE', function () {
|
||||
window.navigator.userAgent = "Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko";
|
||||
expect(KeyboardUtil.getKey({key: 'Shift'})).to.be.equal('Shift');
|
||||
});
|
||||
it('should allow non-printable character key on Edge', function () {
|
||||
window.navigator.userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36 Edge/14.14393";
|
||||
expect(KeyboardUtil.getKey({key: 'Shift'})).to.be.equal('Shift');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getKeysym', function () {
|
||||
@@ -192,6 +145,22 @@ describe('Helpers', function () {
|
||||
expect(KeyboardUtil.getKeysym({code: 'AltRight', key: 'Alt', location: 2})).to.be.equal(0xFFEA);
|
||||
expect(KeyboardUtil.getKeysym({code: 'AltRight', key: 'AltGraph', location: 2})).to.be.equal(0xFE03);
|
||||
});
|
||||
it('should handle Windows key with incorrect location', function () {
|
||||
expect(KeyboardUtil.getKeysym({key: 'Meta', location: 0})).to.be.equal(0xFFEC);
|
||||
});
|
||||
it('should handle Clear/NumLock key with incorrect location', function () {
|
||||
this.skip(); // Broken because of Clear/NumLock override
|
||||
expect(KeyboardUtil.getKeysym({key: 'Clear', code: 'NumLock', location: 3})).to.be.equal(0xFF0B);
|
||||
});
|
||||
it('should handle Meta/Windows distinction', function () {
|
||||
expect(KeyboardUtil.getKeysym({code: 'AltLeft', key: 'Meta', location: 1})).to.be.equal(0xFFE7);
|
||||
expect(KeyboardUtil.getKeysym({code: 'AltRight', key: 'Meta', location: 2})).to.be.equal(0xFFE8);
|
||||
expect(KeyboardUtil.getKeysym({code: 'MetaLeft', key: 'Meta', location: 1})).to.be.equal(0xFFEB);
|
||||
expect(KeyboardUtil.getKeysym({code: 'MetaRight', key: 'Meta', location: 2})).to.be.equal(0xFFEC);
|
||||
});
|
||||
it('should send NumLock even if key is Clear', function () {
|
||||
expect(KeyboardUtil.getKeysym({key: 'Clear', code: 'NumLock'})).to.be.equal(0xFF7F);
|
||||
});
|
||||
it('should return null for unknown keys', function () {
|
||||
expect(KeyboardUtil.getKeysym({key: 'Semicolon'})).to.be.null;
|
||||
expect(KeyboardUtil.getKeysym({key: 'BracketRight'})).to.be.null;
|
||||
@@ -203,7 +172,6 @@ describe('Helpers', function () {
|
||||
|
||||
describe('Numpad', function () {
|
||||
it('should handle Numpad numbers', function () {
|
||||
if (browser.isIE() || browser.isEdge()) this.skip();
|
||||
expect(KeyboardUtil.getKeysym({code: 'Digit5', key: '5', location: 0})).to.be.equal(0x0035);
|
||||
expect(KeyboardUtil.getKeysym({code: 'Numpad5', key: '5', location: 3})).to.be.equal(0xFFB5);
|
||||
});
|
||||
@@ -214,10 +182,42 @@ describe('Helpers', function () {
|
||||
expect(KeyboardUtil.getKeysym({code: 'NumpadDecimal', key: 'Delete', location: 3})).to.be.equal(0xFF9F);
|
||||
});
|
||||
it('should handle Numpad Decimal key', function () {
|
||||
if (browser.isIE() || browser.isEdge()) this.skip();
|
||||
expect(KeyboardUtil.getKeysym({code: 'NumpadDecimal', key: '.', location: 3})).to.be.equal(0xFFAE);
|
||||
expect(KeyboardUtil.getKeysym({code: 'NumpadDecimal', key: ',', location: 3})).to.be.equal(0xFFAC);
|
||||
});
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
/* eslint-disable no-console */
|
||||
const expect = chai.expect;
|
||||
|
||||
import { toUnsigned32bit, toSigned32bit } from '../core/util/int.js';
|
||||
|
||||
describe('Integer casting', function () {
|
||||
it('should cast unsigned to signed', function () {
|
||||
let expected = 4294967286;
|
||||
expect(toUnsigned32bit(-10)).to.equal(expected);
|
||||
});
|
||||
|
||||
it('should cast signed to unsigned', function () {
|
||||
let expected = -10;
|
||||
expect(toSigned32bit(4294967286)).to.equal(expected);
|
||||
});
|
||||
});
|
||||
+190
-179
@@ -1,7 +1,6 @@
|
||||
const expect = chai.expect;
|
||||
|
||||
import Keyboard from '../core/input/keyboard.js';
|
||||
import * as browser from '../core/util/browser.js';
|
||||
|
||||
describe('Key Event Handling', function () {
|
||||
"use strict";
|
||||
@@ -20,7 +19,6 @@ describe('Key Event Handling', function () {
|
||||
|
||||
describe('Decode Keyboard Events', function () {
|
||||
it('should decode keydown events', function (done) {
|
||||
if (browser.isIE() || browser.isEdge()) this.skip();
|
||||
const kbd = new Keyboard(document);
|
||||
kbd.onkeyevent = (keysym, code, down) => {
|
||||
expect(keysym).to.be.equal(0x61);
|
||||
@@ -31,7 +29,6 @@ describe('Key Event Handling', function () {
|
||||
kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));
|
||||
});
|
||||
it('should decode keyup events', function (done) {
|
||||
if (browser.isIE() || browser.isEdge()) this.skip();
|
||||
let calls = 0;
|
||||
const kbd = new Keyboard(document);
|
||||
kbd.onkeyevent = (keysym, code, down) => {
|
||||
@@ -45,118 +42,10 @@ describe('Key Event Handling', function () {
|
||||
kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));
|
||||
kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA', key: 'a'}));
|
||||
});
|
||||
|
||||
describe('Legacy keypress Events', function () {
|
||||
it('should wait for keypress when needed', function () {
|
||||
const kbd = new Keyboard(document);
|
||||
kbd.onkeyevent = sinon.spy();
|
||||
kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41}));
|
||||
expect(kbd.onkeyevent).to.not.have.been.called;
|
||||
});
|
||||
it('should decode keypress events', function (done) {
|
||||
const kbd = new Keyboard(document);
|
||||
kbd.onkeyevent = (keysym, code, down) => {
|
||||
expect(keysym).to.be.equal(0x61);
|
||||
expect(code).to.be.equal('KeyA');
|
||||
expect(down).to.be.equal(true);
|
||||
done();
|
||||
};
|
||||
kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41}));
|
||||
kbd._handleKeyPress(keyevent('keypress', {code: 'KeyA', charCode: 0x61}));
|
||||
});
|
||||
it('should ignore keypress with different code', function () {
|
||||
const kbd = new Keyboard(document);
|
||||
kbd.onkeyevent = sinon.spy();
|
||||
kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41}));
|
||||
kbd._handleKeyPress(keyevent('keypress', {code: 'KeyB', charCode: 0x61}));
|
||||
expect(kbd.onkeyevent).to.not.have.been.called;
|
||||
});
|
||||
it('should handle keypress with missing code', function (done) {
|
||||
const kbd = new Keyboard(document);
|
||||
kbd.onkeyevent = (keysym, code, down) => {
|
||||
expect(keysym).to.be.equal(0x61);
|
||||
expect(code).to.be.equal('KeyA');
|
||||
expect(down).to.be.equal(true);
|
||||
done();
|
||||
};
|
||||
kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41}));
|
||||
kbd._handleKeyPress(keyevent('keypress', {charCode: 0x61}));
|
||||
});
|
||||
it('should guess key if no keypress and numeric key', function (done) {
|
||||
const kbd = new Keyboard(document);
|
||||
kbd.onkeyevent = (keysym, code, down) => {
|
||||
expect(keysym).to.be.equal(0x32);
|
||||
expect(code).to.be.equal('Digit2');
|
||||
expect(down).to.be.equal(true);
|
||||
done();
|
||||
};
|
||||
kbd._handleKeyDown(keyevent('keydown', {code: 'Digit2', keyCode: 0x32}));
|
||||
});
|
||||
it('should guess key if no keypress and alpha key', function (done) {
|
||||
const kbd = new Keyboard(document);
|
||||
kbd.onkeyevent = (keysym, code, down) => {
|
||||
expect(keysym).to.be.equal(0x61);
|
||||
expect(code).to.be.equal('KeyA');
|
||||
expect(down).to.be.equal(true);
|
||||
done();
|
||||
};
|
||||
kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41, shiftKey: false}));
|
||||
});
|
||||
it('should guess key if no keypress and alpha key (with shift)', function (done) {
|
||||
const kbd = new Keyboard(document);
|
||||
kbd.onkeyevent = (keysym, code, down) => {
|
||||
expect(keysym).to.be.equal(0x41);
|
||||
expect(code).to.be.equal('KeyA');
|
||||
expect(down).to.be.equal(true);
|
||||
done();
|
||||
};
|
||||
kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41, shiftKey: true}));
|
||||
});
|
||||
it('should not guess key if no keypress and unknown key', function (done) {
|
||||
const kbd = new Keyboard(document);
|
||||
kbd.onkeyevent = (keysym, code, down) => {
|
||||
expect(keysym).to.be.equal(0);
|
||||
expect(code).to.be.equal('KeyA');
|
||||
expect(down).to.be.equal(true);
|
||||
done();
|
||||
};
|
||||
kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x09}));
|
||||
});
|
||||
});
|
||||
|
||||
describe('suppress the right events at the right time', function () {
|
||||
beforeEach(function () {
|
||||
if (browser.isIE() || browser.isEdge()) this.skip();
|
||||
});
|
||||
it('should suppress anything with a valid key', function () {
|
||||
const kbd = new Keyboard(document, {});
|
||||
const evt1 = keyevent('keydown', {code: 'KeyA', key: 'a'});
|
||||
kbd._handleKeyDown(evt1);
|
||||
expect(evt1.preventDefault).to.have.been.called;
|
||||
const evt2 = keyevent('keyup', {code: 'KeyA', key: 'a'});
|
||||
kbd._handleKeyUp(evt2);
|
||||
expect(evt2.preventDefault).to.have.been.called;
|
||||
});
|
||||
it('should not suppress keys without key', function () {
|
||||
const kbd = new Keyboard(document, {});
|
||||
const evt = keyevent('keydown', {code: 'KeyA', keyCode: 0x41});
|
||||
kbd._handleKeyDown(evt);
|
||||
expect(evt.preventDefault).to.not.have.been.called;
|
||||
});
|
||||
it('should suppress the following keypress event', function () {
|
||||
const kbd = new Keyboard(document, {});
|
||||
const evt1 = keyevent('keydown', {code: 'KeyA', keyCode: 0x41});
|
||||
kbd._handleKeyDown(evt1);
|
||||
const evt2 = keyevent('keypress', {code: 'KeyA', charCode: 0x41});
|
||||
kbd._handleKeyPress(evt2);
|
||||
expect(evt2.preventDefault).to.have.been.called;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Fake keyup', function () {
|
||||
it('should fake keyup events for virtual keyboards', function (done) {
|
||||
if (browser.isIE() || browser.isEdge()) this.skip();
|
||||
let count = 0;
|
||||
const kbd = new Keyboard(document);
|
||||
kbd.onkeyevent = (keysym, code, down) => {
|
||||
@@ -175,60 +64,9 @@ describe('Key Event Handling', function () {
|
||||
};
|
||||
kbd._handleKeyDown(keyevent('keydown', {code: 'Unidentified', key: 'a'}));
|
||||
});
|
||||
|
||||
describe('iOS', function () {
|
||||
let origNavigator;
|
||||
beforeEach(function () {
|
||||
// window.navigator is a protected read-only property in many
|
||||
// environments, so we need to redefine it whilst running these
|
||||
// tests.
|
||||
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
|
||||
if (origNavigator === undefined) {
|
||||
// Object.getOwnPropertyDescriptor() doesn't work
|
||||
// properly in any version of IE
|
||||
this.skip();
|
||||
}
|
||||
|
||||
Object.defineProperty(window, "navigator", {value: {}});
|
||||
if (window.navigator.platform !== undefined) {
|
||||
// Object.defineProperty() doesn't work properly in old
|
||||
// versions of Chrome
|
||||
this.skip();
|
||||
}
|
||||
|
||||
window.navigator.platform = "iPhone 9.0";
|
||||
});
|
||||
afterEach(function () {
|
||||
Object.defineProperty(window, "navigator", origNavigator);
|
||||
});
|
||||
|
||||
it('should fake keyup events on iOS', function (done) {
|
||||
if (browser.isIE() || browser.isEdge()) this.skip();
|
||||
let count = 0;
|
||||
const kbd = new Keyboard(document);
|
||||
kbd.onkeyevent = (keysym, code, down) => {
|
||||
switch (count++) {
|
||||
case 0:
|
||||
expect(keysym).to.be.equal(0x61);
|
||||
expect(code).to.be.equal('KeyA');
|
||||
expect(down).to.be.equal(true);
|
||||
break;
|
||||
case 1:
|
||||
expect(keysym).to.be.equal(0x61);
|
||||
expect(code).to.be.equal('KeyA');
|
||||
expect(down).to.be.equal(false);
|
||||
done();
|
||||
}
|
||||
};
|
||||
kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Track Key State', function () {
|
||||
beforeEach(function () {
|
||||
if (browser.isIE() || browser.isEdge()) this.skip();
|
||||
});
|
||||
it('should send release using the same keysym as the press', function (done) {
|
||||
const kbd = new Keyboard(document);
|
||||
kbd.onkeyevent = (keysym, code, down) => {
|
||||
@@ -304,11 +142,6 @@ describe('Key Event Handling', function () {
|
||||
// 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) {
|
||||
@@ -320,7 +153,9 @@ describe('Key Event Handling', function () {
|
||||
window.navigator.platform = "Mac x86_64";
|
||||
});
|
||||
afterEach(function () {
|
||||
Object.defineProperty(window, "navigator", origNavigator);
|
||||
if (origNavigator !== undefined) {
|
||||
Object.defineProperty(window, "navigator", origNavigator);
|
||||
}
|
||||
});
|
||||
|
||||
it('should change Alt to AltGraph', function () {
|
||||
@@ -362,6 +197,114 @@ describe('Key Event Handling', function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Caps Lock on iOS and macOS', function () {
|
||||
let origNavigator;
|
||||
beforeEach(function () {
|
||||
// window.navigator is a protected read-only property in many
|
||||
// environments, so we need to redefine it whilst running these
|
||||
// tests.
|
||||
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
|
||||
|
||||
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 toggle caps lock on key press on iOS', function () {
|
||||
window.navigator.platform = "iPad";
|
||||
const kbd = new Keyboard(document);
|
||||
kbd.onkeyevent = sinon.spy();
|
||||
kbd._handleKeyDown(keyevent('keydown', {code: 'CapsLock', key: 'CapsLock'}));
|
||||
|
||||
expect(kbd.onkeyevent).to.have.been.calledTwice;
|
||||
expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0xFFE5, "CapsLock", true);
|
||||
expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0xFFE5, "CapsLock", false);
|
||||
});
|
||||
|
||||
it('should toggle caps lock on key press on mac', function () {
|
||||
window.navigator.platform = "Mac";
|
||||
const kbd = new Keyboard(document);
|
||||
kbd.onkeyevent = sinon.spy();
|
||||
kbd._handleKeyDown(keyevent('keydown', {code: 'CapsLock', key: 'CapsLock'}));
|
||||
|
||||
expect(kbd.onkeyevent).to.have.been.calledTwice;
|
||||
expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0xFFE5, "CapsLock", true);
|
||||
expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0xFFE5, "CapsLock", false);
|
||||
});
|
||||
|
||||
it('should toggle caps lock on key release on iOS', function () {
|
||||
window.navigator.platform = "iPad";
|
||||
const kbd = new Keyboard(document);
|
||||
kbd.onkeyevent = sinon.spy();
|
||||
kbd._handleKeyUp(keyevent('keyup', {code: 'CapsLock', key: 'CapsLock'}));
|
||||
|
||||
expect(kbd.onkeyevent).to.have.been.calledTwice;
|
||||
expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0xFFE5, "CapsLock", true);
|
||||
expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0xFFE5, "CapsLock", false);
|
||||
});
|
||||
|
||||
it('should toggle caps lock on key release on mac', function () {
|
||||
window.navigator.platform = "Mac";
|
||||
const kbd = new Keyboard(document);
|
||||
kbd.onkeyevent = sinon.spy();
|
||||
kbd._handleKeyUp(keyevent('keyup', {code: 'CapsLock', key: 'CapsLock'}));
|
||||
|
||||
expect(kbd.onkeyevent).to.have.been.calledTwice;
|
||||
expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0xFFE5, "CapsLock", true);
|
||||
expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0xFFE5, "CapsLock", false);
|
||||
});
|
||||
});
|
||||
|
||||
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 () {
|
||||
let origNavigator;
|
||||
beforeEach(function () {
|
||||
@@ -369,11 +312,6 @@ describe('Key Event Handling', function () {
|
||||
// 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) {
|
||||
@@ -387,8 +325,12 @@ describe('Key Event Handling', function () {
|
||||
this.clock = sinon.useFakeTimers();
|
||||
});
|
||||
afterEach(function () {
|
||||
Object.defineProperty(window, "navigator", origNavigator);
|
||||
this.clock.restore();
|
||||
if (origNavigator !== undefined) {
|
||||
Object.defineProperty(window, "navigator", origNavigator);
|
||||
}
|
||||
if (this.clock !== undefined) {
|
||||
this.clock.restore();
|
||||
}
|
||||
});
|
||||
|
||||
it('should supress ControlLeft until it knows if it is AltGr', function () {
|
||||
@@ -437,7 +379,7 @@ describe('Key Event Handling', function () {
|
||||
expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0x61, "KeyA", true);
|
||||
|
||||
// Check that the timer is properly dead
|
||||
kbd.onkeyevent.reset();
|
||||
kbd.onkeyevent.resetHistory();
|
||||
this.clock.tick(100);
|
||||
expect(kbd.onkeyevent).to.not.have.been.called;
|
||||
});
|
||||
@@ -455,7 +397,7 @@ describe('Key Event Handling', function () {
|
||||
expect(kbd.onkeyevent.thirdCall).to.have.been.calledWith(0x61, "KeyA", false);
|
||||
|
||||
// Check that the timer is properly dead
|
||||
kbd.onkeyevent.reset();
|
||||
kbd.onkeyevent.resetHistory();
|
||||
this.clock.tick(100);
|
||||
expect(kbd.onkeyevent).to.not.have.been.called;
|
||||
});
|
||||
@@ -470,7 +412,7 @@ describe('Key Event Handling', function () {
|
||||
expect(kbd.onkeyevent).to.have.been.calledWith(0xfe03, 'AltRight', true);
|
||||
|
||||
// Check that the timer is properly dead
|
||||
kbd.onkeyevent.reset();
|
||||
kbd.onkeyevent.resetHistory();
|
||||
this.clock.tick(100);
|
||||
expect(kbd.onkeyevent).to.not.have.been.called;
|
||||
});
|
||||
@@ -486,7 +428,7 @@ describe('Key Event Handling', function () {
|
||||
expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0xffea, "AltRight", true);
|
||||
|
||||
// Check that the timer is properly dead
|
||||
kbd.onkeyevent.reset();
|
||||
kbd.onkeyevent.resetHistory();
|
||||
this.clock.tick(100);
|
||||
expect(kbd.onkeyevent).to.not.have.been.called;
|
||||
});
|
||||
@@ -507,4 +449,73 @@ describe('Key Event Handling', function () {
|
||||
expect(kbd.onkeyevent).to.have.been.calledWith(0xfe03, 'AltRight', true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Missing Shift keyup 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 x86_64";
|
||||
|
||||
this.clock = sinon.useFakeTimers();
|
||||
});
|
||||
afterEach(function () {
|
||||
if (origNavigator !== undefined) {
|
||||
Object.defineProperty(window, "navigator", origNavigator);
|
||||
}
|
||||
if (this.clock !== undefined) {
|
||||
this.clock.restore();
|
||||
}
|
||||
});
|
||||
|
||||
it('should fake a left Shift keyup', function () {
|
||||
const kbd = new Keyboard(document);
|
||||
kbd.onkeyevent = sinon.spy();
|
||||
|
||||
kbd._handleKeyDown(keyevent('keydown', {code: 'ShiftLeft', key: 'Shift', location: 1}));
|
||||
expect(kbd.onkeyevent).to.have.been.calledOnce;
|
||||
expect(kbd.onkeyevent).to.have.been.calledWith(0xffe1, 'ShiftLeft', true);
|
||||
kbd.onkeyevent.resetHistory();
|
||||
|
||||
kbd._handleKeyDown(keyevent('keydown', {code: 'ShiftRight', key: 'Shift', location: 2}));
|
||||
expect(kbd.onkeyevent).to.have.been.calledOnce;
|
||||
expect(kbd.onkeyevent).to.have.been.calledWith(0xffe2, 'ShiftRight', true);
|
||||
kbd.onkeyevent.resetHistory();
|
||||
|
||||
kbd._handleKeyUp(keyevent('keyup', {code: 'ShiftLeft', key: 'Shift', location: 1}));
|
||||
expect(kbd.onkeyevent).to.have.been.calledTwice;
|
||||
expect(kbd.onkeyevent).to.have.been.calledWith(0xffe2, 'ShiftRight', false);
|
||||
expect(kbd.onkeyevent).to.have.been.calledWith(0xffe1, 'ShiftLeft', false);
|
||||
});
|
||||
|
||||
it('should fake a right Shift keyup', function () {
|
||||
const kbd = new Keyboard(document);
|
||||
kbd.onkeyevent = sinon.spy();
|
||||
|
||||
kbd._handleKeyDown(keyevent('keydown', {code: 'ShiftLeft', key: 'Shift', location: 1}));
|
||||
expect(kbd.onkeyevent).to.have.been.calledOnce;
|
||||
expect(kbd.onkeyevent).to.have.been.calledWith(0xffe1, 'ShiftLeft', true);
|
||||
kbd.onkeyevent.resetHistory();
|
||||
|
||||
kbd._handleKeyDown(keyevent('keydown', {code: 'ShiftRight', key: 'Shift', location: 2}));
|
||||
expect(kbd.onkeyevent).to.have.been.calledOnce;
|
||||
expect(kbd.onkeyevent).to.have.been.calledWith(0xffe2, 'ShiftRight', true);
|
||||
kbd.onkeyevent.resetHistory();
|
||||
|
||||
kbd._handleKeyUp(keyevent('keyup', {code: 'ShiftRight', key: 'Shift', location: 2}));
|
||||
expect(kbd.onkeyevent).to.have.been.calledTwice;
|
||||
expect(kbd.onkeyevent).to.have.been.calledWith(0xffe2, 'ShiftRight', false);
|
||||
expect(kbd.onkeyevent).to.have.been.calledWith(0xffe1, 'ShiftLeft', false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -11,11 +11,6 @@ describe('Localization', function () {
|
||||
// environments, so we need to redefine it whilst running these
|
||||
// tests.
|
||||
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
|
||||
if (origNavigator === undefined) {
|
||||
// Object.getOwnPropertyDescriptor() doesn't work
|
||||
// properly in any version of IE
|
||||
this.skip();
|
||||
}
|
||||
|
||||
Object.defineProperty(window, "navigator", {value: {}});
|
||||
if (window.navigator.languages !== undefined) {
|
||||
@@ -27,7 +22,9 @@ describe('Localization', function () {
|
||||
window.navigator.languages = [];
|
||||
});
|
||||
afterEach(function () {
|
||||
Object.defineProperty(window, "navigator", origNavigator);
|
||||
if (origNavigator !== undefined) {
|
||||
Object.defineProperty(window, "navigator", origNavigator);
|
||||
}
|
||||
});
|
||||
|
||||
it('should use English by default', function () {
|
||||
|
||||
@@ -1,304 +0,0 @@
|
||||
const expect = chai.expect;
|
||||
|
||||
import Mouse from '../core/input/mouse.js';
|
||||
|
||||
describe('Mouse Event Handling', function () {
|
||||
"use strict";
|
||||
|
||||
let target;
|
||||
|
||||
beforeEach(function () {
|
||||
// For these tests we can assume that the canvas is 100x100
|
||||
// located at coordinates 10x10
|
||||
target = document.createElement('canvas');
|
||||
target.style.position = "absolute";
|
||||
target.style.top = "10px";
|
||||
target.style.left = "10px";
|
||||
target.style.width = "100px";
|
||||
target.style.height = "100px";
|
||||
document.body.appendChild(target);
|
||||
});
|
||||
afterEach(function () {
|
||||
document.body.removeChild(target);
|
||||
target = null;
|
||||
});
|
||||
|
||||
// The real constructors might not work everywhere we
|
||||
// want to run these tests
|
||||
const mouseevent = (typeArg, MouseEventInit) => {
|
||||
const e = { type: typeArg };
|
||||
for (let key in MouseEventInit) {
|
||||
e[key] = MouseEventInit[key];
|
||||
}
|
||||
e.stopPropagation = sinon.spy();
|
||||
e.preventDefault = sinon.spy();
|
||||
return e;
|
||||
};
|
||||
const touchevent = mouseevent;
|
||||
|
||||
describe('Decode Mouse Events', function () {
|
||||
it('should decode mousedown events', function (done) {
|
||||
const mouse = new Mouse(target);
|
||||
mouse.onmousebutton = (x, y, down, bmask) => {
|
||||
expect(bmask).to.be.equal(0x01);
|
||||
expect(down).to.be.equal(1);
|
||||
done();
|
||||
};
|
||||
mouse._handleMouseDown(mouseevent('mousedown', { button: '0x01' }));
|
||||
});
|
||||
it('should decode mouseup events', function (done) {
|
||||
let calls = 0;
|
||||
const mouse = new Mouse(target);
|
||||
mouse.onmousebutton = (x, y, down, bmask) => {
|
||||
expect(bmask).to.be.equal(0x01);
|
||||
if (calls++ === 1) {
|
||||
expect(down).to.not.be.equal(1);
|
||||
done();
|
||||
}
|
||||
};
|
||||
mouse._handleMouseDown(mouseevent('mousedown', { button: '0x01' }));
|
||||
mouse._handleMouseUp(mouseevent('mouseup', { button: '0x01' }));
|
||||
});
|
||||
it('should decode mousemove events', function (done) {
|
||||
const mouse = new Mouse(target);
|
||||
mouse.onmousemove = (x, y) => {
|
||||
// Note that target relative coordinates are sent
|
||||
expect(x).to.be.equal(40);
|
||||
expect(y).to.be.equal(10);
|
||||
done();
|
||||
};
|
||||
mouse._handleMouseMove(mouseevent('mousemove',
|
||||
{ clientX: 50, clientY: 20 }));
|
||||
});
|
||||
it('should decode mousewheel events', function (done) {
|
||||
let calls = 0;
|
||||
const mouse = new Mouse(target);
|
||||
mouse.onmousebutton = (x, y, down, bmask) => {
|
||||
calls++;
|
||||
expect(bmask).to.be.equal(1<<6);
|
||||
if (calls === 1) {
|
||||
expect(down).to.be.equal(1);
|
||||
} else if (calls === 2) {
|
||||
expect(down).to.not.be.equal(1);
|
||||
done();
|
||||
}
|
||||
};
|
||||
mouse._handleMouseWheel(mouseevent('mousewheel',
|
||||
{ deltaX: 50, deltaY: 0,
|
||||
deltaMode: 0}));
|
||||
});
|
||||
});
|
||||
|
||||
describe('Double-click for Touch', function () {
|
||||
|
||||
beforeEach(function () { this.clock = sinon.useFakeTimers(); });
|
||||
afterEach(function () { this.clock.restore(); });
|
||||
|
||||
it('should use same pos for 2nd tap if close enough', function (done) {
|
||||
let calls = 0;
|
||||
const mouse = new Mouse(target);
|
||||
mouse.onmousebutton = (x, y, down, bmask) => {
|
||||
calls++;
|
||||
if (calls === 1) {
|
||||
expect(down).to.be.equal(1);
|
||||
expect(x).to.be.equal(68);
|
||||
expect(y).to.be.equal(36);
|
||||
} else if (calls === 3) {
|
||||
expect(down).to.be.equal(1);
|
||||
expect(x).to.be.equal(68);
|
||||
expect(y).to.be.equal(36);
|
||||
done();
|
||||
}
|
||||
};
|
||||
// touch events are sent in an array of events
|
||||
// with one item for each touch point
|
||||
mouse._handleMouseDown(touchevent(
|
||||
'touchstart', { touches: [{ clientX: 78, clientY: 46 }]}));
|
||||
this.clock.tick(10);
|
||||
mouse._handleMouseUp(touchevent(
|
||||
'touchend', { touches: [{ clientX: 79, clientY: 45 }]}));
|
||||
this.clock.tick(200);
|
||||
mouse._handleMouseDown(touchevent(
|
||||
'touchstart', { touches: [{ clientX: 67, clientY: 35 }]}));
|
||||
this.clock.tick(10);
|
||||
mouse._handleMouseUp(touchevent(
|
||||
'touchend', { touches: [{ clientX: 66, clientY: 36 }]}));
|
||||
});
|
||||
|
||||
it('should not modify 2nd tap pos if far apart', function (done) {
|
||||
let calls = 0;
|
||||
const mouse = new Mouse(target);
|
||||
mouse.onmousebutton = (x, y, down, bmask) => {
|
||||
calls++;
|
||||
if (calls === 1) {
|
||||
expect(down).to.be.equal(1);
|
||||
expect(x).to.be.equal(68);
|
||||
expect(y).to.be.equal(36);
|
||||
} else if (calls === 3) {
|
||||
expect(down).to.be.equal(1);
|
||||
expect(x).to.not.be.equal(68);
|
||||
expect(y).to.not.be.equal(36);
|
||||
done();
|
||||
}
|
||||
};
|
||||
mouse._handleMouseDown(touchevent(
|
||||
'touchstart', { touches: [{ clientX: 78, clientY: 46 }]}));
|
||||
this.clock.tick(10);
|
||||
mouse._handleMouseUp(touchevent(
|
||||
'touchend', { touches: [{ clientX: 79, clientY: 45 }]}));
|
||||
this.clock.tick(200);
|
||||
mouse._handleMouseDown(touchevent(
|
||||
'touchstart', { touches: [{ clientX: 57, clientY: 35 }]}));
|
||||
this.clock.tick(10);
|
||||
mouse._handleMouseUp(touchevent(
|
||||
'touchend', { touches: [{ clientX: 56, clientY: 36 }]}));
|
||||
});
|
||||
|
||||
it('should not modify 2nd tap pos if not soon enough', function (done) {
|
||||
let calls = 0;
|
||||
const mouse = new Mouse(target);
|
||||
mouse.onmousebutton = (x, y, down, bmask) => {
|
||||
calls++;
|
||||
if (calls === 1) {
|
||||
expect(down).to.be.equal(1);
|
||||
expect(x).to.be.equal(68);
|
||||
expect(y).to.be.equal(36);
|
||||
} else if (calls === 3) {
|
||||
expect(down).to.be.equal(1);
|
||||
expect(x).to.not.be.equal(68);
|
||||
expect(y).to.not.be.equal(36);
|
||||
done();
|
||||
}
|
||||
};
|
||||
mouse._handleMouseDown(touchevent(
|
||||
'touchstart', { touches: [{ clientX: 78, clientY: 46 }]}));
|
||||
this.clock.tick(10);
|
||||
mouse._handleMouseUp(touchevent(
|
||||
'touchend', { touches: [{ clientX: 79, clientY: 45 }]}));
|
||||
this.clock.tick(500);
|
||||
mouse._handleMouseDown(touchevent(
|
||||
'touchstart', { touches: [{ clientX: 67, clientY: 35 }]}));
|
||||
this.clock.tick(10);
|
||||
mouse._handleMouseUp(touchevent(
|
||||
'touchend', { touches: [{ clientX: 66, clientY: 36 }]}));
|
||||
});
|
||||
|
||||
it('should not modify 2nd tap pos if not touch', function (done) {
|
||||
let calls = 0;
|
||||
const mouse = new Mouse(target);
|
||||
mouse.onmousebutton = (x, y, down, bmask) => {
|
||||
calls++;
|
||||
if (calls === 1) {
|
||||
expect(down).to.be.equal(1);
|
||||
expect(x).to.be.equal(68);
|
||||
expect(y).to.be.equal(36);
|
||||
} else if (calls === 3) {
|
||||
expect(down).to.be.equal(1);
|
||||
expect(x).to.not.be.equal(68);
|
||||
expect(y).to.not.be.equal(36);
|
||||
done();
|
||||
}
|
||||
};
|
||||
mouse._handleMouseDown(mouseevent(
|
||||
'mousedown', { button: '0x01', clientX: 78, clientY: 46 }));
|
||||
this.clock.tick(10);
|
||||
mouse._handleMouseUp(mouseevent(
|
||||
'mouseup', { button: '0x01', clientX: 79, clientY: 45 }));
|
||||
this.clock.tick(200);
|
||||
mouse._handleMouseDown(mouseevent(
|
||||
'mousedown', { button: '0x01', clientX: 67, clientY: 35 }));
|
||||
this.clock.tick(10);
|
||||
mouse._handleMouseUp(mouseevent(
|
||||
'mouseup', { button: '0x01', clientX: 66, clientY: 36 }));
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('Accumulate mouse wheel events with small delta', function () {
|
||||
|
||||
beforeEach(function () { this.clock = sinon.useFakeTimers(); });
|
||||
afterEach(function () { this.clock.restore(); });
|
||||
|
||||
it('should accumulate wheel events if small enough', function () {
|
||||
const mouse = new Mouse(target);
|
||||
mouse.onmousebutton = sinon.spy();
|
||||
|
||||
mouse._handleMouseWheel(mouseevent(
|
||||
'mousewheel', { clientX: 18, clientY: 40,
|
||||
deltaX: 4, deltaY: 0, deltaMode: 0 }));
|
||||
this.clock.tick(10);
|
||||
mouse._handleMouseWheel(mouseevent(
|
||||
'mousewheel', { clientX: 18, clientY: 40,
|
||||
deltaX: 4, deltaY: 0, deltaMode: 0 }));
|
||||
|
||||
// threshold is 10
|
||||
expect(mouse._accumulatedWheelDeltaX).to.be.equal(8);
|
||||
|
||||
this.clock.tick(10);
|
||||
mouse._handleMouseWheel(mouseevent(
|
||||
'mousewheel', { clientX: 18, clientY: 40,
|
||||
deltaX: 4, deltaY: 0, deltaMode: 0 }));
|
||||
|
||||
expect(mouse.onmousebutton).to.have.callCount(2); // mouse down and up
|
||||
|
||||
this.clock.tick(10);
|
||||
mouse._handleMouseWheel(mouseevent(
|
||||
'mousewheel', { clientX: 18, clientY: 40,
|
||||
deltaX: 4, deltaY: 9, deltaMode: 0 }));
|
||||
|
||||
expect(mouse._accumulatedWheelDeltaX).to.be.equal(4);
|
||||
expect(mouse._accumulatedWheelDeltaY).to.be.equal(9);
|
||||
|
||||
expect(mouse.onmousebutton).to.have.callCount(2); // still
|
||||
});
|
||||
|
||||
it('should not accumulate large wheel events', function () {
|
||||
const mouse = new Mouse(target);
|
||||
mouse.onmousebutton = sinon.spy();
|
||||
|
||||
mouse._handleMouseWheel(mouseevent(
|
||||
'mousewheel', { clientX: 18, clientY: 40,
|
||||
deltaX: 11, deltaY: 0, deltaMode: 0 }));
|
||||
this.clock.tick(10);
|
||||
mouse._handleMouseWheel(mouseevent(
|
||||
'mousewheel', { clientX: 18, clientY: 40,
|
||||
deltaX: 0, deltaY: 70, deltaMode: 0 }));
|
||||
this.clock.tick(10);
|
||||
mouse._handleMouseWheel(mouseevent(
|
||||
'mousewheel', { clientX: 18, clientY: 40,
|
||||
deltaX: 400, deltaY: 400, deltaMode: 0 }));
|
||||
|
||||
expect(mouse.onmousebutton).to.have.callCount(8); // mouse down and up
|
||||
});
|
||||
|
||||
it('should send even small wheel events after a timeout', function () {
|
||||
const mouse = new Mouse(target);
|
||||
mouse.onmousebutton = sinon.spy();
|
||||
|
||||
mouse._handleMouseWheel(mouseevent(
|
||||
'mousewheel', { clientX: 18, clientY: 40,
|
||||
deltaX: 1, deltaY: 0, deltaMode: 0 }));
|
||||
this.clock.tick(51); // timeout on 50 ms
|
||||
|
||||
expect(mouse.onmousebutton).to.have.callCount(2); // mouse down and up
|
||||
});
|
||||
|
||||
it('should account for non-zero deltaMode', function () {
|
||||
const mouse = new Mouse(target);
|
||||
mouse.onmousebutton = sinon.spy();
|
||||
|
||||
mouse._handleMouseWheel(mouseevent(
|
||||
'mousewheel', { clientX: 18, clientY: 40,
|
||||
deltaX: 0, deltaY: 2, deltaMode: 1 }));
|
||||
|
||||
this.clock.tick(10);
|
||||
|
||||
mouse._handleMouseWheel(mouseevent(
|
||||
'mousewheel', { clientX: 18, clientY: 40,
|
||||
deltaX: 1, deltaY: 0, deltaMode: 2 }));
|
||||
|
||||
expect(mouse.onmousebutton).to.have.callCount(4); // mouse down and up
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
+2551
-899
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
+27
-7
@@ -2,6 +2,7 @@
|
||||
const expect = chai.expect;
|
||||
|
||||
import * as Log from '../core/util/logging.js';
|
||||
import { encodeUTF8, decodeUTF8 } from '../core/util/strings.js';
|
||||
|
||||
describe('Utils', function () {
|
||||
"use strict";
|
||||
@@ -21,45 +22,64 @@ describe('Utils', function () {
|
||||
console.warn.restore();
|
||||
console.error.restore();
|
||||
console.info.restore();
|
||||
Log.init_logging();
|
||||
Log.initLogging();
|
||||
});
|
||||
|
||||
it('should use noop for levels lower than the min level', function () {
|
||||
Log.init_logging('warn');
|
||||
Log.initLogging('warn');
|
||||
Log.Debug('hi');
|
||||
Log.Info('hello');
|
||||
expect(console.log).to.not.have.been.called;
|
||||
});
|
||||
|
||||
it('should use console.debug for Debug', function () {
|
||||
Log.init_logging('debug');
|
||||
Log.initLogging('debug');
|
||||
Log.Debug('dbg');
|
||||
expect(console.debug).to.have.been.calledWith('dbg');
|
||||
});
|
||||
|
||||
it('should use console.info for Info', function () {
|
||||
Log.init_logging('debug');
|
||||
Log.initLogging('debug');
|
||||
Log.Info('inf');
|
||||
expect(console.info).to.have.been.calledWith('inf');
|
||||
});
|
||||
|
||||
it('should use console.warn for Warn', function () {
|
||||
Log.init_logging('warn');
|
||||
Log.initLogging('warn');
|
||||
Log.Warn('wrn');
|
||||
expect(console.warn).to.have.been.called;
|
||||
expect(console.warn).to.have.been.calledWith('wrn');
|
||||
});
|
||||
|
||||
it('should use console.error for Error', function () {
|
||||
Log.init_logging('error');
|
||||
Log.initLogging('error');
|
||||
Log.Error('err');
|
||||
expect(console.error).to.have.been.called;
|
||||
expect(console.error).to.have.been.calledWith('err');
|
||||
});
|
||||
});
|
||||
|
||||
describe('string functions', function () {
|
||||
it('should decode UTF-8 to DOMString correctly', function () {
|
||||
const utf8string = '\xd0\x9f';
|
||||
const domstring = decodeUTF8(utf8string);
|
||||
expect(domstring).to.equal("П");
|
||||
});
|
||||
|
||||
it('should encode DOMString to UTF-8 correctly', function () {
|
||||
const domstring = "åäöa";
|
||||
const utf8string = encodeUTF8(domstring);
|
||||
expect(utf8string).to.equal('\xc3\xa5\xc3\xa4\xc3\xb6\x61');
|
||||
});
|
||||
|
||||
it('should allow Latin-1 strings if allowLatin1 is set when decoding', function () {
|
||||
const latin1string = '\xe5\xe4\xf6';
|
||||
expect(() => decodeUTF8(latin1string)).to.throw(Error);
|
||||
expect(decodeUTF8(latin1string, true)).to.equal('åäö');
|
||||
});
|
||||
});
|
||||
|
||||
// TODO(directxman12): test the conf_default and conf_defaults methods
|
||||
// TODO(directxman12): test decodeUTF8
|
||||
// TODO(directxman12): test the event methods (addEvent, removeEvent, stopEvent)
|
||||
// TODO(directxman12): figure out a good way to test getPosition and getEventPosition
|
||||
// TODO(directxman12): figure out how to test the browser detection functions properly
|
||||
|
||||
+167
-56
@@ -13,7 +13,7 @@ describe('Websock', function () {
|
||||
beforeEach(function () {
|
||||
sock = new Websock();
|
||||
// skip init
|
||||
sock._allocate_buffers();
|
||||
sock._allocateBuffers();
|
||||
sock._rQ.set(RQ_TEMPLATE);
|
||||
sock._rQlen = RQ_TEMPLATE.length;
|
||||
});
|
||||
@@ -33,51 +33,51 @@ describe('Websock', function () {
|
||||
|
||||
describe('rQpeek8', 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();
|
||||
expect(sock.rQpeek8()).to.equal(peek);
|
||||
expect(sock.rQlen).to.equal(bef_len);
|
||||
expect(sock.rQlen).to.equal(befLen);
|
||||
});
|
||||
});
|
||||
|
||||
describe('rQshift8()', function () {
|
||||
it('should pop a single byte from the receive queue', function () {
|
||||
const peek = sock.rQpeek8();
|
||||
const bef_len = sock.rQlen;
|
||||
const befLen = sock.rQlen;
|
||||
expect(sock.rQshift8()).to.equal(peek);
|
||||
expect(sock.rQlen).to.equal(bef_len - 1);
|
||||
expect(sock.rQlen).to.equal(befLen - 1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('rQshift16()', function () {
|
||||
it('should pop two bytes from the receive queue and return a single number', function () {
|
||||
const bef_len = sock.rQlen;
|
||||
const befLen = sock.rQlen;
|
||||
const expected = (RQ_TEMPLATE[0] << 8) + RQ_TEMPLATE[1];
|
||||
expect(sock.rQshift16()).to.equal(expected);
|
||||
expect(sock.rQlen).to.equal(bef_len - 2);
|
||||
expect(sock.rQlen).to.equal(befLen - 2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('rQshift32()', function () {
|
||||
it('should pop four bytes from the receive queue and return a single number', function () {
|
||||
const bef_len = sock.rQlen;
|
||||
const befLen = sock.rQlen;
|
||||
const expected = (RQ_TEMPLATE[0] << 24) +
|
||||
(RQ_TEMPLATE[1] << 16) +
|
||||
(RQ_TEMPLATE[2] << 8) +
|
||||
RQ_TEMPLATE[3];
|
||||
expect(sock.rQshift32()).to.equal(expected);
|
||||
expect(sock.rQlen).to.equal(bef_len - 4);
|
||||
expect(sock.rQlen).to.equal(befLen - 4);
|
||||
});
|
||||
});
|
||||
|
||||
describe('rQshiftStr', function () {
|
||||
it('should shift the given number of bytes off of the receive queue and return a string', function () {
|
||||
const bef_len = sock.rQlen;
|
||||
const bef_rQi = sock.rQi;
|
||||
const befLen = sock.rQlen;
|
||||
const befRQi = sock.rQi;
|
||||
const shifted = sock.rQshiftStr(3);
|
||||
expect(shifted).to.be.a('string');
|
||||
expect(shifted).to.equal(String.fromCharCode.apply(null, Array.prototype.slice.call(new Uint8Array(RQ_TEMPLATE.buffer, bef_rQi, 3))));
|
||||
expect(sock.rQlen).to.equal(bef_len - 3);
|
||||
expect(shifted).to.equal(String.fromCharCode.apply(null, Array.prototype.slice.call(new Uint8Array(RQ_TEMPLATE.buffer, befRQi, 3))));
|
||||
expect(sock.rQlen).to.equal(befLen - 3);
|
||||
});
|
||||
|
||||
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 () {
|
||||
it('should shift the given number of bytes of the receive queue and return an array', function () {
|
||||
const bef_len = sock.rQlen;
|
||||
const bef_rQi = sock.rQi;
|
||||
const befLen = sock.rQlen;
|
||||
const befRQi = sock.rQi;
|
||||
const shifted = sock.rQshiftBytes(3);
|
||||
expect(shifted).to.be.an.instanceof(Uint8Array);
|
||||
expect(shifted).to.array.equal(new Uint8Array(RQ_TEMPLATE.buffer, bef_rQi, 3));
|
||||
expect(sock.rQlen).to.equal(bef_len - 3);
|
||||
expect(shifted).to.array.equal(new Uint8Array(RQ_TEMPLATE.buffer, befRQi, 3));
|
||||
expect(sock.rQlen).to.equal(befLen - 3);
|
||||
});
|
||||
|
||||
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 () {
|
||||
const bef_len = sock.rQlen;
|
||||
const befLen = sock.rQlen;
|
||||
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 () {
|
||||
@@ -198,7 +198,7 @@ describe('Websock', function () {
|
||||
sock._websocket.readyState = WebSocket.OPEN;
|
||||
sock._sQ = new Uint8Array([1, 2, 3]);
|
||||
sock._sQlen = 3;
|
||||
const encoded = sock._encode_message();
|
||||
const encoded = sock._encodeMessage();
|
||||
|
||||
sock.flush();
|
||||
expect(sock._websocket.send).to.have.been.calledOnce;
|
||||
@@ -232,35 +232,29 @@ describe('Websock', function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe('send_string', function () {
|
||||
describe('sendString', function () {
|
||||
beforeEach(function () {
|
||||
sock.send = sinon.spy();
|
||||
});
|
||||
|
||||
it('should call send after converting the string to an array', function () {
|
||||
sock.send_string("\x01\x02\x03");
|
||||
sock.sendString("\x01\x02\x03");
|
||||
expect(sock.send).to.have.been.calledWith([1, 2, 3]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('lifecycle methods', function () {
|
||||
let old_WS;
|
||||
let oldWS;
|
||||
before(function () {
|
||||
old_WS = WebSocket;
|
||||
oldWS = WebSocket;
|
||||
});
|
||||
|
||||
let sock;
|
||||
beforeEach(function () {
|
||||
sock = new Websock();
|
||||
// eslint-disable-next-line no-global-assign
|
||||
WebSocket = sinon.spy();
|
||||
WebSocket.OPEN = old_WS.OPEN;
|
||||
WebSocket.CONNECTING = old_WS.CONNECTING;
|
||||
WebSocket.CLOSING = old_WS.CLOSING;
|
||||
WebSocket.CLOSED = old_WS.CLOSED;
|
||||
|
||||
WebSocket.prototype.binaryType = 'arraybuffer';
|
||||
WebSocket = sinon.spy(FakeWebSocket);
|
||||
});
|
||||
|
||||
describe('opening', function () {
|
||||
@@ -276,9 +270,17 @@ describe('Websock', function () {
|
||||
// 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 () {
|
||||
beforeEach(function () {
|
||||
sock.open('ws://');
|
||||
sock.open('ws://localhost');
|
||||
sock._websocket.close = sinon.spy();
|
||||
});
|
||||
|
||||
@@ -306,30 +308,30 @@ describe('Websock', function () {
|
||||
expect(sock._websocket.close).not.to.have.been.called;
|
||||
});
|
||||
|
||||
it('should reset onmessage to not call _recv_message', function () {
|
||||
sinon.spy(sock, '_recv_message');
|
||||
it('should reset onmessage to not call _recvMessage', function () {
|
||||
sinon.spy(sock, '_recvMessage');
|
||||
sock.close();
|
||||
sock._websocket.onmessage(null);
|
||||
try {
|
||||
expect(sock._recv_message).not.to.have.been.called;
|
||||
expect(sock._recvMessage).not.to.have.been.called;
|
||||
} finally {
|
||||
sock._recv_message.restore();
|
||||
sock._recvMessage.restore();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('event handlers', function () {
|
||||
beforeEach(function () {
|
||||
sock._recv_message = sinon.spy();
|
||||
sock._recvMessage = sinon.spy();
|
||||
sock.on('open', sinon.spy());
|
||||
sock.on('close', 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);
|
||||
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 () {
|
||||
@@ -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 () {
|
||||
// eslint-disable-next-line no-global-assign
|
||||
WebSocket = old_WS;
|
||||
WebSocket = oldWS;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -358,13 +447,13 @@ describe('Websock', function () {
|
||||
let sock;
|
||||
beforeEach(function () {
|
||||
sock = new Websock();
|
||||
sock._allocate_buffers();
|
||||
sock._allocateBuffers();
|
||||
});
|
||||
|
||||
it('should support adding binary Uint8Array data to the receive queue', function () {
|
||||
const msg = { data: new Uint8Array([1, 2, 3]) };
|
||||
sock._mode = 'binary';
|
||||
sock._recv_message(msg);
|
||||
sock._recvMessage(msg);
|
||||
expect(sock.rQshiftStr(3)).to.equal('\x01\x02\x03');
|
||||
});
|
||||
|
||||
@@ -372,7 +461,7 @@ describe('Websock', function () {
|
||||
sock._eventHandlers.message = sinon.spy();
|
||||
const msg = { data: new Uint8Array([1, 2, 3]).buffer };
|
||||
sock._mode = 'binary';
|
||||
sock._recv_message(msg);
|
||||
sock._recvMessage(msg);
|
||||
expect(sock._eventHandlers.message).to.have.been.calledOnce;
|
||||
});
|
||||
|
||||
@@ -380,37 +469,59 @@ describe('Websock', function () {
|
||||
sock._eventHandlers.message = sinon.spy();
|
||||
const msg = { data: new Uint8Array([]).buffer };
|
||||
sock._mode = 'binary';
|
||||
sock._recv_message(msg);
|
||||
sock._recvMessage(msg);
|
||||
expect(sock._eventHandlers.message).not.to.have.been.called;
|
||||
});
|
||||
|
||||
it('should compact the receive queue', function () {
|
||||
// NB(sross): while this is an internal implementation detail, it's important to
|
||||
// test, otherwise the receive queue could become very large very quickly
|
||||
it('should compact the receive queue when a message handler empties it', function () {
|
||||
sock._eventHandlers.message = () => { sock.rQi = sock._rQlen; };
|
||||
sock._rQ = new Uint8Array([0, 1, 2, 3, 4, 5, 0, 0, 0, 0]);
|
||||
sock._rQlen = 6;
|
||||
sock.rQi = 6;
|
||||
sock._rQmax = 3;
|
||||
const msg = { data: new Uint8Array([1, 2, 3]).buffer };
|
||||
sock._mode = 'binary';
|
||||
sock._recv_message(msg);
|
||||
expect(sock._rQlen).to.equal(3);
|
||||
sock._recvMessage(msg);
|
||||
expect(sock._rQlen).to.equal(0);
|
||||
expect(sock.rQi).to.equal(0);
|
||||
});
|
||||
|
||||
it('should automatically resize the receive queue if the incoming message is too large', function () {
|
||||
it('should compact the receive queue when we reach the end of the buffer', function () {
|
||||
sock._rQ = new Uint8Array(20);
|
||||
sock._rQbufferSize = 20;
|
||||
sock._rQlen = 20;
|
||||
sock.rQi = 10;
|
||||
const msg = { data: new Uint8Array([1, 2]).buffer };
|
||||
sock._mode = 'binary';
|
||||
sock._recvMessage(msg);
|
||||
expect(sock._rQlen).to.equal(12);
|
||||
expect(sock.rQi).to.equal(0);
|
||||
});
|
||||
|
||||
it('should automatically resize the receive queue if the incoming message is larger than the buffer', function () {
|
||||
sock._rQ = new Uint8Array(20);
|
||||
sock._rQlen = 0;
|
||||
sock.rQi = 0;
|
||||
sock._rQbufferSize = 20;
|
||||
sock._rQmax = 2;
|
||||
const msg = { data: new Uint8Array(30).buffer };
|
||||
sock._mode = 'binary';
|
||||
sock._recv_message(msg);
|
||||
sock._recvMessage(msg);
|
||||
expect(sock._rQlen).to.equal(30);
|
||||
expect(sock.rQi).to.equal(0);
|
||||
expect(sock._rQ.length).to.equal(240); // keep the invariant that rQbufferSize / 8 >= rQlen
|
||||
});
|
||||
|
||||
it('should automatically resize the receive queue if the incoming message is larger than 1/8th of the buffer and we reach the end of the buffer', function () {
|
||||
sock._rQ = new Uint8Array(20);
|
||||
sock._rQlen = 16;
|
||||
sock.rQi = 16;
|
||||
sock._rQbufferSize = 20;
|
||||
const msg = { data: new Uint8Array(6).buffer };
|
||||
sock._mode = 'binary';
|
||||
sock._recvMessage(msg);
|
||||
expect(sock._rQlen).to.equal(6);
|
||||
expect(sock.rQi).to.equal(0);
|
||||
expect(sock._rQ.length).to.equal(48);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Data encoding', function () {
|
||||
@@ -428,13 +539,13 @@ describe('Websock', 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._sQlen = 3;
|
||||
const res = sock._encode_message();
|
||||
const res = sock._encodeMessage();
|
||||
expect(res).to.array.equal(new Uint8Array([1, 2, 3]));
|
||||
});
|
||||
|
||||
it('should properly pass the encoded data off to the actual WebSocket', function () {
|
||||
sock.send([1, 2, 3]);
|
||||
expect(sock._websocket._get_sent_data()).to.array.equal(new Uint8Array([1, 2, 3]));
|
||||
expect(sock._websocket._getSentData()).to.array.equal(new Uint8Array([1, 2, 3]));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+45
-6
@@ -7,6 +7,48 @@ import * as WebUtil from '../app/webutil.js';
|
||||
describe('WebUtil', function () {
|
||||
"use strict";
|
||||
|
||||
describe('config variables', function () {
|
||||
it('should parse query string variables', function () {
|
||||
// history.pushState() will not cause the browser to attempt loading
|
||||
// the URL, this is exactly what we want here for the tests.
|
||||
history.pushState({}, '', "test?myvar=myval");
|
||||
expect(WebUtil.getConfigVar("myvar")).to.be.equal("myval");
|
||||
});
|
||||
it('should return default value when no query match', function () {
|
||||
history.pushState({}, '', "test?myvar=myval");
|
||||
expect(WebUtil.getConfigVar("other", "def")).to.be.equal("def");
|
||||
});
|
||||
it('should handle no query match and no default value', function () {
|
||||
history.pushState({}, '', "test?myvar=myval");
|
||||
expect(WebUtil.getConfigVar("other")).to.be.equal(null);
|
||||
});
|
||||
it('should parse fragment variables', function () {
|
||||
history.pushState({}, '', "test#myvar=myval");
|
||||
expect(WebUtil.getConfigVar("myvar")).to.be.equal("myval");
|
||||
});
|
||||
it('should return default value when no fragment match', function () {
|
||||
history.pushState({}, '', "test#myvar=myval");
|
||||
expect(WebUtil.getConfigVar("other", "def")).to.be.equal("def");
|
||||
});
|
||||
it('should handle no fragment match and no default value', function () {
|
||||
history.pushState({}, '', "test#myvar=myval");
|
||||
expect(WebUtil.getConfigVar("other")).to.be.equal(null);
|
||||
});
|
||||
it('should handle both query and fragment', function () {
|
||||
history.pushState({}, '', "test?myquery=1#myhash=2");
|
||||
expect(WebUtil.getConfigVar("myquery")).to.be.equal("1");
|
||||
expect(WebUtil.getConfigVar("myhash")).to.be.equal("2");
|
||||
});
|
||||
it('should prioritize fragment if both provide same var', function () {
|
||||
history.pushState({}, '', "test?myvar=1#myvar=2");
|
||||
expect(WebUtil.getConfigVar("myvar")).to.be.equal("2");
|
||||
});
|
||||
});
|
||||
|
||||
describe('cookies', function () {
|
||||
// TODO
|
||||
});
|
||||
|
||||
describe('settings', function () {
|
||||
|
||||
describe('localStorage', function () {
|
||||
@@ -22,11 +64,6 @@ describe('WebUtil', function () {
|
||||
let origLocalStorage;
|
||||
beforeEach(function () {
|
||||
origLocalStorage = Object.getOwnPropertyDescriptor(window, "localStorage");
|
||||
if (origLocalStorage === undefined) {
|
||||
// Object.getOwnPropertyDescriptor() doesn't work
|
||||
// properly in any version of IE
|
||||
this.skip();
|
||||
}
|
||||
|
||||
Object.defineProperty(window, "localStorage", {value: {}});
|
||||
if (window.localStorage.setItem !== undefined) {
|
||||
@@ -42,7 +79,9 @@ describe('WebUtil', function () {
|
||||
return WebUtil.initSettings();
|
||||
});
|
||||
afterEach(function () {
|
||||
Object.defineProperty(window, "localStorage", origLocalStorage);
|
||||
if (origLocalStorage !== undefined) {
|
||||
Object.defineProperty(window, "localStorage", origLocalStorage);
|
||||
}
|
||||
});
|
||||
|
||||
describe('writeSetting', function () {
|
||||
|
||||
@@ -2,21 +2,6 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<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>
|
||||
</head>
|
||||
<body>
|
||||
@@ -37,7 +22,5 @@
|
||||
<div id="VNC_screen">
|
||||
<div id="VNC_status">Loading</div>
|
||||
</div>
|
||||
|
||||
<script type="module" src="./playback-ui.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
## 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
|
||||
installed as system-wide.
|
||||
|
||||
|
||||
@@ -9,14 +9,14 @@
|
||||
|
||||
const fs = require('fs');
|
||||
|
||||
let show_help = process.argv.length === 2;
|
||||
let showHelp = process.argv.length === 2;
|
||||
let filename;
|
||||
|
||||
for (let i = 2; i < process.argv.length; ++i) {
|
||||
switch (process.argv[i]) {
|
||||
case "--help":
|
||||
case "-h":
|
||||
show_help = true;
|
||||
showHelp = true;
|
||||
break;
|
||||
case "--file":
|
||||
case "-f":
|
||||
@@ -26,11 +26,11 @@ for (let i = 2; i < process.argv.length; ++i) {
|
||||
}
|
||||
|
||||
if (!filename) {
|
||||
show_help = true;
|
||||
showHelp = true;
|
||||
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("Usage: node parse.js [options] filename:");
|
||||
console.log(" -h [ --help ] Produce this help message");
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
#
|
||||
# Convert image to Javascript compatible base64 Data URI
|
||||
# Copyright (C) 2018 The noVNC Authors
|
||||
# Licensed under MPL 2.0 (see docs/LICENSE.MPL-2.0)
|
||||
#
|
||||
|
||||
import sys, base64
|
||||
|
||||
try:
|
||||
from PIL import Image
|
||||
except:
|
||||
print "python PIL module required (python-imaging package)"
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if len(sys.argv) < 3:
|
||||
print "Usage: %s IMAGE JS_VARIABLE" % sys.argv[0]
|
||||
sys.exit(1)
|
||||
|
||||
fname = sys.argv[1]
|
||||
var = sys.argv[2]
|
||||
|
||||
ext = fname.lower().split('.')[-1]
|
||||
if ext == "png": mime = "image/png"
|
||||
elif ext in ["jpg", "jpeg"]: mime = "image/jpeg"
|
||||
elif ext == "gif": mime = "image/gif"
|
||||
else:
|
||||
print "Only PNG, JPEG and GIF images are supported"
|
||||
sys.exit(1)
|
||||
uri = "data:%s;base64," % mime
|
||||
|
||||
im = Image.open(fname)
|
||||
w, h = im.size
|
||||
|
||||
raw = open(fname).read()
|
||||
|
||||
print '%s = {"width": %s, "height": %s, "data": "%s%s"};' % (
|
||||
var, w, h, uri, base64.b64encode(raw))
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user