Compare commits
210 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 | |||
| 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,49 @@
|
||||
name: Publish
|
||||
|
||||
on:
|
||||
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
|
||||
- run: npm publish --access public
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
|
||||
if: ${{ !github.event.release.prerelease }}
|
||||
- run: npm publish --access public --tag beta
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
|
||||
if: ${{ github.event.release.prerelease }}
|
||||
snap:
|
||||
runs-on: ubuntu-latest
|
||||
container: snapcore/snapcraft
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v1
|
||||
- run: npm install
|
||||
- run: ./utils/use_require.js --clean --as commonjs --with-app
|
||||
- run: |
|
||||
cp utils/launch.sh build/launch.sh
|
||||
cp snap/local/svc_wrapper.sh build/svc_wrapper.sh
|
||||
- run: |
|
||||
VERSION=$(grep '"version"' package.json | cut -d '"' -f 4)
|
||||
echo $VERSION
|
||||
sed -i "s/@VERSION@/$VERSION/g" snap/snapcraft.yaml
|
||||
- run: snapcraft
|
||||
- run: |
|
||||
mkdir .snapcraft
|
||||
echo ${SNAPCRAFT_LOGIN} | base64 --decode --ignore-garbage > .snapcraft/snapcraft.cfg
|
||||
env:
|
||||
SNAPCRAFT_LOGIN: ${{secrets.SNAPCRAFT_LOGIN}}
|
||||
- run: snapcraft push --release=stable *.snap
|
||||
if: ${{ !github.event.release.prerelease }}
|
||||
- run: snapcraft push --release=beta *.snap
|
||||
if: ${{ 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,30 @@
|
||||
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
|
||||
- os: windows-latest
|
||||
browser: IE
|
||||
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
-1
@@ -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
|
||||
|
||||
@@ -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 49, Firefox 44, Safari 11, Opera 36, IE 11, Edge 12
|
||||
|
||||
|
||||
### Server Requirements
|
||||
@@ -115,6 +118,66 @@ proxy.
|
||||
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,73 @@
|
||||
{
|
||||
"Connecting...": "接続しています...",
|
||||
"Disconnecting...": "切断しています...",
|
||||
"Reconnecting...": "再接続しています...",
|
||||
"Internal error": "内部エラー",
|
||||
"Must set host": "ホストを設定する必要があります",
|
||||
"Connected (encrypted) to ": "接続しました (暗号化済み): ",
|
||||
"Connected (unencrypted) to ": "接続しました (暗号化されていません): ",
|
||||
"Something went wrong, connection is closed": "何かが問題で、接続が閉じられました",
|
||||
"Failed to connect to server": "サーバーへの接続に失敗しました",
|
||||
"Disconnected": "切断しました",
|
||||
"New connection has been rejected with reason: ": "新規接続は次の理由で拒否されました: ",
|
||||
"New connection has been rejected": "新規接続は拒否されました",
|
||||
"Password is required": "パスワードが必要です",
|
||||
"noVNC encountered an error:": "noVNC でエラーが発生しました:",
|
||||
"Hide/Show the control bar": "コントロールバーを隠す/表示する",
|
||||
"Move/Drag Viewport": "ビューポートを移動/ドラッグ",
|
||||
"viewport drag": "ビューポートをドラッグ",
|
||||
"Active Mouse Button": "アクティブなマウスボタン",
|
||||
"No mousebutton": "マウスボタンなし",
|
||||
"Left mousebutton": "左マウスボタン",
|
||||
"Middle mousebutton": "中マウスボタン",
|
||||
"Right mousebutton": "右マウスボタン",
|
||||
"Keyboard": "キーボード",
|
||||
"Show Keyboard": "キーボードを表示",
|
||||
"Extra keys": "追加キー",
|
||||
"Show Extra Keys": "追加キーを表示",
|
||||
"Ctrl": "Ctrl",
|
||||
"Toggle Ctrl": "Ctrl キーを切り替え",
|
||||
"Alt": "Alt",
|
||||
"Toggle Alt": "Alt キーを切り替え",
|
||||
"Toggle Windows": "Windows キーを切り替え",
|
||||
"Windows": "Windows",
|
||||
"Send Tab": "Tab キーを送信",
|
||||
"Tab": "Tab",
|
||||
"Esc": "Esc",
|
||||
"Send Escape": "Escape キーを送信",
|
||||
"Ctrl+Alt+Del": "Ctrl+Alt+Del",
|
||||
"Send Ctrl-Alt-Del": "Ctrl-Alt-Del を送信",
|
||||
"Shutdown/Reboot": "シャットダウン/再起動",
|
||||
"Shutdown/Reboot...": "シャットダウン/再起動...",
|
||||
"Power": "電源",
|
||||
"Shutdown": "シャットダウン",
|
||||
"Reboot": "再起動",
|
||||
"Reset": "リセット",
|
||||
"Clipboard": "クリップボード",
|
||||
"Clear": "クリア",
|
||||
"Fullscreen": "全画面表示",
|
||||
"Settings": "設定",
|
||||
"Shared Mode": "共有モード",
|
||||
"View Only": "表示のみ",
|
||||
"Clip to Window": "ウィンドウにクリップ",
|
||||
"Scaling Mode:": "スケーリングモード:",
|
||||
"None": "なし",
|
||||
"Local Scaling": "ローカルスケーリング",
|
||||
"Remote Resizing": "リモートでリサイズ",
|
||||
"Advanced": "高度",
|
||||
"Repeater ID:": "リピーター ID:",
|
||||
"WebSocket": "WebSocket",
|
||||
"Encrypt": "暗号化",
|
||||
"Host:": "ホスト:",
|
||||
"Port:": "ポート:",
|
||||
"Path:": "パス:",
|
||||
"Automatic Reconnect": "自動再接続",
|
||||
"Reconnect Delay (ms):": "再接続する遅延 (ミリ秒):",
|
||||
"Show Dot when No Cursor": "カーソルがないときにドットを表示",
|
||||
"Logging:": "ロギング:",
|
||||
"Disconnect": "切断",
|
||||
"Connect": "接続",
|
||||
"Password:": "パスワード:",
|
||||
"Send Password": "パスワードを送信",
|
||||
"Cancel": "キャンセル"
|
||||
}
|
||||
+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,17 @@ const UI = {
|
||||
// Translate the DOM
|
||||
l10n.translateDOM();
|
||||
|
||||
WebUtil.fetchJSON('./package.json')
|
||||
.then((packageInfo) => {
|
||||
Array.from(document.getElementsByClassName('noVNC_version')).forEach(el => el.innerText = packageInfo.version);
|
||||
})
|
||||
.catch((err) => {
|
||||
Log.Error("Couldn't fetch package.json: " + err);
|
||||
Array.from(document.getElementsByClassName('noVNC_version_wrapper'))
|
||||
.concat(Array.from(document.getElementsByClassName('noVNC_version_separator')))
|
||||
.forEach(el => el.style.display = 'none');
|
||||
});
|
||||
|
||||
// Adapt the interface for touch screen devices
|
||||
if (isTouchDevice) {
|
||||
document.documentElement.classList.add("noVNC_touch");
|
||||
@@ -148,6 +161,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 +234,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 +310,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 +341,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 +386,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 +422,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 +436,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 +494,7 @@ const UI = {
|
||||
}
|
||||
|
||||
// Error messages do not timeout
|
||||
if (status_type !== 'error') {
|
||||
if (statusType !== 'error') {
|
||||
UI.statusTimeout = window.setTimeout(UI.hideStatus, time);
|
||||
}
|
||||
},
|
||||
@@ -507,6 +514,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 +538,7 @@ const UI = {
|
||||
UI.closeAllPanels();
|
||||
document.getElementById('noVNC_control_bar')
|
||||
.classList.remove("noVNC_open");
|
||||
UI.rfb.focus();
|
||||
},
|
||||
|
||||
toggleControlbar() {
|
||||
@@ -821,6 +836,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 +994,7 @@ const UI = {
|
||||
|
||||
if (typeof password === 'undefined') {
|
||||
password = WebUtil.getConfigVar('password');
|
||||
UI.reconnect_password = password;
|
||||
UI.reconnectPassword = password;
|
||||
}
|
||||
|
||||
if (password === null) {
|
||||
@@ -992,7 +1009,6 @@ const UI = {
|
||||
return;
|
||||
}
|
||||
|
||||
UI.closeAllPanels();
|
||||
UI.closeConnectPanel();
|
||||
|
||||
UI.updateVisualState('connecting');
|
||||
@@ -1009,7 +1025,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 +1038,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 +1059,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 +1083,7 @@ const UI = {
|
||||
|
||||
connectFinished(e) {
|
||||
UI.connected = true;
|
||||
UI.inhibit_reconnect = false;
|
||||
UI.inhibitReconnect = false;
|
||||
|
||||
let msg;
|
||||
if (UI.getSetting('encrypt')) {
|
||||
@@ -1100,17 +1117,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 +1156,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 +1278,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 +1323,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 +1551,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 +1572,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 +1583,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 +1624,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 +1634,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 +1652,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,7 +1694,7 @@ const UI = {
|
||||
};
|
||||
|
||||
// Set up translations
|
||||
const LINGUAS = ["cs", "de", "el", "es", "ko", "nl", "pl", "ru", "sv", "tr", "zh_CN", "zh_TW"];
|
||||
const LINGUAS = ["cs", "de", "el", "es", "ja", "ko", "nl", "pl", "ru", "sv", "tr", "zh_CN", "zh_TW"];
|
||||
l10n.setup(LINGUAS);
|
||||
if (l10n.language === "en" || l10n.dictionary !== undefined) {
|
||||
UI.prime();
|
||||
|
||||
+8
-8
@@ -1,21 +1,21 @@
|
||||
/*
|
||||
* noVNC: HTML5 VNC client
|
||||
* Copyright (C) 2018 The noVNC Authors
|
||||
* Copyright (C) 2019 The noVNC Authors
|
||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||
*
|
||||
* See README.md for usage and integration instructions.
|
||||
*/
|
||||
|
||||
import { init_logging as main_init_logging } from '../core/util/logging.js';
|
||||
import { initLogging as mainInitLogging } from '../core/util/logging.js';
|
||||
|
||||
// init log level reading the logging HTTP param
|
||||
export function init_logging(level) {
|
||||
export function initLogging(level) {
|
||||
"use strict";
|
||||
if (typeof level !== "undefined") {
|
||||
main_init_logging(level);
|
||||
mainInitLogging(level);
|
||||
} else {
|
||||
const param = document.location.href.match(/logging=([A-Za-z0-9._-]*)/);
|
||||
main_init_logging(param || undefined);
|
||||
mainInitLogging(param || undefined);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,7 +184,7 @@ export function injectParamIfMissing(path, param, value) {
|
||||
const elem = document.createElement('a');
|
||||
elem.href = path;
|
||||
|
||||
const param_eq = encodeURIComponent(param) + "=";
|
||||
const paramEq = encodeURIComponent(param) + "=";
|
||||
let query;
|
||||
if (elem.search) {
|
||||
query = elem.search.slice(1).split('&');
|
||||
@@ -192,8 +192,8 @@ export function injectParamIfMissing(path, param, value) {
|
||||
query = [];
|
||||
}
|
||||
|
||||
if (!query.some(v => v.startsWith(param_eq))) {
|
||||
query.push(param_eq + encodeURIComponent(value));
|
||||
if (!query.some(v => v.startsWith(paramEq))) {
|
||||
query.push(paramEq + encodeURIComponent(value));
|
||||
elem.search = "?" + query.join("&");
|
||||
}
|
||||
|
||||
|
||||
+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.
|
||||
|
||||
+10
-12
@@ -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.
|
||||
@@ -19,10 +17,10 @@ export default class HextileDecoder {
|
||||
|
||||
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 +39,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);
|
||||
|
||||
|
||||
+8
-10
@@ -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.
|
||||
@@ -26,15 +24,15 @@ 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));
|
||||
let data = sock.rQ;
|
||||
let index = sock.rQi;
|
||||
|
||||
// Convert data if needed
|
||||
if (depth == 8) {
|
||||
const pixels = width * curr_height;
|
||||
const pixels = width * currHeight;
|
||||
const newdata = new Uint8Array(pixels * 4);
|
||||
for (let i = 0; i < pixels; i++) {
|
||||
newdata[i * 4 + 0] = ((data[index + i] >> 0) & 0x3) * 255 / 3;
|
||||
@@ -46,9 +44,9 @@ export default class RawDecoder {
|
||||
index = 0;
|
||||
}
|
||||
|
||||
display.blitImage(x, cur_y, width, curr_height, data, index);
|
||||
sock.rQskipBytes(curr_height * bytesPerLine);
|
||||
this._lines -= curr_height;
|
||||
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.
|
||||
|
||||
+8
-12
@@ -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.
|
||||
@@ -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;
|
||||
}
|
||||
@@ -162,10 +160,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);
|
||||
}
|
||||
|
||||
display.blitRgbImage(x, y, width, height, data, 0, false);
|
||||
@@ -210,10 +207,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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+88
-87
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* noVNC: HTML5 VNC client
|
||||
* Copyright (C) 2018 The noVNC Authors
|
||||
* Copyright (C) 2019 The noVNC Authors
|
||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||
*
|
||||
* See README.md for usage and integration instructions.
|
||||
@@ -9,24 +9,24 @@
|
||||
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;
|
||||
this._tileX = 0;
|
||||
this._tileY = 0;
|
||||
|
||||
Log.Debug(">> Display.constructor");
|
||||
|
||||
@@ -60,8 +60,6 @@ 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");
|
||||
@@ -74,7 +72,6 @@ export default class Display {
|
||||
|
||||
this._scale = 1.0;
|
||||
this._clipViewport = false;
|
||||
this.logo = null;
|
||||
|
||||
// ===== EVENT HANDLERS =====
|
||||
|
||||
@@ -98,11 +95,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 +122,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 +153,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 +191,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 +253,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 +299,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 +311,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 +328,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,27 +353,35 @@ 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;
|
||||
this._tileX = x;
|
||||
this._tileY = y;
|
||||
if (width === 16 && height === 16) {
|
||||
this._tile = this._tile16x16;
|
||||
} else {
|
||||
@@ -430,21 +424,21 @@ export default class Display {
|
||||
|
||||
// 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._drawCtx.putImageData(this._tile, this._tileX, this._tileY);
|
||||
this._damage(this._tileX, this._tileY,
|
||||
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,
|
||||
@@ -455,16 +449,16 @@ export default class Display {
|
||||
}
|
||||
}
|
||||
|
||||
blitRgbImage(x, y, width, height, arr, offset, from_queue) {
|
||||
if (this._renderQ.length !== 0 && !from_queue) {
|
||||
blitRgbImage(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 * 3);
|
||||
new_arr.set(new Uint8Array(arr.buffer, 0, new_arr.length));
|
||||
this._renderQ_push({
|
||||
const newArr = new Uint8Array(width * height * 3);
|
||||
newArr.set(new Uint8Array(arr.buffer, 0, newArr.length));
|
||||
this._renderQPush({
|
||||
'type': 'blitRgb',
|
||||
'data': new_arr,
|
||||
'data': newArr,
|
||||
'x': x,
|
||||
'y': y,
|
||||
'width': width,
|
||||
@@ -475,16 +469,16 @@ export default class Display {
|
||||
}
|
||||
}
|
||||
|
||||
blitRgbxImage(x, y, width, height, arr, offset, from_queue) {
|
||||
if (this._renderQ.length !== 0 && !from_queue) {
|
||||
blitRgbxImage(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': 'blitRgbx',
|
||||
'data': new_arr,
|
||||
'data': newArr,
|
||||
'x': x,
|
||||
'y': y,
|
||||
'width': width,
|
||||
@@ -589,23 +583,23 @@ export default class Display {
|
||||
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 +608,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);
|
||||
@@ -629,11 +623,18 @@ export default class Display {
|
||||
this.blitRgbxImage(a.x, a.y, a.width, a.height, a.data, 0, true);
|
||||
break;
|
||||
case 'img':
|
||||
if (a.img.complete) {
|
||||
/* IE tends to set "complete" prematurely, so check dimensions */
|
||||
if (a.img.complete && (a.img.width !== 0) && (a.img.height !== 0)) {
|
||||
if (a.img.width !== a.width || a.img.height !== a.height) {
|
||||
Log.Error("Decoded image has incorrect dimensions. Got " +
|
||||
a.img.width + "x" + a.img.height + ". Expected " +
|
||||
a.width + "x" + a.height + ".");
|
||||
return;
|
||||
}
|
||||
this.drawImage(a.img, a.x, a.y);
|
||||
} else {
|
||||
a.img._noVNC_display = this;
|
||||
a.img.addEventListener('load', this._resume_renderQ);
|
||||
a.img._noVNCDisplay = this;
|
||||
a.img.addEventListener('load', this._resumeRenderQ);
|
||||
// We need to wait for this image to 'load'
|
||||
// to keep things in-order
|
||||
ready = false;
|
||||
|
||||
+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);
|
||||
}
|
||||
|
||||
@@ -43,12 +43,10 @@ addStandard("CapsLock", KeyTable.XK_Caps_Lock);
|
||||
addLeftRight("Control", KeyTable.XK_Control_L, KeyTable.XK_Control_R);
|
||||
// - Fn
|
||||
// - FnLock
|
||||
addLeftRight("Hyper", KeyTable.XK_Super_L, KeyTable.XK_Super_R);
|
||||
addLeftRight("Meta", KeyTable.XK_Super_L, KeyTable.XK_Super_R);
|
||||
addStandard("NumLock", KeyTable.XK_Num_Lock);
|
||||
addStandard("ScrollLock", KeyTable.XK_Scroll_Lock);
|
||||
addLeftRight("Shift", KeyTable.XK_Shift_L, KeyTable.XK_Shift_R);
|
||||
addLeftRight("Super", KeyTable.XK_Super_L, KeyTable.XK_Super_R);
|
||||
// - Symbol
|
||||
// - SymbolLock
|
||||
|
||||
@@ -72,6 +70,9 @@ addNumpad("PageUp", KeyTable.XK_Prior, KeyTable.XK_KP_Prior);
|
||||
// 2.5. Editing Keys
|
||||
|
||||
addStandard("Backspace", KeyTable.XK_BackSpace);
|
||||
// Browsers send "Clear" for the numpad 5 without NumLock because
|
||||
// Windows uses VK_Clear for that key. But Unix expects KP_Begin for
|
||||
// that scenario.
|
||||
addNumpad("Clear", KeyTable.XK_Clear, KeyTable.XK_KP_Begin);
|
||||
addStandard("Copy", KeyTable.XF86XK_Copy);
|
||||
// - CrSel
|
||||
@@ -194,7 +195,8 @@ addStandard("F35", KeyTable.XK_F35);
|
||||
addStandard("Close", KeyTable.XF86XK_Close);
|
||||
addStandard("MailForward", KeyTable.XF86XK_MailForward);
|
||||
addStandard("MailReply", KeyTable.XF86XK_Reply);
|
||||
addStandard("MainSend", KeyTable.XF86XK_Send);
|
||||
addStandard("MailSend", KeyTable.XF86XK_Send);
|
||||
// - MediaClose
|
||||
addStandard("MediaFastForward", KeyTable.XF86XK_AudioForward);
|
||||
addStandard("MediaPause", KeyTable.XF86XK_AudioPause);
|
||||
addStandard("MediaPlay", KeyTable.XF86XK_AudioPlay);
|
||||
@@ -218,11 +220,9 @@ addStandard("SpellCheck", KeyTable.XF86XK_Spell);
|
||||
|
||||
// - AudioBalanceLeft
|
||||
// - AudioBalanceRight
|
||||
// - AudioBassDown
|
||||
// - AudioBassBoostDown
|
||||
// - AudioBassBoostToggle
|
||||
// - AudioBassBoostUp
|
||||
// - AudioBassUp
|
||||
// - AudioFaderFront
|
||||
// - AudioFaderRear
|
||||
// - AudioSurroundModeNext
|
||||
@@ -243,12 +243,12 @@ addStandard("MicrophoneVolumeMute", KeyTable.XF86XK_AudioMicMute);
|
||||
|
||||
// 2.14. Application Keys
|
||||
|
||||
addStandard("LaunchCalculator", KeyTable.XF86XK_Calculator);
|
||||
addStandard("LaunchApplication1", KeyTable.XF86XK_MyComputer);
|
||||
addStandard("LaunchApplication2", KeyTable.XF86XK_Calculator);
|
||||
addStandard("LaunchCalendar", KeyTable.XF86XK_Calendar);
|
||||
addStandard("LaunchMail", KeyTable.XF86XK_Mail);
|
||||
addStandard("LaunchMediaPlayer", KeyTable.XF86XK_AudioMedia);
|
||||
addStandard("LaunchMusicPlayer", KeyTable.XF86XK_Music);
|
||||
addStandard("LaunchMyComputer", KeyTable.XF86XK_MyComputer);
|
||||
addStandard("LaunchPhone", KeyTable.XF86XK_Phone);
|
||||
addStandard("LaunchScreenSaver", KeyTable.XF86XK_ScreenSaver);
|
||||
addStandard("LaunchSpreadsheet", KeyTable.XF86XK_Excel);
|
||||
|
||||
@@ -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 } };
|
||||
}
|
||||
}
|
||||
+29
-11
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* noVNC: HTML5 VNC client
|
||||
* Copyright (C) 2018 The noVNC Authors
|
||||
* Copyright (C) 2019 The noVNC Authors
|
||||
* Licensed under MPL 2.0 or any later version (see LICENSE.txt)
|
||||
*/
|
||||
|
||||
@@ -118,9 +118,7 @@ export default class Keyboard {
|
||||
|
||||
// We cannot handle keys we cannot track, but we also need
|
||||
// to deal with virtual keyboards which omit key info
|
||||
// (iOS omits tracking info on keyup events, which forces us to
|
||||
// special treat that platform here)
|
||||
if ((code === 'Unidentified') || browser.isIOS()) {
|
||||
if (code === 'Unidentified') {
|
||||
if (keysym) {
|
||||
// If it's a virtual keyboard then it should be
|
||||
// sufficient to just send press and release right
|
||||
@@ -137,7 +135,7 @@ export default class Keyboard {
|
||||
// keys around a bit to make things more sane for the remote
|
||||
// server. This method is used by RealVNC and TigerVNC (and
|
||||
// possibly others).
|
||||
if (browser.isMac()) {
|
||||
if (browser.isMac() || browser.isIOS()) {
|
||||
switch (keysym) {
|
||||
case KeyTable.XK_Super_L:
|
||||
keysym = KeyTable.XK_Alt_L;
|
||||
@@ -164,7 +162,7 @@ export default class Keyboard {
|
||||
// state change events. That gets extra confusing for CapsLock
|
||||
// which toggles on each press, but not on release. So pretend
|
||||
// it was a quick press and release of the button.
|
||||
if (browser.isMac() && (code === 'CapsLock')) {
|
||||
if ((browser.isMac() || browser.isIOS()) && (code === 'CapsLock')) {
|
||||
this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', true);
|
||||
this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', false);
|
||||
stopEvent(e);
|
||||
@@ -276,13 +274,28 @@ export default class Keyboard {
|
||||
}
|
||||
|
||||
// See comment in _handleKeyDown()
|
||||
if (browser.isMac() && (code === 'CapsLock')) {
|
||||
if ((browser.isMac() || browser.isIOS()) && (code === 'CapsLock')) {
|
||||
this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', true);
|
||||
this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', false);
|
||||
return;
|
||||
}
|
||||
|
||||
this._sendKeyEvent(this._keyDownList[code], code, false);
|
||||
|
||||
// Windows has a rather nasty bug where it won't send key
|
||||
// release events for a Shift button if the other Shift is still
|
||||
// pressed
|
||||
if (browser.isWindows() && ((code === 'ShiftLeft') ||
|
||||
(code === 'ShiftRight'))) {
|
||||
if ('ShiftRight' in this._keyDownList) {
|
||||
this._sendKeyEvent(this._keyDownList['ShiftRight'],
|
||||
'ShiftRight', false);
|
||||
}
|
||||
if ('ShiftLeft' in this._keyDownList) {
|
||||
this._sendKeyEvent(this._keyDownList['ShiftLeft'],
|
||||
'ShiftLeft', false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_handleAltGrTimeout() {
|
||||
@@ -299,8 +312,11 @@ export default class Keyboard {
|
||||
Log.Debug("<< Keyboard.allKeysUp");
|
||||
}
|
||||
|
||||
// Firefox Alt workaround, see below
|
||||
// Alt workaround for Firefox on Windows, see below
|
||||
_checkAlt(e) {
|
||||
if (e.skipCheckAlt) {
|
||||
return;
|
||||
}
|
||||
if (e.altKey) {
|
||||
return;
|
||||
}
|
||||
@@ -315,6 +331,7 @@ export default class Keyboard {
|
||||
const event = new KeyboardEvent('keyup',
|
||||
{ key: downList[code],
|
||||
code: code });
|
||||
event.skipCheckAlt = true;
|
||||
target.dispatchEvent(event);
|
||||
});
|
||||
}
|
||||
@@ -331,9 +348,10 @@ export default class Keyboard {
|
||||
// Release (key up) if window loses focus
|
||||
window.addEventListener('blur', this._eventHandlers.blur);
|
||||
|
||||
// Firefox has broken handling of Alt, so we need to poll as
|
||||
// best we can for releases (still doesn't prevent the menu
|
||||
// from popping up though as we can't call preventDefault())
|
||||
// Firefox on Windows has broken handling of Alt, so we need to
|
||||
// poll as best we can for releases (still doesn't prevent the
|
||||
// menu from popping up though as we can't call
|
||||
// preventDefault())
|
||||
if (browser.isWindows() && browser.isFirefox()) {
|
||||
const handler = this._eventHandlers.checkalt;
|
||||
['mousedown', 'mouseup', 'mousemove', 'wheel',
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
+47
-3
@@ -1,3 +1,4 @@
|
||||
import KeyTable from "./keysym.js";
|
||||
import keysyms from "./keysymdef.js";
|
||||
import vkeys from "./vkeys.js";
|
||||
import fixedkeys from "./fixedkeys.js";
|
||||
@@ -91,6 +92,8 @@ export function getKey(evt) {
|
||||
// Mozilla isn't fully in sync with the spec yet
|
||||
switch (evt.key) {
|
||||
case 'OS': return 'Meta';
|
||||
case 'LaunchMyComputer': return 'LaunchApplication1';
|
||||
case 'LaunchCalculator': return 'LaunchApplication2';
|
||||
}
|
||||
|
||||
// iOS leaks some OS names
|
||||
@@ -102,9 +105,21 @@ export function getKey(evt) {
|
||||
case 'UIKeyInputEscape': return 'Escape';
|
||||
}
|
||||
|
||||
// IE and Edge have broken handling of AltGraph so we cannot
|
||||
// trust them for printable characters
|
||||
if ((evt.key.length !== 1) || (!browser.isIE() && !browser.isEdge())) {
|
||||
// Broken behaviour in Chrome
|
||||
if ((evt.key === '\x00') && (evt.code === 'NumpadDecimal')) {
|
||||
return 'Delete';
|
||||
}
|
||||
|
||||
// IE and Edge need special handling, but for everyone else we
|
||||
// can trust the value provided
|
||||
if (!browser.isIE() && !browser.isEdge()) {
|
||||
return evt.key;
|
||||
}
|
||||
|
||||
// IE and Edge have broken handling of AltGraph so we can only
|
||||
// trust them for non-printable characters (and unfortunately
|
||||
// they also specify 'Unidentified' for some problem keys)
|
||||
if ((evt.key.length !== 1) && (evt.key !== 'Unidentified')) {
|
||||
return evt.key;
|
||||
}
|
||||
}
|
||||
@@ -141,10 +156,39 @@ export function getKeysym(evt) {
|
||||
location = 2;
|
||||
}
|
||||
|
||||
// And for Clear
|
||||
if ((key === 'Clear') && (location === 3)) {
|
||||
let code = getKeycode(evt);
|
||||
if (code === 'NumLock') {
|
||||
location = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if ((location === undefined) || (location > 3)) {
|
||||
location = 0;
|
||||
}
|
||||
|
||||
// The original Meta key now gets confused with the Windows key
|
||||
// https://bugs.chromium.org/p/chromium/issues/detail?id=1020141
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=1232918
|
||||
if (key === 'Meta') {
|
||||
let code = getKeycode(evt);
|
||||
if (code === 'AltLeft') {
|
||||
return KeyTable.XK_Meta_L;
|
||||
} else if (code === 'AltRight') {
|
||||
return KeyTable.XK_Meta_R;
|
||||
}
|
||||
}
|
||||
|
||||
// macOS has Clear instead of NumLock, but the remote system is
|
||||
// probably not macOS, so lying here is probably best...
|
||||
if (key === 'Clear') {
|
||||
let code = getKeycode(evt);
|
||||
if (code === 'NumLock') {
|
||||
return KeyTable.XK_Num_Lock;
|
||||
}
|
||||
}
|
||||
|
||||
return DOMKeyTable[key][location];
|
||||
}
|
||||
|
||||
|
||||
+1216
-290
File diff suppressed because it is too large
Load Diff
+36
-6
@@ -1,9 +1,11 @@
|
||||
/*
|
||||
* noVNC: HTML5 VNC client
|
||||
* Copyright (C) 2018 The noVNC Authors
|
||||
* Copyright (C) 2019 The noVNC Authors
|
||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||
*
|
||||
* See README.md for usage and integration instructions.
|
||||
*
|
||||
* Browser feature support detection
|
||||
*/
|
||||
|
||||
import * as Log from './logging.js';
|
||||
@@ -31,7 +33,7 @@ try {
|
||||
const target = document.createElement('canvas');
|
||||
target.style.cursor = 'url("data:image/x-icon;base64,AAACAAEACAgAAAIAAgA4AQAAFgAAACgAAAAIAAAAEAAAAAEAIAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAAAAAAAAAAAAAAAA==") 2 2, default';
|
||||
|
||||
if (target.style.cursor) {
|
||||
if (target.style.cursor.indexOf("url") === 0) {
|
||||
Log.Info("Data URI scheme cursor supported");
|
||||
_supportsCursorURIs = true;
|
||||
} else {
|
||||
@@ -52,6 +54,38 @@ try {
|
||||
}
|
||||
export const supportsImageMetadata = _supportsImageMetadata;
|
||||
|
||||
let _hasScrollbarGutter = true;
|
||||
try {
|
||||
// Create invisible container
|
||||
const container = document.createElement('div');
|
||||
container.style.visibility = 'hidden';
|
||||
container.style.overflow = 'scroll'; // forcing scrollbars
|
||||
document.body.appendChild(container);
|
||||
|
||||
// Create a div and place it in the container
|
||||
const child = document.createElement('div');
|
||||
container.appendChild(child);
|
||||
|
||||
// Calculate the difference between the container's full width
|
||||
// and the child's width - the difference is the scrollbars
|
||||
const scrollbarWidth = (container.offsetWidth - child.offsetWidth);
|
||||
|
||||
// Clean up
|
||||
container.parentNode.removeChild(container);
|
||||
|
||||
_hasScrollbarGutter = scrollbarWidth != 0;
|
||||
} catch (exc) {
|
||||
Log.Error("Scrollbar test exception: " + exc);
|
||||
}
|
||||
export const hasScrollbarGutter = _hasScrollbarGutter;
|
||||
|
||||
/*
|
||||
* The functions for detection of platforms and browsers below are exported
|
||||
* but the use of these should be minimized as much as possible.
|
||||
*
|
||||
* It's better to use feature detection than platform detection.
|
||||
*/
|
||||
|
||||
export function isMac() {
|
||||
return navigator && !!(/mac/i).exec(navigator.platform);
|
||||
}
|
||||
@@ -67,10 +101,6 @@ export function isIOS() {
|
||||
!!(/ipod/i).exec(navigator.platform));
|
||||
}
|
||||
|
||||
export function isAndroid() {
|
||||
return navigator && !!(/android/i).exec(navigator.userAgent);
|
||||
}
|
||||
|
||||
export function isSafari() {
|
||||
return navigator && (navigator.userAgent.indexOf('Safari') !== -1 &&
|
||||
navigator.userAgent.indexOf('Chrome') === -1);
|
||||
|
||||
+66
-34
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* noVNC: HTML5 VNC client
|
||||
* Copyright (C) 2018 The noVNC Authors
|
||||
* Copyright (C) 2019 The noVNC Authors
|
||||
* Licensed under MPL 2.0 or any later version (see LICENSE.txt)
|
||||
*/
|
||||
|
||||
@@ -20,7 +20,6 @@ export default class Cursor {
|
||||
this._canvas.style.pointerEvents = 'none';
|
||||
// Can't use "display" because of Firefox bug #1445997
|
||||
this._canvas.style.visibility = 'hidden';
|
||||
document.body.appendChild(this._canvas);
|
||||
}
|
||||
|
||||
this._position = { x: 0, y: 0 };
|
||||
@@ -31,9 +30,6 @@ export default class Cursor {
|
||||
'mouseleave': this._handleMouseLeave.bind(this),
|
||||
'mousemove': this._handleMouseMove.bind(this),
|
||||
'mouseup': this._handleMouseUp.bind(this),
|
||||
'touchstart': this._handleTouchStart.bind(this),
|
||||
'touchmove': this._handleTouchMove.bind(this),
|
||||
'touchend': this._handleTouchEnd.bind(this),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -45,6 +41,8 @@ export default class Cursor {
|
||||
this._target = target;
|
||||
|
||||
if (useFallback) {
|
||||
document.body.appendChild(this._canvas);
|
||||
|
||||
// FIXME: These don't fire properly except for mouse
|
||||
/// movement in IE. We want to also capture element
|
||||
// movement, size changes, visibility, etc.
|
||||
@@ -53,17 +51,16 @@ export default class Cursor {
|
||||
this._target.addEventListener('mouseleave', this._eventHandlers.mouseleave, options);
|
||||
this._target.addEventListener('mousemove', this._eventHandlers.mousemove, options);
|
||||
this._target.addEventListener('mouseup', this._eventHandlers.mouseup, options);
|
||||
|
||||
// There is no "touchleave" so we monitor touchstart globally
|
||||
window.addEventListener('touchstart', this._eventHandlers.touchstart, options);
|
||||
this._target.addEventListener('touchmove', this._eventHandlers.touchmove, options);
|
||||
this._target.addEventListener('touchend', this._eventHandlers.touchend, options);
|
||||
}
|
||||
|
||||
this.clear();
|
||||
}
|
||||
|
||||
detach() {
|
||||
if (!this._target) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (useFallback) {
|
||||
const options = { capture: true, passive: true };
|
||||
this._target.removeEventListener('mouseover', this._eventHandlers.mouseover, options);
|
||||
@@ -71,9 +68,7 @@ export default class Cursor {
|
||||
this._target.removeEventListener('mousemove', this._eventHandlers.mousemove, options);
|
||||
this._target.removeEventListener('mouseup', this._eventHandlers.mouseup, options);
|
||||
|
||||
window.removeEventListener('touchstart', this._eventHandlers.touchstart, options);
|
||||
this._target.removeEventListener('touchmove', this._eventHandlers.touchmove, options);
|
||||
this._target.removeEventListener('touchend', this._eventHandlers.touchend, options);
|
||||
document.body.removeChild(this._canvas);
|
||||
}
|
||||
|
||||
this._target = null;
|
||||
@@ -124,6 +119,27 @@ export default class Cursor {
|
||||
this._hotSpot.y = 0;
|
||||
}
|
||||
|
||||
// Mouse events might be emulated, this allows
|
||||
// moving the cursor in such cases
|
||||
move(clientX, clientY) {
|
||||
if (!useFallback) {
|
||||
return;
|
||||
}
|
||||
// clientX/clientY are relative the _visual viewport_,
|
||||
// but our position is relative the _layout viewport_,
|
||||
// so try to compensate when we can
|
||||
if (window.visualViewport) {
|
||||
this._position.x = clientX + window.visualViewport.offsetLeft;
|
||||
this._position.y = clientY + window.visualViewport.offsetTop;
|
||||
} else {
|
||||
this._position.x = clientX;
|
||||
this._position.y = clientY;
|
||||
}
|
||||
this._updatePosition();
|
||||
let target = document.elementFromPoint(clientX, clientY);
|
||||
this._updateVisibility(target);
|
||||
}
|
||||
|
||||
_handleMouseOver(event) {
|
||||
// This event could be because we're entering the target, or
|
||||
// moving around amongst its sub elements. Let the move handler
|
||||
@@ -132,7 +148,8 @@ export default class Cursor {
|
||||
}
|
||||
|
||||
_handleMouseLeave(event) {
|
||||
this._hideCursor();
|
||||
// Check if we should show the cursor on the element we are leaving to
|
||||
this._updateVisibility(event.relatedTarget);
|
||||
}
|
||||
|
||||
_handleMouseMove(event) {
|
||||
@@ -150,27 +167,29 @@ export default class Cursor {
|
||||
// now and adjust visibility based on that.
|
||||
let target = document.elementFromPoint(event.clientX, event.clientY);
|
||||
this._updateVisibility(target);
|
||||
}
|
||||
|
||||
_handleTouchStart(event) {
|
||||
// Just as for mouseover, we let the move handler deal with it
|
||||
this._handleTouchMove(event);
|
||||
}
|
||||
|
||||
_handleTouchMove(event) {
|
||||
this._updateVisibility(event.target);
|
||||
|
||||
this._position.x = event.changedTouches[0].clientX - this._hotSpot.x;
|
||||
this._position.y = event.changedTouches[0].clientY - this._hotSpot.y;
|
||||
|
||||
this._updatePosition();
|
||||
}
|
||||
|
||||
_handleTouchEnd(event) {
|
||||
// Same principle as for mouseup
|
||||
let target = document.elementFromPoint(event.changedTouches[0].clientX,
|
||||
event.changedTouches[0].clientY);
|
||||
this._updateVisibility(target);
|
||||
// Captures end with a mouseup but we can't know the event order of
|
||||
// mouseup vs releaseCapture.
|
||||
//
|
||||
// In the cases when releaseCapture comes first, the code above is
|
||||
// enough.
|
||||
//
|
||||
// In the cases when the mouseup comes first, we need wait for the
|
||||
// browser to flush all events and then check again if the cursor
|
||||
// should be visible.
|
||||
if (this._captureIsActive()) {
|
||||
window.setTimeout(() => {
|
||||
// We might have detached at this point
|
||||
if (!this._target) {
|
||||
return;
|
||||
}
|
||||
// Refresh the target from elementFromPoint since queued events
|
||||
// might have altered the DOM
|
||||
target = document.elementFromPoint(event.clientX,
|
||||
event.clientY);
|
||||
this._updateVisibility(target);
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
|
||||
_showCursor() {
|
||||
@@ -189,6 +208,9 @@ export default class Cursor {
|
||||
// (i.e. are we over the target, or a child of the target without a
|
||||
// different cursor set)
|
||||
_shouldShowCursor(target) {
|
||||
if (!target) {
|
||||
return false;
|
||||
}
|
||||
// Easy case
|
||||
if (target === this._target) {
|
||||
return true;
|
||||
@@ -207,6 +229,11 @@ export default class Cursor {
|
||||
}
|
||||
|
||||
_updateVisibility(target) {
|
||||
// When the cursor target has capture we want to show the cursor.
|
||||
// So, if a capture is active - look at the captured element instead.
|
||||
if (this._captureIsActive()) {
|
||||
target = document.captureElement;
|
||||
}
|
||||
if (this._shouldShowCursor(target)) {
|
||||
this._showCursor();
|
||||
} else {
|
||||
@@ -218,4 +245,9 @@ export default class Cursor {
|
||||
this._canvas.style.left = this._position.x + "px";
|
||||
this._canvas.style.top = this._position.y + "px";
|
||||
}
|
||||
|
||||
_captureIsActive() {
|
||||
return document.captureElement &&
|
||||
document.documentElement.contains(document.captureElement);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
+47
-44
@@ -21,7 +21,8 @@ export function stopEvent(e) {
|
||||
|
||||
// Emulate Element.setCapture() when not supported
|
||||
let _captureRecursion = false;
|
||||
let _captureElem = null;
|
||||
let _elementForUnflushedEvents = null;
|
||||
document.captureElement = null;
|
||||
function _captureProxy(e) {
|
||||
// Recursion protection as we'll see our own event
|
||||
if (_captureRecursion) return;
|
||||
@@ -30,7 +31,11 @@ function _captureProxy(e) {
|
||||
const newEv = new e.constructor(e.type, e);
|
||||
|
||||
_captureRecursion = true;
|
||||
_captureElem.dispatchEvent(newEv);
|
||||
if (document.captureElement) {
|
||||
document.captureElement.dispatchEvent(newEv);
|
||||
} else {
|
||||
_elementForUnflushedEvents.dispatchEvent(newEv);
|
||||
}
|
||||
_captureRecursion = false;
|
||||
|
||||
// Avoid double events
|
||||
@@ -48,58 +53,56 @@ function _captureProxy(e) {
|
||||
}
|
||||
|
||||
// Follow cursor style of target element
|
||||
function _captureElemChanged() {
|
||||
const captureElem = document.getElementById("noVNC_mouse_capture_elem");
|
||||
captureElem.style.cursor = window.getComputedStyle(_captureElem).cursor;
|
||||
function _capturedElemChanged() {
|
||||
const proxyElem = document.getElementById("noVNC_mouse_capture_elem");
|
||||
proxyElem.style.cursor = window.getComputedStyle(document.captureElement).cursor;
|
||||
}
|
||||
|
||||
const _captureObserver = new MutationObserver(_captureElemChanged);
|
||||
const _captureObserver = new MutationObserver(_capturedElemChanged);
|
||||
|
||||
let _captureIndex = 0;
|
||||
export function setCapture(target) {
|
||||
if (target.setCapture) {
|
||||
|
||||
export function setCapture(elem) {
|
||||
if (elem.setCapture) {
|
||||
|
||||
elem.setCapture();
|
||||
target.setCapture();
|
||||
document.captureElement = target;
|
||||
|
||||
// IE releases capture on 'click' events which might not trigger
|
||||
elem.addEventListener('mouseup', releaseCapture);
|
||||
target.addEventListener('mouseup', releaseCapture);
|
||||
|
||||
} else {
|
||||
// Release any existing capture in case this method is
|
||||
// called multiple times without coordination
|
||||
releaseCapture();
|
||||
|
||||
let captureElem = document.getElementById("noVNC_mouse_capture_elem");
|
||||
let proxyElem = document.getElementById("noVNC_mouse_capture_elem");
|
||||
|
||||
if (captureElem === null) {
|
||||
captureElem = document.createElement("div");
|
||||
captureElem.id = "noVNC_mouse_capture_elem";
|
||||
captureElem.style.position = "fixed";
|
||||
captureElem.style.top = "0px";
|
||||
captureElem.style.left = "0px";
|
||||
captureElem.style.width = "100%";
|
||||
captureElem.style.height = "100%";
|
||||
captureElem.style.zIndex = 10000;
|
||||
captureElem.style.display = "none";
|
||||
document.body.appendChild(captureElem);
|
||||
if (proxyElem === null) {
|
||||
proxyElem = document.createElement("div");
|
||||
proxyElem.id = "noVNC_mouse_capture_elem";
|
||||
proxyElem.style.position = "fixed";
|
||||
proxyElem.style.top = "0px";
|
||||
proxyElem.style.left = "0px";
|
||||
proxyElem.style.width = "100%";
|
||||
proxyElem.style.height = "100%";
|
||||
proxyElem.style.zIndex = 10000;
|
||||
proxyElem.style.display = "none";
|
||||
document.body.appendChild(proxyElem);
|
||||
|
||||
// This is to make sure callers don't get confused by having
|
||||
// our blocking element as the target
|
||||
captureElem.addEventListener('contextmenu', _captureProxy);
|
||||
proxyElem.addEventListener('contextmenu', _captureProxy);
|
||||
|
||||
captureElem.addEventListener('mousemove', _captureProxy);
|
||||
captureElem.addEventListener('mouseup', _captureProxy);
|
||||
proxyElem.addEventListener('mousemove', _captureProxy);
|
||||
proxyElem.addEventListener('mouseup', _captureProxy);
|
||||
}
|
||||
|
||||
_captureElem = elem;
|
||||
_captureIndex++;
|
||||
document.captureElement = target;
|
||||
|
||||
// Track cursor and get initial cursor
|
||||
_captureObserver.observe(elem, {attributes: true});
|
||||
_captureElemChanged();
|
||||
_captureObserver.observe(target, {attributes: true});
|
||||
_capturedElemChanged();
|
||||
|
||||
captureElem.style.display = "";
|
||||
proxyElem.style.display = "";
|
||||
|
||||
// We listen to events on window in order to keep tracking if it
|
||||
// happens to leave the viewport
|
||||
@@ -112,26 +115,26 @@ export function releaseCapture() {
|
||||
if (document.releaseCapture) {
|
||||
|
||||
document.releaseCapture();
|
||||
document.captureElement = null;
|
||||
|
||||
} else {
|
||||
if (!_captureElem) {
|
||||
if (!document.captureElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
// There might be events already queued, so we need to wait for
|
||||
// them to flush. E.g. contextmenu in Microsoft Edge
|
||||
window.setTimeout((expected) => {
|
||||
// Only clear it if it's the expected grab (i.e. no one
|
||||
// else has initiated a new grab)
|
||||
if (_captureIndex === expected) {
|
||||
_captureElem = null;
|
||||
}
|
||||
}, 0, _captureIndex);
|
||||
// There might be events already queued. The event proxy needs
|
||||
// access to the captured element for these queued events.
|
||||
// E.g. contextmenu (right-click) in Microsoft Edge
|
||||
//
|
||||
// Before removing the capturedElem pointer we save it to a
|
||||
// temporary variable that the unflushed events can use.
|
||||
_elementForUnflushedEvents = document.captureElement;
|
||||
document.captureElement = null;
|
||||
|
||||
_captureObserver.disconnect();
|
||||
|
||||
const captureElem = document.getElementById("noVNC_mouse_capture_elem");
|
||||
captureElem.style.display = "none";
|
||||
const proxyElem = document.getElementById("noVNC_mouse_capture_elem");
|
||||
proxyElem.style.display = "none";
|
||||
|
||||
window.removeEventListener('mousemove', _captureProxy);
|
||||
window.removeEventListener('mouseup', _captureProxy);
|
||||
|
||||
@@ -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,6 +1,6 @@
|
||||
/*
|
||||
* noVNC: HTML5 VNC client
|
||||
* Copyright (C) 2018 The noVNC Authors
|
||||
* Copyright (C) 2020 The noVNC Authors
|
||||
* Licensed under MPL 2.0 or any later version (see LICENSE.txt)
|
||||
*/
|
||||
|
||||
@@ -52,3 +52,10 @@ if (typeof Object.assign != 'function') {
|
||||
window.CustomEvent = CustomEvent;
|
||||
}
|
||||
})();
|
||||
|
||||
/* Number.isInteger() (taken from MDN) */
|
||||
Number.isInteger = Number.isInteger || function isInteger(value) {
|
||||
return typeof value === 'number' &&
|
||||
isFinite(value) &&
|
||||
Math.floor(value) === value;
|
||||
};
|
||||
|
||||
+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));
|
||||
}
|
||||
|
||||
+35
-31
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Websock: high-performance binary WebSockets
|
||||
* Copyright (C) 2018 The noVNC Authors
|
||||
* Copyright (C) 2019 The noVNC Authors
|
||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||
*
|
||||
* Websock is similar to the standard WebSocket object but with extra
|
||||
@@ -17,6 +17,8 @@ import * as Log from './util/logging.js';
|
||||
// this has performance issues in some versions Chromium, and
|
||||
// doesn't gain a tremendous amount of performance increase in Firefox
|
||||
// at the moment. It may be valuable to turn it on in the future.
|
||||
// Also copyWithin() for TypedArrays is not supported in IE 11 or
|
||||
// Safari 13 (at the moment we want to support Safari 11).
|
||||
const ENABLE_COPYWITHIN = false;
|
||||
const MAX_RQ_GROW_SIZE = 40 * 1024 * 1024; // 40 MiB
|
||||
|
||||
@@ -27,7 +29,6 @@ export default class Websock {
|
||||
this._rQi = 0; // Receive queue index
|
||||
this._rQlen = 0; // Next write position in the receive queue
|
||||
this._rQbufferSize = 1024 * 1024 * 4; // Receive queue buffer size (4 MiB)
|
||||
this._rQmax = this._rQbufferSize / 8;
|
||||
// called in init: this._rQ = new Uint8Array(this._rQbufferSize);
|
||||
this._rQ = null; // Receive queue
|
||||
|
||||
@@ -143,7 +144,7 @@ export default class Websock {
|
||||
|
||||
flush() {
|
||||
if (this._sQlen > 0 && this._websocket.readyState === WebSocket.OPEN) {
|
||||
this._websocket.send(this._encode_message());
|
||||
this._websocket.send(this._encodeMessage());
|
||||
this._sQlen = 0;
|
||||
}
|
||||
}
|
||||
@@ -154,7 +155,7 @@ export default class Websock {
|
||||
this.flush();
|
||||
}
|
||||
|
||||
send_string(str) {
|
||||
sendString(str) {
|
||||
this.send(str.split('').map(chr => chr.charCodeAt(0)));
|
||||
}
|
||||
|
||||
@@ -167,13 +168,13 @@ export default class Websock {
|
||||
this._eventHandlers[evt] = handler;
|
||||
}
|
||||
|
||||
_allocate_buffers() {
|
||||
_allocateBuffers() {
|
||||
this._rQ = new Uint8Array(this._rQbufferSize);
|
||||
this._sQ = new Uint8Array(this._sQbufferSize);
|
||||
}
|
||||
|
||||
init() {
|
||||
this._allocate_buffers();
|
||||
this._allocateBuffers();
|
||||
this._rQi = 0;
|
||||
this._websocket = null;
|
||||
}
|
||||
@@ -184,7 +185,7 @@ export default class Websock {
|
||||
this._websocket = new WebSocket(uri, protocols);
|
||||
this._websocket.binaryType = 'arraybuffer';
|
||||
|
||||
this._websocket.onmessage = this._recv_message.bind(this);
|
||||
this._websocket.onmessage = this._recvMessage.bind(this);
|
||||
this._websocket.onopen = () => {
|
||||
Log.Debug('>> WebSock.onopen');
|
||||
if (this._websocket.protocol) {
|
||||
@@ -219,42 +220,46 @@ export default class Websock {
|
||||
}
|
||||
|
||||
// private methods
|
||||
_encode_message() {
|
||||
_encodeMessage() {
|
||||
// Put in a binary arraybuffer
|
||||
// according to the spec, you can send ArrayBufferViews with the send method
|
||||
return new Uint8Array(this._sQ.buffer, 0, this._sQlen);
|
||||
}
|
||||
|
||||
_expand_compact_rQ(min_fit) {
|
||||
const resizeNeeded = min_fit || this.rQlen > this._rQbufferSize / 2;
|
||||
// We want to move all the unread data to the start of the queue,
|
||||
// e.g. compacting.
|
||||
// The function also expands the receive que if needed, and for
|
||||
// performance reasons we combine these two actions to avoid
|
||||
// unneccessary copying.
|
||||
_expandCompactRQ(minFit) {
|
||||
// if we're using less than 1/8th of the buffer even with the incoming bytes, compact in place
|
||||
// instead of resizing
|
||||
const requiredBufferSize = (this._rQlen - this._rQi + minFit) * 8;
|
||||
const resizeNeeded = this._rQbufferSize < requiredBufferSize;
|
||||
|
||||
if (resizeNeeded) {
|
||||
if (!min_fit) {
|
||||
// just double the size if we need to do compaction
|
||||
this._rQbufferSize *= 2;
|
||||
} else {
|
||||
// otherwise, make sure we satisy rQlen - rQi + min_fit < rQbufferSize / 8
|
||||
this._rQbufferSize = (this.rQlen + min_fit) * 8;
|
||||
}
|
||||
// Make sure we always *at least* double the buffer size, and have at least space for 8x
|
||||
// the current amount of data
|
||||
this._rQbufferSize = Math.max(this._rQbufferSize * 2, requiredBufferSize);
|
||||
}
|
||||
|
||||
// we don't want to grow unboundedly
|
||||
if (this._rQbufferSize > MAX_RQ_GROW_SIZE) {
|
||||
this._rQbufferSize = MAX_RQ_GROW_SIZE;
|
||||
if (this._rQbufferSize - this.rQlen < min_fit) {
|
||||
if (this._rQbufferSize - this.rQlen < minFit) {
|
||||
throw new Error("Receive Queue buffer exceeded " + MAX_RQ_GROW_SIZE + " bytes, and the new message could not fit");
|
||||
}
|
||||
}
|
||||
|
||||
if (resizeNeeded) {
|
||||
const old_rQbuffer = this._rQ.buffer;
|
||||
this._rQmax = this._rQbufferSize / 8;
|
||||
const oldRQbuffer = this._rQ.buffer;
|
||||
this._rQ = new Uint8Array(this._rQbufferSize);
|
||||
this._rQ.set(new Uint8Array(old_rQbuffer, this._rQi));
|
||||
this._rQ.set(new Uint8Array(oldRQbuffer, this._rQi, this._rQlen - this._rQi));
|
||||
} else {
|
||||
if (ENABLE_COPYWITHIN) {
|
||||
this._rQ.copyWithin(0, this._rQi);
|
||||
this._rQ.copyWithin(0, this._rQi, this._rQlen);
|
||||
} else {
|
||||
this._rQ.set(new Uint8Array(this._rQ.buffer, this._rQi));
|
||||
this._rQ.set(new Uint8Array(this._rQ.buffer, this._rQi, this._rQlen - this._rQi));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -262,26 +267,25 @@ export default class Websock {
|
||||
this._rQi = 0;
|
||||
}
|
||||
|
||||
_decode_message(data) {
|
||||
// push arraybuffer values onto the end
|
||||
// push arraybuffer values onto the end of the receive que
|
||||
_DecodeMessage(data) {
|
||||
const u8 = new Uint8Array(data);
|
||||
if (u8.length > this._rQbufferSize - this._rQlen) {
|
||||
this._expand_compact_rQ(u8.length);
|
||||
this._expandCompactRQ(u8.length);
|
||||
}
|
||||
this._rQ.set(u8, this._rQlen);
|
||||
this._rQlen += u8.length;
|
||||
}
|
||||
|
||||
_recv_message(e) {
|
||||
this._decode_message(e.data);
|
||||
_recvMessage(e) {
|
||||
this._DecodeMessage(e.data);
|
||||
if (this.rQlen > 0) {
|
||||
this._eventHandlers.message();
|
||||
// Compact the receive queue
|
||||
if (this._rQlen == this._rQi) {
|
||||
// All data has now been processed, this means we
|
||||
// can reset the receive queue.
|
||||
this._rQlen = 0;
|
||||
this._rQi = 0;
|
||||
} else if (this._rQlen > this._rQmax) {
|
||||
this._expand_compact_rQ();
|
||||
}
|
||||
} else {
|
||||
Log.Debug("Ignoring empty message");
|
||||
|
||||
+9
-37
@@ -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,12 +73,11 @@ 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
|
||||
| imageRect | (x, y, width, height, mime, arr) | Draw a rectangle with an image
|
||||
| startTile | (x, y, width, height, color) | Begin updating a tile
|
||||
| subTile | (tile, x, y, w, h, color) | Update a sub-rectangle within the given tile
|
||||
| finishTile | () | Draw the current tile to the display
|
||||
@@ -115,7 +87,7 @@ None
|
||||
| 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
|
||||
| ------- | ---------- | ------------
|
||||
|
||||
+19
-9
@@ -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
|
||||
@@ -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.
|
||||
|
||||
@@ -61,6 +61,10 @@ 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.
|
||||
|
||||
|
||||
+20
-66
@@ -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 = {
|
||||
@@ -80,7 +55,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 +71,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 +82,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);
|
||||
};
|
||||
|
||||
+34
-28
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@novnc/novnc",
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.0",
|
||||
"description": "An HTML5 VNC client",
|
||||
"browser": "lib/rfb",
|
||||
"directories": {
|
||||
@@ -19,7 +19,7 @@
|
||||
"vendor/pako"
|
||||
],
|
||||
"scripts": {
|
||||
"lint": "eslint app core po tests utils",
|
||||
"lint": "eslint app core po/po2js po/xgettext-html tests utils",
|
||||
"test": "karma start karma.conf.js",
|
||||
"prepublish": "node ./utils/use_require.js --as commonjs --clean"
|
||||
},
|
||||
@@ -40,36 +40,42 @@
|
||||
},
|
||||
"homepage": "https://github.com/novnc/noVNC",
|
||||
"devDependencies": {
|
||||
"babel-core": "^6.22.1",
|
||||
"babel-plugin-add-module-exports": "^0.2.1",
|
||||
"@babel/core": "*",
|
||||
"@babel/plugin-syntax-dynamic-import": "*",
|
||||
"@babel/plugin-transform-modules-amd": "*",
|
||||
"@babel/plugin-transform-modules-commonjs": "*",
|
||||
"@babel/plugin-transform-modules-systemjs": "*",
|
||||
"@babel/plugin-transform-modules-umd": "*",
|
||||
"@babel/preset-env": "*",
|
||||
"@babel/cli": "*",
|
||||
"babel-plugin-import-redirect": "*",
|
||||
"babel-plugin-syntax-dynamic-import": "^6.18.0",
|
||||
"babel-plugin-transform-es2015-modules-amd": "^6.22.0",
|
||||
"babel-plugin-transform-es2015-modules-commonjs": "^6.18.0",
|
||||
"babel-plugin-transform-es2015-modules-systemjs": "^6.22.0",
|
||||
"babel-plugin-transform-es2015-modules-umd": "^6.22.0",
|
||||
"babel-preset-es2015": "^6.24.1",
|
||||
"babelify": "^7.3.0",
|
||||
"browserify": "^13.1.0",
|
||||
"chai": "^3.5.0",
|
||||
"commander": "^2.9.0",
|
||||
"es-module-loader": "^2.1.0",
|
||||
"eslint": "^4.16.0",
|
||||
"fs-extra": "^1.0.0",
|
||||
"browserify": "*",
|
||||
"babelify": "*",
|
||||
"core-js": "*",
|
||||
"chai": "*",
|
||||
"commander": "*",
|
||||
"es-module-loader": "*",
|
||||
"eslint": "*",
|
||||
"fs-extra": "*",
|
||||
"jsdom": "*",
|
||||
"karma": "^1.3.0",
|
||||
"karma-mocha": "^1.3.0",
|
||||
"karma-mocha-reporter": "^2.2.0",
|
||||
"karma-sauce-launcher": "^1.0.0",
|
||||
"karma-sinon-chai": "^2.0.0",
|
||||
"mocha": "^3.1.2",
|
||||
"karma": "*",
|
||||
"karma-mocha": "*",
|
||||
"karma-chrome-launcher": "*",
|
||||
"@chiragrupani/karma-chromium-edge-launcher": "*",
|
||||
"karma-firefox-launcher": "*",
|
||||
"karma-ie-launcher": "*",
|
||||
"karma-mocha-reporter": "*",
|
||||
"karma-safari-launcher": "*",
|
||||
"karma-script-launcher": "*",
|
||||
"karma-sinon-chai": "*",
|
||||
"mocha": "*",
|
||||
"node-getopt": "*",
|
||||
"po2json": "*",
|
||||
"requirejs": "^2.3.2",
|
||||
"rollup": "^0.41.4",
|
||||
"rollup-plugin-node-resolve": "^2.0.0",
|
||||
"sinon": "^4.0.0",
|
||||
"sinon-chai": "^2.8.0"
|
||||
"requirejs": "*",
|
||||
"rollup": "*",
|
||||
"rollup-plugin-node-resolve": "*",
|
||||
"sinon": "*",
|
||||
"sinon-chai": "*"
|
||||
},
|
||||
"dependencies": {},
|
||||
"keywords": [
|
||||
|
||||
@@ -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 ja ko nl pl ru sv tr zh_CN zh_TW
|
||||
|
||||
VERSION := $(shell grep '"version"' ../package.json | cut -d '"' -f 4)
|
||||
|
||||
|
||||
@@ -0,0 +1,303 @@
|
||||
# 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: 2019-01-16 11:06+0100\n"
|
||||
"PO-Revision-Date: 2020-06-15 17:44+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"
|
||||
|
||||
#: ../app/ui.js:387
|
||||
msgid "Connecting..."
|
||||
msgstr "接続しています..."
|
||||
|
||||
#: ../app/ui.js:394
|
||||
msgid "Disconnecting..."
|
||||
msgstr "切断しています..."
|
||||
|
||||
#: ../app/ui.js:400
|
||||
msgid "Reconnecting..."
|
||||
msgstr "再接続しています..."
|
||||
|
||||
#: ../app/ui.js:405
|
||||
msgid "Internal error"
|
||||
msgstr "内部エラー"
|
||||
|
||||
#: ../app/ui.js:995
|
||||
msgid "Must set host"
|
||||
msgstr "ホストを設定する必要があります"
|
||||
|
||||
#: ../app/ui.js:1077
|
||||
msgid "Connected (encrypted) to "
|
||||
msgstr "接続しました (暗号化済み): "
|
||||
|
||||
#: ../app/ui.js:1079
|
||||
msgid "Connected (unencrypted) to "
|
||||
msgstr "接続しました (暗号化されていません): "
|
||||
|
||||
#: ../app/ui.js:1102
|
||||
msgid "Something went wrong, connection is closed"
|
||||
msgstr "何かが問題で、接続が閉じられました"
|
||||
|
||||
#: ../app/ui.js:1105
|
||||
msgid "Failed to connect to server"
|
||||
msgstr "サーバーへの接続に失敗しました"
|
||||
|
||||
#: ../app/ui.js:1115
|
||||
msgid "Disconnected"
|
||||
msgstr "切断しました"
|
||||
|
||||
#: ../app/ui.js:1128
|
||||
msgid "New connection has been rejected with reason: "
|
||||
msgstr "新規接続は次の理由で拒否されました: "
|
||||
|
||||
#: ../app/ui.js:1131
|
||||
msgid "New connection has been rejected"
|
||||
msgstr "新規接続は拒否されました"
|
||||
|
||||
#: ../app/ui.js:1151
|
||||
msgid "Password is required"
|
||||
msgstr "パスワードが必要です"
|
||||
|
||||
#: ../vnc.html:84
|
||||
msgid "noVNC encountered an error:"
|
||||
msgstr "noVNC でエラーが発生しました:"
|
||||
|
||||
#: ../vnc.html:94
|
||||
msgid "Hide/Show the control bar"
|
||||
msgstr "コントロールバーを隠す/表示する"
|
||||
|
||||
#: ../vnc.html:101
|
||||
msgid "Move/Drag Viewport"
|
||||
msgstr "ビューポートを移動/ドラッグ"
|
||||
|
||||
#: ../vnc.html:101
|
||||
msgid "viewport drag"
|
||||
msgstr "ビューポートをドラッグ"
|
||||
|
||||
#: ../vnc.html:107 ../vnc.html:110 ../vnc.html:113 ../vnc.html:116
|
||||
msgid "Active Mouse Button"
|
||||
msgstr "アクティブなマウスボタン"
|
||||
|
||||
#: ../vnc.html:107
|
||||
msgid "No mousebutton"
|
||||
msgstr "マウスボタンなし"
|
||||
|
||||
#: ../vnc.html:110
|
||||
msgid "Left mousebutton"
|
||||
msgstr "左マウスボタン"
|
||||
|
||||
#: ../vnc.html:113
|
||||
msgid "Middle mousebutton"
|
||||
msgstr "中マウスボタン"
|
||||
|
||||
#: ../vnc.html:116
|
||||
msgid "Right mousebutton"
|
||||
msgstr "右マウスボタン"
|
||||
|
||||
#: ../vnc.html:119
|
||||
msgid "Keyboard"
|
||||
msgstr "キーボード"
|
||||
|
||||
#: ../vnc.html:119
|
||||
msgid "Show Keyboard"
|
||||
msgstr "キーボードを表示"
|
||||
|
||||
#: ../vnc.html:126
|
||||
msgid "Extra keys"
|
||||
msgstr "追加キー"
|
||||
|
||||
#: ../vnc.html:126
|
||||
msgid "Show Extra Keys"
|
||||
msgstr "追加キーを表示"
|
||||
|
||||
#: ../vnc.html:131
|
||||
msgid "Ctrl"
|
||||
msgstr "Ctrl"
|
||||
|
||||
#: ../vnc.html:131
|
||||
msgid "Toggle Ctrl"
|
||||
msgstr "Ctrl キーを切り替え"
|
||||
|
||||
#: ../vnc.html:134
|
||||
msgid "Alt"
|
||||
msgstr "Alt"
|
||||
|
||||
#: ../vnc.html:134
|
||||
msgid "Toggle Alt"
|
||||
msgstr "Alt キーを切り替え"
|
||||
|
||||
#: ../vnc.html:137
|
||||
msgid "Toggle Windows"
|
||||
msgstr "Windows キーを切り替え"
|
||||
|
||||
#: ../vnc.html:137
|
||||
msgid "Windows"
|
||||
msgstr "Windows"
|
||||
|
||||
#: ../vnc.html:140
|
||||
msgid "Send Tab"
|
||||
msgstr "Tab キーを送信"
|
||||
|
||||
#: ../vnc.html:140
|
||||
msgid "Tab"
|
||||
msgstr "Tab"
|
||||
|
||||
#: ../vnc.html:143
|
||||
msgid "Esc"
|
||||
msgstr "Esc"
|
||||
|
||||
#: ../vnc.html:143
|
||||
msgid "Send Escape"
|
||||
msgstr "Escape キーを送信"
|
||||
|
||||
#: ../vnc.html:146
|
||||
msgid "Ctrl+Alt+Del"
|
||||
msgstr "Ctrl+Alt+Del"
|
||||
|
||||
#: ../vnc.html:146
|
||||
msgid "Send Ctrl-Alt-Del"
|
||||
msgstr "Ctrl-Alt-Del を送信"
|
||||
|
||||
#: ../vnc.html:154
|
||||
msgid "Shutdown/Reboot"
|
||||
msgstr "シャットダウン/再起動"
|
||||
|
||||
#: ../vnc.html:154
|
||||
msgid "Shutdown/Reboot..."
|
||||
msgstr "シャットダウン/再起動..."
|
||||
|
||||
#: ../vnc.html:160
|
||||
msgid "Power"
|
||||
msgstr "電源"
|
||||
|
||||
#: ../vnc.html:162
|
||||
msgid "Shutdown"
|
||||
msgstr "シャットダウン"
|
||||
|
||||
#: ../vnc.html:163
|
||||
msgid "Reboot"
|
||||
msgstr "再起動"
|
||||
|
||||
#: ../vnc.html:164
|
||||
msgid "Reset"
|
||||
msgstr "リセット"
|
||||
|
||||
#: ../vnc.html:169 ../vnc.html:175
|
||||
msgid "Clipboard"
|
||||
msgstr "クリップボード"
|
||||
|
||||
#: ../vnc.html:179
|
||||
msgid "Clear"
|
||||
msgstr "クリア"
|
||||
|
||||
#: ../vnc.html:185
|
||||
msgid "Fullscreen"
|
||||
msgstr "全画面表示"
|
||||
|
||||
#: ../vnc.html:190 ../vnc.html:197
|
||||
msgid "Settings"
|
||||
msgstr "設定"
|
||||
|
||||
#: ../vnc.html:200
|
||||
msgid "Shared Mode"
|
||||
msgstr "共有モード"
|
||||
|
||||
#: ../vnc.html:203
|
||||
msgid "View Only"
|
||||
msgstr "表示のみ"
|
||||
|
||||
#: ../vnc.html:207
|
||||
msgid "Clip to Window"
|
||||
msgstr "ウィンドウにクリップ"
|
||||
|
||||
#: ../vnc.html:210
|
||||
msgid "Scaling Mode:"
|
||||
msgstr "スケーリングモード:"
|
||||
|
||||
#: ../vnc.html:212
|
||||
msgid "None"
|
||||
msgstr "なし"
|
||||
|
||||
#: ../vnc.html:213
|
||||
msgid "Local Scaling"
|
||||
msgstr "ローカルスケーリング"
|
||||
|
||||
#: ../vnc.html:214
|
||||
msgid "Remote Resizing"
|
||||
msgstr "リモートでリサイズ"
|
||||
|
||||
#: ../vnc.html:219
|
||||
msgid "Advanced"
|
||||
msgstr "高度"
|
||||
|
||||
#: ../vnc.html:222
|
||||
msgid "Repeater ID:"
|
||||
msgstr "リピーター ID:"
|
||||
|
||||
#: ../vnc.html:226
|
||||
msgid "WebSocket"
|
||||
msgstr "WebSocket"
|
||||
|
||||
#: ../vnc.html:229
|
||||
msgid "Encrypt"
|
||||
msgstr "暗号化"
|
||||
|
||||
#: ../vnc.html:232
|
||||
msgid "Host:"
|
||||
msgstr "ホスト:"
|
||||
|
||||
#: ../vnc.html:236
|
||||
msgid "Port:"
|
||||
msgstr "ポート:"
|
||||
|
||||
#: ../vnc.html:240
|
||||
msgid "Path:"
|
||||
msgstr "パス:"
|
||||
|
||||
#: ../vnc.html:247
|
||||
msgid "Automatic Reconnect"
|
||||
msgstr "自動再接続"
|
||||
|
||||
#: ../vnc.html:250
|
||||
msgid "Reconnect Delay (ms):"
|
||||
msgstr "再接続する遅延 (ミリ秒):"
|
||||
|
||||
#: ../vnc.html:255
|
||||
msgid "Show Dot when No Cursor"
|
||||
msgstr "カーソルがないときにドットを表示"
|
||||
|
||||
#: ../vnc.html:260
|
||||
msgid "Logging:"
|
||||
msgstr "ロギング:"
|
||||
|
||||
#: ../vnc.html:272
|
||||
msgid "Disconnect"
|
||||
msgstr "切断"
|
||||
|
||||
#: ../vnc.html:291
|
||||
msgid "Connect"
|
||||
msgstr "接続"
|
||||
|
||||
#: ../vnc.html:301
|
||||
msgid "Password:"
|
||||
msgstr "パスワード:"
|
||||
|
||||
#: ../vnc.html:305
|
||||
msgid "Send Password"
|
||||
msgstr "パスワードを送信"
|
||||
|
||||
#: ../vnc.html:315
|
||||
msgid "Cancel"
|
||||
msgstr "キャンセル"
|
||||
+89
-93
@@ -6,9 +6,9 @@
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: noVNC 1.1.0\n"
|
||||
"Project-Id-Version: noVNC 1.2.0\n"
|
||||
"Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
|
||||
"POT-Creation-Date: 2019-01-16 11:06+0100\n"
|
||||
"POT-Creation-Date: 2020-07-03 16:11+0200\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"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:394
|
||||
msgid "Connecting..."
|
||||
msgstr ""
|
||||
|
||||
#: ../app/ui.js:394
|
||||
#: ../app/ui.js:401
|
||||
msgid "Disconnecting..."
|
||||
msgstr ""
|
||||
|
||||
#: ../app/ui.js:400
|
||||
#: ../app/ui.js:407
|
||||
msgid "Reconnecting..."
|
||||
msgstr ""
|
||||
|
||||
#: ../app/ui.js:405
|
||||
#: ../app/ui.js:412
|
||||
msgid "Internal error"
|
||||
msgstr ""
|
||||
|
||||
#: ../app/ui.js:995
|
||||
#: ../app/ui.js:1008
|
||||
msgid "Must set host"
|
||||
msgstr ""
|
||||
|
||||
#: ../app/ui.js:1077
|
||||
#: ../app/ui.js:1090
|
||||
msgid "Connected (encrypted) to "
|
||||
msgstr ""
|
||||
|
||||
#: ../app/ui.js:1079
|
||||
#: ../app/ui.js:1092
|
||||
msgid "Connected (unencrypted) to "
|
||||
msgstr ""
|
||||
|
||||
#: ../app/ui.js:1102
|
||||
#: ../app/ui.js:1115
|
||||
msgid "Something went wrong, connection is closed"
|
||||
msgstr ""
|
||||
|
||||
#: ../app/ui.js:1105
|
||||
#: ../app/ui.js:1118
|
||||
msgid "Failed to connect to server"
|
||||
msgstr ""
|
||||
|
||||
#: ../app/ui.js:1115
|
||||
#: ../app/ui.js:1128
|
||||
msgid "Disconnected"
|
||||
msgstr ""
|
||||
|
||||
#: ../app/ui.js:1128
|
||||
#: ../app/ui.js:1143
|
||||
msgid "New connection has been rejected with reason: "
|
||||
msgstr ""
|
||||
|
||||
#: ../app/ui.js:1131
|
||||
#: ../app/ui.js:1146
|
||||
msgid "New connection has been rejected"
|
||||
msgstr ""
|
||||
|
||||
#: ../app/ui.js:1151
|
||||
msgid "Password is required"
|
||||
#: ../app/ui.js:1181
|
||||
msgid "Credentials are required"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:84
|
||||
#: ../vnc.html:74
|
||||
msgid "noVNC encountered an error:"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:94
|
||||
#: ../vnc.html:84
|
||||
msgid "Hide/Show the control bar"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:101
|
||||
#: ../vnc.html:91
|
||||
msgid "Drag"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:91
|
||||
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:97
|
||||
msgid "Keyboard"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:119
|
||||
#: ../vnc.html:97
|
||||
msgid "Show Keyboard"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:126
|
||||
#: ../vnc.html:102
|
||||
msgid "Extra keys"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:126
|
||||
#: ../vnc.html:102
|
||||
msgid "Show Extra Keys"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:131
|
||||
#: ../vnc.html:107
|
||||
msgid "Ctrl"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:131
|
||||
#: ../vnc.html:107
|
||||
msgid "Toggle Ctrl"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:134
|
||||
#: ../vnc.html:110
|
||||
msgid "Alt"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:134
|
||||
#: ../vnc.html:110
|
||||
msgid "Toggle Alt"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:137
|
||||
#: ../vnc.html:113
|
||||
msgid "Toggle Windows"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:137
|
||||
#: ../vnc.html:113
|
||||
msgid "Windows"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:140
|
||||
#: ../vnc.html:116
|
||||
msgid "Send Tab"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:140
|
||||
#: ../vnc.html:116
|
||||
msgid "Tab"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:143
|
||||
#: ../vnc.html:119
|
||||
msgid "Esc"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:143
|
||||
#: ../vnc.html:119
|
||||
msgid "Send Escape"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:146
|
||||
#: ../vnc.html:122
|
||||
msgid "Ctrl+Alt+Del"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:146
|
||||
#: ../vnc.html:122
|
||||
msgid "Send Ctrl-Alt-Del"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:154
|
||||
#: ../vnc.html:129
|
||||
msgid "Shutdown/Reboot"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:154
|
||||
#: ../vnc.html:129
|
||||
msgid "Shutdown/Reboot..."
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:160
|
||||
#: ../vnc.html:135
|
||||
msgid "Power"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:162
|
||||
#: ../vnc.html:137
|
||||
msgid "Shutdown"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:163
|
||||
#: ../vnc.html:138
|
||||
msgid "Reboot"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:164
|
||||
#: ../vnc.html:139
|
||||
msgid "Reset"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:169 ../vnc.html:175
|
||||
#: ../vnc.html:144 ../vnc.html:150
|
||||
msgid "Clipboard"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:179
|
||||
#: ../vnc.html:154
|
||||
msgid "Clear"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:185
|
||||
#: ../vnc.html:160
|
||||
msgid "Fullscreen"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:190 ../vnc.html:197
|
||||
#: ../vnc.html:165 ../vnc.html:172
|
||||
msgid "Settings"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:200
|
||||
#: ../vnc.html:175
|
||||
msgid "Shared Mode"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:203
|
||||
#: ../vnc.html:178
|
||||
msgid "View Only"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:207
|
||||
#: ../vnc.html:182
|
||||
msgid "Clip to Window"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:210
|
||||
#: ../vnc.html:185
|
||||
msgid "Scaling Mode:"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:212
|
||||
#: ../vnc.html:187
|
||||
msgid "None"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:213
|
||||
#: ../vnc.html:188
|
||||
msgid "Local Scaling"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:214
|
||||
#: ../vnc.html:189
|
||||
msgid "Remote Resizing"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:219
|
||||
#: ../vnc.html:194
|
||||
msgid "Advanced"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:222
|
||||
#: ../vnc.html:197
|
||||
msgid "Quality:"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:201
|
||||
msgid "Compression level:"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:206
|
||||
msgid "Repeater ID:"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:226
|
||||
#: ../vnc.html:210
|
||||
msgid "WebSocket"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:229
|
||||
#: ../vnc.html:213
|
||||
msgid "Encrypt"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:232
|
||||
#: ../vnc.html:216
|
||||
msgid "Host:"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:236
|
||||
#: ../vnc.html:220
|
||||
msgid "Port:"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:240
|
||||
#: ../vnc.html:224
|
||||
msgid "Path:"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:247
|
||||
#: ../vnc.html:231
|
||||
msgid "Automatic Reconnect"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:250
|
||||
#: ../vnc.html:234
|
||||
msgid "Reconnect Delay (ms):"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:255
|
||||
#: ../vnc.html:239
|
||||
msgid "Show Dot when No Cursor"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:260
|
||||
#: ../vnc.html:244
|
||||
msgid "Logging:"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:272
|
||||
#: ../vnc.html:253
|
||||
msgid "Version:"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:261
|
||||
msgid "Disconnect"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:291
|
||||
#: ../vnc.html:280
|
||||
msgid "Connect"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:301
|
||||
#: ../vnc.html:290
|
||||
msgid "Username:"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:294
|
||||
msgid "Password:"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:305
|
||||
msgid "Send Password"
|
||||
#: ../vnc.html:298
|
||||
msgid "Send Credentials"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:315
|
||||
#: ../vnc.html:308
|
||||
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);
|
||||
|
||||
@@ -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.2.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: 2020-07-03 16:11+0200\n"
|
||||
"PO-Revision-Date: 2020-07-08 23: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:394
|
||||
msgid "Connecting..."
|
||||
msgstr "Ansluter..."
|
||||
|
||||
#: ../app/ui.js:394
|
||||
#: ../app/ui.js:401
|
||||
msgid "Disconnecting..."
|
||||
msgstr "Kopplar ner..."
|
||||
|
||||
#: ../app/ui.js:400
|
||||
#: ../app/ui.js:407
|
||||
msgid "Reconnecting..."
|
||||
msgstr "Återansluter..."
|
||||
|
||||
#: ../app/ui.js:405
|
||||
#: ../app/ui.js:412
|
||||
msgid "Internal error"
|
||||
msgstr "Internt fel"
|
||||
|
||||
#: ../app/ui.js:995
|
||||
#: ../app/ui.js:1008
|
||||
msgid "Must set host"
|
||||
msgstr "Du måste specifiera en värd"
|
||||
|
||||
#: ../app/ui.js:1077
|
||||
#: ../app/ui.js:1090
|
||||
msgid "Connected (encrypted) to "
|
||||
msgstr "Ansluten (krypterat) till "
|
||||
|
||||
#: ../app/ui.js:1079
|
||||
#: ../app/ui.js:1092
|
||||
msgid "Connected (unencrypted) to "
|
||||
msgstr "Ansluten (okrypterat) till "
|
||||
|
||||
#: ../app/ui.js:1102
|
||||
#: ../app/ui.js:1115
|
||||
msgid "Something went wrong, connection is closed"
|
||||
msgstr "Något gick fel, anslutningen avslutades"
|
||||
|
||||
#: ../app/ui.js:1105
|
||||
#: ../app/ui.js:1118
|
||||
msgid "Failed to connect to server"
|
||||
msgstr "Misslyckades att ansluta till servern"
|
||||
|
||||
#: ../app/ui.js:1115
|
||||
#: ../app/ui.js:1128
|
||||
msgid "Disconnected"
|
||||
msgstr "Frånkopplad"
|
||||
|
||||
#: ../app/ui.js:1128
|
||||
#: ../app/ui.js:1143
|
||||
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:1146
|
||||
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:1181
|
||||
msgid "Credentials are required"
|
||||
msgstr "Användaruppgifter krävs"
|
||||
|
||||
#: ../vnc.html:84
|
||||
#: ../vnc.html:74
|
||||
msgid "noVNC encountered an error:"
|
||||
msgstr "noVNC stötte på ett problem:"
|
||||
|
||||
#: ../vnc.html:94
|
||||
#: ../vnc.html:84
|
||||
msgid "Hide/Show the control bar"
|
||||
msgstr "Göm/Visa kontrollbaren"
|
||||
|
||||
#: ../vnc.html:101
|
||||
#: ../vnc.html:91
|
||||
msgid "Drag"
|
||||
msgstr "Dra"
|
||||
|
||||
#: ../vnc.html:91
|
||||
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:97
|
||||
msgid "Keyboard"
|
||||
msgstr "Tangentbord"
|
||||
|
||||
#: ../vnc.html:119
|
||||
#: ../vnc.html:97
|
||||
msgid "Show Keyboard"
|
||||
msgstr "Visa Tangentbord"
|
||||
|
||||
#: ../vnc.html:126
|
||||
#: ../vnc.html:102
|
||||
msgid "Extra keys"
|
||||
msgstr "Extraknappar"
|
||||
|
||||
#: ../vnc.html:126
|
||||
#: ../vnc.html:102
|
||||
msgid "Show Extra Keys"
|
||||
msgstr "Visa Extraknappar"
|
||||
|
||||
#: ../vnc.html:131
|
||||
#: ../vnc.html:107
|
||||
msgid "Ctrl"
|
||||
msgstr "Ctrl"
|
||||
|
||||
#: ../vnc.html:131
|
||||
#: ../vnc.html:107
|
||||
msgid "Toggle Ctrl"
|
||||
msgstr "Växla Ctrl"
|
||||
|
||||
#: ../vnc.html:134
|
||||
#: ../vnc.html:110
|
||||
msgid "Alt"
|
||||
msgstr "Alt"
|
||||
|
||||
#: ../vnc.html:134
|
||||
#: ../vnc.html:110
|
||||
msgid "Toggle Alt"
|
||||
msgstr "Växla Alt"
|
||||
|
||||
#: ../vnc.html:137
|
||||
#: ../vnc.html:113
|
||||
msgid "Toggle Windows"
|
||||
msgstr "Växla Windows"
|
||||
|
||||
#: ../vnc.html:137
|
||||
#: ../vnc.html:113
|
||||
msgid "Windows"
|
||||
msgstr "Windows"
|
||||
|
||||
#: ../vnc.html:140
|
||||
#: ../vnc.html:116
|
||||
msgid "Send Tab"
|
||||
msgstr "Skicka Tab"
|
||||
|
||||
#: ../vnc.html:140
|
||||
#: ../vnc.html:116
|
||||
msgid "Tab"
|
||||
msgstr "Tab"
|
||||
|
||||
#: ../vnc.html:143
|
||||
#: ../vnc.html:119
|
||||
msgid "Esc"
|
||||
msgstr "Esc"
|
||||
|
||||
#: ../vnc.html:143
|
||||
#: ../vnc.html:119
|
||||
msgid "Send Escape"
|
||||
msgstr "Skicka Escape"
|
||||
|
||||
#: ../vnc.html:146
|
||||
#: ../vnc.html:122
|
||||
msgid "Ctrl+Alt+Del"
|
||||
msgstr "Ctrl+Alt+Del"
|
||||
|
||||
#: ../vnc.html:146
|
||||
#: ../vnc.html:122
|
||||
msgid "Send Ctrl-Alt-Del"
|
||||
msgstr "Skicka Ctrl-Alt-Del"
|
||||
|
||||
#: ../vnc.html:154
|
||||
#: ../vnc.html:129
|
||||
msgid "Shutdown/Reboot"
|
||||
msgstr "Stäng av/Boota om"
|
||||
|
||||
#: ../vnc.html:154
|
||||
#: ../vnc.html:129
|
||||
msgid "Shutdown/Reboot..."
|
||||
msgstr "Stäng av/Boota om..."
|
||||
|
||||
#: ../vnc.html:160
|
||||
#: ../vnc.html:135
|
||||
msgid "Power"
|
||||
msgstr "Ström"
|
||||
|
||||
#: ../vnc.html:162
|
||||
#: ../vnc.html:137
|
||||
msgid "Shutdown"
|
||||
msgstr "Stäng av"
|
||||
|
||||
#: ../vnc.html:163
|
||||
#: ../vnc.html:138
|
||||
msgid "Reboot"
|
||||
msgstr "Boota om"
|
||||
|
||||
#: ../vnc.html:164
|
||||
#: ../vnc.html:139
|
||||
msgid "Reset"
|
||||
msgstr "Återställ"
|
||||
|
||||
#: ../vnc.html:169 ../vnc.html:175
|
||||
#: ../vnc.html:144 ../vnc.html:150
|
||||
msgid "Clipboard"
|
||||
msgstr "Urklipp"
|
||||
|
||||
#: ../vnc.html:179
|
||||
#: ../vnc.html:154
|
||||
msgid "Clear"
|
||||
msgstr "Rensa"
|
||||
|
||||
#: ../vnc.html:185
|
||||
#: ../vnc.html:160
|
||||
msgid "Fullscreen"
|
||||
msgstr "Fullskärm"
|
||||
|
||||
#: ../vnc.html:190 ../vnc.html:197
|
||||
#: ../vnc.html:165 ../vnc.html:172
|
||||
msgid "Settings"
|
||||
msgstr "Inställningar"
|
||||
|
||||
#: ../vnc.html:200
|
||||
#: ../vnc.html:175
|
||||
msgid "Shared Mode"
|
||||
msgstr "Delat Läge"
|
||||
|
||||
#: ../vnc.html:203
|
||||
#: ../vnc.html:178
|
||||
msgid "View Only"
|
||||
msgstr "Endast Visning"
|
||||
|
||||
#: ../vnc.html:207
|
||||
#: ../vnc.html:182
|
||||
msgid "Clip to Window"
|
||||
msgstr "Begränsa till Fönster"
|
||||
|
||||
#: ../vnc.html:210
|
||||
#: ../vnc.html:185
|
||||
msgid "Scaling Mode:"
|
||||
msgstr "Skalningsläge:"
|
||||
|
||||
#: ../vnc.html:212
|
||||
#: ../vnc.html:187
|
||||
msgid "None"
|
||||
msgstr "Ingen"
|
||||
|
||||
#: ../vnc.html:213
|
||||
#: ../vnc.html:188
|
||||
msgid "Local Scaling"
|
||||
msgstr "Lokal Skalning"
|
||||
|
||||
#: ../vnc.html:214
|
||||
#: ../vnc.html:189
|
||||
msgid "Remote Resizing"
|
||||
msgstr "Ändra Storlek"
|
||||
|
||||
#: ../vnc.html:219
|
||||
#: ../vnc.html:194
|
||||
msgid "Advanced"
|
||||
msgstr "Avancerat"
|
||||
|
||||
#: ../vnc.html:222
|
||||
#: ../vnc.html:197
|
||||
msgid "Quality:"
|
||||
msgstr "Kvalitet:"
|
||||
|
||||
#: ../vnc.html:201
|
||||
msgid "Compression level:"
|
||||
msgstr "Kompressionsnivå:"
|
||||
|
||||
#: ../vnc.html:206
|
||||
msgid "Repeater ID:"
|
||||
msgstr "Repeater-ID:"
|
||||
|
||||
#: ../vnc.html:226
|
||||
#: ../vnc.html:210
|
||||
msgid "WebSocket"
|
||||
msgstr "WebSocket"
|
||||
|
||||
#: ../vnc.html:229
|
||||
#: ../vnc.html:213
|
||||
msgid "Encrypt"
|
||||
msgstr "Kryptera"
|
||||
|
||||
#: ../vnc.html:232
|
||||
#: ../vnc.html:216
|
||||
msgid "Host:"
|
||||
msgstr "Värd:"
|
||||
|
||||
#: ../vnc.html:236
|
||||
#: ../vnc.html:220
|
||||
msgid "Port:"
|
||||
msgstr "Port:"
|
||||
|
||||
#: ../vnc.html:240
|
||||
#: ../vnc.html:224
|
||||
msgid "Path:"
|
||||
msgstr "Sökväg:"
|
||||
|
||||
#: ../vnc.html:247
|
||||
#: ../vnc.html:231
|
||||
msgid "Automatic Reconnect"
|
||||
msgstr "Automatisk Återanslutning"
|
||||
|
||||
#: ../vnc.html:250
|
||||
#: ../vnc.html:234
|
||||
msgid "Reconnect Delay (ms):"
|
||||
msgstr "Fördröjning (ms):"
|
||||
|
||||
#: ../vnc.html:255
|
||||
#: ../vnc.html:239
|
||||
msgid "Show Dot when No Cursor"
|
||||
msgstr "Visa prick när ingen muspekare finns"
|
||||
|
||||
#: ../vnc.html:260
|
||||
#: ../vnc.html:244
|
||||
msgid "Logging:"
|
||||
msgstr "Loggning:"
|
||||
|
||||
#: ../vnc.html:272
|
||||
#: ../vnc.html:253
|
||||
msgid "Version:"
|
||||
msgstr "Version:"
|
||||
|
||||
#: ../vnc.html:261
|
||||
msgid "Disconnect"
|
||||
msgstr "Koppla från"
|
||||
|
||||
#: ../vnc.html:291
|
||||
#: ../vnc.html:280
|
||||
msgid "Connect"
|
||||
msgstr "Anslut"
|
||||
|
||||
#: ../vnc.html:301
|
||||
#: ../vnc.html:290
|
||||
msgid "Username:"
|
||||
msgstr "Användarnamn:"
|
||||
|
||||
#: ../vnc.html:294
|
||||
msgid "Password:"
|
||||
msgstr "Lösenord:"
|
||||
|
||||
#: ../vnc.html:305
|
||||
msgid "Send Password"
|
||||
msgstr "Skicka lösenord"
|
||||
#: ../vnc.html:298
|
||||
msgid "Send Credentials"
|
||||
msgstr "Skicka Användaruppgifter"
|
||||
|
||||
#: ../vnc.html:315
|
||||
#: ../vnc.html:308
|
||||
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"
|
||||
msgstr "Avbryt"
|
||||
+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/launch.sh --listen $listen_port --vnc $vnc_host_port &
|
||||
fi
|
||||
done
|
||||
@@ -0,0 +1,36 @@
|
||||
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: build/
|
||||
plugin: dump
|
||||
stage-packages:
|
||||
- websockify
|
||||
- bash
|
||||
- jq
|
||||
- python-numpy
|
||||
- python3-numpy
|
||||
|
||||
hooks:
|
||||
configure:
|
||||
plugs: [network, network-bind]
|
||||
|
||||
apps:
|
||||
novnc:
|
||||
command: ./launch.sh
|
||||
plugs: [network, network-bind]
|
||||
novncsvc:
|
||||
command: ./svc_wrapper.sh
|
||||
daemon: forking
|
||||
plugs: [network, network-bind]
|
||||
+14
-14
@@ -1,32 +1,32 @@
|
||||
// noVNC specific assertions
|
||||
chai.use(function (_chai, utils) {
|
||||
_chai.Assertion.addMethod('displayed', function (target_data) {
|
||||
_chai.Assertion.addMethod('displayed', function (targetData) {
|
||||
const obj = this._obj;
|
||||
const ctx = obj._target.getContext('2d');
|
||||
const data_cl = ctx.getImageData(0, 0, obj._target.width, obj._target.height).data;
|
||||
const dataCl = 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 = new Uint8Array(dataCl);
|
||||
const len = dataCl.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 (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 +34,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 +48,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));
|
||||
});
|
||||
|
||||
|
||||
+16
-16
@@ -1,7 +1,7 @@
|
||||
import Base64 from '../core/base64.js';
|
||||
|
||||
// PhantomJS can't create Event objects directly, so we need to use this
|
||||
function make_event(name, props) {
|
||||
function makeEvent(name, props) {
|
||||
const evt = document.createEvent('Event');
|
||||
evt.initEvent(name, true, true);
|
||||
if (props) {
|
||||
@@ -24,18 +24,18 @@ export default class FakeWebSocket {
|
||||
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(makeEvent("close", { 'code': code, 'reason': reason, 'wasClean': true }));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,12 +45,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 +58,16 @@ export default class FakeWebSocket {
|
||||
_open() {
|
||||
this.readyState = FakeWebSocket.OPEN;
|
||||
if (this.onopen) {
|
||||
this.onopen(make_event('open'));
|
||||
this.onopen(makeEvent('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(makeEvent("message", { 'data': buf }));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -77,20 +77,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,4 +1,4 @@
|
||||
/* global VNC_frame_data, VNC_frame_encoding */
|
||||
/* global vncFrameData, vncFrameEncoding */
|
||||
|
||||
import * as WebUtil from '../app/webutil.js';
|
||||
import RecordingPlayer from './playback.js';
|
||||
@@ -41,7 +41,7 @@ function enableUI() {
|
||||
document.getElementById('mode1').checked = true;
|
||||
}
|
||||
|
||||
message("Loaded " + VNC_frame_data.length + " frames");
|
||||
message("Loaded " + vncFrameData.length + " frames");
|
||||
|
||||
const startButton = document.getElementById('startButton');
|
||||
startButton.disabled = false;
|
||||
@@ -49,12 +49,12 @@ function enableUI() {
|
||||
|
||||
message("Converting...");
|
||||
|
||||
frames = VNC_frame_data;
|
||||
frames = vncFrameData;
|
||||
|
||||
let encoding;
|
||||
// Only present in older recordings
|
||||
if (window.VNC_frame_encoding) {
|
||||
encoding = VNC_frame_encoding;
|
||||
if (window.vncFrameEncoding) {
|
||||
encoding = vncFrameEncoding;
|
||||
} else {
|
||||
let frame = frames[0];
|
||||
let start = frame.indexOf('{', 1) + 1;
|
||||
@@ -102,7 +102,7 @@ class IterationPlayer {
|
||||
this._iteration = undefined;
|
||||
this._player = undefined;
|
||||
|
||||
this._start_time = undefined;
|
||||
this._startTime = undefined;
|
||||
|
||||
this._frames = frames;
|
||||
|
||||
@@ -115,7 +115,7 @@ class IterationPlayer {
|
||||
|
||||
start(realtime) {
|
||||
this._iteration = 0;
|
||||
this._start_time = (new Date()).getTime();
|
||||
this._startTime = (new Date()).getTime();
|
||||
|
||||
this._realtime = realtime;
|
||||
|
||||
@@ -139,7 +139,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:
|
||||
|
||||
+15
-15
@@ -49,10 +49,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;
|
||||
|
||||
@@ -72,8 +72,8 @@ export default class RecordingPlayer {
|
||||
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;
|
||||
@@ -97,22 +97,22 @@ export default class RecordingPlayer {
|
||||
_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 +134,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._frameIndex++;
|
||||
|
||||
this._queueNextPacket();
|
||||
}
|
||||
@@ -155,13 +155,13 @@ export default class RecordingPlayer {
|
||||
this._running = false;
|
||||
this._rfb._sock._eventHandlers.close({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,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);
|
||||
|
||||
});
|
||||
});
|
||||
+67
-71
@@ -4,28 +4,28 @@ 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 Uint8Array([
|
||||
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
||||
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
||||
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
|
||||
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
|
||||
]);
|
||||
|
||||
const basic_data = new Uint8Array([0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0xff, 0xff, 0xff, 255]);
|
||||
const basicData = new Uint8Array([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) {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = 4;
|
||||
canvas.height = 4;
|
||||
const ctx = canvas.getContext('2d');
|
||||
const data = ctx.createImageData(4, 4);
|
||||
for (let i = 0; i < checked_data.length; i++) { data.data[i] = input_data[i]; }
|
||||
for (let i = 0; i < checkedData.length; i++) { data.data[i] = inputData[i]; }
|
||||
ctx.putImageData(data, 0, 0);
|
||||
return canvas;
|
||||
}
|
||||
|
||||
function make_image_png(input_data) {
|
||||
const canvas = make_image_canvas(input_data);
|
||||
function makeImagePng(inputData) {
|
||||
const canvas = makeImageCanvas(inputData);
|
||||
const url = canvas.toDataURL();
|
||||
const data = url.split(",")[1];
|
||||
return Base64.decode(data);
|
||||
@@ -44,11 +44,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), 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,8 +123,8 @@ 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 () {
|
||||
@@ -223,6 +223,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 +270,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.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;
|
||||
@@ -309,7 +288,7 @@ describe('Display/Canvas Helper', function () {
|
||||
display.fillRect(0, 0, 2, 2, [0xff, 0, 0]);
|
||||
display.fillRect(2, 2, 2, 2, [0xff, 0, 0]);
|
||||
display.flip();
|
||||
expect(display).to.have.displayed(checked_data);
|
||||
expect(display).to.have.displayed(checkedData);
|
||||
});
|
||||
|
||||
it('should support copying an portion of the canvas via #copyImage', function () {
|
||||
@@ -317,14 +296,14 @@ describe('Display/Canvas Helper', function () {
|
||||
display.fillRect(0, 0, 2, 2, [0xff, 0, 0x00]);
|
||||
display.copyImage(0, 0, 2, 2, 2, 2);
|
||||
display.flip();
|
||||
expect(display).to.have.displayed(checked_data);
|
||||
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));
|
||||
display.flip();
|
||||
display.onflush = () => {
|
||||
expect(display).to.have.displayed(checked_data);
|
||||
expect(display).to.have.displayed(checkedData);
|
||||
done();
|
||||
};
|
||||
display.flush();
|
||||
@@ -336,12 +315,12 @@ describe('Display/Canvas Helper', function () {
|
||||
display.subTile(2, 2, 2, 2, [0xff, 0, 0]);
|
||||
display.finishTile();
|
||||
display.flip();
|
||||
expect(display).to.have.displayed(checked_data);
|
||||
expect(display).to.have.displayed(checkedData);
|
||||
});
|
||||
|
||||
// We have a special cache for 16x16 tiles that we need to test
|
||||
it('should support drawing a 16x16 tile', function () {
|
||||
const large_checked_data = new Uint8Array(16*16*4);
|
||||
const largeCheckedData = new Uint8Array(16*16*4);
|
||||
display.resize(16, 16);
|
||||
|
||||
for (let y = 0;y < 16;y++) {
|
||||
@@ -349,11 +328,11 @@ describe('Display/Canvas Helper', function () {
|
||||
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);
|
||||
pixel = Array.prototype.slice.call(checkedData, (y*4+x)*4, (y*4+x+1)*4);
|
||||
} else {
|
||||
pixel = [0, 0xff, 0, 255];
|
||||
}
|
||||
large_checked_data.set(pixel, (y*16+x)*4);
|
||||
largeCheckedData.set(pixel, (y*16+x)*4);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -362,39 +341,39 @@ describe('Display/Canvas Helper', function () {
|
||||
display.subTile(2, 2, 2, 2, [0xff, 0, 0]);
|
||||
display.finishTile();
|
||||
display.flip();
|
||||
expect(display).to.have.displayed(large_checked_data);
|
||||
expect(display).to.have.displayed(largeCheckedData);
|
||||
});
|
||||
|
||||
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];
|
||||
data[i * 4] = checkedData[i * 4 + 2];
|
||||
data[i * 4 + 1] = checkedData[i * 4 + 1];
|
||||
data[i * 4 + 2] = checkedData[i * 4];
|
||||
data[i * 4 + 3] = checkedData[i * 4 + 3];
|
||||
}
|
||||
display.blitImage(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 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];
|
||||
data[i * 3] = checkedData[i * 4];
|
||||
data[i * 3 + 1] = checkedData[i * 4 + 1];
|
||||
data[i * 3 + 2] = checkedData[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);
|
||||
display.drawImage(img, 0, 0);
|
||||
display.flip();
|
||||
expect(display).to.have.displayed(checked_data);
|
||||
expect(display).to.have.displayed(checkedData);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -403,38 +382,55 @@ 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;
|
||||
});
|
||||
|
||||
it('should wait if an image is incorrectly loaded', function () {
|
||||
const img = { complete: true, width: 0, height: 0, addEventListener: sinon.spy() };
|
||||
display._renderQ = [{ type: 'img', x: 3, y: 4, width: 4, height: 4, img: img },
|
||||
{ type: 'fill', x: 1, y: 2, width: 3, height: 4, color: 5 }];
|
||||
display.drawImage = sinon.spy();
|
||||
display.fillRect = sinon.spy();
|
||||
|
||||
display._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._renderQ[0].img.width = 4;
|
||||
display._renderQ[0].img.height = 4;
|
||||
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 +446,35 @@ 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] });
|
||||
display._renderQPush({ 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
+35
-2
@@ -86,7 +86,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 () {
|
||||
@@ -110,6 +112,9 @@ describe('Helpers', function () {
|
||||
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');
|
||||
});
|
||||
@@ -147,7 +152,9 @@ describe('Helpers', function () {
|
||||
}
|
||||
});
|
||||
afterEach(function () {
|
||||
Object.defineProperty(window, "navigator", origNavigator);
|
||||
if (origNavigator !== undefined) {
|
||||
Object.defineProperty(window, "navigator", origNavigator);
|
||||
}
|
||||
});
|
||||
|
||||
it('should ignore printable character key on IE', function () {
|
||||
@@ -166,6 +173,16 @@ describe('Helpers', function () {
|
||||
window.navigator.userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36 Edge/14.14393";
|
||||
expect(KeyboardUtil.getKey({key: 'Shift'})).to.be.equal('Shift');
|
||||
});
|
||||
it('should allow printable character key with charCode on IE', function () {
|
||||
window.navigator.userAgent = "Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko";
|
||||
expect(KeyboardUtil.getKey({key: 'a', charCode: 0x61})).to.be.equal('a');
|
||||
expect(KeyboardUtil.getKey({key: 'Unidentified', charCode: 0x61})).to.be.equal('a');
|
||||
});
|
||||
it('should allow printable character key with charCode on Edge', function () {
|
||||
window.navigator.userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36 Edge/14.14393";
|
||||
expect(KeyboardUtil.getKey({key: 'a', charCode: 0x61})).to.be.equal('a');
|
||||
expect(KeyboardUtil.getKey({key: 'Unidentified', charCode: 0x61})).to.be.equal('a');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -192,6 +209,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;
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
+163
-55
@@ -175,54 +175,6 @@ 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 () {
|
||||
@@ -320,7 +272,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 +316,82 @@ 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");
|
||||
if (origNavigator === undefined) {
|
||||
// Object.getOwnPropertyDescriptor() doesn't work
|
||||
// properly in any version of IE
|
||||
this.skip();
|
||||
}
|
||||
|
||||
Object.defineProperty(window, "navigator", {value: {}});
|
||||
if (window.navigator.platform !== undefined) {
|
||||
// Object.defineProperty() doesn't work properly in old
|
||||
// versions of Chrome
|
||||
this.skip();
|
||||
}
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
if (origNavigator !== undefined) {
|
||||
Object.defineProperty(window, "navigator", origNavigator);
|
||||
}
|
||||
});
|
||||
|
||||
it('should toggle caps lock on key press on iOS', function (done) {
|
||||
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);
|
||||
done();
|
||||
});
|
||||
|
||||
it('should toggle caps lock on key press on mac', function (done) {
|
||||
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);
|
||||
done();
|
||||
});
|
||||
|
||||
it('should toggle caps lock on key release on iOS', function (done) {
|
||||
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);
|
||||
done();
|
||||
});
|
||||
|
||||
it('should toggle caps lock on key release on mac', function (done) {
|
||||
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);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Escape AltGraph on Windows', function () {
|
||||
let origNavigator;
|
||||
beforeEach(function () {
|
||||
@@ -387,8 +417,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 +471,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 +489,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 +504,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 +520,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 +541,78 @@ 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");
|
||||
if (origNavigator === undefined) {
|
||||
// Object.getOwnPropertyDescriptor() doesn't work
|
||||
// properly in any version of IE
|
||||
this.skip();
|
||||
}
|
||||
|
||||
Object.defineProperty(window, "navigator", {value: {}});
|
||||
if (window.navigator.platform !== undefined) {
|
||||
// Object.defineProperty() doesn't work properly in old
|
||||
// versions of Chrome
|
||||
this.skip();
|
||||
}
|
||||
|
||||
window.navigator.platform = "Windows x86_64";
|
||||
|
||||
this.clock = sinon.useFakeTimers();
|
||||
});
|
||||
afterEach(function () {
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -27,7 +27,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
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
+2450
-513
File diff suppressed because it is too large
Load Diff
+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
|
||||
|
||||
+73
-51
@@ -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,22 +232,22 @@ 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;
|
||||
@@ -255,10 +255,10 @@ describe('Websock', 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.OPEN = oldWS.OPEN;
|
||||
WebSocket.CONNECTING = oldWS.CONNECTING;
|
||||
WebSocket.CLOSING = oldWS.CLOSING;
|
||||
WebSocket.CLOSED = oldWS.CLOSED;
|
||||
|
||||
WebSocket.prototype.binaryType = 'arraybuffer';
|
||||
});
|
||||
@@ -306,30 +306,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://');
|
||||
});
|
||||
|
||||
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 () {
|
||||
@@ -350,7 +350,7 @@ describe('Websock', function () {
|
||||
|
||||
after(function () {
|
||||
// eslint-disable-next-line no-global-assign
|
||||
WebSocket = old_WS;
|
||||
WebSocket = oldWS;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -358,13 +358,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 +372,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 +380,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 +450,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]));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -42,7 +42,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 () {
|
||||
|
||||
@@ -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))
|
||||
@@ -1,206 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
'''
|
||||
Use matplotlib to generate performance charts
|
||||
Copyright (C) 2018 The noVNC Authors
|
||||
Licensed under MPL-2.0 (see docs/LICENSE.MPL-2.0)
|
||||
'''
|
||||
|
||||
# a bar plot with errorbars
|
||||
import sys, json
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
from matplotlib.font_manager import FontProperties
|
||||
|
||||
def usage():
|
||||
print "%s json_file level1 level2 level3 [legend_height]\n\n" % sys.argv[0]
|
||||
print "Description:\n"
|
||||
print "level1, level2, and level3 are one each of the following:\n";
|
||||
print " select=ITEM - select only ITEM at this level";
|
||||
print " bar - each item on this level becomes a graph bar";
|
||||
print " group - items on this level become groups of bars";
|
||||
print "\n";
|
||||
print "json_file is a file containing json data in the following format:\n"
|
||||
print ' {';
|
||||
print ' "conf": {';
|
||||
print ' "order_l1": [';
|
||||
print ' "level1_label1",';
|
||||
print ' "level1_label2",';
|
||||
print ' ...';
|
||||
print ' ],';
|
||||
print ' "order_l2": [';
|
||||
print ' "level2_label1",';
|
||||
print ' "level2_label2",';
|
||||
print ' ...';
|
||||
print ' ],';
|
||||
print ' "order_l3": [';
|
||||
print ' "level3_label1",';
|
||||
print ' "level3_label2",';
|
||||
print ' ...';
|
||||
print ' ]';
|
||||
print ' },';
|
||||
print ' "stats": {';
|
||||
print ' "level1_label1": {';
|
||||
print ' "level2_label1": {';
|
||||
print ' "level3_label1": [val1, val2, val3],';
|
||||
print ' "level3_label2": [val1, val2, val3],';
|
||||
print ' ...';
|
||||
print ' },';
|
||||
print ' "level2_label2": {';
|
||||
print ' ...';
|
||||
print ' },';
|
||||
print ' },';
|
||||
print ' "level1_label2": {';
|
||||
print ' ...';
|
||||
print ' },';
|
||||
print ' ...';
|
||||
print ' },';
|
||||
print ' }';
|
||||
sys.exit(2)
|
||||
|
||||
def error(msg):
|
||||
print msg
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
#colors = ['#ff0000', '#0863e9', '#00f200', '#ffa100',
|
||||
# '#800000', '#805100', '#013075', '#007900']
|
||||
colors = ['#ff0000', '#00ff00', '#0000ff',
|
||||
'#dddd00', '#dd00dd', '#00dddd',
|
||||
'#dd6622', '#dd2266', '#66dd22',
|
||||
'#8844dd', '#44dd88', '#4488dd']
|
||||
|
||||
if len(sys.argv) < 5:
|
||||
usage()
|
||||
|
||||
filename = sys.argv[1]
|
||||
L1 = sys.argv[2]
|
||||
L2 = sys.argv[3]
|
||||
L3 = sys.argv[4]
|
||||
if len(sys.argv) > 5:
|
||||
legendHeight = float(sys.argv[5])
|
||||
else:
|
||||
legendHeight = 0.75
|
||||
|
||||
# Load the JSON data from the file
|
||||
data = json.loads(file(filename).read())
|
||||
conf = data['conf']
|
||||
stats = data['stats']
|
||||
|
||||
# Sanity check data hierarchy
|
||||
if len(conf['order_l1']) != len(stats.keys()):
|
||||
error("conf.order_l1 does not match stats level 1")
|
||||
for l1 in stats.keys():
|
||||
if len(conf['order_l2']) != len(stats[l1].keys()):
|
||||
error("conf.order_l2 does not match stats level 2 for %s" % l1)
|
||||
if conf['order_l1'].count(l1) < 1:
|
||||
error("%s not found in conf.order_l1" % l1)
|
||||
for l2 in stats[l1].keys():
|
||||
if len(conf['order_l3']) != len(stats[l1][l2].keys()):
|
||||
error("conf.order_l3 does not match stats level 3")
|
||||
if conf['order_l2'].count(l2) < 1:
|
||||
error("%s not found in conf.order_l2" % l2)
|
||||
for l3 in stats[l1][l2].keys():
|
||||
if conf['order_l3'].count(l3) < 1:
|
||||
error("%s not found in conf.order_l3" % l3)
|
||||
|
||||
#
|
||||
# Generate the data based on the level specifications
|
||||
#
|
||||
bar_labels = None
|
||||
group_labels = None
|
||||
bar_vals = []
|
||||
bar_sdvs = []
|
||||
if L3.startswith("select="):
|
||||
select_label = l3 = L3.split("=")[1]
|
||||
bar_labels = conf['order_l1']
|
||||
group_labels = conf['order_l2']
|
||||
bar_vals = [[0]*len(group_labels) for i in bar_labels]
|
||||
bar_sdvs = [[0]*len(group_labels) for i in bar_labels]
|
||||
for b in range(len(bar_labels)):
|
||||
l1 = bar_labels[b]
|
||||
for g in range(len(group_labels)):
|
||||
l2 = group_labels[g]
|
||||
bar_vals[b][g] = np.mean(stats[l1][l2][l3])
|
||||
bar_sdvs[b][g] = np.std(stats[l1][l2][l3])
|
||||
elif L2.startswith("select="):
|
||||
select_label = l2 = L2.split("=")[1]
|
||||
bar_labels = conf['order_l1']
|
||||
group_labels = conf['order_l3']
|
||||
bar_vals = [[0]*len(group_labels) for i in bar_labels]
|
||||
bar_sdvs = [[0]*len(group_labels) for i in bar_labels]
|
||||
for b in range(len(bar_labels)):
|
||||
l1 = bar_labels[b]
|
||||
for g in range(len(group_labels)):
|
||||
l3 = group_labels[g]
|
||||
bar_vals[b][g] = np.mean(stats[l1][l2][l3])
|
||||
bar_sdvs[b][g] = np.std(stats[l1][l2][l3])
|
||||
elif L1.startswith("select="):
|
||||
select_label = l1 = L1.split("=")[1]
|
||||
bar_labels = conf['order_l2']
|
||||
group_labels = conf['order_l3']
|
||||
bar_vals = [[0]*len(group_labels) for i in bar_labels]
|
||||
bar_sdvs = [[0]*len(group_labels) for i in bar_labels]
|
||||
for b in range(len(bar_labels)):
|
||||
l2 = bar_labels[b]
|
||||
for g in range(len(group_labels)):
|
||||
l3 = group_labels[g]
|
||||
bar_vals[b][g] = np.mean(stats[l1][l2][l3])
|
||||
bar_sdvs[b][g] = np.std(stats[l1][l2][l3])
|
||||
else:
|
||||
usage()
|
||||
|
||||
# If group is before bar then flip (zip) the data
|
||||
if [L1, L2, L3].index("group") < [L1, L2, L3].index("bar"):
|
||||
bar_labels, group_labels = group_labels, bar_labels
|
||||
bar_vals = zip(*bar_vals)
|
||||
bar_sdvs = zip(*bar_sdvs)
|
||||
|
||||
print "bar_vals:", bar_vals
|
||||
|
||||
#
|
||||
# Now render the bar graph
|
||||
#
|
||||
ind = np.arange(len(group_labels)) # the x locations for the groups
|
||||
width = 0.8 * (1.0/len(bar_labels)) # the width of the bars
|
||||
|
||||
fig = plt.figure(figsize=(10,6), dpi=80)
|
||||
plot = fig.add_subplot(1, 1, 1)
|
||||
|
||||
rects = []
|
||||
for i in range(len(bar_vals)):
|
||||
rects.append(plot.bar(ind+width*i, bar_vals[i], width, color=colors[i],
|
||||
yerr=bar_sdvs[i], align='center'))
|
||||
|
||||
# add some
|
||||
plot.set_ylabel('Milliseconds (less is better)')
|
||||
plot.set_title("Javascript array test: %s" % select_label)
|
||||
plot.set_xticks(ind+width)
|
||||
plot.set_xticklabels( group_labels )
|
||||
|
||||
fontP = FontProperties()
|
||||
fontP.set_size('small')
|
||||
plot.legend( [r[0] for r in rects], bar_labels, prop=fontP,
|
||||
loc = 'center right', bbox_to_anchor = (1.0, legendHeight))
|
||||
|
||||
def autolabel(rects):
|
||||
# attach some text labels
|
||||
for rect in rects:
|
||||
height = rect.get_height()
|
||||
if np.isnan(height):
|
||||
height = 0.0
|
||||
plot.text(rect.get_x()+rect.get_width()/2., height+20, '%d'%int(height),
|
||||
ha='center', va='bottom', size='7')
|
||||
|
||||
for rect in rects:
|
||||
autolabel(rect)
|
||||
|
||||
# Adjust axis sizes
|
||||
axis = list(plot.axis())
|
||||
axis[0] = -width # Make sure left side has enough for bar
|
||||
#axis[1] = axis[1] * 1.20 # Add 20% to the right to make sure it fits
|
||||
axis[2] = 0 # Make y-axis start at 0
|
||||
axis[3] = axis[3] * 1.10 # Add 10% to the top
|
||||
plot.axis(axis)
|
||||
|
||||
plt.show()
|
||||
+19
-5
@@ -17,8 +17,10 @@ usage() {
|
||||
echo " Default: 6080"
|
||||
echo " --vnc VNC_HOST:PORT VNC server host:port proxy target"
|
||||
echo " Default: localhost:5900"
|
||||
echo " --cert CERT Path to combined cert/key file"
|
||||
echo " --cert CERT Path to combined cert/key file, or just"
|
||||
echo " the cert file if used with --key"
|
||||
echo " Default: self.pem"
|
||||
echo " --key KEY Path to key file, when not combined with cert"
|
||||
echo " --web WEB Path to web files (e.g. vnc.html)"
|
||||
echo " Default: ./"
|
||||
echo " --ssl-only Disable non-https connections."
|
||||
@@ -34,6 +36,7 @@ HERE="$(cd "$(dirname "$REAL_NAME")" && pwd)"
|
||||
PORT="6080"
|
||||
VNC_DEST="localhost:5900"
|
||||
CERT=""
|
||||
KEY=""
|
||||
WEB=""
|
||||
proxy_pid=""
|
||||
SSLONLY=""
|
||||
@@ -63,6 +66,7 @@ while [ "$*" ]; do
|
||||
--listen) PORT="${OPTARG}"; shift ;;
|
||||
--vnc) VNC_DEST="${OPTARG}"; shift ;;
|
||||
--cert) CERT="${OPTARG}"; shift ;;
|
||||
--key) KEY="${OPTARG}"; shift ;;
|
||||
--web) WEB="${OPTARG}"; shift ;;
|
||||
--ssl-only) SSLONLY="--ssl-only" ;;
|
||||
--record) RECORD_ARG="--record ${OPTARG}"; shift ;;
|
||||
@@ -116,8 +120,15 @@ else
|
||||
echo "Warning: could not find self.pem"
|
||||
fi
|
||||
|
||||
# Check key file
|
||||
if [ -n "${KEY}" ]; then
|
||||
if [ ! -e "${KEY}" ]; then
|
||||
die "Could not find ${KEY}"
|
||||
fi
|
||||
fi
|
||||
|
||||
# try to find websockify (prefer local, try global, then download local)
|
||||
if [[ -e ${HERE}/websockify ]]; then
|
||||
if [[ -d ${HERE}/websockify ]]; then
|
||||
WEBSOCKIFY=${HERE}/websockify/run
|
||||
|
||||
if [[ ! -x $WEBSOCKIFY ]]; then
|
||||
@@ -128,9 +139,12 @@ if [[ -e ${HERE}/websockify ]]; then
|
||||
|
||||
echo "Using local websockify at $WEBSOCKIFY"
|
||||
else
|
||||
WEBSOCKIFY=$(which websockify 2>/dev/null)
|
||||
WEBSOCKIFY_FROMSYSTEM=$(which websockify 2>/dev/null)
|
||||
WEBSOCKIFY_FROMSNAP=${HERE}/../usr/bin/python2-websockify
|
||||
[ -f $WEBSOCKIFY_FROMSYSTEM ] && WEBSOCKIFY=$WEBSOCKIFY_FROMSYSTEM
|
||||
[ -f $WEBSOCKIFY_FROMSNAP ] && WEBSOCKIFY=$WEBSOCKIFY_FROMSNAP
|
||||
|
||||
if [[ $? -ne 0 ]]; then
|
||||
if [ ! -f "$WEBSOCKIFY" ]; then
|
||||
echo "No installed websockify, attempting to clone websockify..."
|
||||
WEBSOCKIFY=${HERE}/websockify/run
|
||||
git clone https://github.com/novnc/websockify ${HERE}/websockify
|
||||
@@ -148,7 +162,7 @@ fi
|
||||
|
||||
echo "Starting webserver and WebSockets proxy on port ${PORT}"
|
||||
#${HERE}/websockify --web ${WEB} ${CERT:+--cert ${CERT}} ${PORT} ${VNC_DEST} &
|
||||
${WEBSOCKIFY} ${SSLONLY} --web ${WEB} ${CERT:+--cert ${CERT}} ${PORT} ${VNC_DEST} ${RECORD_ARG} &
|
||||
${WEBSOCKIFY} ${SSLONLY} --web ${WEB} ${CERT:+--cert ${CERT}} ${KEY:+--key ${KEY}} ${PORT} ${VNC_DEST} ${RECORD_ARG} &
|
||||
proxy_pid="$!"
|
||||
sleep 1
|
||||
if ! ps -p ${proxy_pid} >/dev/null; then
|
||||
|
||||
+138
-129
@@ -4,7 +4,7 @@ const path = require('path');
|
||||
const program = require('commander');
|
||||
const fs = require('fs');
|
||||
const fse = require('fs-extra');
|
||||
const babel = require('babel-core');
|
||||
const babel = require('@babel/core');
|
||||
|
||||
const SUPPORTED_FORMATS = new Set(['amd', 'commonjs', 'systemjs', 'umd']);
|
||||
|
||||
@@ -22,28 +22,31 @@ const paths = {
|
||||
core: path.resolve(__dirname, '..', 'core'),
|
||||
app: path.resolve(__dirname, '..', 'app'),
|
||||
vendor: path.resolve(__dirname, '..', 'vendor'),
|
||||
out_dir_base: path.resolve(__dirname, '..', 'build'),
|
||||
lib_dir_base: path.resolve(__dirname, '..', 'lib'),
|
||||
outDirBase: path.resolve(__dirname, '..', 'build'),
|
||||
libDirBase: path.resolve(__dirname, '..', 'lib'),
|
||||
};
|
||||
|
||||
const no_copy_files = new Set([
|
||||
const noCopyFiles = new Set([
|
||||
// skip these -- they don't belong in the processed application
|
||||
path.join(paths.vendor, 'sinon.js'),
|
||||
path.join(paths.vendor, 'browser-es-module-loader'),
|
||||
path.join(paths.vendor, 'promise.js'),
|
||||
path.join(paths.app, 'images', 'icons', 'Makefile'),
|
||||
]);
|
||||
|
||||
const no_transform_files = new Set([
|
||||
const onlyLegacyScripts = new Set([
|
||||
path.join(paths.vendor, 'promise.js'),
|
||||
]);
|
||||
|
||||
const noTransformFiles = new Set([
|
||||
// don't transform this -- we want it imported as-is to properly catch loading errors
|
||||
path.join(paths.app, 'error-handler.js'),
|
||||
]);
|
||||
|
||||
no_copy_files.forEach(file => no_transform_files.add(file));
|
||||
noCopyFiles.forEach(file => noTransformFiles.add(file));
|
||||
|
||||
// util.promisify requires Node.js 8.x, so we have our own
|
||||
function promisify(original) {
|
||||
return function promise_wrap() {
|
||||
return function promiseWrap() {
|
||||
const args = Array.prototype.slice.call(arguments);
|
||||
return new Promise((resolve, reject) => {
|
||||
original.apply(this, args.concat((err, value) => {
|
||||
@@ -69,10 +72,10 @@ const babelTransformFile = promisify(babel.transformFile);
|
||||
|
||||
// walkDir *recursively* walks directories trees,
|
||||
// calling the callback for all normal files found.
|
||||
function walkDir(base_path, cb, filter) {
|
||||
return readdir(base_path)
|
||||
function walkDir(basePath, cb, filter) {
|
||||
return readdir(basePath)
|
||||
.then((files) => {
|
||||
const paths = files.map(filename => path.join(base_path, filename));
|
||||
const paths = files.map(filename => path.join(basePath, filename));
|
||||
return Promise.all(paths.map(filepath => lstat(filepath)
|
||||
.then((stats) => {
|
||||
if (filter !== undefined && !filter(filepath, stats)) return;
|
||||
@@ -84,198 +87,204 @@ function walkDir(base_path, cb, filter) {
|
||||
});
|
||||
}
|
||||
|
||||
function transform_html(legacy_scripts, only_legacy) {
|
||||
function transformHtml(legacyScripts, onlyLegacy) {
|
||||
// write out the modified vnc.html file that works with the bundle
|
||||
const src_html_path = path.resolve(__dirname, '..', 'vnc.html');
|
||||
const out_html_path = path.resolve(paths.out_dir_base, 'vnc.html');
|
||||
return readFile(src_html_path)
|
||||
.then((contents_raw) => {
|
||||
let contents = contents_raw.toString();
|
||||
const srcHtmlPath = path.resolve(__dirname, '..', 'vnc.html');
|
||||
const outHtmlPath = path.resolve(paths.outDirBase, 'vnc.html');
|
||||
return readFile(srcHtmlPath)
|
||||
.then((contentsRaw) => {
|
||||
let contents = contentsRaw.toString();
|
||||
|
||||
const start_marker = '<!-- begin scripts -->\n';
|
||||
const end_marker = '<!-- end scripts -->';
|
||||
const start_ind = contents.indexOf(start_marker) + start_marker.length;
|
||||
const end_ind = contents.indexOf(end_marker, start_ind);
|
||||
const startMarker = '<!-- begin scripts -->\n';
|
||||
const endMarker = '<!-- end scripts -->';
|
||||
const startInd = contents.indexOf(startMarker) + startMarker.length;
|
||||
const endInd = contents.indexOf(endMarker, startInd);
|
||||
|
||||
let new_script = '';
|
||||
let newScript = '';
|
||||
|
||||
if (only_legacy) {
|
||||
if (onlyLegacy) {
|
||||
// Only legacy version, so include things directly
|
||||
for (let i = 0;i < legacy_scripts.length;i++) {
|
||||
new_script += ` <script src="${legacy_scripts[i]}"></script>\n`;
|
||||
for (let i = 0;i < legacyScripts.length;i++) {
|
||||
newScript += ` <script src="${legacyScripts[i]}"></script>\n`;
|
||||
}
|
||||
} else {
|
||||
// Otherwise detect if it's a modern browser and select
|
||||
// variant accordingly
|
||||
new_script += `\
|
||||
<script type="module">\n\
|
||||
window._noVNC_has_module_support = true;\n\
|
||||
</script>\n\
|
||||
<script>\n\
|
||||
window.addEventListener("load", function() {\n\
|
||||
if (window._noVNC_has_module_support) return;\n\
|
||||
let legacy_scripts = ${JSON.stringify(legacy_scripts)};\n\
|
||||
for (let i = 0;i < legacy_scripts.length;i++) {\n\
|
||||
let script = document.createElement("script");\n\
|
||||
script.src = legacy_scripts[i];\n\
|
||||
script.async = false;\n\
|
||||
document.head.appendChild(script);\n\
|
||||
}\n\
|
||||
});\n\
|
||||
</script>\n`;
|
||||
|
||||
// Original, ES6 modules
|
||||
new_script += ' <script type="module" crossorigin="anonymous" src="app/ui.js"></script>\n';
|
||||
// Otherwise include both modules and legacy fallbacks
|
||||
newScript += ' <script type="module" crossorigin="anonymous" src="app/ui.js"></script>\n';
|
||||
for (let i = 0;i < legacyScripts.length;i++) {
|
||||
newScript += ` <script nomodule src="${legacyScripts[i]}"></script>\n`;
|
||||
}
|
||||
}
|
||||
|
||||
contents = contents.slice(0, start_ind) + `${new_script}\n` + contents.slice(end_ind);
|
||||
contents = contents.slice(0, startInd) + `${newScript}\n` + contents.slice(endInd);
|
||||
|
||||
return contents;
|
||||
})
|
||||
.then((contents) => {
|
||||
console.log(`Writing ${out_html_path}`);
|
||||
return writeFile(out_html_path, contents);
|
||||
console.log(`Writing ${outHtmlPath}`);
|
||||
return writeFile(outHtmlPath, contents);
|
||||
});
|
||||
}
|
||||
|
||||
function make_lib_files(import_format, source_maps, with_app_dir, only_legacy) {
|
||||
if (!import_format) {
|
||||
function makeLibFiles(importFormat, sourceMaps, withAppDir, onlyLegacy) {
|
||||
if (!importFormat) {
|
||||
throw new Error("you must specify an import format to generate compiled noVNC libraries");
|
||||
} else if (!SUPPORTED_FORMATS.has(import_format)) {
|
||||
throw new Error(`unsupported output format "${import_format}" for import/export -- only ${Array.from(SUPPORTED_FORMATS)} are supported`);
|
||||
} else if (!SUPPORTED_FORMATS.has(importFormat)) {
|
||||
throw new Error(`unsupported output format "${importFormat}" for import/export -- only ${Array.from(SUPPORTED_FORMATS)} are supported`);
|
||||
}
|
||||
|
||||
// NB: we need to make a copy of babel_opts, since babel sets some defaults on it
|
||||
const babel_opts = () => ({
|
||||
plugins: [`transform-es2015-modules-${import_format}`],
|
||||
presets: ['es2015'],
|
||||
// NB: we need to make a copy of babelOpts, since babel sets some defaults on it
|
||||
const babelOpts = () => ({
|
||||
plugins: [],
|
||||
presets: [
|
||||
[ '@babel/preset-env',
|
||||
{ targets: 'ie >= 11',
|
||||
modules: importFormat } ]
|
||||
],
|
||||
ast: false,
|
||||
sourceMaps: source_maps,
|
||||
sourceMaps: sourceMaps,
|
||||
});
|
||||
|
||||
// No point in duplicate files without the app, so force only converted files
|
||||
if (!with_app_dir) {
|
||||
only_legacy = true;
|
||||
if (!withAppDir) {
|
||||
onlyLegacy = true;
|
||||
}
|
||||
|
||||
let in_path;
|
||||
let out_path_base;
|
||||
if (with_app_dir) {
|
||||
out_path_base = paths.out_dir_base;
|
||||
in_path = paths.main;
|
||||
let inPath;
|
||||
let outPathBase;
|
||||
if (withAppDir) {
|
||||
outPathBase = paths.outDirBase;
|
||||
inPath = paths.main;
|
||||
} else {
|
||||
out_path_base = paths.lib_dir_base;
|
||||
outPathBase = paths.libDirBase;
|
||||
}
|
||||
const legacy_path_base = only_legacy ? out_path_base : path.join(out_path_base, 'legacy');
|
||||
const legacyPathBase = onlyLegacy ? outPathBase : path.join(outPathBase, 'legacy');
|
||||
|
||||
fse.ensureDirSync(out_path_base);
|
||||
fse.ensureDirSync(outPathBase);
|
||||
|
||||
const helpers = require('./use_require_helpers');
|
||||
const helper = helpers[import_format];
|
||||
const helper = helpers[importFormat];
|
||||
|
||||
const outFiles = [];
|
||||
const legacyFiles = [];
|
||||
|
||||
const handleDir = (js_only, vendor_rewrite, in_path_base, filename) => Promise.resolve()
|
||||
const handleDir = (jsOnly, vendorRewrite, inPathBase, filename) => Promise.resolve()
|
||||
.then(() => {
|
||||
if (no_copy_files.has(filename)) return;
|
||||
|
||||
const out_path = path.join(out_path_base, path.relative(in_path_base, filename));
|
||||
const legacy_path = path.join(legacy_path_base, path.relative(in_path_base, filename));
|
||||
const outPath = path.join(outPathBase, path.relative(inPathBase, filename));
|
||||
const legacyPath = path.join(legacyPathBase, path.relative(inPathBase, filename));
|
||||
|
||||
if (path.extname(filename) !== '.js') {
|
||||
if (!js_only) {
|
||||
console.log(`Writing ${out_path}`);
|
||||
return copy(filename, out_path);
|
||||
if (!jsOnly) {
|
||||
console.log(`Writing ${outPath}`);
|
||||
return copy(filename, outPath);
|
||||
}
|
||||
return; // skip non-javascript files
|
||||
}
|
||||
|
||||
if (noTransformFiles.has(filename)) {
|
||||
return ensureDir(path.dirname(outPath))
|
||||
.then(() => {
|
||||
console.log(`Writing ${outPath}`);
|
||||
return copy(filename, outPath);
|
||||
});
|
||||
}
|
||||
|
||||
if (onlyLegacyScripts.has(filename)) {
|
||||
legacyFiles.push(legacyPath);
|
||||
return ensureDir(path.dirname(legacyPath))
|
||||
.then(() => {
|
||||
console.log(`Writing ${legacyPath}`);
|
||||
return copy(filename, legacyPath);
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.resolve()
|
||||
.then(() => {
|
||||
if (only_legacy && !no_transform_files.has(filename)) {
|
||||
if (onlyLegacy) {
|
||||
return;
|
||||
}
|
||||
return ensureDir(path.dirname(out_path))
|
||||
return ensureDir(path.dirname(outPath))
|
||||
.then(() => {
|
||||
console.log(`Writing ${out_path}`);
|
||||
return copy(filename, out_path);
|
||||
console.log(`Writing ${outPath}`);
|
||||
return copy(filename, outPath);
|
||||
});
|
||||
})
|
||||
.then(() => ensureDir(path.dirname(legacy_path)))
|
||||
.then(() => ensureDir(path.dirname(legacyPath)))
|
||||
.then(() => {
|
||||
if (no_transform_files.has(filename)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const opts = babel_opts();
|
||||
const opts = babelOpts();
|
||||
if (helper && helpers.optionsOverride) {
|
||||
helper.optionsOverride(opts);
|
||||
}
|
||||
// Adjust for the fact that we move the core files relative
|
||||
// to the vendor directory
|
||||
if (vendor_rewrite) {
|
||||
if (vendorRewrite) {
|
||||
opts.plugins.push(["import-redirect",
|
||||
{"root": legacy_path_base,
|
||||
{"root": legacyPathBase,
|
||||
"redirect": { "vendor/(.+)": "./vendor/$1"}}]);
|
||||
}
|
||||
|
||||
return babelTransformFile(filename, opts)
|
||||
.then((res) => {
|
||||
console.log(`Writing ${legacy_path}`);
|
||||
console.log(`Writing ${legacyPath}`);
|
||||
const {map} = res;
|
||||
let {code} = res;
|
||||
if (source_maps === true) {
|
||||
if (sourceMaps === true) {
|
||||
// append URL for external source map
|
||||
code += `\n//# sourceMappingURL=${path.basename(legacy_path)}.map\n`;
|
||||
code += `\n//# sourceMappingURL=${path.basename(legacyPath)}.map\n`;
|
||||
}
|
||||
outFiles.push(`${legacy_path}`);
|
||||
return writeFile(legacy_path, code)
|
||||
outFiles.push(`${legacyPath}`);
|
||||
return writeFile(legacyPath, code)
|
||||
.then(() => {
|
||||
if (source_maps === true || source_maps === 'both') {
|
||||
console.log(` and ${legacy_path}.map`);
|
||||
outFiles.push(`${legacy_path}.map`);
|
||||
return writeFile(`${legacy_path}.map`, JSON.stringify(map));
|
||||
if (sourceMaps === true || sourceMaps === 'both') {
|
||||
console.log(` and ${legacyPath}.map`);
|
||||
outFiles.push(`${legacyPath}.map`);
|
||||
return writeFile(`${legacyPath}.map`, JSON.stringify(map));
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
if (with_app_dir && helper && helper.noCopyOverride) {
|
||||
helper.noCopyOverride(paths, no_copy_files);
|
||||
}
|
||||
|
||||
Promise.resolve()
|
||||
.then(() => {
|
||||
const handler = handleDir.bind(null, true, false, in_path || paths.main);
|
||||
const filter = (filename, stats) => !no_copy_files.has(filename);
|
||||
const handler = handleDir.bind(null, true, false, inPath || paths.main);
|
||||
const filter = (filename, stats) => !noCopyFiles.has(filename);
|
||||
return walkDir(paths.vendor, handler, filter);
|
||||
})
|
||||
.then(() => {
|
||||
const handler = handleDir.bind(null, true, !in_path, in_path || paths.core);
|
||||
const filter = (filename, stats) => !no_copy_files.has(filename);
|
||||
const handler = handleDir.bind(null, true, !inPath, inPath || paths.core);
|
||||
const filter = (filename, stats) => !noCopyFiles.has(filename);
|
||||
return walkDir(paths.core, handler, filter);
|
||||
})
|
||||
.then(() => {
|
||||
if (!with_app_dir) return;
|
||||
const handler = handleDir.bind(null, false, false, in_path);
|
||||
const filter = (filename, stats) => !no_copy_files.has(filename);
|
||||
if (!withAppDir) return;
|
||||
const handler = handleDir.bind(null, false, false, inPath);
|
||||
const filter = (filename, stats) => !noCopyFiles.has(filename);
|
||||
return walkDir(paths.app, handler, filter);
|
||||
})
|
||||
.then(() => {
|
||||
if (!with_app_dir) return;
|
||||
if (!withAppDir) return;
|
||||
|
||||
if (!helper || !helper.appWriter) {
|
||||
throw new Error(`Unable to generate app for the ${import_format} format!`);
|
||||
throw new Error(`Unable to generate app for the ${importFormat} format!`);
|
||||
}
|
||||
|
||||
const out_app_path = path.join(legacy_path_base, 'app.js');
|
||||
console.log(`Writing ${out_app_path}`);
|
||||
return helper.appWriter(out_path_base, legacy_path_base, out_app_path)
|
||||
.then((extra_scripts) => {
|
||||
const rel_app_path = path.relative(out_path_base, out_app_path);
|
||||
const legacy_scripts = extra_scripts.concat([rel_app_path]);
|
||||
transform_html(legacy_scripts, only_legacy);
|
||||
const outAppPath = path.join(legacyPathBase, 'app.js');
|
||||
console.log(`Writing ${outAppPath}`);
|
||||
return helper.appWriter(outPathBase, legacyPathBase, outAppPath)
|
||||
.then((extraScripts) => {
|
||||
let legacyScripts = [];
|
||||
|
||||
legacyFiles.forEach((file) => {
|
||||
let relFilePath = path.relative(outPathBase, file);
|
||||
legacyScripts.push(relFilePath);
|
||||
});
|
||||
|
||||
legacyScripts = legacyScripts.concat(extraScripts);
|
||||
|
||||
let relAppPath = path.relative(outPathBase, outAppPath);
|
||||
legacyScripts.push(relAppPath);
|
||||
|
||||
transformHtml(legacyScripts, onlyLegacy);
|
||||
})
|
||||
.then(() => {
|
||||
if (!helper.removeModules) return;
|
||||
@@ -283,15 +292,15 @@ function make_lib_files(import_format, source_maps, with_app_dir, only_legacy) {
|
||||
return Promise.all(outFiles.map((filepath) => {
|
||||
unlink(filepath)
|
||||
.then(() => {
|
||||
// Try to clean up any empty directories if this
|
||||
// was the last file in there
|
||||
const rmdir_r = dir =>
|
||||
// Try to clean up any empty directories if
|
||||
// this was the last file in there
|
||||
const rmdirR = dir =>
|
||||
rmdir(dir)
|
||||
.then(() => rmdir_r(path.dirname(dir)))
|
||||
.then(() => rmdirR(path.dirname(dir)))
|
||||
.catch(() => {
|
||||
// Assume the error was ENOTEMPTY and ignore it
|
||||
// Assume the error was ENOTEMPTY and ignore it
|
||||
});
|
||||
return rmdir_r(path.dirname(filepath));
|
||||
return rmdirR(path.dirname(filepath));
|
||||
});
|
||||
}));
|
||||
});
|
||||
@@ -303,11 +312,11 @@ function make_lib_files(import_format, source_maps, with_app_dir, only_legacy) {
|
||||
}
|
||||
|
||||
if (program.clean) {
|
||||
console.log(`Removing ${paths.lib_dir_base}`);
|
||||
fse.removeSync(paths.lib_dir_base);
|
||||
console.log(`Removing ${paths.libDirBase}`);
|
||||
fse.removeSync(paths.libDirBase);
|
||||
|
||||
console.log(`Removing ${paths.out_dir_base}`);
|
||||
fse.removeSync(paths.out_dir_base);
|
||||
console.log(`Removing ${paths.outDirBase}`);
|
||||
fse.removeSync(paths.outDirBase);
|
||||
}
|
||||
|
||||
make_lib_files(program.as, program.withSourceMaps, program.withApp, program.onlyLegacy);
|
||||
makeLibFiles(program.as, program.withSourceMaps, program.withApp, program.onlyLegacy);
|
||||
|
||||
@@ -4,7 +4,7 @@ const path = require('path');
|
||||
|
||||
// util.promisify requires Node.js 8.x, so we have our own
|
||||
function promisify(original) {
|
||||
return function promise_wrap() {
|
||||
return function promiseWrap() {
|
||||
const args = Array.prototype.slice.call(arguments);
|
||||
return new Promise((resolve, reject) => {
|
||||
original.apply(this, args.concat((err, value) => {
|
||||
@@ -19,58 +19,42 @@ const writeFile = promisify(fs.writeFile);
|
||||
|
||||
module.exports = {
|
||||
'amd': {
|
||||
appWriter: (base_out_path, script_base_path, out_path) => {
|
||||
appWriter: (baseOutPath, scriptBasePath, outPath) => {
|
||||
// setup for requirejs
|
||||
const ui_path = path.relative(base_out_path,
|
||||
path.join(script_base_path, 'app', 'ui'));
|
||||
return writeFile(out_path, `requirejs(["${ui_path}"], (ui) => {});`)
|
||||
const uiPath = path.relative(baseOutPath,
|
||||
path.join(scriptBasePath, 'app', 'ui'));
|
||||
return writeFile(outPath, `requirejs(["${uiPath}"], function (ui) {});`)
|
||||
.then(() => {
|
||||
console.log(`Please place RequireJS in ${path.join(script_base_path, 'require.js')}`);
|
||||
const require_path = path.relative(base_out_path,
|
||||
path.join(script_base_path, 'require.js'));
|
||||
return [ require_path ];
|
||||
console.log(`Please place RequireJS in ${path.join(scriptBasePath, 'require.js')}`);
|
||||
const requirePath = path.relative(baseOutPath,
|
||||
path.join(scriptBasePath, 'require.js'));
|
||||
return [ requirePath ];
|
||||
});
|
||||
},
|
||||
noCopyOverride: () => {},
|
||||
},
|
||||
'commonjs': {
|
||||
optionsOverride: (opts) => {
|
||||
// CommonJS supports properly shifting the default export to work as normal
|
||||
opts.plugins.unshift("add-module-exports");
|
||||
},
|
||||
appWriter: (base_out_path, script_base_path, out_path) => {
|
||||
appWriter: (baseOutPath, scriptBasePath, outPath) => {
|
||||
const browserify = require('browserify');
|
||||
const b = browserify(path.join(script_base_path, 'app/ui.js'), {});
|
||||
const b = browserify(path.join(scriptBasePath, 'app/ui.js'), {});
|
||||
return promisify(b.bundle).call(b)
|
||||
.then(buf => writeFile(out_path, buf))
|
||||
.then(buf => writeFile(outPath, buf))
|
||||
.then(() => []);
|
||||
},
|
||||
noCopyOverride: () => {},
|
||||
removeModules: true,
|
||||
},
|
||||
'systemjs': {
|
||||
appWriter: (base_out_path, script_base_path, out_path) => {
|
||||
const ui_path = path.relative(base_out_path,
|
||||
path.join(script_base_path, 'app', 'ui.js'));
|
||||
return writeFile(out_path, `SystemJS.import("${ui_path}");`)
|
||||
appWriter: (baseOutPath, scriptBasePath, outPath) => {
|
||||
const uiPath = path.relative(baseOutPath,
|
||||
path.join(scriptBasePath, 'app', 'ui.js'));
|
||||
return writeFile(outPath, `SystemJS.import("${uiPath}");`)
|
||||
.then(() => {
|
||||
console.log(`Please place SystemJS in ${path.join(script_base_path, 'system-production.js')}`);
|
||||
// FIXME: Should probably be in the legacy directory
|
||||
const promise_path = path.relative(base_out_path,
|
||||
path.join(base_out_path, 'vendor', 'promise.js'));
|
||||
const systemjs_path = path.relative(base_out_path,
|
||||
path.join(script_base_path, 'system-production.js'));
|
||||
return [ promise_path, systemjs_path ];
|
||||
console.log(`Please place SystemJS in ${path.join(scriptBasePath, 'system-production.js')}`);
|
||||
const systemjsPath = path.relative(baseOutPath,
|
||||
path.join(scriptBasePath, 'system-production.js'));
|
||||
return [ systemjsPath ];
|
||||
});
|
||||
},
|
||||
noCopyOverride: (paths, no_copy_files) => {
|
||||
no_copy_files.delete(path.join(paths.vendor, 'promise.js'));
|
||||
},
|
||||
},
|
||||
'umd': {
|
||||
optionsOverride: (opts) => {
|
||||
// umd supports properly shifting the default export to work as normal
|
||||
opts.plugins.unshift("add-module-exports");
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
+2
-2
@@ -6,8 +6,8 @@ It's based heavily on
|
||||
https://github.com/ModuleLoader/browser-es-module-loader, but uses
|
||||
WebWorkers to compile the modules in the background.
|
||||
|
||||
To generate, run `rollup -c` in this directory, and then run `browserify
|
||||
src/babel-worker.js > dist/babel-worker.js`.
|
||||
To generate, run `npx rollup -c` in this directory, and then run
|
||||
`./genworker.js`.
|
||||
|
||||
LICENSE
|
||||
-------
|
||||
|
||||
+105889
-40230
File diff suppressed because one or more lines are too long
+1352
-1357
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
+13
@@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
var fs = require("fs");
|
||||
var browserify = require("browserify");
|
||||
|
||||
browserify("src/babel-worker.js")
|
||||
.transform("babelify", {
|
||||
presets: [ [ "@babel/preset-env", { targets: "ie >= 11" } ] ],
|
||||
global: true,
|
||||
ignore: [ "../../node_modules/core-js" ]
|
||||
})
|
||||
.bundle()
|
||||
.pipe(fs.createWriteStream("dist/babel-worker.js"));
|
||||
+7
-8
@@ -1,16 +1,15 @@
|
||||
import nodeResolve from 'rollup-plugin-node-resolve';
|
||||
|
||||
export default {
|
||||
entry: 'src/browser-es-module-loader.js',
|
||||
dest: 'dist/browser-es-module-loader.js',
|
||||
format: 'umd',
|
||||
moduleName: 'BrowserESModuleLoader',
|
||||
sourceMap: true,
|
||||
input: 'src/browser-es-module-loader.js',
|
||||
output: {
|
||||
file: 'dist/browser-es-module-loader.js',
|
||||
format: 'umd',
|
||||
name: 'BrowserESModuleLoader',
|
||||
sourcemap: true,
|
||||
},
|
||||
|
||||
plugins: [
|
||||
nodeResolve(),
|
||||
],
|
||||
|
||||
// skip rollup warnings (specifically the eval warning)
|
||||
onwarn: function() {}
|
||||
};
|
||||
|
||||
+8
-10
@@ -1,12 +1,10 @@
|
||||
/*import { transform as babelTransform } from 'babel-core';
|
||||
import babelTransformDynamicImport from 'babel-plugin-syntax-dynamic-import';
|
||||
import babelTransformES2015ModulesSystemJS from 'babel-plugin-transform-es2015-modules-systemjs';*/
|
||||
// Polyfills needed for Babel to function
|
||||
require("core-js");
|
||||
|
||||
// sadly, due to how rollup works, we can't use es6 imports here
|
||||
var babelTransform = require('babel-core').transform;
|
||||
var babelTransformDynamicImport = require('babel-plugin-syntax-dynamic-import');
|
||||
var babelTransformES2015ModulesSystemJS = require('babel-plugin-transform-es2015-modules-systemjs');
|
||||
var babelPresetES2015 = require('babel-preset-es2015');
|
||||
var babelTransform = require('@babel/core').transform;
|
||||
var babelTransformDynamicImport = require('@babel/plugin-syntax-dynamic-import');
|
||||
var babelTransformModulesSystemJS = require('@babel/plugin-transform-modules-systemjs');
|
||||
var babelPresetEnv = require('@babel/preset-env');
|
||||
|
||||
self.onmessage = function (evt) {
|
||||
// transform source with Babel
|
||||
@@ -17,8 +15,8 @@ self.onmessage = function (evt) {
|
||||
moduleIds: false,
|
||||
sourceMaps: 'inline',
|
||||
babelrc: false,
|
||||
plugins: [babelTransformDynamicImport, babelTransformES2015ModulesSystemJS],
|
||||
presets: [babelPresetES2015],
|
||||
plugins: [babelTransformDynamicImport, babelTransformModulesSystemJS],
|
||||
presets: [ [ babelPresetEnv, { targets: 'ie >= 11' } ] ],
|
||||
});
|
||||
|
||||
self.postMessage({key: evt.data.key, code: output.code, source: evt.data.source});
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import RegisterLoader from 'es-module-loader/core/register-loader.js';
|
||||
import { InternalModuleNamespace as ModuleNamespace } from 'es-module-loader/core/loader-polyfill.js';
|
||||
|
||||
import { baseURI, global, isBrowser } from 'es-module-loader/core/common.js';
|
||||
import { resolveIfNotPlain } from 'es-module-loader/core/resolve.js';
|
||||
|
||||
Vendored
+30
-30
@@ -9,51 +9,51 @@ import msg from "./messages.js";
|
||||
|
||||
|
||||
/* Allowed flush values; see deflate() and inflate() below for details */
|
||||
var Z_NO_FLUSH = 0;
|
||||
var Z_PARTIAL_FLUSH = 1;
|
||||
//var Z_SYNC_FLUSH = 2;
|
||||
var Z_FULL_FLUSH = 3;
|
||||
var Z_FINISH = 4;
|
||||
var Z_BLOCK = 5;
|
||||
//var Z_TREES = 6;
|
||||
export const Z_NO_FLUSH = 0;
|
||||
export const Z_PARTIAL_FLUSH = 1;
|
||||
//export const Z_SYNC_FLUSH = 2;
|
||||
export const Z_FULL_FLUSH = 3;
|
||||
export const Z_FINISH = 4;
|
||||
export const Z_BLOCK = 5;
|
||||
//export const Z_TREES = 6;
|
||||
|
||||
|
||||
/* Return codes for the compression/decompression functions. Negative values
|
||||
* are errors, positive values are used for special but normal events.
|
||||
*/
|
||||
var Z_OK = 0;
|
||||
var Z_STREAM_END = 1;
|
||||
//var Z_NEED_DICT = 2;
|
||||
//var Z_ERRNO = -1;
|
||||
var Z_STREAM_ERROR = -2;
|
||||
var Z_DATA_ERROR = -3;
|
||||
//var Z_MEM_ERROR = -4;
|
||||
var Z_BUF_ERROR = -5;
|
||||
//var Z_VERSION_ERROR = -6;
|
||||
export const Z_OK = 0;
|
||||
export const Z_STREAM_END = 1;
|
||||
//export const Z_NEED_DICT = 2;
|
||||
//export const Z_ERRNO = -1;
|
||||
export const Z_STREAM_ERROR = -2;
|
||||
export const Z_DATA_ERROR = -3;
|
||||
//export const Z_MEM_ERROR = -4;
|
||||
export const Z_BUF_ERROR = -5;
|
||||
//export const Z_VERSION_ERROR = -6;
|
||||
|
||||
|
||||
/* compression levels */
|
||||
//var Z_NO_COMPRESSION = 0;
|
||||
//var Z_BEST_SPEED = 1;
|
||||
//var Z_BEST_COMPRESSION = 9;
|
||||
var Z_DEFAULT_COMPRESSION = -1;
|
||||
//export const Z_NO_COMPRESSION = 0;
|
||||
//export const Z_BEST_SPEED = 1;
|
||||
//export const Z_BEST_COMPRESSION = 9;
|
||||
export const Z_DEFAULT_COMPRESSION = -1;
|
||||
|
||||
|
||||
var Z_FILTERED = 1;
|
||||
var Z_HUFFMAN_ONLY = 2;
|
||||
var Z_RLE = 3;
|
||||
var Z_FIXED = 4;
|
||||
var Z_DEFAULT_STRATEGY = 0;
|
||||
export const Z_FILTERED = 1;
|
||||
export const Z_HUFFMAN_ONLY = 2;
|
||||
export const Z_RLE = 3;
|
||||
export const Z_FIXED = 4;
|
||||
export const Z_DEFAULT_STRATEGY = 0;
|
||||
|
||||
/* Possible values of the data_type field (though see inflate()) */
|
||||
//var Z_BINARY = 0;
|
||||
//var Z_TEXT = 1;
|
||||
//var Z_ASCII = 1; // = Z_TEXT
|
||||
var Z_UNKNOWN = 2;
|
||||
//export const Z_BINARY = 0;
|
||||
//export const Z_TEXT = 1;
|
||||
//export const Z_ASCII = 1; // = Z_TEXT
|
||||
export const Z_UNKNOWN = 2;
|
||||
|
||||
|
||||
/* The deflate compression method */
|
||||
var Z_DEFLATED = 8;
|
||||
export const Z_DEFLATED = 8;
|
||||
|
||||
/*============================================================================*/
|
||||
|
||||
|
||||
Vendored
+17
-17
@@ -13,30 +13,30 @@ var DISTS = 2;
|
||||
|
||||
|
||||
/* Allowed flush values; see deflate() and inflate() below for details */
|
||||
//var Z_NO_FLUSH = 0;
|
||||
//var Z_PARTIAL_FLUSH = 1;
|
||||
//var Z_SYNC_FLUSH = 2;
|
||||
//var Z_FULL_FLUSH = 3;
|
||||
var Z_FINISH = 4;
|
||||
var Z_BLOCK = 5;
|
||||
var Z_TREES = 6;
|
||||
//export const Z_NO_FLUSH = 0;
|
||||
//export const Z_PARTIAL_FLUSH = 1;
|
||||
//export const Z_SYNC_FLUSH = 2;
|
||||
//export const Z_FULL_FLUSH = 3;
|
||||
export const Z_FINISH = 4;
|
||||
export const Z_BLOCK = 5;
|
||||
export const Z_TREES = 6;
|
||||
|
||||
|
||||
/* Return codes for the compression/decompression functions. Negative values
|
||||
* are errors, positive values are used for special but normal events.
|
||||
*/
|
||||
var Z_OK = 0;
|
||||
var Z_STREAM_END = 1;
|
||||
var Z_NEED_DICT = 2;
|
||||
//var Z_ERRNO = -1;
|
||||
var Z_STREAM_ERROR = -2;
|
||||
var Z_DATA_ERROR = -3;
|
||||
var Z_MEM_ERROR = -4;
|
||||
var Z_BUF_ERROR = -5;
|
||||
//var Z_VERSION_ERROR = -6;
|
||||
export const Z_OK = 0;
|
||||
export const Z_STREAM_END = 1;
|
||||
export const Z_NEED_DICT = 2;
|
||||
//export const Z_ERRNO = -1;
|
||||
export const Z_STREAM_ERROR = -2;
|
||||
export const Z_DATA_ERROR = -3;
|
||||
export const Z_MEM_ERROR = -4;
|
||||
export const Z_BUF_ERROR = -5;
|
||||
//export const Z_VERSION_ERROR = -6;
|
||||
|
||||
/* The deflate compression method */
|
||||
var Z_DEFLATED = 8;
|
||||
export const Z_DEFLATED = 8;
|
||||
|
||||
|
||||
/* STATES ====================================================================*/
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
<!--
|
||||
noVNC example: simple example using default UI
|
||||
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).
|
||||
|
||||
@@ -16,6 +16,10 @@
|
||||
<title>noVNC</title>
|
||||
|
||||
<meta charset="utf-8">
|
||||
|
||||
<!-- Always force latest IE rendering engine (even in intranet) & Chrome Frame
|
||||
Remove this if you use the .htaccess -->
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
|
||||
<!-- Icons (see app/images/icons/Makefile for what the sizes are for) -->
|
||||
<link rel="icon" sizes="16x16" type="image/png" href="app/images/icons/novnc-16x16.png">
|
||||
@@ -57,17 +61,7 @@
|
||||
<!-- 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>
|
||||
<script nomodule src="vendor/browser-es-module-loader/dist/browser-es-module-loader.js"></script>
|
||||
<!-- actual script modules -->
|
||||
<script type="module" crossorigin="anonymous" src="app/ui.js"></script>
|
||||
<!-- end scripts -->
|
||||
@@ -94,55 +88,41 @@
|
||||
<h1 class="noVNC_logo" translate="no"><span>no</span><br>VNC</h1>
|
||||
|
||||
<!-- Drag/Pan the viewport -->
|
||||
<input type="image" alt="viewport drag" src="app/images/drag.svg"
|
||||
<input type="image" alt="Drag" src="app/images/drag.svg"
|
||||
id="noVNC_view_drag_button" class="noVNC_button noVNC_hidden"
|
||||
title="Move/Drag Viewport">
|
||||
|
||||
<!--noVNC Touch Device only buttons-->
|
||||
<div id="noVNC_mobile_buttons">
|
||||
<input type="image" alt="No mousebutton" src="app/images/mouse_none.svg"
|
||||
id="noVNC_mouse_button0" class="noVNC_button"
|
||||
title="Active Mouse Button">
|
||||
<input type="image" alt="Left mousebutton" src="app/images/mouse_left.svg"
|
||||
id="noVNC_mouse_button1" class="noVNC_button"
|
||||
title="Active Mouse Button">
|
||||
<input type="image" alt="Middle mousebutton" src="app/images/mouse_middle.svg"
|
||||
id="noVNC_mouse_button2" class="noVNC_button"
|
||||
title="Active Mouse Button">
|
||||
<input type="image" alt="Right mousebutton" src="app/images/mouse_right.svg"
|
||||
id="noVNC_mouse_button4" class="noVNC_button"
|
||||
title="Active Mouse Button">
|
||||
<input type="image" alt="Keyboard" src="app/images/keyboard.svg"
|
||||
id="noVNC_keyboard_button" class="noVNC_button" title="Show Keyboard">
|
||||
</div>
|
||||
|
||||
<!-- Extra manual keys -->
|
||||
<div id="noVNC_extra_keys">
|
||||
<input type="image" alt="Extra keys" src="app/images/toggleextrakeys.svg"
|
||||
id="noVNC_toggle_extra_keys_button" class="noVNC_button"
|
||||
title="Show Extra Keys">
|
||||
<div class="noVNC_vcenter">
|
||||
<div id="noVNC_modifiers" class="noVNC_panel">
|
||||
<input type="image" alt="Ctrl" src="app/images/ctrl.svg"
|
||||
id="noVNC_toggle_ctrl_button" class="noVNC_button"
|
||||
title="Toggle Ctrl">
|
||||
<input type="image" alt="Alt" src="app/images/alt.svg"
|
||||
id="noVNC_toggle_alt_button" class="noVNC_button"
|
||||
title="Toggle Alt">
|
||||
<input type="image" alt="Windows" src="app/images/windows.svg"
|
||||
id="noVNC_toggle_windows_button" class="noVNC_button"
|
||||
title="Toggle Windows">
|
||||
<input type="image" alt="Tab" src="app/images/tab.svg"
|
||||
id="noVNC_send_tab_button" class="noVNC_button"
|
||||
title="Send Tab">
|
||||
<input type="image" alt="Esc" src="app/images/esc.svg"
|
||||
id="noVNC_send_esc_button" class="noVNC_button"
|
||||
title="Send Escape">
|
||||
<input type="image" alt="Ctrl+Alt+Del" src="app/images/ctrlaltdel.svg"
|
||||
id="noVNC_send_ctrl_alt_del_button" class="noVNC_button"
|
||||
title="Send Ctrl-Alt-Del">
|
||||
</div>
|
||||
</div>
|
||||
<input type="image" alt="Extra keys" src="app/images/toggleextrakeys.svg"
|
||||
id="noVNC_toggle_extra_keys_button" class="noVNC_button"
|
||||
title="Show Extra Keys">
|
||||
<div class="noVNC_vcenter">
|
||||
<div id="noVNC_modifiers" class="noVNC_panel">
|
||||
<input type="image" alt="Ctrl" src="app/images/ctrl.svg"
|
||||
id="noVNC_toggle_ctrl_button" class="noVNC_button"
|
||||
title="Toggle Ctrl">
|
||||
<input type="image" alt="Alt" src="app/images/alt.svg"
|
||||
id="noVNC_toggle_alt_button" class="noVNC_button"
|
||||
title="Toggle Alt">
|
||||
<input type="image" alt="Windows" src="app/images/windows.svg"
|
||||
id="noVNC_toggle_windows_button" class="noVNC_button"
|
||||
title="Toggle Windows">
|
||||
<input type="image" alt="Tab" src="app/images/tab.svg"
|
||||
id="noVNC_send_tab_button" class="noVNC_button"
|
||||
title="Send Tab">
|
||||
<input type="image" alt="Esc" src="app/images/esc.svg"
|
||||
id="noVNC_send_esc_button" class="noVNC_button"
|
||||
title="Send Escape">
|
||||
<input type="image" alt="Ctrl+Alt+Del" src="app/images/ctrlaltdel.svg"
|
||||
id="noVNC_send_ctrl_alt_del_button" class="noVNC_button"
|
||||
title="Send Ctrl-Alt-Del">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Shutdown/Reboot -->
|
||||
@@ -213,6 +193,15 @@
|
||||
<li>
|
||||
<div class="noVNC_expander">Advanced</div>
|
||||
<div><ul>
|
||||
<li>
|
||||
<label for="noVNC_setting_quality">Quality:</label>
|
||||
<input id="noVNC_setting_quality" type="range" min="0" max="9" value="6">
|
||||
</li>
|
||||
<li>
|
||||
<label for="noVNC_setting_compression">Compression level:</label>
|
||||
<input id="noVNC_setting_compression" type="range" min="0" max="9" value="2">
|
||||
</li>
|
||||
<li><hr></li>
|
||||
<li>
|
||||
<label for="noVNC_setting_repeaterID">Repeater ID:</label>
|
||||
<input id="noVNC_setting_repeaterID" type="text" value="">
|
||||
@@ -259,6 +248,11 @@
|
||||
</li>
|
||||
</ul></div>
|
||||
</li>
|
||||
<li class="noVNC_version_separator"><hr></li>
|
||||
<li class="noVNC_version_wrapper">
|
||||
<span>Version:</span>
|
||||
<span class="noVNC_version"></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -290,14 +284,18 @@
|
||||
|
||||
<!-- Password Dialog -->
|
||||
<div class="noVNC_center noVNC_connect_layer">
|
||||
<div id="noVNC_password_dlg" class="noVNC_panel"><form>
|
||||
<div id="noVNC_credentials_dlg" class="noVNC_panel"><form>
|
||||
<ul>
|
||||
<li>
|
||||
<li id="noVNC_username_block">
|
||||
<label>Username:</label>
|
||||
<input id="noVNC_username_input">
|
||||
</li>
|
||||
<li id="noVNC_password_block">
|
||||
<label>Password:</label>
|
||||
<input id="noVNC_password_input" type="password">
|
||||
</li>
|
||||
<li>
|
||||
<input id="noVNC_password_button" type="submit" value="Send Password" class="noVNC_submit">
|
||||
<input id="noVNC_credentials_button" type="submit" value="Send Credentials" class="noVNC_submit">
|
||||
</li>
|
||||
</ul>
|
||||
</form></div>
|
||||
|
||||
+7
-15
@@ -7,7 +7,7 @@
|
||||
|
||||
This is a self-contained file which doesn't import WebUtil or external CSS.
|
||||
|
||||
Copyright (C) 2018 The noVNC Authors
|
||||
Copyright (C) 2019 The noVNC Authors
|
||||
noVNC is licensed under the MPL 2.0 (see LICENSE.txt)
|
||||
This file is licensed under the 2-Clause BSD license (see LICENSE.txt).
|
||||
|
||||
@@ -18,6 +18,10 @@
|
||||
|
||||
<meta charset="utf-8">
|
||||
|
||||
<!-- Always force latest IE rendering engine (even in intranet) &
|
||||
Chrome Frame. Remove this if you use the .htaccess -->
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
|
||||
<style>
|
||||
|
||||
body {
|
||||
@@ -61,18 +65,7 @@
|
||||
<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;
|
||||
const loader = document.createElement("script");
|
||||
loader.src = "vendor/browser-es-module-loader/dist/" +
|
||||
"browser-es-module-loader.js";
|
||||
document.head.appendChild(loader);
|
||||
});
|
||||
</script>
|
||||
<script nomodule src="vendor/browser-es-module-loader/dist/browser-es-module-loader.js"></script>
|
||||
|
||||
<!-- actual script modules -->
|
||||
<script type="module" crossorigin="anonymous">
|
||||
@@ -134,7 +127,6 @@
|
||||
// because Firefox < 53 has a bug w.r.t location.search
|
||||
const re = new RegExp('.*[?&]' + name + '=([^&#]*)'),
|
||||
match = document.location.href.match(re);
|
||||
if (typeof defaultValue === 'undefined') { defaultValue = null; }
|
||||
|
||||
if (match) {
|
||||
// We have to decode the URL since want the cleartext value
|
||||
@@ -151,7 +143,7 @@
|
||||
// By default, use the host and port of server that served this file
|
||||
const host = readQueryVariable('host', window.location.hostname);
|
||||
let port = readQueryVariable('port', window.location.port);
|
||||
const password = readQueryVariable('password', '');
|
||||
const password = readQueryVariable('password');
|
||||
const path = readQueryVariable('path', 'websockify');
|
||||
|
||||
// | | | | | |
|
||||
|
||||
Reference in New Issue
Block a user