Compare commits
311 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d3aaf4d5b3 | |||
| 796e924e47 | |||
| 829725b30e | |||
| 9ac632deee | |||
| 7d2dad0f9e | |||
| 5ebc297164 | |||
| a792b7f39e | |||
| f1174023c1 | |||
| 85a465288b | |||
| a0b7c0dac5 | |||
| bf12c24f4c | |||
| 370f21b117 | |||
| 72f6810797 | |||
| e81602d705 | |||
| b40a45a11b | |||
| 01bb36d431 | |||
| 295004cabe | |||
| a30f609de4 | |||
| ca6527c1bf | |||
| ccef89f556 | |||
| f8b65f9fe1 | |||
| 7356d4e60b | |||
| 3fc0cb0cb7 | |||
| b146de6d69 | |||
| b298bf9e90 | |||
| 2a7db6f647 | |||
| 45cedea78f | |||
| 12d2e7832d | |||
| 0ccc679d32 | |||
| d0203a5995 | |||
| aaa4eb8c3c | |||
| e01dd27be4 | |||
| 55ffe8fc51 | |||
| 0180bc81c1 | |||
| fb3c8f64e9 | |||
| ae9b042df1 | |||
| 87143b361e | |||
| e8ad466e45 | |||
| eb0ad829d2 | |||
| d33f5ce77f | |||
| 8ae789daf0 | |||
| 9e02f4d01d | |||
| 9c7576a587 | |||
| e07ca6a8e2 | |||
| 336ec86997 | |||
| 0c80c68e92 | |||
| 13fa6b5908 | |||
| 549ccc7121 | |||
| 42bc251eb4 | |||
| afbb1da4d5 | |||
| 458405e05d | |||
| 0ee0e96f34 | |||
| 71bb6f02cd | |||
| 79f099108f | |||
| 0679c8a801 | |||
| 29a50620ff | |||
| c7c293279b | |||
| cd231e53ed | |||
| 3ef57d1600 | |||
| da75689f4c | |||
| 9b115a4485 | |||
| 775ccaa74c | |||
| 0dd9678e64 | |||
| 91307951d3 | |||
| c2d6a06d6d | |||
| a565ae559f | |||
| 0374b4c0fc | |||
| cd1a63b737 | |||
| 05b6d2ad67 | |||
| 2a21bee245 | |||
| cbbd9ab069 | |||
| 2a675b3394 | |||
| b16f19f9ce | |||
| 681632bc9f | |||
| a4453c9a26 | |||
| 747603c0d5 | |||
| c1d2449fb8 | |||
| 46292477c8 | |||
| 9a06058f66 | |||
| 8decca7353 | |||
| 4558104196 | |||
| 6751cc1236 | |||
| 9985950bfa | |||
| a0e6e7b1d8 | |||
| 90455eef06 | |||
| 51677f5c70 | |||
| 823e7cfca3 | |||
| f974b73137 | |||
| 5b7d2a622e | |||
| 3553a451d8 | |||
| b76358e9bf | |||
| 5f689f9bc8 | |||
| 022fc8c374 | |||
| 367bfd2962 | |||
| 934e3de356 | |||
| 74fe694cc4 | |||
| ce534b85c1 | |||
| caf0ecc99b | |||
| ceadcd6e83 | |||
| e16b3b8620 | |||
| d4197932d6 | |||
| 7e7e3ac07d | |||
| 1ff2ecd9f0 | |||
| 5de478d6e7 | |||
| 12a7c6f0de | |||
| a187821e4f | |||
| 8fb30fb9dc | |||
| ee5e3c5fa3 | |||
| 4a34ee4b1e | |||
| 88a36370a9 | |||
| 28c9670427 | |||
| 262a90b0e0 | |||
| 7f4a9eebc8 | |||
| f172633715 | |||
| e6fce71d6a | |||
| 820b39c7d3 | |||
| bd2d3a58b0 | |||
| 2b449b208e | |||
| 30f230b74c | |||
| 6e1d842850 | |||
| 139f087187 | |||
| 416e21151b | |||
| 9e9d5ef17d | |||
| c8d37ae8bd | |||
| 079889a13a | |||
| 9649b8ee25 | |||
| 034fd376ac | |||
| 99a9c03f3c | |||
| ec45911456 | |||
| b776e1495e | |||
| 4fb2d6c497 | |||
| d8b3ec99fa | |||
| 05baf14256 | |||
| e7ef963a8f | |||
| f1550c69d9 | |||
| 6d7d45ba08 | |||
| 90f120c139 | |||
| f983c78d17 | |||
| 156b9a99e2 | |||
| 1ff035c330 | |||
| 333e075d7b | |||
| 6e1eec3025 | |||
| 98364c3daa | |||
| 837cc75a8c | |||
| 6a650ade2d | |||
| d083ba269e | |||
| 69c1d8a1b9 | |||
| b676122642 | |||
| 9107ae3a10 | |||
| 8c1b6e19c7 | |||
| 3cf2bb9b59 | |||
| 80897091e0 | |||
| 36510f7d16 | |||
| cc703babcb | |||
| da4f3f30ea | |||
| 654066f2c4 | |||
| 4050f0e248 | |||
| 629a6cacb9 | |||
| 63528570bc | |||
| 80ea7e17ec | |||
| 564a89bcb9 | |||
| a714e1b003 | |||
| fa8ff5e09d | |||
| 7519f2d4ad | |||
| faf921b023 | |||
| 2ff09d6f10 | |||
| 1e500883f6 | |||
| 86adcdd3a3 | |||
| 9c13ea3dd2 | |||
| ac6adc61d5 | |||
| 8c961ab7c6 | |||
| f820ec86f0 | |||
| c43e499357 | |||
| 52178e9381 | |||
| 2d6302e359 | |||
| fc5bb6dab6 | |||
| 4a0999a34e | |||
| f19e328dce | |||
| 2825529a13 | |||
| 0cb5f2341c | |||
| 429a08da89 | |||
| 64d3d60120 | |||
| e674ee4d8e | |||
| d9b2606d8c | |||
| 64ffdc18e0 | |||
| 4cfe0fffcd | |||
| 2dd5600f3d | |||
| 081f9d2a13 | |||
| 7e29e02ce4 | |||
| 584ce06698 | |||
| 0ef75824a4 | |||
| 138df46825 | |||
| 5c684cce2a | |||
| d3913c0dde | |||
| 82253c1f1a | |||
| f0fea1fccd | |||
| 6b2357061e | |||
| dd713bee63 | |||
| 2d559fb2e1 | |||
| 6eb17b27a0 | |||
| 6b555f1f74 | |||
| 0410cbc190 | |||
| 337fb06535 | |||
| fee115b13f | |||
| f59be0586f | |||
| 3141c0e01b | |||
| 8e660ba3e8 | |||
| cd94c2aed2 | |||
| 1971823a4f | |||
| 8715ed9e70 | |||
| f0c3af3c67 | |||
| 88ccfdc193 | |||
| a0c4214823 | |||
| 9761278df8 | |||
| 9a6e0d47d0 | |||
| 145d235094 | |||
| 4ecb44111d | |||
| bdc0bbbb4f | |||
| a8488d5b32 | |||
| f887abdb38 | |||
| a1e11e6d00 | |||
| 615b36a067 | |||
| 6f55527514 | |||
| e1f8232bc9 | |||
| 58dfb7df45 | |||
| c101a31520 | |||
| 1a101443a7 | |||
| af10b0c5e4 | |||
| 32f9033863 | |||
| efb2400833 | |||
| d5b8425d42 | |||
| 69e0f0f5db | |||
| 5d8ede61f9 | |||
| edc7520e27 | |||
| 2f1e11b54a | |||
| 832937292e | |||
| 1d148a8478 | |||
| df8d005de9 | |||
| 795494ade1 | |||
| e1174e813b | |||
| 6719b932cf | |||
| 5671072dfe | |||
| 084030fe68 | |||
| 05d68e118d | |||
| 8a7089c0c6 | |||
| faedcd0210 | |||
| cdfb336651 | |||
| 658e415796 | |||
| 97f6657146 | |||
| 1075cd8e19 | |||
| 19aa9ad6a3 | |||
| 42ec5f3321 | |||
| e21ed2e689 | |||
| 98664c7887 | |||
| 7730814b8d | |||
| da623156d3 | |||
| 7be06b4d7d | |||
| 15a0608e04 | |||
| ced6431ac5 | |||
| a73b5acfbb | |||
| cbe54acd1f | |||
| cf7f7b57c5 | |||
| 240efb94da | |||
| eac11d5799 | |||
| a1709b999e | |||
| 80a7c1dbf1 | |||
| 679b45fa3b | |||
| 0ff0844a14 | |||
| 6cd69705d6 | |||
| acc30093ad | |||
| c0d4dc8eb3 | |||
| a7b96087d7 | |||
| a78a7bf8aa | |||
| 78eda3c040 | |||
| 44c10255ad | |||
| e965832e0a | |||
| 6710410356 | |||
| 721eaa4f50 | |||
| 65d6357cdf | |||
| 7f84160147 | |||
| bfb6ac259d | |||
| 1691617f39 | |||
| c278b24eb4 | |||
| c88083b86a | |||
| 466f1f9af6 | |||
| d4c887e23f | |||
| c143a852b1 | |||
| 2f602da961 | |||
| 7ad4e60df6 | |||
| 301714928b | |||
| 096449da35 | |||
| 99cf540e1a | |||
| c2980d15e9 | |||
| bbbcab692a | |||
| 8c09b99b4e | |||
| a012f98b61 | |||
| 98243fc68f | |||
| 303e5ef87b | |||
| 2c48df4560 | |||
| 98cdc076a0 | |||
| a5499bbffe | |||
| 463c39e4af | |||
| 6f0eb2b01a | |||
| 22fe8e383f | |||
| 264a6d82ea | |||
| a85c85fb5f | |||
| d971c0fe55 | |||
| ff077f4656 | |||
| bfefd81d4c | |||
| 0fd0d57fcd | |||
| a98d72e2e9 |
@@ -1,10 +1,11 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es6": true
|
||||
"es2020": true
|
||||
},
|
||||
"parserOptions": {
|
||||
"sourceType": "module"
|
||||
"sourceType": "module",
|
||||
"ecmaVersion": 2020
|
||||
},
|
||||
"extends": "eslint:recommended",
|
||||
"rules": {
|
||||
@@ -25,10 +26,13 @@
|
||||
|
||||
"brace-style": ["error", "1tbs", { "allowSingleLine": true }],
|
||||
"indent": ["error", 4, { "SwitchCase": 1,
|
||||
"VariableDeclarator": "first",
|
||||
"FunctionDeclaration": { "parameters": "first" },
|
||||
"FunctionExpression": { "parameters": "first" },
|
||||
"CallExpression": { "arguments": "first" },
|
||||
"ArrayExpression": "first",
|
||||
"ObjectExpression": "first",
|
||||
"ImportDeclaration": "first",
|
||||
"ignoreComments": true }],
|
||||
"comma-spacing": ["error"],
|
||||
"comma-style": ["error"],
|
||||
|
||||
@@ -10,47 +10,88 @@ jobs:
|
||||
npm:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v1
|
||||
- uses: actions/checkout@v3
|
||||
- run: |
|
||||
GITREV=$(git rev-parse --short HEAD)
|
||||
echo $GITREV
|
||||
sed -i "s/^\(.*\"version\".*\)\"\([^\"]\+\)\"\(.*\)\$/\1\"\2-g$GITREV\"\3/" package.json
|
||||
if: github.event_name != 'release'
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
# Needs to be explicitly specified for auth to work
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
- run: npm install
|
||||
- uses: actions/upload-artifact@v2
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: npm
|
||||
path: lib
|
||||
- run: npm publish --access public
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
|
||||
if: ${{ github.event_name == 'release' && !github.event.release.prerelease }}
|
||||
if: |
|
||||
github.repository == 'novnc/noVNC' &&
|
||||
github.event_name == 'release' &&
|
||||
!github.event.release.prerelease
|
||||
- run: npm publish --access public --tag beta
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
|
||||
if: ${{ github.event_name == 'release' && github.event.release.prerelease }}
|
||||
if: |
|
||||
github.repository == 'novnc/noVNC' &&
|
||||
github.event_name == 'release' &&
|
||||
github.event.release.prerelease
|
||||
- run: npm publish --access public --tag dev
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
|
||||
if: |
|
||||
github.repository == 'novnc/noVNC' &&
|
||||
github.event_name == 'push' &&
|
||||
github.event.ref == 'refs/heads/master'
|
||||
snap:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- run: |
|
||||
GITREV=$(git rev-parse --short HEAD)
|
||||
echo $GITREV
|
||||
sed -i "s/^\(.*\"version\".*\)\"\([^\"]\+\)\"\(.*\)\$/\1\"\2-g$GITREV\"\3/" package.json
|
||||
if: github.event_name != 'release'
|
||||
- run: |
|
||||
VERSION=$(grep '"version"' package.json | cut -d '"' -f 4)
|
||||
echo $VERSION
|
||||
sed -i "s/@VERSION@/$VERSION/g" snap/snapcraft.yaml
|
||||
sed -i "s/^version:.*/version: '$VERSION'/" snap/snapcraft.yaml
|
||||
- uses: snapcore/action-build@v1
|
||||
id: snapcraft
|
||||
- uses: actions/upload-artifact@v2
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: snap
|
||||
path: ${{ steps.snapcraft.outputs.snap }}
|
||||
- uses: snapcore/action-publish@v1
|
||||
with:
|
||||
store_login: ${{ secrets.SNAPCRAFT_LOGIN }}
|
||||
snap: ${{ steps.build.outputs.snap }}
|
||||
snap: ${{ steps.snapcraft.outputs.snap }}
|
||||
release: stable
|
||||
if: ${{ github.event_name == 'release' && !github.event.release.prerelease }}
|
||||
env:
|
||||
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_LOGIN }}
|
||||
if: |
|
||||
github.repository == 'novnc/noVNC' &&
|
||||
github.event_name == 'release' &&
|
||||
!github.event.release.prerelease
|
||||
- uses: snapcore/action-publish@v1
|
||||
with:
|
||||
store_login: ${{ secrets.SNAPCRAFT_LOGIN }}
|
||||
snap: ${{ steps.build.outputs.snap }}
|
||||
snap: ${{ steps.snapcraft.outputs.snap }}
|
||||
release: beta
|
||||
if: ${{ github.event_name == 'release' && github.event.release.prerelease }}
|
||||
env:
|
||||
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_LOGIN }}
|
||||
if: |
|
||||
github.repository == 'novnc/noVNC' &&
|
||||
github.event_name == 'release' &&
|
||||
github.event.release.prerelease
|
||||
- uses: snapcore/action-publish@v1
|
||||
with:
|
||||
snap: ${{ steps.snapcraft.outputs.snap }}
|
||||
release: edge
|
||||
env:
|
||||
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_LOGIN }}
|
||||
if: |
|
||||
github.repository == 'novnc/noVNC' &&
|
||||
github.event_name == 'push' &&
|
||||
github.event.ref == 'refs/heads/master'
|
||||
|
||||
@@ -6,14 +6,14 @@ jobs:
|
||||
eslint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v1
|
||||
- run: npm install
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
- run: npm update
|
||||
- run: npm run lint
|
||||
html:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v1
|
||||
- run: npm install
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
- run: npm update
|
||||
- run: git ls-tree --name-only -r HEAD | grep -E "[.](html|css)$" | xargs ./utils/validate
|
||||
|
||||
@@ -20,9 +20,9 @@ jobs:
|
||||
fail-fast: false
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v1
|
||||
- run: npm install
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
- run: npm update
|
||||
- run: npm run test
|
||||
env:
|
||||
TEST_BROWSER_NAME: ${{ matrix.browser }}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
name: Translate
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
translate:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
- run: npm update
|
||||
- run: sudo apt-get install gettext
|
||||
- run: make -C po update-pot
|
||||
- run: make -C po update-po
|
||||
- run: make -C po update-js
|
||||
@@ -1,9 +1,9 @@
|
||||
maintainers:
|
||||
- Joel Martin (@kanaka)
|
||||
- Solly Ross (@directxman12)
|
||||
- Samuel Mannehed for Cendio AB (@samhed)
|
||||
- Pierre Ossman for Cendio AB (@CendioOssman)
|
||||
maintainersEmeritus:
|
||||
- Joel Martin (@kanaka)
|
||||
- Solly Ross (@directxman12)
|
||||
- @astrand
|
||||
contributors:
|
||||
# There are a bunch of people that should be here.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
noVNC is Copyright (C) 2019 The noVNC Authors
|
||||
noVNC is Copyright (C) 2022 The noVNC Authors
|
||||
(./AUTHORS)
|
||||
|
||||
The noVNC core library files are licensed under the MPL 2.0 (Mozilla
|
||||
|
||||
@@ -65,10 +65,14 @@ Please tweet [@noVNC](http://www.twitter.com/noVNC) if you do.
|
||||
### Features
|
||||
|
||||
* Supports all modern browsers including mobile (iOS, Android)
|
||||
* Supported VNC encodings: raw, copyrect, rre, hextile, tight, tightPNG
|
||||
* Supported authentication methods: none, classical VNC, RealVNC's
|
||||
RSA-AES, Tight, VeNCrypt Plain, XVP, Apple's Diffie-Hellman,
|
||||
UltraVNC's MSLogonII
|
||||
* Supported VNC encodings: raw, copyrect, rre, hextile, tight, tightPNG,
|
||||
ZRLE, JPEG
|
||||
* Supports scaling, clipping and resizing the desktop
|
||||
* Local cursor rendering
|
||||
* Clipboard copy/paste
|
||||
* Clipboard copy/paste with full Unicode support
|
||||
* Translations
|
||||
* Touch gestures for emulating common mouse actions
|
||||
* Licensed mainly under the [MPL 2.0](http://www.mozilla.org/MPL/2.0/), see
|
||||
@@ -113,8 +117,13 @@ proxy.
|
||||
used to specify the location of a running VNC server:
|
||||
|
||||
`./utils/novnc_proxy --vnc localhost:5901`
|
||||
|
||||
* If you don't need to expose the web server to public internet, you can
|
||||
bind to localhost:
|
||||
|
||||
`./utils/novnc_proxy --vnc localhost:5901 --listen localhost:6081`
|
||||
|
||||
* Point your browser to the cut-and-paste URL that is output by the `novnc_procy`
|
||||
* Point your browser to the cut-and-paste URL that is output by the `novnc_proxy`
|
||||
script. Hit the Connect button, enter a password if the VNC server has one
|
||||
configured, and enjoy!
|
||||
|
||||
@@ -123,13 +132,17 @@ Running the command below will install the latest release of noVNC from Snap:
|
||||
|
||||
`sudo snap install novnc`
|
||||
|
||||
#### Running noVNC
|
||||
#### Running noVNC from Snap Directly
|
||||
|
||||
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)
|
||||
If you want to use certificate files, due to standard Snap confinement restrictions you need to have them in the /home/\<user\>/snap/novnc/current/ directory. If your username is jsmith an example command would be:
|
||||
|
||||
`novnc --listen 8443 --cert ~jsmith/snap/novnc/current/self.crt --key ~jsmith/snap/novnc/current/self.key --vnc ubuntu.example.com:5901`
|
||||
|
||||
#### Running noVNC from Snap 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).
|
||||
@@ -194,15 +207,18 @@ See [AUTHORS](AUTHORS) for a (full-ish) list of authors. If you're not on
|
||||
that list and you think you should be, feel free to send a PR to fix that.
|
||||
|
||||
* Core team:
|
||||
* [Joel Martin](https://github.com/kanaka)
|
||||
* [Samuel Mannehed](https://github.com/samhed) (Cendio)
|
||||
* [Solly Ross](https://github.com/DirectXMan12) (Red Hat / OpenStack)
|
||||
* [Pierre Ossman](https://github.com/CendioOssman) (Cendio)
|
||||
|
||||
* Previous core contributors:
|
||||
* [Joel Martin](https://github.com/kanaka) (Project founder)
|
||||
* [Solly Ross](https://github.com/DirectXMan12) (Red Hat / OpenStack)
|
||||
|
||||
* Notable contributions:
|
||||
* UI and Icons : Pierre Ossman, Chris Gordon
|
||||
* Original Logo : Michael Sersen
|
||||
* tight encoding : Michael Tinglof (Mercuri.ca)
|
||||
* RealVNC RSA AES authentication : USTC Vlab Team
|
||||
|
||||
* Included libraries:
|
||||
* base64 : Martijn Pieters (Digital Creations 2), Samuel Sieb (sieb.net)
|
||||
|
||||
@@ -6,61 +6,74 @@
|
||||
* 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.
|
||||
// Fallback for all uncought errors
|
||||
function handleError(event, err) {
|
||||
try {
|
||||
const msg = document.getElementById('noVNC_fallback_errormsg');
|
||||
|
||||
// No ES6 can be used in this file since it's used for the translation
|
||||
/* eslint-disable prefer-arrow-callback */
|
||||
|
||||
(function _scope() {
|
||||
"use strict";
|
||||
|
||||
// Fallback for all uncought errors
|
||||
function handleError(event, err) {
|
||||
try {
|
||||
const msg = document.getElementById('noVNC_fallback_errormsg');
|
||||
|
||||
// Only show the initial error
|
||||
if (msg.hasChildNodes()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let div = document.createElement("div");
|
||||
div.classList.add('noVNC_message');
|
||||
div.appendChild(document.createTextNode(event.message));
|
||||
msg.appendChild(div);
|
||||
|
||||
if (event.filename) {
|
||||
div = document.createElement("div");
|
||||
div.className = 'noVNC_location';
|
||||
let text = event.filename;
|
||||
if (event.lineno !== undefined) {
|
||||
text += ":" + event.lineno;
|
||||
if (event.colno !== undefined) {
|
||||
text += ":" + event.colno;
|
||||
}
|
||||
}
|
||||
div.appendChild(document.createTextNode(text));
|
||||
msg.appendChild(div);
|
||||
}
|
||||
|
||||
if (err && err.stack) {
|
||||
div = document.createElement("div");
|
||||
div.className = 'noVNC_stack';
|
||||
div.appendChild(document.createTextNode(err.stack));
|
||||
msg.appendChild(div);
|
||||
}
|
||||
|
||||
document.getElementById('noVNC_fallback_error')
|
||||
.classList.add("noVNC_open");
|
||||
} catch (exc) {
|
||||
document.write("noVNC encountered an error.");
|
||||
// Work around Firefox bug:
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=1685038
|
||||
if (event.message === "ResizeObserver loop completed with undelivered notifications.") {
|
||||
return false;
|
||||
}
|
||||
// Don't return true since this would prevent the error
|
||||
// from being printed to the browser console.
|
||||
return false;
|
||||
|
||||
// Only show the initial error
|
||||
if (msg.hasChildNodes()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let div = document.createElement("div");
|
||||
div.classList.add('noVNC_message');
|
||||
div.appendChild(document.createTextNode(event.message));
|
||||
msg.appendChild(div);
|
||||
|
||||
if (event.filename) {
|
||||
div = document.createElement("div");
|
||||
div.className = 'noVNC_location';
|
||||
let text = event.filename;
|
||||
if (event.lineno !== undefined) {
|
||||
text += ":" + event.lineno;
|
||||
if (event.colno !== undefined) {
|
||||
text += ":" + event.colno;
|
||||
}
|
||||
}
|
||||
div.appendChild(document.createTextNode(text));
|
||||
msg.appendChild(div);
|
||||
}
|
||||
|
||||
if (err && err.stack) {
|
||||
div = document.createElement("div");
|
||||
div.className = 'noVNC_stack';
|
||||
div.appendChild(document.createTextNode(err.stack));
|
||||
msg.appendChild(div);
|
||||
}
|
||||
|
||||
document.getElementById('noVNC_fallback_error')
|
||||
.classList.add("noVNC_open");
|
||||
|
||||
} catch (exc) {
|
||||
document.write("noVNC encountered an error.");
|
||||
}
|
||||
window.addEventListener('error', function onerror(evt) { handleError(evt, evt.error); });
|
||||
window.addEventListener('unhandledrejection', function onreject(evt) { handleError(evt.reason, evt.reason); });
|
||||
})();
|
||||
|
||||
// Try to disable keyboard interaction, best effort
|
||||
try {
|
||||
// Remove focus from the currently focused element in order to
|
||||
// prevent keyboard interaction from continuing
|
||||
if (document.activeElement) { document.activeElement.blur(); }
|
||||
|
||||
// Don't let any element be focusable when showing the error
|
||||
let keyboardFocusable = 'a[href], button, input, textarea, select, details, [tabindex]';
|
||||
document.querySelectorAll(keyboardFocusable).forEach((elem) => {
|
||||
elem.setAttribute("tabindex", "-1");
|
||||
});
|
||||
} catch (exc) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
// Don't return true since this would prevent the error
|
||||
// from being printed to the browser console.
|
||||
return false;
|
||||
}
|
||||
|
||||
window.addEventListener('error', evt => handleError(evt, evt.error));
|
||||
window.addEventListener('unhandledrejection', evt => handleError(evt.reason, evt.reason));
|
||||
|
||||
@@ -1,42 +1,42 @@
|
||||
ICONS := \
|
||||
novnc-16x16.png \
|
||||
novnc-24x24.png \
|
||||
novnc-32x32.png \
|
||||
novnc-48x48.png \
|
||||
novnc-64x64.png
|
||||
BROWSER_SIZES := 16 24 32 48 64
|
||||
#ANDROID_SIZES := 72 96 144 192
|
||||
# FIXME: The ICO is limited to 8 icons due to a Chrome bug:
|
||||
# https://bugs.chromium.org/p/chromium/issues/detail?id=1381393
|
||||
ANDROID_SIZES := 96 144 192
|
||||
WEB_ICON_SIZES := $(BROWSER_SIZES) $(ANDROID_SIZES)
|
||||
|
||||
ANDROID_LAUNCHER := \
|
||||
novnc-48x48.png \
|
||||
novnc-72x72.png \
|
||||
novnc-96x96.png \
|
||||
novnc-144x144.png \
|
||||
novnc-192x192.png
|
||||
#IOS_1X_SIZES := 20 29 40 76 # No such devices exist anymore
|
||||
IOS_2X_SIZES := 40 58 80 120 152 167
|
||||
IOS_3X_SIZES := 60 87 120 180
|
||||
ALL_IOS_SIZES := $(IOS_1X_SIZES) $(IOS_2X_SIZES) $(IOS_3X_SIZES)
|
||||
|
||||
IPHONE_LAUNCHER := \
|
||||
novnc-60x60.png \
|
||||
novnc-120x120.png
|
||||
|
||||
IPAD_LAUNCHER := \
|
||||
novnc-76x76.png \
|
||||
novnc-152x152.png
|
||||
|
||||
ALL_ICONS := $(ICONS) $(ANDROID_LAUNCHER) $(IPHONE_LAUNCHER) $(IPAD_LAUNCHER)
|
||||
ALL_ICONS := \
|
||||
$(ALL_IOS_SIZES:%=novnc-ios-%.png) \
|
||||
novnc.ico
|
||||
|
||||
all: $(ALL_ICONS)
|
||||
|
||||
novnc-16x16.png: novnc-icon-sm.svg
|
||||
convert -density 90 \
|
||||
-background transparent "$<" "$@"
|
||||
novnc-24x24.png: novnc-icon-sm.svg
|
||||
convert -density 135 \
|
||||
-background transparent "$<" "$@"
|
||||
novnc-32x32.png: novnc-icon-sm.svg
|
||||
convert -density 180 \
|
||||
-background transparent "$<" "$@"
|
||||
# Our testing shows that the ICO file need to be sorted in largest to
|
||||
# smallest to get the apporpriate behviour
|
||||
WEB_ICON_SIZES_REVERSE := $(shell echo $(WEB_ICON_SIZES) | tr ' ' '\n' | sort -nr | tr '\n' ' ')
|
||||
WEB_BASE_ICONS := $(WEB_ICON_SIZES_REVERSE:%=novnc-%.png)
|
||||
.INTERMEDIATE: $(WEB_BASE_ICONS)
|
||||
|
||||
novnc.ico: $(WEB_BASE_ICONS)
|
||||
convert $(WEB_BASE_ICONS) "$@"
|
||||
|
||||
# General conversion
|
||||
novnc-%.png: novnc-icon.svg
|
||||
convert -density $$[`echo $* | cut -d x -f 1` * 90 / 48] \
|
||||
-background transparent "$<" "$@"
|
||||
convert -depth 8 -background transparent \
|
||||
-size $*x$* "$(lastword $^)" "$@"
|
||||
|
||||
# iOS icons use their own SVG
|
||||
novnc-ios-%.png: novnc-ios-icon.svg
|
||||
convert -depth 8 -background transparent \
|
||||
-size $*x$* "$(lastword $^)" "$@"
|
||||
|
||||
# The smallest sizes are generated using a different SVG
|
||||
novnc-16.png novnc-24.png novnc-32.png: novnc-icon-sm.svg
|
||||
|
||||
clean:
|
||||
rm -f *.png
|
||||
|
||||
|
Before Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 675 B |
|
Before Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 1000 B |
|
Before Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
@@ -0,0 +1,183 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="48"
|
||||
height="48"
|
||||
viewBox="0 0 48 48.000001"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
||||
sodipodi:docname="novnc-ios-icon.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<defs
|
||||
id="defs4" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="11.313708"
|
||||
inkscape:cx="27.356195"
|
||||
inkscape:cy="17.810253"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
units="px"
|
||||
inkscape:object-nodes="true"
|
||||
inkscape:snap-smooth-nodes="true"
|
||||
inkscape:snap-midpoints="true"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1371"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid4169" />
|
||||
</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" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-1004.3621)">
|
||||
<rect
|
||||
style="opacity:1;fill:#494949;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="rect4167"
|
||||
width="48"
|
||||
height="48"
|
||||
x="0"
|
||||
y="1004.3621"
|
||||
inkscape:label="background" />
|
||||
<path
|
||||
style="opacity:1;fill:#313131;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
d="m 0,1004.3621 v 48 h 20 c 15.512,0 28,-16.948 28,-38 v -10 z"
|
||||
id="rect4173"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccccc"
|
||||
inkscape:label="darker_grey_plate" />
|
||||
<g
|
||||
id="g4300"
|
||||
style="display:inline;fill:#000000;fill-opacity:1;stroke:none"
|
||||
transform="translate(0.5,0.5)"
|
||||
inkscape:label="shadows">
|
||||
<g
|
||||
id="g4302"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none"
|
||||
inkscape:label="no">
|
||||
<path
|
||||
sodipodi:nodetypes="scsccsssscccs"
|
||||
d="m 11.986926,1016.3621 c 0.554325,0 1.025987,0.2121 1.414987,0.6362 0.398725,0.4138 0.600909,0.9155 0.598087,1.5052 v 6.8586 h -2 v -6.8914 c 0,-0.072 -0.03404,-0.1086 -0.102113,-0.1086 H 7.1021125 C 7.0340375,1018.3621 7,1018.3983 7,1018.4707 v 6.8914 H 5 v -9 z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
id="path4304"
|
||||
inkscape:connector-curvature="0"
|
||||
inkscape:label="n" />
|
||||
<path
|
||||
sodipodi:nodetypes="sscsscsscsscssssssssss"
|
||||
d="m 17.013073,1016.3621 h 4.973854 c 0.554325,0 1.025987,0.2121 1.414986,0.6362 0.398725,0.4138 0.598087,0.9155 0.598087,1.5052 v 4.7172 c 0,0.5897 -0.199362,1.0966 -0.598087,1.5207 -0.388999,0.4138 -0.860661,0.6207 -1.414986,0.6207 h -4.973854 c -0.554325,0 -1.030849,-0.2069 -1.429574,-0.6207 C 15.1945,1024.3173 15,1023.8104 15,1023.2207 v -4.7172 c 0,-0.5897 0.1945,-1.0914 0.583499,-1.5052 0.398725,-0.4241 0.875249,-0.6362 1.429574,-0.6362 z m 4.884815,2 h -4.795776 c -0.06808,0 -0.102112,0.036 -0.102112,0.1086 v 4.7828 c 0,0.072 0.03404,0.1086 0.102112,0.1086 h 4.795776 c 0.06807,0 0.102112,-0.036 0.102112,-0.1086 v -4.7828 c 0,-0.072 -0.03404,-0.1086 -0.102112,-0.1086 z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
id="path4306"
|
||||
inkscape:connector-curvature="0"
|
||||
inkscape:label="o" />
|
||||
</g>
|
||||
<g
|
||||
id="g4308"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none"
|
||||
inkscape:label="VNC">
|
||||
<path
|
||||
sodipodi:nodetypes="cccccccc"
|
||||
d="m 12,1036.9177 4.768114,-8.5556 H 19 l -6,11 h -2 l -6,-11 h 2.2318854 z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
id="path4310"
|
||||
inkscape:connector-curvature="0"
|
||||
inkscape:label="V" />
|
||||
<path
|
||||
sodipodi:nodetypes="ccccccccccc"
|
||||
d="m 29,1036.3621 v -8 h 2 v 11 h -2 l -7,-8 v 8 h -2 v -11 h 2 z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
id="path4312"
|
||||
inkscape:connector-curvature="0"
|
||||
inkscape:label="N" />
|
||||
<path
|
||||
sodipodi:nodetypes="cssssccscsscscc"
|
||||
d="m 43,1030.3621 h -8.897887 c -0.06808,0 -0.102113,0.036 -0.102113,0.1069 v 6.7862 c 0,0.071 0.03404,0.1069 0.102113,0.1069 H 43 v 2 h -8.972339 c -0.56405,0 -1.045437,-0.2037 -1.444162,-0.6111 C 32.1945,1038.3334 32,1037.8292 32,1037.2385 v -6.7528 c 0,-0.5907 0.1945,-1.0898 0.583499,-1.4972 0.398725,-0.4176 0.880112,-0.6264 1.444162,-0.6264 H 43 Z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
id="path4314"
|
||||
inkscape:connector-curvature="0"
|
||||
inkscape:label="C" />
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
id="g4291"
|
||||
style="stroke:none"
|
||||
inkscape:label="noVNC">
|
||||
<g
|
||||
id="g4282"
|
||||
style="stroke:none"
|
||||
inkscape:label="no">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path4143"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#008000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 11.986926,1016.3621 c 0.554325,0 1.025987,0.2121 1.414987,0.6362 0.398725,0.4138 0.600909,0.9155 0.598087,1.5052 l 0,6.8586 -2,0 0,-6.8914 c 0,-0.072 -0.03404,-0.1086 -0.102113,-0.1086 l -4.7957745,0 C 7.0340375,1018.3621 7,1018.3983 7,1018.4707 l 0,6.8914 -2,0 0,-9 z"
|
||||
sodipodi:nodetypes="scsccsssscccs"
|
||||
inkscape:label="n" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path4145"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#008000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 17.013073,1016.3621 4.973854,0 c 0.554325,0 1.025987,0.2121 1.414986,0.6362 0.398725,0.4138 0.598087,0.9155 0.598087,1.5052 l 0,4.7172 c 0,0.5897 -0.199362,1.0966 -0.598087,1.5207 -0.388999,0.4138 -0.860661,0.6207 -1.414986,0.6207 l -4.973854,0 c -0.554325,0 -1.030849,-0.2069 -1.429574,-0.6207 C 15.1945,1024.3173 15,1023.8104 15,1023.2207 l 0,-4.7172 c 0,-0.5897 0.1945,-1.0914 0.583499,-1.5052 0.398725,-0.4241 0.875249,-0.6362 1.429574,-0.6362 z m 4.884815,2 -4.795776,0 c -0.06808,0 -0.102112,0.036 -0.102112,0.1086 l 0,4.7828 c 0,0.072 0.03404,0.1086 0.102112,0.1086 l 4.795776,0 c 0.06807,0 0.102112,-0.036 0.102112,-0.1086 l 0,-4.7828 c 0,-0.072 -0.03404,-0.1086 -0.102112,-0.1086 z"
|
||||
sodipodi:nodetypes="sscsscsscsscssssssssss"
|
||||
inkscape:label="o" />
|
||||
</g>
|
||||
<g
|
||||
id="g4286"
|
||||
style="stroke:none"
|
||||
inkscape:label="VNC">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path4147"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffff00;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 12,1036.9177 4.768114,-8.5556 2.231886,0 -6,11 -2,0 -6,-11 2.2318854,0 z"
|
||||
sodipodi:nodetypes="cccccccc"
|
||||
inkscape:label="V" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path4149"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffff00;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 29,1036.3621 0,-8 2,0 0,11 -2,0 -7,-8 0,8 -2,0 0,-11 2,0 z"
|
||||
sodipodi:nodetypes="ccccccccccc"
|
||||
inkscape:label="N" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path4151"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffff00;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 43,1030.3621 -8.897887,0 c -0.06808,0 -0.102113,0.036 -0.102113,0.1069 l 0,6.7862 c 0,0.071 0.03404,0.1069 0.102113,0.1069 l 8.897887,0 0,2 -8.972339,0 c -0.56405,0 -1.045437,-0.2037 -1.444162,-0.6111 C 32.1945,1038.3334 32,1037.8292 32,1037.2385 l 0,-6.7528 c 0,-0.5907 0.1945,-1.0898 0.583499,-1.4972 0.398725,-0.4176 0.880112,-0.6264 1.444162,-0.6264 l 8.972339,0 z"
|
||||
sodipodi:nodetypes="cssssccscsscscc"
|
||||
inkscape:label="C" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 303 KiB |
@@ -4,9 +4,9 @@
|
||||
"Connected (unencrypted) to ": "Conectado (sin encriptación) a",
|
||||
"Disconnecting...": "Desconectando...",
|
||||
"Disconnected": "Desconectado",
|
||||
"Must set host": "Debes configurar el host",
|
||||
"Must set host": "Se debe configurar el host",
|
||||
"Reconnecting...": "Reconectando...",
|
||||
"Password is required": "Contraseña es obligatoria",
|
||||
"Password is required": "La contraseña es obligatoria",
|
||||
"Disconnect timeout": "Tiempo de desconexión agotado",
|
||||
"noVNC encountered an error:": "noVNC ha encontrado un error:",
|
||||
"Hide/Show the control bar": "Ocultar/Mostrar la barra de control",
|
||||
@@ -41,6 +41,7 @@
|
||||
"Clear": "Vaciar",
|
||||
"Fullscreen": "Pantalla Completa",
|
||||
"Settings": "Configuraciones",
|
||||
"Encrypt": "Encriptar",
|
||||
"Shared Mode": "Modo Compartido",
|
||||
"View Only": "Solo visualización",
|
||||
"Clip to Window": "Recortar al tamaño de la ventana",
|
||||
@@ -51,18 +52,17 @@
|
||||
"Remote Resizing": "Cambio de tamaño remoto",
|
||||
"Advanced": "Avanzado",
|
||||
"Local Cursor": "Cursor Local",
|
||||
"Repeater ID:": "ID del Repetidor",
|
||||
"Repeater ID:": "ID del Repetidor:",
|
||||
"WebSocket": "WebSocket",
|
||||
"Encrypt": "",
|
||||
"Host:": "Host",
|
||||
"Port:": "Puesto",
|
||||
"Path:": "Ruta",
|
||||
"Host:": "Host:",
|
||||
"Port:": "Puerto:",
|
||||
"Path:": "Ruta:",
|
||||
"Automatic Reconnect": "Reconexión automática",
|
||||
"Reconnect Delay (ms):": "Retraso en la reconexión (ms)",
|
||||
"Logging:": "Logging",
|
||||
"Reconnect Delay (ms):": "Retraso en la reconexión (ms):",
|
||||
"Logging:": "Registrando:",
|
||||
"Disconnect": "Desconectar",
|
||||
"Connect": "Conectar",
|
||||
"Password:": "Contraseña",
|
||||
"Password:": "Contraseña:",
|
||||
"Cancel": "Cancelar",
|
||||
"Canvas not supported.": "Canvas no está soportado"
|
||||
"Canvas not supported.": "Canvas no soportado."
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
{
|
||||
"HTTPS is required for full functionality": "",
|
||||
"Connecting...": "En cours de connexion...",
|
||||
"Disconnecting...": "Déconnexion en cours...",
|
||||
"Reconnecting...": "Reconnexion en cours...",
|
||||
"Internal error": "Erreur interne",
|
||||
"Must set host": "Doit définir l'hôte",
|
||||
"Connected (encrypted) to ": "Connecté (chiffré) à ",
|
||||
"Connected (unencrypted) to ": "Connecté (non chiffré) à ",
|
||||
"Something went wrong, connection is closed": "Quelque chose s'est mal passé, la connexion a été fermée",
|
||||
"Failed to connect to server": "Échec de connexion au serveur",
|
||||
"Disconnected": "Déconnecté",
|
||||
"New connection has been rejected with reason: ": "Une nouvelle connexion a été rejetée avec motif : ",
|
||||
"New connection has been rejected": "Une nouvelle connexion a été rejetée",
|
||||
"Credentials are required": "Les identifiants sont requis",
|
||||
"noVNC encountered an error:": "noVNC a rencontré une erreur :",
|
||||
"Hide/Show the control bar": "Masquer/Afficher la barre de contrôle",
|
||||
"Drag": "Faire glisser",
|
||||
"Move/Drag Viewport": "Déplacer/faire glisser le Viewport",
|
||||
"Keyboard": "Clavier",
|
||||
"Show Keyboard": "Afficher le clavier",
|
||||
"Extra keys": "Touches supplémentaires",
|
||||
"Show Extra Keys": "Afficher les touches supplémentaires",
|
||||
"Ctrl": "Ctrl",
|
||||
"Toggle Ctrl": "Basculer Ctrl",
|
||||
"Alt": "Alt",
|
||||
"Toggle Alt": "Basculer Alt",
|
||||
"Toggle Windows": "Basculer Windows",
|
||||
"Windows": "Windows",
|
||||
"Send Tab": "Envoyer l'onglet",
|
||||
"Tab": "l'onglet",
|
||||
"Esc": "Esc",
|
||||
"Send Escape": "Envoyer Escape",
|
||||
"Ctrl+Alt+Del": "Ctrl+Alt+Del",
|
||||
"Send Ctrl-Alt-Del": "Envoyer Ctrl-Alt-Del",
|
||||
"Shutdown/Reboot": "Arrêter/Redémarrer",
|
||||
"Shutdown/Reboot...": "Arrêter/Redémarrer...",
|
||||
"Power": "Alimentation",
|
||||
"Shutdown": "Arrêter",
|
||||
"Reboot": "Redémarrer",
|
||||
"Reset": "Réinitialiser",
|
||||
"Clipboard": "Presse-papiers",
|
||||
"Edit clipboard content in the textarea below.": "",
|
||||
"Settings": "Paramètres",
|
||||
"Shared Mode": "Mode partagé",
|
||||
"View Only": "Afficher uniquement",
|
||||
"Clip to Window": "Clip à fenêtre",
|
||||
"Scaling Mode:": "Mode mise à l'échelle :",
|
||||
"None": "Aucun",
|
||||
"Local Scaling": "Mise à l'échelle locale",
|
||||
"Remote Resizing": "Redimensionnement à distance",
|
||||
"Advanced": "Avancé",
|
||||
"Quality:": "Qualité :",
|
||||
"Compression level:": "Niveau de compression :",
|
||||
"Repeater ID:": "ID Répéteur :",
|
||||
"WebSocket": "WebSocket",
|
||||
"Encrypt": "Chiffrer",
|
||||
"Host:": "Hôte :",
|
||||
"Port:": "Port :",
|
||||
"Path:": "Chemin :",
|
||||
"Automatic Reconnect": "Reconnecter automatiquemen",
|
||||
"Reconnect Delay (ms):": "Délai de reconnexion (ms) :",
|
||||
"Show Dot when No Cursor": "Afficher le point lorsqu'il n'y a pas de curseur",
|
||||
"Logging:": "Se connecter :",
|
||||
"Version:": "Version :",
|
||||
"Disconnect": "Déconnecter",
|
||||
"Connect": "Connecter",
|
||||
"Server identity": "",
|
||||
"The server has provided the following identifying information:": "",
|
||||
"Fingerprint:": "",
|
||||
"Please verify that the information is correct and press \"Approve\". Otherwise press \"Reject\".": "",
|
||||
"Approve": "",
|
||||
"Reject": "",
|
||||
"Username:": "Nom d'utilisateur :",
|
||||
"Password:": "Mot de passe :",
|
||||
"Send Credentials": "Envoyer les identifiants",
|
||||
"Cancel": "Annuler"
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
{
|
||||
"Connecting...": "Connessione in corso...",
|
||||
"Disconnecting...": "Disconnessione...",
|
||||
"Reconnecting...": "Riconnessione...",
|
||||
"Internal error": "Errore interno",
|
||||
"Must set host": "Devi impostare l'host",
|
||||
"Connected (encrypted) to ": "Connesso (crittografato) a ",
|
||||
"Connected (unencrypted) to ": "Connesso (non crittografato) a",
|
||||
"Something went wrong, connection is closed": "Qualcosa è andato storto, la connessione è stata chiusa",
|
||||
"Failed to connect to server": "Impossibile connettersi al server",
|
||||
"Disconnected": "Disconnesso",
|
||||
"New connection has been rejected with reason: ": "La nuova connessione è stata rifiutata con motivo: ",
|
||||
"New connection has been rejected": "La nuova connessione è stata rifiutata",
|
||||
"Credentials are required": "Le credenziali sono obbligatorie",
|
||||
"noVNC encountered an error:": "noVNC ha riscontrato un errore:",
|
||||
"Hide/Show the control bar": "Nascondi/Mostra la barra di controllo",
|
||||
"Drag": "",
|
||||
"Move/Drag Viewport": "",
|
||||
"Keyboard": "Tastiera",
|
||||
"Show Keyboard": "Mostra tastiera",
|
||||
"Extra keys": "Tasti Aggiuntivi",
|
||||
"Show Extra Keys": "Mostra Tasti Aggiuntivi",
|
||||
"Ctrl": "Ctrl",
|
||||
"Toggle Ctrl": "Tieni premuto Ctrl",
|
||||
"Alt": "Alt",
|
||||
"Toggle Alt": "Tieni premuto Alt",
|
||||
"Toggle Windows": "Tieni premuto Windows",
|
||||
"Windows": "Windows",
|
||||
"Send Tab": "Invia Tab",
|
||||
"Tab": "Tab",
|
||||
"Esc": "Esc",
|
||||
"Send Escape": "Invia Esc",
|
||||
"Ctrl+Alt+Del": "Ctrl+Alt+Canc",
|
||||
"Send Ctrl-Alt-Del": "Invia Ctrl-Alt-Canc",
|
||||
"Shutdown/Reboot": "Spegnimento/Riavvio",
|
||||
"Shutdown/Reboot...": "Spegnimento/Riavvio...",
|
||||
"Power": "Alimentazione",
|
||||
"Shutdown": "Spegnimento",
|
||||
"Reboot": "Riavvio",
|
||||
"Reset": "Reset",
|
||||
"Clipboard": "Clipboard",
|
||||
"Clear": "Pulisci",
|
||||
"Fullscreen": "Schermo intero",
|
||||
"Settings": "Impostazioni",
|
||||
"Shared Mode": "Modalità condivisa",
|
||||
"View Only": "Sola Visualizzazione",
|
||||
"Clip to Window": "",
|
||||
"Scaling Mode:": "Modalità di ridimensionamento:",
|
||||
"None": "Nessuna",
|
||||
"Local Scaling": "Ridimensionamento Locale",
|
||||
"Remote Resizing": "Ridimensionamento Remoto",
|
||||
"Advanced": "Avanzate",
|
||||
"Quality:": "Qualità:",
|
||||
"Compression level:": "Livello Compressione:",
|
||||
"Repeater ID:": "ID Ripetitore:",
|
||||
"WebSocket": "WebSocket",
|
||||
"Encrypt": "Crittografa",
|
||||
"Host:": "Host:",
|
||||
"Port:": "Porta:",
|
||||
"Path:": "Percorso:",
|
||||
"Automatic Reconnect": "Riconnessione Automatica",
|
||||
"Reconnect Delay (ms):": "Ritardo Riconnessione (ms):",
|
||||
"Show Dot when No Cursor": "Mostra Punto quando Nessun Cursore",
|
||||
"Logging:": "",
|
||||
"Version:": "Versione:",
|
||||
"Disconnect": "Disconnetti",
|
||||
"Connect": "Connetti",
|
||||
"Username:": "Utente:",
|
||||
"Password:": "Password:",
|
||||
"Send Credentials": "Invia Credenziale",
|
||||
"Cancel": "Annulla"
|
||||
}
|
||||
@@ -9,26 +9,21 @@
|
||||
"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": "Требуется пароль",
|
||||
"New connection has been rejected with reason: ": "Новое соединение отклонено по причине: ",
|
||||
"New connection has been rejected": "Новое соединение отклонено",
|
||||
"Credentials are required": "Требуются учетные данные",
|
||||
"noVNC encountered an error:": "Ошибка noVNC: ",
|
||||
"Hide/Show the control bar": "Скрыть/Показать контрольную панель",
|
||||
"Drag": "Переместить",
|
||||
"Move/Drag Viewport": "Переместить окно",
|
||||
"viewport drag": "Переместить окно",
|
||||
"Active Mouse Button": "Активировать кнопки мыши",
|
||||
"No mousebutton": "Отключить кнопки мыши",
|
||||
"Left mousebutton": "Левая кнопка мыши",
|
||||
"Middle mousebutton": "Средняя кнопка мыши",
|
||||
"Right mousebutton": "Правая кнопка мыши",
|
||||
"Keyboard": "Клавиатура",
|
||||
"Show Keyboard": "Показать клавиатуру",
|
||||
"Extra keys": "Доп. кнопки",
|
||||
"Show Extra Keys": "Показать дополнительные кнопки",
|
||||
"Extra keys": "Дополнительные Кнопки",
|
||||
"Show Extra Keys": "Показать Дополнительные Кнопки",
|
||||
"Ctrl": "Ctrl",
|
||||
"Toggle Ctrl": "Передать нажатие Ctrl",
|
||||
"Toggle Ctrl": "Переключение нажатия Ctrl",
|
||||
"Alt": "Alt",
|
||||
"Toggle Alt": "Передать нажатие Alt",
|
||||
"Toggle Alt": "Переключение нажатия Alt",
|
||||
"Toggle Windows": "Переключение вкладок",
|
||||
"Windows": "Вкладка",
|
||||
"Send Tab": "Передать нажатие Tab",
|
||||
@@ -48,13 +43,15 @@
|
||||
"Fullscreen": "Во весь экран",
|
||||
"Settings": "Настройки",
|
||||
"Shared Mode": "Общий режим",
|
||||
"View Only": "Просмотр",
|
||||
"View Only": "Только Просмотр",
|
||||
"Clip to Window": "В окно",
|
||||
"Scaling Mode:": "Масштаб:",
|
||||
"None": "Нет",
|
||||
"Local Scaling": "Локльный масштаб",
|
||||
"Remote Resizing": "Удаленный масштаб",
|
||||
"Remote Resizing": "Удаленная перенастройка размера",
|
||||
"Advanced": "Дополнительно",
|
||||
"Quality:": "Качество",
|
||||
"Compression level:": "Уровень Сжатия",
|
||||
"Repeater ID:": "Идентификатор ID:",
|
||||
"WebSocket": "WebSocket",
|
||||
"Encrypt": "Шифрование",
|
||||
@@ -65,9 +62,11 @@
|
||||
"Reconnect Delay (ms):": "Задержка переподключения (мс):",
|
||||
"Show Dot when No Cursor": "Показать точку вместо курсора",
|
||||
"Logging:": "Лог:",
|
||||
"Version:": "Версия",
|
||||
"Disconnect": "Отключение",
|
||||
"Connect": "Подключение",
|
||||
"Username:": "Имя Пользователя",
|
||||
"Password:": "Пароль:",
|
||||
"Send Password": "Пароль: ",
|
||||
"Send Credentials": "Передача Учетных Данных",
|
||||
"Cancel": "Выход"
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"HTTPS is required for full functionality": "HTTPS krävs för full funktionalitet",
|
||||
"Connecting...": "Ansluter...",
|
||||
"Disconnecting...": "Kopplar ner...",
|
||||
"Reconnecting...": "Återansluter...",
|
||||
@@ -39,8 +40,8 @@
|
||||
"Reboot": "Boota om",
|
||||
"Reset": "Återställ",
|
||||
"Clipboard": "Urklipp",
|
||||
"Clear": "Rensa",
|
||||
"Fullscreen": "Fullskärm",
|
||||
"Edit clipboard content in the textarea below.": "Redigera urklippets innehåll i fältet nedan.",
|
||||
"Full Screen": "Fullskärm",
|
||||
"Settings": "Inställningar",
|
||||
"Shared Mode": "Delat Läge",
|
||||
"View Only": "Endast Visning",
|
||||
@@ -65,6 +66,13 @@
|
||||
"Version:": "Version:",
|
||||
"Disconnect": "Koppla från",
|
||||
"Connect": "Anslut",
|
||||
"Server identity": "Server-identitet",
|
||||
"The server has provided the following identifying information:": "Servern har gett följande identifierande information:",
|
||||
"Fingerprint:": "Fingeravtryck:",
|
||||
"Please verify that the information is correct and press \"Approve\". Otherwise press \"Reject\".": "Kontrollera att informationen är korrekt och tryck sedan \"Godkänn\". Tryck annars \"Neka\".",
|
||||
"Approve": "Godkänn",
|
||||
"Reject": "Neka",
|
||||
"Credentials": "Användaruppgifter",
|
||||
"Username:": "Användarnamn:",
|
||||
"Password:": "Lösenord:",
|
||||
"Send Credentials": "Skicka Användaruppgifter",
|
||||
|
||||
@@ -16,13 +16,19 @@ export class Localizer {
|
||||
this.language = 'en';
|
||||
|
||||
// Current dictionary of translations
|
||||
this.dictionary = undefined;
|
||||
this._dictionary = undefined;
|
||||
}
|
||||
|
||||
// Configure suitable language based on user preferences
|
||||
setup(supportedLanguages) {
|
||||
async setup(supportedLanguages, baseURL) {
|
||||
this.language = 'en'; // Default: US English
|
||||
this._dictionary = undefined;
|
||||
|
||||
this._setupLanguage(supportedLanguages);
|
||||
await this._setupDictionary(baseURL);
|
||||
}
|
||||
|
||||
_setupLanguage(supportedLanguages) {
|
||||
/*
|
||||
* Navigator.languages only available in Chrome (32+) and FireFox (32+)
|
||||
* Fall back to navigator.language for other browsers
|
||||
@@ -40,12 +46,6 @@ export class Localizer {
|
||||
.replace("_", "-")
|
||||
.split("-");
|
||||
|
||||
// Built-in default?
|
||||
if ((userLang[0] === 'en') &&
|
||||
((userLang[1] === undefined) || (userLang[1] === 'us'))) {
|
||||
return;
|
||||
}
|
||||
|
||||
// First pass: perfect match
|
||||
for (let j = 0; j < supportedLanguages.length; j++) {
|
||||
const supLang = supportedLanguages[j]
|
||||
@@ -64,7 +64,12 @@ export class Localizer {
|
||||
return;
|
||||
}
|
||||
|
||||
// Second pass: fallback
|
||||
// Second pass: English fallback
|
||||
if (userLang[0] === 'en') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Third pass pass: other fallback
|
||||
for (let j = 0;j < supportedLanguages.length;j++) {
|
||||
const supLang = supportedLanguages[j]
|
||||
.toLowerCase()
|
||||
@@ -84,10 +89,32 @@ export class Localizer {
|
||||
}
|
||||
}
|
||||
|
||||
async _setupDictionary(baseURL) {
|
||||
if (baseURL) {
|
||||
if (!baseURL.endsWith("/")) {
|
||||
baseURL = baseURL + "/";
|
||||
}
|
||||
} else {
|
||||
baseURL = "";
|
||||
}
|
||||
|
||||
if (this.language === "en") {
|
||||
return;
|
||||
}
|
||||
|
||||
let response = await fetch(baseURL + this.language + ".json");
|
||||
if (!response.ok) {
|
||||
throw Error("" + response.status + " " + response.statusText);
|
||||
}
|
||||
|
||||
this._dictionary = await response.json();
|
||||
}
|
||||
|
||||
// Retrieve localised text
|
||||
get(id) {
|
||||
if (typeof this.dictionary !== 'undefined' && this.dictionary[id]) {
|
||||
return this.dictionary[id];
|
||||
if (typeof this._dictionary !== 'undefined' &&
|
||||
this._dictionary[id]) {
|
||||
return this._dictionary[id];
|
||||
} else {
|
||||
return id;
|
||||
}
|
||||
@@ -103,13 +130,20 @@ export class Localizer {
|
||||
return items.indexOf(searchElement) !== -1;
|
||||
}
|
||||
|
||||
function translateString(str) {
|
||||
// We assume surrounding whitespace, and whitespace around line
|
||||
// breaks is just for source formatting
|
||||
str = str.split("\n").map(s => s.trim()).join(" ").trim();
|
||||
return self.get(str);
|
||||
}
|
||||
|
||||
function translateAttribute(elem, attr) {
|
||||
const str = self.get(elem.getAttribute(attr));
|
||||
const str = translateString(elem.getAttribute(attr));
|
||||
elem.setAttribute(attr, str);
|
||||
}
|
||||
|
||||
function translateTextNode(node) {
|
||||
const str = self.get(node.data.trim());
|
||||
const str = translateString(node.data);
|
||||
node.data = str;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,10 +19,23 @@
|
||||
* 10000: Max (used for polyfills)
|
||||
*/
|
||||
|
||||
/*
|
||||
* State variables (set on :root):
|
||||
*
|
||||
* noVNC_loading: Page is still loading
|
||||
* noVNC_connecting: Connecting to server
|
||||
* noVNC_reconnecting: Re-establishing a connection
|
||||
* noVNC_connected: Connected to server (most common state)
|
||||
* noVNC_disconnecting: Disconnecting from server
|
||||
*/
|
||||
|
||||
:root {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
margin:0;
|
||||
padding:0;
|
||||
font-family: Helvetica;
|
||||
/*Background image with light grey curve.*/
|
||||
background-color:#494949;
|
||||
background-repeat:no-repeat;
|
||||
@@ -78,144 +91,6 @@ html {
|
||||
50% { box-shadow: 60px 10px 0 rgba(255, 255, 255, 0); width: 10px; }
|
||||
}
|
||||
|
||||
/* ----------------------------------------
|
||||
* Input Elements
|
||||
* ----------------------------------------
|
||||
*/
|
||||
|
||||
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;
|
||||
background: none;
|
||||
|
||||
margin: 2px;
|
||||
padding: 2px;
|
||||
border: 1px solid rgb(192, 192, 192);
|
||||
border-radius: 5px;
|
||||
color: black;
|
||||
background: linear-gradient(to top, rgb(255, 255, 255) 80%, rgb(240, 240, 240));
|
||||
}
|
||||
|
||||
input[type=button],
|
||||
input[type=color],
|
||||
input[type=reset],
|
||||
input[type=submit],
|
||||
select {
|
||||
/* Disable default rendering */
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
background: none;
|
||||
|
||||
margin: 2px;
|
||||
padding: 2px;
|
||||
border: 1px solid rgb(192, 192, 192);
|
||||
border-bottom-width: 2px;
|
||||
border-radius: 5px;
|
||||
color: black;
|
||||
background: linear-gradient(to top, rgb(255, 255, 255), rgb(240, 240, 240));
|
||||
|
||||
/* This avoids it jumping around when :active */
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
input[type=button],
|
||||
input[type=color],
|
||||
input[type=reset],
|
||||
input[type=submit] {
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
option {
|
||||
color: black;
|
||||
background: white;
|
||||
}
|
||||
|
||||
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,
|
||||
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: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=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));
|
||||
}
|
||||
|
||||
/* ----------------------------------------
|
||||
* WebKit centering hacks
|
||||
* ----------------------------------------
|
||||
@@ -242,13 +117,15 @@ select:active {
|
||||
pointer-events: auto;
|
||||
}
|
||||
.noVNC_vcenter {
|
||||
display: flex;
|
||||
display: flex !important;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
pointer-events: none;
|
||||
}
|
||||
.noVNC_vcenter > * {
|
||||
@@ -272,13 +149,20 @@ select:active {
|
||||
#noVNC_fallback_error {
|
||||
z-index: 1000;
|
||||
visibility: hidden;
|
||||
/* Put a dark background in front of everything but the error,
|
||||
and don't let mouse events pass through */
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
pointer-events: all;
|
||||
}
|
||||
#noVNC_fallback_error.noVNC_open {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
#noVNC_fallback_error > div {
|
||||
max-width: 90%;
|
||||
max-width: calc(100vw - 30px - 30px);
|
||||
max-height: calc(100vh - 30px - 30px);
|
||||
overflow: auto;
|
||||
|
||||
padding: 15px;
|
||||
|
||||
transition: 0.5s ease-in-out;
|
||||
@@ -317,7 +201,6 @@ select:active {
|
||||
}
|
||||
|
||||
#noVNC_fallback_error .noVNC_stack {
|
||||
max-height: 50vh;
|
||||
padding: 10px;
|
||||
margin: 10px;
|
||||
font-size: 0.8em;
|
||||
@@ -361,6 +244,9 @@ select:active {
|
||||
background-color: rgb(110, 132, 163);
|
||||
border-radius: 0 10px 10px 0;
|
||||
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-webkit-touch-callout: none; /* Disable iOS image long-press popup */
|
||||
}
|
||||
#noVNC_control_bar.noVNC_open {
|
||||
box-shadow: 6px 6px 0px rgba(0, 0, 0, 0.5);
|
||||
@@ -433,38 +319,50 @@ select:active {
|
||||
.noVNC_right #noVNC_control_bar.noVNC_open #noVNC_control_bar_handle:after {
|
||||
transform: none;
|
||||
}
|
||||
/* Larger touch area for the handle, used when a touch screen is available */
|
||||
#noVNC_control_bar_handle div {
|
||||
position: absolute;
|
||||
right: -35px;
|
||||
top: 0;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
:root:not(.noVNC_touch) #noVNC_control_bar_handle div {
|
||||
height: 100%;
|
||||
display: none;
|
||||
}
|
||||
@media (any-pointer: coarse) {
|
||||
#noVNC_control_bar_handle div {
|
||||
display: initial;
|
||||
}
|
||||
}
|
||||
.noVNC_right #noVNC_control_bar_handle div {
|
||||
left: -35px;
|
||||
right: auto;
|
||||
}
|
||||
|
||||
#noVNC_control_bar .noVNC_scroll {
|
||||
#noVNC_control_bar > .noVNC_scroll {
|
||||
max-height: 100vh; /* Chrome is buggy with 100% */
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
padding: 0 10px 0 5px;
|
||||
padding: 0 10px;
|
||||
}
|
||||
.noVNC_right #noVNC_control_bar .noVNC_scroll {
|
||||
padding: 0 5px 0 10px;
|
||||
|
||||
#noVNC_control_bar > .noVNC_scroll > * {
|
||||
display: block;
|
||||
margin: 10px auto;
|
||||
}
|
||||
|
||||
/* Control bar hint */
|
||||
#noVNC_control_bar_hint {
|
||||
#noVNC_hint_anchor {
|
||||
position: fixed;
|
||||
left: calc(100vw - 50px);
|
||||
right: -50px;
|
||||
left: auto;
|
||||
}
|
||||
#noVNC_control_bar_anchor.noVNC_right + #noVNC_hint_anchor {
|
||||
left: -50px;
|
||||
right: auto;
|
||||
top: 50%;
|
||||
transform: translateY(-50%) scale(0);
|
||||
}
|
||||
#noVNC_control_bar_hint {
|
||||
position: relative;
|
||||
transform: scale(0);
|
||||
width: 100px;
|
||||
height: 50%;
|
||||
max-height: 600px;
|
||||
@@ -477,61 +375,65 @@ select:active {
|
||||
border-radius: 10px;
|
||||
transition-delay: 0s;
|
||||
}
|
||||
#noVNC_control_bar_anchor.noVNC_right #noVNC_control_bar_hint{
|
||||
left: auto;
|
||||
right: calc(100vw - 50px);
|
||||
}
|
||||
#noVNC_control_bar_hint.noVNC_active {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
transition-delay: 0.2s;
|
||||
transform: translateY(-50%) scale(1);
|
||||
transform: scale(1);
|
||||
}
|
||||
#noVNC_control_bar_hint.noVNC_notransition {
|
||||
transition: none !important;
|
||||
}
|
||||
|
||||
/* General button style */
|
||||
.noVNC_button {
|
||||
display: block;
|
||||
/* Control bar buttons */
|
||||
#noVNC_control_bar .noVNC_button {
|
||||
padding: 4px 4px;
|
||||
margin: 10px 0;
|
||||
vertical-align: middle;
|
||||
border:1px solid rgba(255, 255, 255, 0.2);
|
||||
border-radius: 6px;
|
||||
background-color: transparent;
|
||||
background-image: unset; /* we don't want the gradiant from input.css */
|
||||
}
|
||||
.noVNC_button.noVNC_selected {
|
||||
#noVNC_control_bar .noVNC_button.noVNC_selected {
|
||||
border-color: rgba(0, 0, 0, 0.8);
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
.noVNC_button:disabled {
|
||||
opacity: 0.4;
|
||||
#noVNC_control_bar .noVNC_button.noVNC_selected:not(:disabled):hover {
|
||||
border-color: rgba(0, 0, 0, 0.4);
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
.noVNC_button:focus {
|
||||
outline: none;
|
||||
#noVNC_control_bar .noVNC_button:not(:disabled):hover {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
.noVNC_button:active {
|
||||
#noVNC_control_bar .noVNC_button:not(:disabled):active {
|
||||
padding-top: 5px;
|
||||
padding-bottom: 3px;
|
||||
}
|
||||
/* Android browsers don't properly update hover state if touch events
|
||||
* are intercepted, but focus should be safe to display */
|
||||
:root:not(.noVNC_touch) .noVNC_button.noVNC_selected:hover,
|
||||
.noVNC_button.noVNC_selected:focus {
|
||||
border-color: rgba(0, 0, 0, 0.4);
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
#noVNC_control_bar .noVNC_button.noVNC_hidden {
|
||||
display: none !important;
|
||||
}
|
||||
:root:not(.noVNC_touch) .noVNC_button:hover,
|
||||
.noVNC_button:focus {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
.noVNC_button.noVNC_hidden {
|
||||
display: none;
|
||||
|
||||
/* Android browsers don't properly update hover state if touch events are
|
||||
* intercepted, like they are when clicking on the remote screen. */
|
||||
@media (any-pointer: coarse) {
|
||||
#noVNC_control_bar .noVNC_button:not(:disabled):hover {
|
||||
background-color: transparent;
|
||||
}
|
||||
#noVNC_control_bar .noVNC_button.noVNC_selected:not(:disabled):hover {
|
||||
border-color: rgba(0, 0, 0, 0.8);
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Panels */
|
||||
.noVNC_panel {
|
||||
transform: translateX(25px);
|
||||
|
||||
transition: 0.5s ease-in-out;
|
||||
|
||||
box-sizing: border-box; /* so max-width don't have to care about padding */
|
||||
max-width: calc(100vw - 75px - 25px); /* minus left and right margins */
|
||||
max-height: 100vh; /* Chrome is buggy with 100% */
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
@@ -563,6 +465,17 @@ select:active {
|
||||
transform: translateX(-75px);
|
||||
}
|
||||
|
||||
.noVNC_panel > * {
|
||||
display: block;
|
||||
margin: 10px auto;
|
||||
}
|
||||
.noVNC_panel > *:first-child {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
.noVNC_panel > *:last-child {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.noVNC_panel hr {
|
||||
border: none;
|
||||
border-top: 1px solid rgb(192, 192, 192);
|
||||
@@ -571,6 +484,11 @@ select:active {
|
||||
.noVNC_panel label {
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.noVNC_panel li {
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.noVNC_panel .noVNC_heading {
|
||||
@@ -581,7 +499,6 @@ select:active {
|
||||
padding-right: 8px;
|
||||
color: white;
|
||||
font-size: 20px;
|
||||
margin-bottom: 10px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.noVNC_panel .noVNC_heading img {
|
||||
@@ -622,6 +539,12 @@ select:active {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.noVNC_logo + hr {
|
||||
/* Remove all but top border */
|
||||
border: none;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
:root:not(.noVNC_connected) #noVNC_view_drag_button {
|
||||
display: none;
|
||||
}
|
||||
@@ -630,8 +553,15 @@ select:active {
|
||||
:root:not(.noVNC_connected) #noVNC_mobile_buttons {
|
||||
display: none;
|
||||
}
|
||||
:root:not(.noVNC_touch) #noVNC_mobile_buttons {
|
||||
display: none;
|
||||
@media not all and (any-pointer: coarse) {
|
||||
/* FIXME: The button for the virtual keyboard is the only button in this
|
||||
group of "mobile buttons". It is bad to assume that no touch
|
||||
devices have physical keyboards available. Hopefully we can get
|
||||
a media query for this:
|
||||
https://github.com/w3c/csswg-drafts/issues/3871 */
|
||||
:root.noVNC_connected #noVNC_mobile_buttons {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Extra manual keys */
|
||||
@@ -642,7 +572,7 @@ select:active {
|
||||
#noVNC_modifiers {
|
||||
background-color: rgb(92, 92, 92);
|
||||
border: none;
|
||||
padding: 0 10px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
/* Shutdown/Reboot */
|
||||
@@ -663,13 +593,16 @@ select:active {
|
||||
:root:not(.noVNC_connected) #noVNC_clipboard_button {
|
||||
display: none;
|
||||
}
|
||||
#noVNC_clipboard {
|
||||
/* Full screen, minus padding and left and right margins */
|
||||
max-width: calc(100vw - 2*15px - 75px - 25px);
|
||||
}
|
||||
#noVNC_clipboard_text {
|
||||
width: 500px;
|
||||
width: 360px;
|
||||
min-width: 150px;
|
||||
height: 160px;
|
||||
min-height: 70px;
|
||||
|
||||
box-sizing: border-box;
|
||||
max-width: 100%;
|
||||
/* minus approximate height of title, height of subtitle, and margin */
|
||||
max-height: calc(100vh - 10em - 25px);
|
||||
}
|
||||
|
||||
/* Settings */
|
||||
@@ -677,7 +610,6 @@ select:active {
|
||||
}
|
||||
#noVNC_settings ul {
|
||||
list-style: none;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
#noVNC_setting_port {
|
||||
@@ -729,7 +661,7 @@ select:active {
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
|
||||
line-height: 25px;
|
||||
line-height: 1.6;
|
||||
word-wrap: break-word;
|
||||
color: #fff;
|
||||
|
||||
@@ -803,36 +735,32 @@ select:active {
|
||||
font-size: calc(25vw - 30px);
|
||||
}
|
||||
}
|
||||
#noVNC_connect_button {
|
||||
cursor: pointer;
|
||||
#noVNC_connect_dlg div {
|
||||
padding: 12px;
|
||||
|
||||
padding: 10px;
|
||||
|
||||
color: white;
|
||||
background-color: rgb(110, 132, 163);
|
||||
border-radius: 12px;
|
||||
|
||||
text-align: center;
|
||||
font-size: 20px;
|
||||
|
||||
box-shadow: 6px 6px 0px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
#noVNC_connect_button div {
|
||||
margin: 2px;
|
||||
#noVNC_connect_button {
|
||||
width: 100%;
|
||||
padding: 5px 30px;
|
||||
border: 1px solid rgb(83, 99, 122);
|
||||
border-bottom-width: 2px;
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
border-color: rgb(83, 99, 122);
|
||||
border-radius: 5px;
|
||||
|
||||
background: linear-gradient(to top, rgb(110, 132, 163), rgb(99, 119, 147));
|
||||
color: white;
|
||||
|
||||
/* This avoids it jumping around when :active */
|
||||
vertical-align: middle;
|
||||
}
|
||||
#noVNC_connect_button div:active {
|
||||
border-bottom-width: 1px;
|
||||
margin-top: 3px;
|
||||
}
|
||||
:root:not(.noVNC_touch) #noVNC_connect_button div:hover {
|
||||
#noVNC_connect_button:hover {
|
||||
background: linear-gradient(to top, rgb(110, 132, 163), rgb(105, 125, 155));
|
||||
}
|
||||
|
||||
@@ -841,6 +769,23 @@ select:active {
|
||||
height: 1.3em;
|
||||
}
|
||||
|
||||
/* ----------------------------------------
|
||||
* Server verification Dialog
|
||||
* ----------------------------------------
|
||||
*/
|
||||
|
||||
#noVNC_verify_server_dlg {
|
||||
position: relative;
|
||||
|
||||
transform: translateY(-50px);
|
||||
}
|
||||
#noVNC_verify_server_dlg.noVNC_open {
|
||||
transform: translateY(0);
|
||||
}
|
||||
#noVNC_fingerprint_block {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
/* ----------------------------------------
|
||||
* Password Dialog
|
||||
* ----------------------------------------
|
||||
@@ -854,12 +799,8 @@ select:active {
|
||||
#noVNC_credentials_dlg.noVNC_open {
|
||||
transform: translateY(0);
|
||||
}
|
||||
#noVNC_credentials_dlg ul {
|
||||
list-style: none;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
.noVNC_hidden {
|
||||
#noVNC_username_block.noVNC_hidden,
|
||||
#noVNC_password_block.noVNC_hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -871,7 +812,11 @@ select:active {
|
||||
|
||||
/* Transition screen */
|
||||
#noVNC_transition {
|
||||
display: none;
|
||||
transition: 0.5s ease-in-out;
|
||||
|
||||
display: flex;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
|
||||
position: fixed;
|
||||
top: 0;
|
||||
@@ -892,7 +837,8 @@ select:active {
|
||||
:root.noVNC_connecting #noVNC_transition,
|
||||
:root.noVNC_disconnecting #noVNC_transition,
|
||||
:root.noVNC_reconnecting #noVNC_transition {
|
||||
display: flex;
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
:root:not(.noVNC_reconnecting) #noVNC_cancel_reconnect_button {
|
||||
display: none;
|
||||
@@ -908,6 +854,12 @@ select:active {
|
||||
background-color: #313131;
|
||||
border-bottom-right-radius: 800px 600px;
|
||||
/*border-top-left-radius: 800px 600px;*/
|
||||
|
||||
/* If selection isn't disabled, long-pressing stuff in the sidebar
|
||||
can accidentally select the container or the canvas. This can
|
||||
happen when attempting to move the handle. */
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
|
||||
#noVNC_keyboardinput {
|
||||
@@ -935,7 +887,7 @@ select:active {
|
||||
.noVNC_logo {
|
||||
color:yellow;
|
||||
font-family: 'Orbitron', 'OrbitronTTF', sans-serif;
|
||||
line-height:90%;
|
||||
line-height: 0.9;
|
||||
text-shadow: 0.1em 0.1em 0 black;
|
||||
}
|
||||
.noVNC_logo span{
|
||||
|
||||
@@ -0,0 +1,281 @@
|
||||
/*
|
||||
* noVNC general input element CSS
|
||||
* Copyright (C) 2022 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).
|
||||
*/
|
||||
|
||||
/*
|
||||
* Common for all inputs
|
||||
*/
|
||||
input, input::file-selector-button, button, select, textarea {
|
||||
/* Respect standard font settings */
|
||||
font: inherit;
|
||||
|
||||
/* Disable default rendering */
|
||||
appearance: none;
|
||||
background: none;
|
||||
|
||||
padding: 5px;
|
||||
border: 1px solid rgb(192, 192, 192);
|
||||
border-radius: 5px;
|
||||
color: black;
|
||||
--bg-gradient: linear-gradient(to top, rgb(255, 255, 255) 80%, rgb(240, 240, 240));
|
||||
background-image: var(--bg-gradient);
|
||||
}
|
||||
|
||||
/*
|
||||
* Buttons
|
||||
*/
|
||||
input[type=button],
|
||||
input[type=color],
|
||||
input[type=image],
|
||||
input[type=reset],
|
||||
input[type=submit],
|
||||
input::file-selector-button,
|
||||
button,
|
||||
select {
|
||||
border-bottom-width: 2px;
|
||||
|
||||
/* This avoids it jumping around when :active */
|
||||
vertical-align: middle;
|
||||
margin-top: 0;
|
||||
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
|
||||
/* Disable Chrome's touch tap highlight */
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
/*
|
||||
* Select dropdowns
|
||||
*/
|
||||
select {
|
||||
--select-arrow: url('data:image/svg+xml;utf8, \
|
||||
<svg width="8" height="6" version="1.1" viewBox="0 0 8 6" \
|
||||
xmlns="http://www.w3.org/2000/svg"> \
|
||||
<path d="m6.5 1.5 -2.5 3 -2.5 -3 5 0" stroke-width="3" \
|
||||
stroke="rgb(31,31,31)" fill="none" \
|
||||
stroke-linecap="round" stroke-linejoin="round" /> \
|
||||
</svg>');
|
||||
background-image: var(--select-arrow), var(--bg-gradient);
|
||||
background-position: calc(100% - 7px), left top;
|
||||
background-repeat: no-repeat;
|
||||
padding-right: calc(2*7px + 8px);
|
||||
padding-left: 7px;
|
||||
}
|
||||
/* FIXME: :active isn't set when the <select> is opened in Firefox:
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=1805406 */
|
||||
select:active {
|
||||
/* Rotated arrow */
|
||||
background-image: url('data:image/svg+xml;utf8, \
|
||||
<svg width="8" height="6" version="1.1" viewBox="0 0 8 6" \
|
||||
xmlns="http://www.w3.org/2000/svg" transform="rotate(180)" > \
|
||||
<path d="m6.5 1.5 -2.5 3 -2.5 -3 5 0" stroke-width="3" \
|
||||
stroke="rgb(31,31,31)" fill="none" \
|
||||
stroke-linecap="round" stroke-linejoin="round" /> \
|
||||
</svg>'), var(--bg-gradient);
|
||||
}
|
||||
option {
|
||||
color: black;
|
||||
background: white;
|
||||
}
|
||||
|
||||
/*
|
||||
* Checkboxes
|
||||
*/
|
||||
input[type=checkbox] {
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: white;
|
||||
background-image: unset;
|
||||
border: 1px solid dimgrey;
|
||||
border-radius: 3px;
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
padding: 0;
|
||||
margin-right: 6px;
|
||||
vertical-align: bottom;
|
||||
transition: 0.2s background-color linear;
|
||||
}
|
||||
input[type=checkbox]:checked {
|
||||
background-color: rgb(110, 132, 163);
|
||||
border-color: rgb(110, 132, 163);
|
||||
}
|
||||
input[type=checkbox]:checked::after {
|
||||
content: "";
|
||||
display: block; /* width & height doesn't work on inline elements */
|
||||
width: 3px;
|
||||
height: 7px;
|
||||
border: 1px solid white;
|
||||
border-width: 0 2px 2px 0;
|
||||
transform: rotate(40deg) translateY(-1px);
|
||||
}
|
||||
|
||||
/*
|
||||
* Radiobuttons
|
||||
*/
|
||||
input[type=radio] {
|
||||
border-radius: 50%;
|
||||
border: 1px solid dimgrey;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
padding: 0;
|
||||
margin-right: 6px;
|
||||
transition: 0.2s border linear;
|
||||
}
|
||||
input[type=radio]:checked {
|
||||
border: 6px solid rgb(110, 132, 163);
|
||||
}
|
||||
|
||||
/*
|
||||
* Range sliders
|
||||
*/
|
||||
input[type=range] {
|
||||
border: unset;
|
||||
border-radius: 3px;
|
||||
height: 20px;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
}
|
||||
/* -webkit-slider.. & -moz-range.. cant be in selector lists:
|
||||
https://bugs.chromium.org/p/chromium/issues/detail?id=1154623 */
|
||||
input[type=range]::-webkit-slider-runnable-track {
|
||||
background-color: rgb(110, 132, 163);
|
||||
height: 6px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
input[type=range]::-moz-range-track {
|
||||
background-color: rgb(110, 132, 163);
|
||||
height: 6px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
input[type=range]::-webkit-slider-thumb {
|
||||
appearance: none;
|
||||
width: 18px;
|
||||
height: 20px;
|
||||
border-radius: 5px;
|
||||
background-color: white;
|
||||
border: 1px solid dimgray;
|
||||
margin-top: -7px;
|
||||
}
|
||||
input[type=range]::-moz-range-thumb {
|
||||
appearance: none;
|
||||
width: 18px;
|
||||
height: 20px;
|
||||
border-radius: 5px;
|
||||
background-color: white;
|
||||
border: 1px solid dimgray;
|
||||
margin-top: -7px;
|
||||
}
|
||||
|
||||
/*
|
||||
* File choosers
|
||||
*/
|
||||
input[type=file] {
|
||||
background-image: none;
|
||||
border: none;
|
||||
}
|
||||
input::file-selector-button {
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
/*
|
||||
* Hover
|
||||
*/
|
||||
input[type=button]:hover,
|
||||
input[type=color]:hover,
|
||||
input[type=image]:hover,
|
||||
input[type=reset]:hover,
|
||||
input[type=submit]:hover,
|
||||
input::file-selector-button:hover,
|
||||
button:hover {
|
||||
background-image: linear-gradient(to top, rgb(255, 255, 255), rgb(250, 250, 250));
|
||||
}
|
||||
select:hover {
|
||||
background-image: var(--select-arrow),
|
||||
linear-gradient(to top, rgb(255, 255, 255), rgb(250, 250, 250));
|
||||
background-position: calc(100% - 7px), left top;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
@media (any-pointer: coarse) {
|
||||
/* We don't want a hover style after touch input */
|
||||
input[type=button]:hover,
|
||||
input[type=color]:hover,
|
||||
input[type=image]:hover,
|
||||
input[type=reset]:hover,
|
||||
input[type=submit]:hover,
|
||||
input::file-selector-button:hover,
|
||||
button:hover {
|
||||
background-image: var(--bg-gradient);
|
||||
}
|
||||
select:hover {
|
||||
background-image: var(--select-arrow), var(--bg-gradient);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Active (clicked)
|
||||
*/
|
||||
input[type=button]:active,
|
||||
input[type=color]:active,
|
||||
input[type=image]:active,
|
||||
input[type=reset]:active,
|
||||
input[type=submit]:active,
|
||||
input::file-selector-button:active,
|
||||
button:active,
|
||||
select:active {
|
||||
border-bottom-width: 1px;
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
/*
|
||||
* Focus (tab)
|
||||
*/
|
||||
input:focus-visible,
|
||||
input:focus-visible::file-selector-button,
|
||||
button:focus-visible,
|
||||
select:focus-visible,
|
||||
textarea:focus-visible {
|
||||
outline: 2px solid rgb(74, 144, 217);
|
||||
outline-offset: 1px;
|
||||
}
|
||||
input[type=file]:focus-visible {
|
||||
outline: none; /* We outline the button instead of the entire element */
|
||||
}
|
||||
|
||||
/*
|
||||
* Disabled
|
||||
*/
|
||||
input:disabled,
|
||||
input:disabled::file-selector-button,
|
||||
button:disabled,
|
||||
select:disabled,
|
||||
textarea:disabled {
|
||||
opacity: 0.4;
|
||||
}
|
||||
input[type=button]:disabled,
|
||||
input[type=color]:disabled,
|
||||
input[type=image]:disabled,
|
||||
input[type=reset]:disabled,
|
||||
input[type=submit]:disabled,
|
||||
input:disabled::file-selector-button,
|
||||
button:disabled,
|
||||
select:disabled {
|
||||
background-image: var(--bg-gradient);
|
||||
border-bottom-width: 2px;
|
||||
margin-top: 0;
|
||||
}
|
||||
input[type=file]:disabled {
|
||||
background-image: none;
|
||||
}
|
||||
select:disabled {
|
||||
background-image: var(--select-arrow), var(--bg-gradient);
|
||||
}
|
||||
input[type=image]:disabled {
|
||||
/* See Firefox bug:
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=1798304 */
|
||||
cursor: default;
|
||||
}
|
||||
@@ -8,7 +8,8 @@
|
||||
|
||||
import * as Log from '../core/util/logging.js';
|
||||
import _, { l10n } from './localization.js';
|
||||
import { isTouchDevice, isSafari, hasScrollbarGutter, dragThreshold }
|
||||
import { isTouchDevice, isMac, isIOS, isAndroid, isChromeOS, isSafari,
|
||||
hasScrollbarGutter, dragThreshold }
|
||||
from '../core/util/browser.js';
|
||||
import { setCapture, getPointerEvent } from '../core/util/events.js';
|
||||
import KeyTable from "../core/input/keysym.js";
|
||||
@@ -61,6 +62,14 @@ const UI = {
|
||||
// Translate the DOM
|
||||
l10n.translateDOM();
|
||||
|
||||
// We rely on modern APIs which might not be available in an
|
||||
// insecure context
|
||||
if (!window.isSecureContext) {
|
||||
// FIXME: This gets hidden when connecting
|
||||
UI.showStatus(_("HTTPS is required for full functionality"), 'error');
|
||||
}
|
||||
|
||||
// Try to fetch version number
|
||||
fetch('./package.json')
|
||||
.then((response) => {
|
||||
if (!response.ok) {
|
||||
@@ -80,7 +89,6 @@ const UI = {
|
||||
|
||||
// Adapt the interface for touch screen devices
|
||||
if (isTouchDevice) {
|
||||
document.documentElement.classList.add("noVNC_touch");
|
||||
// Remove the address bar
|
||||
setTimeout(() => window.scrollTo(0, 1), 100);
|
||||
}
|
||||
@@ -316,6 +324,10 @@ const UI = {
|
||||
document.getElementById("noVNC_cancel_reconnect_button")
|
||||
.addEventListener('click', UI.cancelReconnect);
|
||||
|
||||
document.getElementById("noVNC_approve_server_button")
|
||||
.addEventListener('click', UI.approveServer);
|
||||
document.getElementById("noVNC_reject_server_button")
|
||||
.addEventListener('click', UI.rejectServer);
|
||||
document.getElementById("noVNC_credentials_button")
|
||||
.addEventListener('click', UI.setCredentials);
|
||||
},
|
||||
@@ -325,8 +337,6 @@ const UI = {
|
||||
.addEventListener('click', UI.toggleClipboardPanel);
|
||||
document.getElementById("noVNC_clipboard_text")
|
||||
.addEventListener('change', UI.clipboardSend);
|
||||
document.getElementById("noVNC_clipboard_clear_button")
|
||||
.addEventListener('click', UI.clipboardClear);
|
||||
},
|
||||
|
||||
// Add a call to save settings when the element changes,
|
||||
@@ -445,6 +455,8 @@ const UI = {
|
||||
// State change closes dialogs as they may not be relevant
|
||||
// anymore
|
||||
UI.closeAllPanels();
|
||||
document.getElementById('noVNC_verify_server_dlg')
|
||||
.classList.remove('noVNC_open');
|
||||
document.getElementById('noVNC_credentials_dlg')
|
||||
.classList.remove('noVNC_open');
|
||||
},
|
||||
@@ -577,10 +589,20 @@ const UI = {
|
||||
|
||||
// Consider this a movement of the handle
|
||||
UI.controlbarDrag = true;
|
||||
|
||||
// The user has "followed" hint, let's hide it until the next drag
|
||||
UI.showControlbarHint(false, false);
|
||||
},
|
||||
|
||||
showControlbarHint(show) {
|
||||
showControlbarHint(show, animate=true) {
|
||||
const hint = document.getElementById('noVNC_control_bar_hint');
|
||||
|
||||
if (animate) {
|
||||
hint.classList.remove("noVNC_notransition");
|
||||
} else {
|
||||
hint.classList.add("noVNC_notransition");
|
||||
}
|
||||
|
||||
if (show) {
|
||||
hint.classList.add("noVNC_active");
|
||||
} else {
|
||||
@@ -954,11 +976,6 @@ const UI = {
|
||||
Log.Debug("<< UI.clipboardReceive");
|
||||
},
|
||||
|
||||
clipboardClear() {
|
||||
document.getElementById('noVNC_clipboard_text').value = "";
|
||||
UI.rfb.clipboardPasteFrom("");
|
||||
},
|
||||
|
||||
clipboardSend() {
|
||||
const text = document.getElementById('noVNC_clipboard_text').value;
|
||||
Log.Debug(">> UI.clipboardSend: " + text.substr(0, 40) + "...");
|
||||
@@ -1024,14 +1041,24 @@ const UI = {
|
||||
}
|
||||
url += '/' + path;
|
||||
|
||||
UI.rfb = new RFB(document.getElementById('noVNC_container'), url,
|
||||
{ shared: UI.getSetting('shared'),
|
||||
repeaterID: UI.getSetting('repeaterID'),
|
||||
credentials: { password: password } });
|
||||
try {
|
||||
UI.rfb = new RFB(document.getElementById('noVNC_container'), url,
|
||||
{ shared: UI.getSetting('shared'),
|
||||
repeaterID: UI.getSetting('repeaterID'),
|
||||
credentials: { password: password } });
|
||||
} catch (exc) {
|
||||
Log.Error("Failed to connect to server: " + exc);
|
||||
UI.updateVisualState('disconnected');
|
||||
UI.showStatus(_("Failed to connect to server: ") + exc, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
UI.rfb.addEventListener("connect", UI.connectFinished);
|
||||
UI.rfb.addEventListener("disconnect", UI.disconnectFinished);
|
||||
UI.rfb.addEventListener("serververification", UI.serverVerify);
|
||||
UI.rfb.addEventListener("credentialsrequired", UI.credentials);
|
||||
UI.rfb.addEventListener("securityfailure", UI.securityFailed);
|
||||
UI.rfb.addEventListener("clippingviewport", UI.updateViewDrag);
|
||||
UI.rfb.addEventListener("capabilities", UI.updatePowerButton);
|
||||
UI.rfb.addEventListener("clipboard", UI.clipboardReceive);
|
||||
UI.rfb.addEventListener("bell", UI.bell);
|
||||
@@ -1118,7 +1145,9 @@ const UI = {
|
||||
} else {
|
||||
UI.showStatus(_("Failed to connect to server"), 'error');
|
||||
}
|
||||
} else if (UI.getSetting('reconnect', false) === true && !UI.inhibitReconnect) {
|
||||
}
|
||||
// If reconnecting is allowed process it now
|
||||
if (UI.getSetting('reconnect', false) === true && !UI.inhibitReconnect) {
|
||||
UI.updateVisualState('reconnecting');
|
||||
|
||||
const delay = parseInt(UI.getSetting('reconnect_delay'));
|
||||
@@ -1152,6 +1181,37 @@ const UI = {
|
||||
/* ------^-------
|
||||
* /CONNECTION
|
||||
* ==============
|
||||
* SERVER VERIFY
|
||||
* ------v------*/
|
||||
|
||||
async serverVerify(e) {
|
||||
const type = e.detail.type;
|
||||
if (type === 'RSA') {
|
||||
const publickey = e.detail.publickey;
|
||||
let fingerprint = await window.crypto.subtle.digest("SHA-1", publickey);
|
||||
// The same fingerprint format as RealVNC
|
||||
fingerprint = Array.from(new Uint8Array(fingerprint).slice(0, 8)).map(
|
||||
x => x.toString(16).padStart(2, '0')).join('-');
|
||||
document.getElementById('noVNC_verify_server_dlg').classList.add('noVNC_open');
|
||||
document.getElementById('noVNC_fingerprint').innerHTML = fingerprint;
|
||||
}
|
||||
},
|
||||
|
||||
approveServer(e) {
|
||||
e.preventDefault();
|
||||
document.getElementById('noVNC_verify_server_dlg').classList.remove('noVNC_open');
|
||||
UI.rfb.approveServer();
|
||||
},
|
||||
|
||||
rejectServer(e) {
|
||||
e.preventDefault();
|
||||
document.getElementById('noVNC_verify_server_dlg').classList.remove('noVNC_open');
|
||||
UI.disconnect();
|
||||
},
|
||||
|
||||
/* ------^-------
|
||||
* /SERVER VERIFY
|
||||
* ==============
|
||||
* PASSWORD
|
||||
* ------v------*/
|
||||
|
||||
@@ -1275,13 +1335,25 @@ const UI = {
|
||||
|
||||
const scaling = UI.getSetting('resize') === 'scale';
|
||||
|
||||
// Some platforms have overlay scrollbars that are difficult
|
||||
// to use in our case, which means we have to force panning
|
||||
// FIXME: Working scrollbars can still be annoying to use with
|
||||
// touch, so we should ideally be able to have both
|
||||
// panning and scrollbars at the same time
|
||||
|
||||
let brokenScrollbars = false;
|
||||
|
||||
if (!hasScrollbarGutter) {
|
||||
if (isIOS() || isAndroid() || isMac() || isChromeOS()) {
|
||||
brokenScrollbars = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (scaling) {
|
||||
// Can't be clipping if viewport is scaled to fit
|
||||
UI.forceSetting('view_clip', false);
|
||||
UI.rfb.clipViewport = false;
|
||||
} else if (!hasScrollbarGutter) {
|
||||
// Some platforms have scrollbars that are difficult
|
||||
// to use in our case, so we always use our own panning
|
||||
} else if (brokenScrollbars) {
|
||||
UI.forceSetting('view_clip', true);
|
||||
UI.rfb.clipViewport = true;
|
||||
} else {
|
||||
@@ -1312,7 +1384,8 @@ const UI = {
|
||||
|
||||
const viewDragButton = document.getElementById('noVNC_view_drag_button');
|
||||
|
||||
if (!UI.rfb.clipViewport && UI.rfb.dragViewport) {
|
||||
if ((!UI.rfb.clipViewport || !UI.rfb.clippingViewport) &&
|
||||
UI.rfb.dragViewport) {
|
||||
// We are no longer clipping the viewport. Make sure
|
||||
// viewport drag isn't active when it can't be used.
|
||||
UI.rfb.dragViewport = false;
|
||||
@@ -1329,6 +1402,8 @@ const UI = {
|
||||
} else {
|
||||
viewDragButton.classList.add("noVNC_hidden");
|
||||
}
|
||||
|
||||
viewDragButton.disabled = !UI.rfb.clippingViewport;
|
||||
},
|
||||
|
||||
/* ------^-------
|
||||
@@ -1695,21 +1770,9 @@ const UI = {
|
||||
};
|
||||
|
||||
// Set up translations
|
||||
const LINGUAS = ["cs", "de", "el", "es", "fr", "ja", "ko", "nl", "pl", "pt_BR", "ru", "sv", "tr", "zh_CN", "zh_TW"];
|
||||
l10n.setup(LINGUAS);
|
||||
if (l10n.language === "en" || l10n.dictionary !== undefined) {
|
||||
UI.prime();
|
||||
} else {
|
||||
fetch('app/locale/' + l10n.language + '.json')
|
||||
.then((response) => {
|
||||
if (!response.ok) {
|
||||
throw Error("" + response.status + " " + response.statusText);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then((translations) => { l10n.dictionary = translations; })
|
||||
.catch(err => Log.Error("Failed to load translations: " + err))
|
||||
.then(UI.prime);
|
||||
}
|
||||
const LINGUAS = ["cs", "de", "el", "es", "fr", "it", "ja", "ko", "nl", "pl", "pt_BR", "ru", "sv", "tr", "zh_CN", "zh_TW"];
|
||||
l10n.setup(LINGUAS, "app/locale/")
|
||||
.catch(err => Log.Error("Failed to load translations: " + err))
|
||||
.then(UI.prime);
|
||||
|
||||
export default UI;
|
||||
|
||||
@@ -6,16 +6,16 @@
|
||||
* See README.md for usage and integration instructions.
|
||||
*/
|
||||
|
||||
import { initLogging as mainInitLogging } from '../core/util/logging.js';
|
||||
import * as Log from '../core/util/logging.js';
|
||||
|
||||
// init log level reading the logging HTTP param
|
||||
export function initLogging(level) {
|
||||
"use strict";
|
||||
if (typeof level !== "undefined") {
|
||||
mainInitLogging(level);
|
||||
Log.initLogging(level);
|
||||
} else {
|
||||
const param = document.location.href.match(/logging=([A-Za-z0-9._-]*)/);
|
||||
mainInitLogging(param || undefined);
|
||||
Log.initLogging(param || undefined);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,14 +25,14 @@ export function initLogging(level) {
|
||||
//
|
||||
// For privacy (Using a hastag #, the parameters will not be sent to the server)
|
||||
// the url can be requested in the following way:
|
||||
// https://www.example.com#myqueryparam=myvalue&password=secreatvalue
|
||||
// https://www.example.com#myqueryparam=myvalue&password=secretvalue
|
||||
//
|
||||
// Even Mixing public and non public parameters will work:
|
||||
// https://www.example.com?nonsecretparam=example.com#password=secreatvalue
|
||||
// https://www.example.com?nonsecretparam=example.com#password=secretvalue
|
||||
export function getQueryVar(name, defVal) {
|
||||
"use strict";
|
||||
const re = new RegExp('.*[?&]' + name + '=([^&#]*)'),
|
||||
match = ''.concat(document.location.href, window.location.hash).match(re);
|
||||
match = document.location.href.match(re);
|
||||
if (typeof defVal === 'undefined') { defVal = null; }
|
||||
|
||||
if (match) {
|
||||
@@ -46,7 +46,7 @@ export function getQueryVar(name, defVal) {
|
||||
export function getHashVar(name, defVal) {
|
||||
"use strict";
|
||||
const re = new RegExp('.*[&#]' + name + '=([^&]*)'),
|
||||
match = document.location.hash.match(re);
|
||||
match = document.location.hash.match(re);
|
||||
if (typeof defVal === 'undefined') { defVal = null; }
|
||||
|
||||
if (match) {
|
||||
@@ -146,7 +146,7 @@ export function writeSetting(name, value) {
|
||||
if (window.chrome && window.chrome.storage) {
|
||||
window.chrome.storage.sync.set(settings);
|
||||
} else {
|
||||
localStorage.setItem(name, value);
|
||||
localStorageSet(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,7 +156,7 @@ export function readSetting(name, defaultValue) {
|
||||
if ((name in settings) || (window.chrome && window.chrome.storage)) {
|
||||
value = settings[name];
|
||||
} else {
|
||||
value = localStorage.getItem(name);
|
||||
value = localStorageGet(name);
|
||||
settings[name] = value;
|
||||
}
|
||||
if (typeof value === "undefined") {
|
||||
@@ -181,6 +181,70 @@ export function eraseSetting(name) {
|
||||
if (window.chrome && window.chrome.storage) {
|
||||
window.chrome.storage.sync.remove(name);
|
||||
} else {
|
||||
localStorage.removeItem(name);
|
||||
localStorageRemove(name);
|
||||
}
|
||||
}
|
||||
|
||||
let loggedMsgs = [];
|
||||
function logOnce(msg, level = "warn") {
|
||||
if (!loggedMsgs.includes(msg)) {
|
||||
switch (level) {
|
||||
case "error":
|
||||
Log.Error(msg);
|
||||
break;
|
||||
case "warn":
|
||||
Log.Warn(msg);
|
||||
break;
|
||||
case "debug":
|
||||
Log.Debug(msg);
|
||||
break;
|
||||
default:
|
||||
Log.Info(msg);
|
||||
}
|
||||
loggedMsgs.push(msg);
|
||||
}
|
||||
}
|
||||
|
||||
let cookiesMsg = "Couldn't access noVNC settings, are cookies disabled?";
|
||||
|
||||
function localStorageGet(name) {
|
||||
let r;
|
||||
try {
|
||||
r = localStorage.getItem(name);
|
||||
} catch (e) {
|
||||
if (e instanceof DOMException) {
|
||||
logOnce(cookiesMsg);
|
||||
logOnce("'localStorage.getItem(" + name + ")' failed: " + e,
|
||||
"debug");
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
return r;
|
||||
}
|
||||
function localStorageSet(name, value) {
|
||||
try {
|
||||
localStorage.setItem(name, value);
|
||||
} catch (e) {
|
||||
if (e instanceof DOMException) {
|
||||
logOnce(cookiesMsg);
|
||||
logOnce("'localStorage.setItem(" + name + "," + value +
|
||||
")' failed: " + e, "debug");
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
function localStorageRemove(name) {
|
||||
try {
|
||||
localStorage.removeItem(name);
|
||||
} catch (e) {
|
||||
if (e instanceof DOMException) {
|
||||
logOnce(cookiesMsg);
|
||||
logOnce("'localStorage.removeItem(" + name + ")' failed: " + e,
|
||||
"debug");
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
export class AESECBCipher {
|
||||
constructor() {
|
||||
this._key = null;
|
||||
}
|
||||
|
||||
get algorithm() {
|
||||
return { name: "AES-ECB" };
|
||||
}
|
||||
|
||||
static async importKey(key, _algorithm, extractable, keyUsages) {
|
||||
const cipher = new AESECBCipher;
|
||||
await cipher._importKey(key, extractable, keyUsages);
|
||||
return cipher;
|
||||
}
|
||||
|
||||
async _importKey(key, extractable, keyUsages) {
|
||||
this._key = await window.crypto.subtle.importKey(
|
||||
"raw", key, {name: "AES-CBC"}, extractable, keyUsages);
|
||||
}
|
||||
|
||||
async encrypt(_algorithm, plaintext) {
|
||||
const x = new Uint8Array(plaintext);
|
||||
if (x.length % 16 !== 0 || this._key === null) {
|
||||
return null;
|
||||
}
|
||||
const n = x.length / 16;
|
||||
for (let i = 0; i < n; i++) {
|
||||
const y = new Uint8Array(await window.crypto.subtle.encrypt({
|
||||
name: "AES-CBC",
|
||||
iv: new Uint8Array(16),
|
||||
}, this._key, x.slice(i * 16, i * 16 + 16))).slice(0, 16);
|
||||
x.set(y, i * 16);
|
||||
}
|
||||
return x;
|
||||
}
|
||||
}
|
||||
|
||||
export class AESEAXCipher {
|
||||
constructor() {
|
||||
this._rawKey = null;
|
||||
this._ctrKey = null;
|
||||
this._cbcKey = null;
|
||||
this._zeroBlock = new Uint8Array(16);
|
||||
this._prefixBlock0 = this._zeroBlock;
|
||||
this._prefixBlock1 = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);
|
||||
this._prefixBlock2 = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2]);
|
||||
}
|
||||
|
||||
get algorithm() {
|
||||
return { name: "AES-EAX" };
|
||||
}
|
||||
|
||||
async _encryptBlock(block) {
|
||||
const encrypted = await window.crypto.subtle.encrypt({
|
||||
name: "AES-CBC",
|
||||
iv: this._zeroBlock,
|
||||
}, this._cbcKey, block);
|
||||
return new Uint8Array(encrypted).slice(0, 16);
|
||||
}
|
||||
|
||||
async _initCMAC() {
|
||||
const k1 = await this._encryptBlock(this._zeroBlock);
|
||||
const k2 = new Uint8Array(16);
|
||||
const v = k1[0] >>> 6;
|
||||
for (let i = 0; i < 15; i++) {
|
||||
k2[i] = (k1[i + 1] >> 6) | (k1[i] << 2);
|
||||
k1[i] = (k1[i + 1] >> 7) | (k1[i] << 1);
|
||||
}
|
||||
const lut = [0x0, 0x87, 0x0e, 0x89];
|
||||
k2[14] ^= v >>> 1;
|
||||
k2[15] = (k1[15] << 2) ^ lut[v];
|
||||
k1[15] = (k1[15] << 1) ^ lut[v >> 1];
|
||||
this._k1 = k1;
|
||||
this._k2 = k2;
|
||||
}
|
||||
|
||||
async _encryptCTR(data, counter) {
|
||||
const encrypted = await window.crypto.subtle.encrypt({
|
||||
name: "AES-CTR",
|
||||
counter: counter,
|
||||
length: 128
|
||||
}, this._ctrKey, data);
|
||||
return new Uint8Array(encrypted);
|
||||
}
|
||||
|
||||
async _decryptCTR(data, counter) {
|
||||
const decrypted = await window.crypto.subtle.decrypt({
|
||||
name: "AES-CTR",
|
||||
counter: counter,
|
||||
length: 128
|
||||
}, this._ctrKey, data);
|
||||
return new Uint8Array(decrypted);
|
||||
}
|
||||
|
||||
async _computeCMAC(data, prefixBlock) {
|
||||
if (prefixBlock.length !== 16) {
|
||||
return null;
|
||||
}
|
||||
const n = Math.floor(data.length / 16);
|
||||
const m = Math.ceil(data.length / 16);
|
||||
const r = data.length - n * 16;
|
||||
const cbcData = new Uint8Array((m + 1) * 16);
|
||||
cbcData.set(prefixBlock);
|
||||
cbcData.set(data, 16);
|
||||
if (r === 0) {
|
||||
for (let i = 0; i < 16; i++) {
|
||||
cbcData[n * 16 + i] ^= this._k1[i];
|
||||
}
|
||||
} else {
|
||||
cbcData[(n + 1) * 16 + r] = 0x80;
|
||||
for (let i = 0; i < 16; i++) {
|
||||
cbcData[(n + 1) * 16 + i] ^= this._k2[i];
|
||||
}
|
||||
}
|
||||
let cbcEncrypted = await window.crypto.subtle.encrypt({
|
||||
name: "AES-CBC",
|
||||
iv: this._zeroBlock,
|
||||
}, this._cbcKey, cbcData);
|
||||
|
||||
cbcEncrypted = new Uint8Array(cbcEncrypted);
|
||||
const mac = cbcEncrypted.slice(cbcEncrypted.length - 32, cbcEncrypted.length - 16);
|
||||
return mac;
|
||||
}
|
||||
|
||||
static async importKey(key, _algorithm, _extractable, _keyUsages) {
|
||||
const cipher = new AESEAXCipher;
|
||||
await cipher._importKey(key);
|
||||
return cipher;
|
||||
}
|
||||
|
||||
async _importKey(key) {
|
||||
this._rawKey = key;
|
||||
this._ctrKey = await window.crypto.subtle.importKey(
|
||||
"raw", key, {name: "AES-CTR"}, false, ["encrypt", "decrypt"]);
|
||||
this._cbcKey = await window.crypto.subtle.importKey(
|
||||
"raw", key, {name: "AES-CBC"}, false, ["encrypt"]);
|
||||
await this._initCMAC();
|
||||
}
|
||||
|
||||
async encrypt(algorithm, message) {
|
||||
const ad = algorithm.additionalData;
|
||||
const nonce = algorithm.iv;
|
||||
const nCMAC = await this._computeCMAC(nonce, this._prefixBlock0);
|
||||
const encrypted = await this._encryptCTR(message, nCMAC);
|
||||
const adCMAC = await this._computeCMAC(ad, this._prefixBlock1);
|
||||
const mac = await this._computeCMAC(encrypted, this._prefixBlock2);
|
||||
for (let i = 0; i < 16; i++) {
|
||||
mac[i] ^= nCMAC[i] ^ adCMAC[i];
|
||||
}
|
||||
const res = new Uint8Array(16 + encrypted.length);
|
||||
res.set(encrypted);
|
||||
res.set(mac, encrypted.length);
|
||||
return res;
|
||||
}
|
||||
|
||||
async decrypt(algorithm, data) {
|
||||
const encrypted = data.slice(0, data.length - 16);
|
||||
const ad = algorithm.additionalData;
|
||||
const nonce = algorithm.iv;
|
||||
const mac = data.slice(data.length - 16);
|
||||
const nCMAC = await this._computeCMAC(nonce, this._prefixBlock0);
|
||||
const adCMAC = await this._computeCMAC(ad, this._prefixBlock1);
|
||||
const computedMac = await this._computeCMAC(encrypted, this._prefixBlock2);
|
||||
for (let i = 0; i < 16; i++) {
|
||||
computedMac[i] ^= nCMAC[i] ^ adCMAC[i];
|
||||
}
|
||||
if (computedMac.length !== mac.length) {
|
||||
return null;
|
||||
}
|
||||
for (let i = 0; i < mac.length; i++) {
|
||||
if (computedMac[i] !== mac[i]) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
const res = await this._decryptCTR(encrypted, nCMAC);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
export function modPow(b, e, m) {
|
||||
let r = 1n;
|
||||
b = b % m;
|
||||
while (e > 0n) {
|
||||
if ((e & 1n) === 1n) {
|
||||
r = (r * b) % m;
|
||||
}
|
||||
e = e >> 1n;
|
||||
b = (b * b) % m;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
export function bigIntToU8Array(bigint, padLength=0) {
|
||||
let hex = bigint.toString(16);
|
||||
if (padLength === 0) {
|
||||
padLength = Math.ceil(hex.length / 2);
|
||||
}
|
||||
hex = hex.padStart(padLength * 2, '0');
|
||||
const length = hex.length / 2;
|
||||
const arr = new Uint8Array(length);
|
||||
for (let i = 0; i < length; i++) {
|
||||
arr[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
export function u8ArrayToBigInt(arr) {
|
||||
let hex = '0x';
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
hex += arr[i].toString(16).padStart(2, '0');
|
||||
}
|
||||
return BigInt(hex);
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
import { AESECBCipher, AESEAXCipher } from "./aes.js";
|
||||
import { DESCBCCipher, DESECBCipher } from "./des.js";
|
||||
import { RSACipher } from "./rsa.js";
|
||||
import { DHCipher } from "./dh.js";
|
||||
import { MD5 } from "./md5.js";
|
||||
|
||||
// A single interface for the cryptographic algorithms not supported by SubtleCrypto.
|
||||
// Both synchronous and asynchronous implmentations are allowed.
|
||||
class LegacyCrypto {
|
||||
constructor() {
|
||||
this._algorithms = {
|
||||
"AES-ECB": AESECBCipher,
|
||||
"AES-EAX": AESEAXCipher,
|
||||
"DES-ECB": DESECBCipher,
|
||||
"DES-CBC": DESCBCCipher,
|
||||
"RSA-PKCS1-v1_5": RSACipher,
|
||||
"DH": DHCipher,
|
||||
"MD5": MD5,
|
||||
};
|
||||
}
|
||||
|
||||
encrypt(algorithm, key, data) {
|
||||
if (key.algorithm.name !== algorithm.name) {
|
||||
throw new Error("algorithm does not match");
|
||||
}
|
||||
if (typeof key.encrypt !== "function") {
|
||||
throw new Error("key does not support encryption");
|
||||
}
|
||||
return key.encrypt(algorithm, data);
|
||||
}
|
||||
|
||||
decrypt(algorithm, key, data) {
|
||||
if (key.algorithm.name !== algorithm.name) {
|
||||
throw new Error("algorithm does not match");
|
||||
}
|
||||
if (typeof key.decrypt !== "function") {
|
||||
throw new Error("key does not support encryption");
|
||||
}
|
||||
return key.decrypt(algorithm, data);
|
||||
}
|
||||
|
||||
importKey(format, keyData, algorithm, extractable, keyUsages) {
|
||||
if (format !== "raw") {
|
||||
throw new Error("key format is not supported");
|
||||
}
|
||||
const alg = this._algorithms[algorithm.name];
|
||||
if (typeof alg === "undefined" || typeof alg.importKey !== "function") {
|
||||
throw new Error("algorithm is not supported");
|
||||
}
|
||||
return alg.importKey(keyData, algorithm, extractable, keyUsages);
|
||||
}
|
||||
|
||||
generateKey(algorithm, extractable, keyUsages) {
|
||||
const alg = this._algorithms[algorithm.name];
|
||||
if (typeof alg === "undefined" || typeof alg.generateKey !== "function") {
|
||||
throw new Error("algorithm is not supported");
|
||||
}
|
||||
return alg.generateKey(algorithm, extractable, keyUsages);
|
||||
}
|
||||
|
||||
exportKey(format, key) {
|
||||
if (format !== "raw") {
|
||||
throw new Error("key format is not supported");
|
||||
}
|
||||
if (typeof key.exportKey !== "function") {
|
||||
throw new Error("key does not support exportKey");
|
||||
}
|
||||
return key.exportKey();
|
||||
}
|
||||
|
||||
digest(algorithm, data) {
|
||||
const alg = this._algorithms[algorithm];
|
||||
if (typeof alg !== "function") {
|
||||
throw new Error("algorithm is not supported");
|
||||
}
|
||||
return alg(data);
|
||||
}
|
||||
|
||||
deriveBits(algorithm, key, length) {
|
||||
if (key.algorithm.name !== algorithm.name) {
|
||||
throw new Error("algorithm does not match");
|
||||
}
|
||||
if (typeof key.deriveBits !== "function") {
|
||||
throw new Error("key does not support deriveBits");
|
||||
}
|
||||
return key.deriveBits(algorithm, length);
|
||||
}
|
||||
}
|
||||
|
||||
export default new LegacyCrypto;
|
||||
@@ -81,7 +81,7 @@
|
||||
const PC2 = [13,16,10,23, 0, 4, 2,27,14, 5,20, 9,22,18,11, 3,
|
||||
25, 7,15, 6,26,19,12, 1,40,51,30,36,46,54,29,39,
|
||||
50,44,32,47,43,48,38,55,33,52,45,41,49,35,28,31 ],
|
||||
totrot = [ 1, 2, 4, 6, 8,10,12,14,15,17,19,21,23,25,27,28];
|
||||
totrot = [ 1, 2, 4, 6, 8,10,12,14,15,17,19,21,23,25,27,28];
|
||||
|
||||
const z = 0x0;
|
||||
let a,b,c,d,e,f;
|
||||
@@ -128,7 +128,7 @@ const SP8 = [b|f,z|e,a|z,c|f,b|z,b|f,z|d,b|z,a|d,c|z,c|f,a|e,c|e,a|f,z|e,z|d,
|
||||
|
||||
/* eslint-enable comma-spacing */
|
||||
|
||||
export default class DES {
|
||||
class DES {
|
||||
constructor(password) {
|
||||
this.keys = [];
|
||||
|
||||
@@ -258,9 +258,73 @@ export default class DES {
|
||||
}
|
||||
return b;
|
||||
}
|
||||
}
|
||||
|
||||
// Encrypt 16 bytes of text using passwd as key
|
||||
encrypt(t) {
|
||||
return this.enc8(t.slice(0, 8)).concat(this.enc8(t.slice(8, 16)));
|
||||
export class DESECBCipher {
|
||||
constructor() {
|
||||
this._cipher = null;
|
||||
}
|
||||
|
||||
get algorithm() {
|
||||
return { name: "DES-ECB" };
|
||||
}
|
||||
|
||||
static importKey(key, _algorithm, _extractable, _keyUsages) {
|
||||
const cipher = new DESECBCipher;
|
||||
cipher._importKey(key);
|
||||
return cipher;
|
||||
}
|
||||
|
||||
_importKey(key, _extractable, _keyUsages) {
|
||||
this._cipher = new DES(key);
|
||||
}
|
||||
|
||||
encrypt(_algorithm, plaintext) {
|
||||
const x = new Uint8Array(plaintext);
|
||||
if (x.length % 8 !== 0 || this._cipher === null) {
|
||||
return null;
|
||||
}
|
||||
const n = x.length / 8;
|
||||
for (let i = 0; i < n; i++) {
|
||||
x.set(this._cipher.enc8(x.slice(i * 8, i * 8 + 8)), i * 8);
|
||||
}
|
||||
return x;
|
||||
}
|
||||
}
|
||||
|
||||
export class DESCBCCipher {
|
||||
constructor() {
|
||||
this._cipher = null;
|
||||
}
|
||||
|
||||
get algorithm() {
|
||||
return { name: "DES-CBC" };
|
||||
}
|
||||
|
||||
static importKey(key, _algorithm, _extractable, _keyUsages) {
|
||||
const cipher = new DESCBCCipher;
|
||||
cipher._importKey(key);
|
||||
return cipher;
|
||||
}
|
||||
|
||||
_importKey(key) {
|
||||
this._cipher = new DES(key);
|
||||
}
|
||||
|
||||
encrypt(algorithm, plaintext) {
|
||||
const x = new Uint8Array(plaintext);
|
||||
let y = new Uint8Array(algorithm.iv);
|
||||
if (x.length % 8 !== 0 || this._cipher === null) {
|
||||
return null;
|
||||
}
|
||||
const n = x.length / 8;
|
||||
for (let i = 0; i < n; i++) {
|
||||
for (let j = 0; j < 8; j++) {
|
||||
y[j] ^= plaintext[i * 8 + j];
|
||||
}
|
||||
y = this._cipher.enc8(y);
|
||||
x.set(y, i * 8);
|
||||
}
|
||||
return x;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import { modPow, bigIntToU8Array, u8ArrayToBigInt } from "./bigint.js";
|
||||
|
||||
class DHPublicKey {
|
||||
constructor(key) {
|
||||
this._key = key;
|
||||
}
|
||||
|
||||
get algorithm() {
|
||||
return { name: "DH" };
|
||||
}
|
||||
|
||||
exportKey() {
|
||||
return this._key;
|
||||
}
|
||||
}
|
||||
|
||||
export class DHCipher {
|
||||
constructor() {
|
||||
this._g = null;
|
||||
this._p = null;
|
||||
this._gBigInt = null;
|
||||
this._pBigInt = null;
|
||||
this._privateKey = null;
|
||||
}
|
||||
|
||||
get algorithm() {
|
||||
return { name: "DH" };
|
||||
}
|
||||
|
||||
static generateKey(algorithm, _extractable) {
|
||||
const cipher = new DHCipher;
|
||||
cipher._generateKey(algorithm);
|
||||
return { privateKey: cipher, publicKey: new DHPublicKey(cipher._publicKey) };
|
||||
}
|
||||
|
||||
_generateKey(algorithm) {
|
||||
const g = algorithm.g;
|
||||
const p = algorithm.p;
|
||||
this._keyBytes = p.length;
|
||||
this._gBigInt = u8ArrayToBigInt(g);
|
||||
this._pBigInt = u8ArrayToBigInt(p);
|
||||
this._privateKey = window.crypto.getRandomValues(new Uint8Array(this._keyBytes));
|
||||
this._privateKeyBigInt = u8ArrayToBigInt(this._privateKey);
|
||||
this._publicKey = bigIntToU8Array(modPow(
|
||||
this._gBigInt, this._privateKeyBigInt, this._pBigInt), this._keyBytes);
|
||||
}
|
||||
|
||||
deriveBits(algorithm, length) {
|
||||
const bytes = Math.ceil(length / 8);
|
||||
const pkey = new Uint8Array(algorithm.public);
|
||||
const len = bytes > this._keyBytes ? bytes : this._keyBytes;
|
||||
const secret = modPow(u8ArrayToBigInt(pkey), this._privateKeyBigInt, this._pBigInt);
|
||||
return bigIntToU8Array(secret, len).slice(0, len);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* noVNC: HTML5 VNC client
|
||||
* Copyright (C) 2021 The noVNC Authors
|
||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||
*
|
||||
* See README.md for usage and integration instructions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Performs MD5 hashing on an array of bytes, returns an array of bytes
|
||||
*/
|
||||
|
||||
export async function MD5(d) {
|
||||
let s = "";
|
||||
for (let i = 0; i < d.length; i++) {
|
||||
s += String.fromCharCode(d[i]);
|
||||
}
|
||||
return M(V(Y(X(s), 8 * s.length)));
|
||||
}
|
||||
|
||||
function M(d) {
|
||||
let f = new Uint8Array(d.length);
|
||||
for (let i=0;i<d.length;i++) {
|
||||
f[i] = d.charCodeAt(i);
|
||||
}
|
||||
return f;
|
||||
}
|
||||
|
||||
function X(d) {
|
||||
let r = Array(d.length >> 2);
|
||||
for (let m = 0; m < r.length; m++) r[m] = 0;
|
||||
for (let m = 0; m < 8 * d.length; m += 8) r[m >> 5] |= (255 & d.charCodeAt(m / 8)) << m % 32;
|
||||
return r;
|
||||
}
|
||||
|
||||
function V(d) {
|
||||
let r = "";
|
||||
for (let m = 0; m < 32 * d.length; m += 8) r += String.fromCharCode(d[m >> 5] >>> m % 32 & 255);
|
||||
return r;
|
||||
}
|
||||
|
||||
function Y(d, g) {
|
||||
d[g >> 5] |= 128 << g % 32, d[14 + (g + 64 >>> 9 << 4)] = g;
|
||||
let m = 1732584193, f = -271733879, r = -1732584194, i = 271733878;
|
||||
for (let n = 0; n < d.length; n += 16) {
|
||||
let h = m,
|
||||
t = f,
|
||||
g = r,
|
||||
e = i;
|
||||
f = ii(f = ii(f = ii(f = ii(f = hh(f = hh(f = hh(f = hh(f = gg(f = gg(f = gg(f = gg(f = ff(f = ff(f = ff(f = ff(f, r = ff(r, i = ff(i, m = ff(m, f, r, i, d[n + 0], 7, -680876936), f, r, d[n + 1], 12, -389564586), m, f, d[n + 2], 17, 606105819), i, m, d[n + 3], 22, -1044525330), r = ff(r, i = ff(i, m = ff(m, f, r, i, d[n + 4], 7, -176418897), f, r, d[n + 5], 12, 1200080426), m, f, d[n + 6], 17, -1473231341), i, m, d[n + 7], 22, -45705983), r = ff(r, i = ff(i, m = ff(m, f, r, i, d[n + 8], 7, 1770035416), f, r, d[n + 9], 12, -1958414417), m, f, d[n + 10], 17, -42063), i, m, d[n + 11], 22, -1990404162), r = ff(r, i = ff(i, m = ff(m, f, r, i, d[n + 12], 7, 1804603682), f, r, d[n + 13], 12, -40341101), m, f, d[n + 14], 17, -1502002290), i, m, d[n + 15], 22, 1236535329), r = gg(r, i = gg(i, m = gg(m, f, r, i, d[n + 1], 5, -165796510), f, r, d[n + 6], 9, -1069501632), m, f, d[n + 11], 14, 643717713), i, m, d[n + 0], 20, -373897302), r = gg(r, i = gg(i, m = gg(m, f, r, i, d[n + 5], 5, -701558691), f, r, d[n + 10], 9, 38016083), m, f, d[n + 15], 14, -660478335), i, m, d[n + 4], 20, -405537848), r = gg(r, i = gg(i, m = gg(m, f, r, i, d[n + 9], 5, 568446438), f, r, d[n + 14], 9, -1019803690), m, f, d[n + 3], 14, -187363961), i, m, d[n + 8], 20, 1163531501), r = gg(r, i = gg(i, m = gg(m, f, r, i, d[n + 13], 5, -1444681467), f, r, d[n + 2], 9, -51403784), m, f, d[n + 7], 14, 1735328473), i, m, d[n + 12], 20, -1926607734), r = hh(r, i = hh(i, m = hh(m, f, r, i, d[n + 5], 4, -378558), f, r, d[n + 8], 11, -2022574463), m, f, d[n + 11], 16, 1839030562), i, m, d[n + 14], 23, -35309556), r = hh(r, i = hh(i, m = hh(m, f, r, i, d[n + 1], 4, -1530992060), f, r, d[n + 4], 11, 1272893353), m, f, d[n + 7], 16, -155497632), i, m, d[n + 10], 23, -1094730640), r = hh(r, i = hh(i, m = hh(m, f, r, i, d[n + 13], 4, 681279174), f, r, d[n + 0], 11, -358537222), m, f, d[n + 3], 16, -722521979), i, m, d[n + 6], 23, 76029189), r = hh(r, i = hh(i, m = hh(m, f, r, i, d[n + 9], 4, -640364487), f, r, d[n + 12], 11, -421815835), m, f, d[n + 15], 16, 530742520), i, m, d[n + 2], 23, -995338651), r = ii(r, i = ii(i, m = ii(m, f, r, i, d[n + 0], 6, -198630844), f, r, d[n + 7], 10, 1126891415), m, f, d[n + 14], 15, -1416354905), i, m, d[n + 5], 21, -57434055), r = ii(r, i = ii(i, m = ii(m, f, r, i, d[n + 12], 6, 1700485571), f, r, d[n + 3], 10, -1894986606), m, f, d[n + 10], 15, -1051523), i, m, d[n + 1], 21, -2054922799), r = ii(r, i = ii(i, m = ii(m, f, r, i, d[n + 8], 6, 1873313359), f, r, d[n + 15], 10, -30611744), m, f, d[n + 6], 15, -1560198380), i, m, d[n + 13], 21, 1309151649), r = ii(r, i = ii(i, m = ii(m, f, r, i, d[n + 4], 6, -145523070), f, r, d[n + 11], 10, -1120210379), m, f, d[n + 2], 15, 718787259), i, m, d[n + 9], 21, -343485551), m = add(m, h), f = add(f, t), r = add(r, g), i = add(i, e);
|
||||
}
|
||||
return Array(m, f, r, i);
|
||||
}
|
||||
|
||||
function cmn(d, g, m, f, r, i) {
|
||||
return add(rol(add(add(g, d), add(f, i)), r), m);
|
||||
}
|
||||
|
||||
function ff(d, g, m, f, r, i, n) {
|
||||
return cmn(g & m | ~g & f, d, g, r, i, n);
|
||||
}
|
||||
|
||||
function gg(d, g, m, f, r, i, n) {
|
||||
return cmn(g & f | m & ~f, d, g, r, i, n);
|
||||
}
|
||||
|
||||
function hh(d, g, m, f, r, i, n) {
|
||||
return cmn(g ^ m ^ f, d, g, r, i, n);
|
||||
}
|
||||
|
||||
function ii(d, g, m, f, r, i, n) {
|
||||
return cmn(m ^ (g | ~f), d, g, r, i, n);
|
||||
}
|
||||
|
||||
function add(d, g) {
|
||||
let m = (65535 & d) + (65535 & g);
|
||||
return (d >> 16) + (g >> 16) + (m >> 16) << 16 | 65535 & m;
|
||||
}
|
||||
|
||||
function rol(d, g) {
|
||||
return d << g | d >>> 32 - g;
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
import Base64 from "../base64.js";
|
||||
import { modPow, bigIntToU8Array, u8ArrayToBigInt } from "./bigint.js";
|
||||
|
||||
export class RSACipher {
|
||||
constructor() {
|
||||
this._keyLength = 0;
|
||||
this._keyBytes = 0;
|
||||
this._n = null;
|
||||
this._e = null;
|
||||
this._d = null;
|
||||
this._nBigInt = null;
|
||||
this._eBigInt = null;
|
||||
this._dBigInt = null;
|
||||
this._extractable = false;
|
||||
}
|
||||
|
||||
get algorithm() {
|
||||
return { name: "RSA-PKCS1-v1_5" };
|
||||
}
|
||||
|
||||
_base64urlDecode(data) {
|
||||
data = data.replace(/-/g, "+").replace(/_/g, "/");
|
||||
data = data.padEnd(Math.ceil(data.length / 4) * 4, "=");
|
||||
return Base64.decode(data);
|
||||
}
|
||||
|
||||
_padArray(arr, length) {
|
||||
const res = new Uint8Array(length);
|
||||
res.set(arr, length - arr.length);
|
||||
return res;
|
||||
}
|
||||
|
||||
static async generateKey(algorithm, extractable, _keyUsages) {
|
||||
const cipher = new RSACipher;
|
||||
await cipher._generateKey(algorithm, extractable);
|
||||
return { privateKey: cipher };
|
||||
}
|
||||
|
||||
async _generateKey(algorithm, extractable) {
|
||||
this._keyLength = algorithm.modulusLength;
|
||||
this._keyBytes = Math.ceil(this._keyLength / 8);
|
||||
const key = await window.crypto.subtle.generateKey(
|
||||
{
|
||||
name: "RSA-OAEP",
|
||||
modulusLength: algorithm.modulusLength,
|
||||
publicExponent: algorithm.publicExponent,
|
||||
hash: {name: "SHA-256"},
|
||||
},
|
||||
true, ["encrypt", "decrypt"]);
|
||||
const privateKey = await window.crypto.subtle.exportKey("jwk", key.privateKey);
|
||||
this._n = this._padArray(this._base64urlDecode(privateKey.n), this._keyBytes);
|
||||
this._nBigInt = u8ArrayToBigInt(this._n);
|
||||
this._e = this._padArray(this._base64urlDecode(privateKey.e), this._keyBytes);
|
||||
this._eBigInt = u8ArrayToBigInt(this._e);
|
||||
this._d = this._padArray(this._base64urlDecode(privateKey.d), this._keyBytes);
|
||||
this._dBigInt = u8ArrayToBigInt(this._d);
|
||||
this._extractable = extractable;
|
||||
}
|
||||
|
||||
static async importKey(key, _algorithm, extractable, keyUsages) {
|
||||
if (keyUsages.length !== 1 || keyUsages[0] !== "encrypt") {
|
||||
throw new Error("only support importing RSA public key");
|
||||
}
|
||||
const cipher = new RSACipher;
|
||||
await cipher._importKey(key, extractable);
|
||||
return cipher;
|
||||
}
|
||||
|
||||
async _importKey(key, extractable) {
|
||||
const n = key.n;
|
||||
const e = key.e;
|
||||
if (n.length !== e.length) {
|
||||
throw new Error("the sizes of modulus and public exponent do not match");
|
||||
}
|
||||
this._keyBytes = n.length;
|
||||
this._keyLength = this._keyBytes * 8;
|
||||
this._n = new Uint8Array(this._keyBytes);
|
||||
this._e = new Uint8Array(this._keyBytes);
|
||||
this._n.set(n);
|
||||
this._e.set(e);
|
||||
this._nBigInt = u8ArrayToBigInt(this._n);
|
||||
this._eBigInt = u8ArrayToBigInt(this._e);
|
||||
this._extractable = extractable;
|
||||
}
|
||||
|
||||
async encrypt(_algorithm, message) {
|
||||
if (message.length > this._keyBytes - 11) {
|
||||
return null;
|
||||
}
|
||||
const ps = new Uint8Array(this._keyBytes - message.length - 3);
|
||||
window.crypto.getRandomValues(ps);
|
||||
for (let i = 0; i < ps.length; i++) {
|
||||
ps[i] = Math.floor(ps[i] * 254 / 255 + 1);
|
||||
}
|
||||
const em = new Uint8Array(this._keyBytes);
|
||||
em[1] = 0x02;
|
||||
em.set(ps, 2);
|
||||
em.set(message, ps.length + 3);
|
||||
const emBigInt = u8ArrayToBigInt(em);
|
||||
const c = modPow(emBigInt, this._eBigInt, this._nBigInt);
|
||||
return bigIntToU8Array(c, this._keyBytes);
|
||||
}
|
||||
|
||||
async decrypt(_algorithm, message) {
|
||||
if (message.length !== this._keyBytes) {
|
||||
return null;
|
||||
}
|
||||
const msgBigInt = u8ArrayToBigInt(message);
|
||||
const emBigInt = modPow(msgBigInt, this._dBigInt, this._nBigInt);
|
||||
const em = bigIntToU8Array(emBigInt, this._keyBytes);
|
||||
if (em[0] !== 0x00 || em[1] !== 0x02) {
|
||||
return null;
|
||||
}
|
||||
let i = 2;
|
||||
for (; i < em.length; i++) {
|
||||
if (em[i] === 0x00) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i === em.length) {
|
||||
return null;
|
||||
}
|
||||
return em.slice(i + 1, em.length);
|
||||
}
|
||||
|
||||
async exportKey() {
|
||||
if (!this._extractable) {
|
||||
throw new Error("key is not extractable");
|
||||
}
|
||||
return { n: this._n, e: this._e, d: this._d };
|
||||
}
|
||||
}
|
||||
@@ -31,10 +31,7 @@ export default class HextileDecoder {
|
||||
return false;
|
||||
}
|
||||
|
||||
let rQ = sock.rQ;
|
||||
let rQi = sock.rQi;
|
||||
|
||||
let subencoding = rQ[rQi]; // Peek
|
||||
let subencoding = sock.rQpeek8();
|
||||
if (subencoding > 30) { // Raw
|
||||
throw new Error("Illegal hextile subencoding (subencoding: " +
|
||||
subencoding + ")");
|
||||
@@ -65,7 +62,7 @@ export default class HextileDecoder {
|
||||
return false;
|
||||
}
|
||||
|
||||
let subrects = rQ[rQi + bytes - 1]; // Peek
|
||||
let subrects = sock.rQpeekBytes(bytes).at(-1);
|
||||
if (subencoding & 0x10) { // SubrectsColoured
|
||||
bytes += subrects * (4 + 2);
|
||||
} else {
|
||||
@@ -79,7 +76,7 @@ export default class HextileDecoder {
|
||||
}
|
||||
|
||||
// We know the encoding and have a whole tile
|
||||
rQi++;
|
||||
sock.rQshift8();
|
||||
if (subencoding === 0) {
|
||||
if (this._lastsubencoding & 0x01) {
|
||||
// Weird: ignore blanks are RAW
|
||||
@@ -89,42 +86,36 @@ export default class HextileDecoder {
|
||||
}
|
||||
} else if (subencoding & 0x01) { // Raw
|
||||
let pixels = tw * th;
|
||||
let data = sock.rQshiftBytes(pixels * 4, false);
|
||||
// Max sure the image is fully opaque
|
||||
for (let i = 0;i < pixels;i++) {
|
||||
rQ[rQi + i * 4 + 3] = 255;
|
||||
data[i * 4 + 3] = 255;
|
||||
}
|
||||
display.blitImage(tx, ty, tw, th, rQ, rQi);
|
||||
rQi += bytes - 1;
|
||||
display.blitImage(tx, ty, tw, th, data, 0);
|
||||
} else {
|
||||
if (subencoding & 0x02) { // Background
|
||||
this._background = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]];
|
||||
rQi += 4;
|
||||
this._background = new Uint8Array(sock.rQshiftBytes(4));
|
||||
}
|
||||
if (subencoding & 0x04) { // Foreground
|
||||
this._foreground = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]];
|
||||
rQi += 4;
|
||||
this._foreground = new Uint8Array(sock.rQshiftBytes(4));
|
||||
}
|
||||
|
||||
this._startTile(tx, ty, tw, th, this._background);
|
||||
if (subencoding & 0x08) { // AnySubrects
|
||||
let subrects = rQ[rQi];
|
||||
rQi++;
|
||||
let subrects = sock.rQshift8();
|
||||
|
||||
for (let s = 0; s < subrects; s++) {
|
||||
let color;
|
||||
if (subencoding & 0x10) { // SubrectsColoured
|
||||
color = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]];
|
||||
rQi += 4;
|
||||
color = sock.rQshiftBytes(4);
|
||||
} else {
|
||||
color = this._foreground;
|
||||
}
|
||||
const xy = rQ[rQi];
|
||||
rQi++;
|
||||
const xy = sock.rQshift8();
|
||||
const sx = (xy >> 4);
|
||||
const sy = (xy & 0x0f);
|
||||
|
||||
const wh = rQ[rQi];
|
||||
rQi++;
|
||||
const wh = sock.rQshift8();
|
||||
const sw = (wh >> 4) + 1;
|
||||
const sh = (wh & 0x0f) + 1;
|
||||
|
||||
@@ -133,7 +124,6 @@ export default class HextileDecoder {
|
||||
}
|
||||
this._finishTile(display);
|
||||
}
|
||||
sock.rQi = rQi;
|
||||
this._lastsubencoding = subencoding;
|
||||
this._tiles--;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
export default class JPEGDecoder {
|
||||
constructor() {
|
||||
// RealVNC will reuse the quantization tables
|
||||
// and Huffman tables, so we need to cache them.
|
||||
this._cachedQuantTables = [];
|
||||
this._cachedHuffmanTables = [];
|
||||
|
||||
this._segments = [];
|
||||
}
|
||||
|
||||
decodeRect(x, y, width, height, sock, display, depth) {
|
||||
// A rect of JPEG encodings is simply a JPEG file
|
||||
while (true) {
|
||||
let segment = this._readSegment(sock);
|
||||
if (segment === null) {
|
||||
return false;
|
||||
}
|
||||
this._segments.push(segment);
|
||||
// End of image?
|
||||
if (segment[1] === 0xD9) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let huffmanTables = [];
|
||||
let quantTables = [];
|
||||
for (let segment of this._segments) {
|
||||
let type = segment[1];
|
||||
if (type === 0xC4) {
|
||||
// Huffman tables
|
||||
huffmanTables.push(segment);
|
||||
} else if (type === 0xDB) {
|
||||
// Quantization tables
|
||||
quantTables.push(segment);
|
||||
}
|
||||
}
|
||||
|
||||
const sofIndex = this._segments.findIndex(
|
||||
x => x[1] == 0xC0 || x[1] == 0xC2
|
||||
);
|
||||
if (sofIndex == -1) {
|
||||
throw new Error("Illegal JPEG image without SOF");
|
||||
}
|
||||
|
||||
if (quantTables.length === 0) {
|
||||
this._segments.splice(sofIndex+1, 0,
|
||||
...this._cachedQuantTables);
|
||||
}
|
||||
if (huffmanTables.length === 0) {
|
||||
this._segments.splice(sofIndex+1, 0,
|
||||
...this._cachedHuffmanTables);
|
||||
}
|
||||
|
||||
let length = 0;
|
||||
for (let segment of this._segments) {
|
||||
length += segment.length;
|
||||
}
|
||||
|
||||
let data = new Uint8Array(length);
|
||||
length = 0;
|
||||
for (let segment of this._segments) {
|
||||
data.set(segment, length);
|
||||
length += segment.length;
|
||||
}
|
||||
|
||||
display.imageRect(x, y, width, height, "image/jpeg", data);
|
||||
|
||||
if (huffmanTables.length !== 0) {
|
||||
this._cachedHuffmanTables = huffmanTables;
|
||||
}
|
||||
if (quantTables.length !== 0) {
|
||||
this._cachedQuantTables = quantTables;
|
||||
}
|
||||
|
||||
this._segments = [];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
_readSegment(sock) {
|
||||
if (sock.rQwait("JPEG", 2)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let marker = sock.rQshift8();
|
||||
if (marker != 0xFF) {
|
||||
throw new Error("Illegal JPEG marker received (byte: " +
|
||||
marker + ")");
|
||||
}
|
||||
let type = sock.rQshift8();
|
||||
if (type >= 0xD0 && type <= 0xD9 || type == 0x01) {
|
||||
// No length after marker
|
||||
return new Uint8Array([marker, type]);
|
||||
}
|
||||
|
||||
if (sock.rQwait("JPEG", 2, 2)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let length = sock.rQshift16();
|
||||
if (length < 2) {
|
||||
throw new Error("Illegal JPEG length received (length: " +
|
||||
length + ")");
|
||||
}
|
||||
|
||||
if (sock.rQwait("JPEG", length-2, 4)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let extra = 0;
|
||||
if (type === 0xDA) {
|
||||
// start of scan
|
||||
extra += 2;
|
||||
while (true) {
|
||||
if (sock.rQwait("JPEG", length-2+extra, 4)) {
|
||||
return null;
|
||||
}
|
||||
let data = sock.rQpeekBytes(length-2+extra, false);
|
||||
if (data.at(-2) === 0xFF && data.at(-1) !== 0x00 &&
|
||||
!(data.at(-1) >= 0xD0 && data.at(-1) <= 0xD7)) {
|
||||
extra -= 2;
|
||||
break;
|
||||
}
|
||||
extra++;
|
||||
}
|
||||
}
|
||||
|
||||
let segment = new Uint8Array(2 + length + extra);
|
||||
segment[0] = marker;
|
||||
segment[1] = type;
|
||||
segment[2] = length >> 8;
|
||||
segment[3] = length;
|
||||
segment.set(sock.rQshiftBytes(length-2+extra, false), 4);
|
||||
|
||||
return segment;
|
||||
}
|
||||
}
|
||||
@@ -24,41 +24,34 @@ export default class RawDecoder {
|
||||
const pixelSize = depth == 8 ? 1 : 4;
|
||||
const bytesPerLine = width * pixelSize;
|
||||
|
||||
if (sock.rQwait("RAW", bytesPerLine)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const curY = y + (height - this._lines);
|
||||
const currHeight = Math.min(this._lines,
|
||||
Math.floor(sock.rQlen / bytesPerLine));
|
||||
const pixels = width * currHeight;
|
||||
|
||||
let data = sock.rQ;
|
||||
let index = sock.rQi;
|
||||
|
||||
// Convert data if needed
|
||||
if (depth == 8) {
|
||||
const newdata = new Uint8Array(pixels * 4);
|
||||
for (let i = 0; i < pixels; i++) {
|
||||
newdata[i * 4 + 0] = ((data[index + i] >> 0) & 0x3) * 255 / 3;
|
||||
newdata[i * 4 + 1] = ((data[index + i] >> 2) & 0x3) * 255 / 3;
|
||||
newdata[i * 4 + 2] = ((data[index + i] >> 4) & 0x3) * 255 / 3;
|
||||
newdata[i * 4 + 3] = 255;
|
||||
while (this._lines > 0) {
|
||||
if (sock.rQwait("RAW", bytesPerLine)) {
|
||||
return false;
|
||||
}
|
||||
data = newdata;
|
||||
index = 0;
|
||||
}
|
||||
|
||||
// Max sure the image is fully opaque
|
||||
for (let i = 0; i < pixels; i++) {
|
||||
data[i * 4 + 3] = 255;
|
||||
}
|
||||
const curY = y + (height - this._lines);
|
||||
|
||||
display.blitImage(x, curY, width, currHeight, data, index);
|
||||
sock.rQskipBytes(currHeight * bytesPerLine);
|
||||
this._lines -= currHeight;
|
||||
if (this._lines > 0) {
|
||||
return false;
|
||||
let data = sock.rQshiftBytes(bytesPerLine, false);
|
||||
|
||||
// Convert data if needed
|
||||
if (depth == 8) {
|
||||
const newdata = new Uint8Array(width * 4);
|
||||
for (let i = 0; i < width; i++) {
|
||||
newdata[i * 4 + 0] = ((data[i] >> 0) & 0x3) * 255 / 3;
|
||||
newdata[i * 4 + 1] = ((data[i] >> 2) & 0x3) * 255 / 3;
|
||||
newdata[i * 4 + 2] = ((data[i] >> 4) & 0x3) * 255 / 3;
|
||||
newdata[i * 4 + 3] = 255;
|
||||
}
|
||||
data = newdata;
|
||||
}
|
||||
|
||||
// Max sure the image is fully opaque
|
||||
for (let i = 0; i < width; i++) {
|
||||
data[i * 4 + 3] = 255;
|
||||
}
|
||||
|
||||
display.blitImage(x, curY, width, 1, data, 0);
|
||||
this._lines--;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -76,12 +76,8 @@ export default class TightDecoder {
|
||||
return false;
|
||||
}
|
||||
|
||||
const rQi = sock.rQi;
|
||||
const rQ = sock.rQ;
|
||||
|
||||
display.fillRect(x, y, width, height,
|
||||
[rQ[rQi], rQ[rQi + 1], rQ[rQi + 2]], false);
|
||||
sock.rQskipBytes(3);
|
||||
let pixel = sock.rQshiftBytes(3);
|
||||
display.fillRect(x, y, width, height, pixel, false);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -316,7 +312,7 @@ export default class TightDecoder {
|
||||
return null;
|
||||
}
|
||||
|
||||
let data = sock.rQshiftBytes(this._len);
|
||||
let data = sock.rQshiftBytes(this._len, false);
|
||||
this._len = 0;
|
||||
|
||||
return data;
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
* noVNC: HTML5 VNC client
|
||||
* Copyright (C) 2021 The noVNC Authors
|
||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||
*
|
||||
* See README.md for usage and integration instructions.
|
||||
*
|
||||
*/
|
||||
|
||||
import Inflate from "../inflator.js";
|
||||
|
||||
const ZRLE_TILE_WIDTH = 64;
|
||||
const ZRLE_TILE_HEIGHT = 64;
|
||||
|
||||
export default class ZRLEDecoder {
|
||||
constructor() {
|
||||
this._length = 0;
|
||||
this._inflator = new Inflate();
|
||||
|
||||
this._pixelBuffer = new Uint8Array(ZRLE_TILE_WIDTH * ZRLE_TILE_HEIGHT * 4);
|
||||
this._tileBuffer = new Uint8Array(ZRLE_TILE_WIDTH * ZRLE_TILE_HEIGHT * 4);
|
||||
}
|
||||
|
||||
decodeRect(x, y, width, height, sock, display, depth) {
|
||||
if (this._length === 0) {
|
||||
if (sock.rQwait("ZLib data length", 4)) {
|
||||
return false;
|
||||
}
|
||||
this._length = sock.rQshift32();
|
||||
}
|
||||
if (sock.rQwait("Zlib data", this._length)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const data = sock.rQshiftBytes(this._length, false);
|
||||
|
||||
this._inflator.setInput(data);
|
||||
|
||||
for (let ty = y; ty < y + height; ty += ZRLE_TILE_HEIGHT) {
|
||||
let th = Math.min(ZRLE_TILE_HEIGHT, y + height - ty);
|
||||
|
||||
for (let tx = x; tx < x + width; tx += ZRLE_TILE_WIDTH) {
|
||||
let tw = Math.min(ZRLE_TILE_WIDTH, x + width - tx);
|
||||
|
||||
const tileSize = tw * th;
|
||||
const subencoding = this._inflator.inflate(1)[0];
|
||||
if (subencoding === 0) {
|
||||
// raw data
|
||||
const data = this._readPixels(tileSize);
|
||||
display.blitImage(tx, ty, tw, th, data, 0, false);
|
||||
} else if (subencoding === 1) {
|
||||
// solid
|
||||
const background = this._readPixels(1);
|
||||
display.fillRect(tx, ty, tw, th, [background[0], background[1], background[2]]);
|
||||
} else if (subencoding >= 2 && subencoding <= 16) {
|
||||
const data = this._decodePaletteTile(subencoding, tileSize, tw, th);
|
||||
display.blitImage(tx, ty, tw, th, data, 0, false);
|
||||
} else if (subencoding === 128) {
|
||||
const data = this._decodeRLETile(tileSize);
|
||||
display.blitImage(tx, ty, tw, th, data, 0, false);
|
||||
} else if (subencoding >= 130 && subencoding <= 255) {
|
||||
const data = this._decodeRLEPaletteTile(subencoding - 128, tileSize);
|
||||
display.blitImage(tx, ty, tw, th, data, 0, false);
|
||||
} else {
|
||||
throw new Error('Unknown subencoding: ' + subencoding);
|
||||
}
|
||||
}
|
||||
}
|
||||
this._length = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
_getBitsPerPixelInPalette(paletteSize) {
|
||||
if (paletteSize <= 2) {
|
||||
return 1;
|
||||
} else if (paletteSize <= 4) {
|
||||
return 2;
|
||||
} else if (paletteSize <= 16) {
|
||||
return 4;
|
||||
}
|
||||
}
|
||||
|
||||
_readPixels(pixels) {
|
||||
let data = this._pixelBuffer;
|
||||
const buffer = this._inflator.inflate(3*pixels);
|
||||
for (let i = 0, j = 0; i < pixels*4; i += 4, j += 3) {
|
||||
data[i] = buffer[j];
|
||||
data[i + 1] = buffer[j + 1];
|
||||
data[i + 2] = buffer[j + 2];
|
||||
data[i + 3] = 255; // Add the Alpha
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
_decodePaletteTile(paletteSize, tileSize, tilew, tileh) {
|
||||
const data = this._tileBuffer;
|
||||
const palette = this._readPixels(paletteSize);
|
||||
const bitsPerPixel = this._getBitsPerPixelInPalette(paletteSize);
|
||||
const mask = (1 << bitsPerPixel) - 1;
|
||||
|
||||
let offset = 0;
|
||||
let encoded = this._inflator.inflate(1)[0];
|
||||
|
||||
for (let y=0; y<tileh; y++) {
|
||||
let shift = 8-bitsPerPixel;
|
||||
for (let x=0; x<tilew; x++) {
|
||||
if (shift<0) {
|
||||
shift=8-bitsPerPixel;
|
||||
encoded = this._inflator.inflate(1)[0];
|
||||
}
|
||||
let indexInPalette = (encoded>>shift) & mask;
|
||||
|
||||
data[offset] = palette[indexInPalette * 4];
|
||||
data[offset + 1] = palette[indexInPalette * 4 + 1];
|
||||
data[offset + 2] = palette[indexInPalette * 4 + 2];
|
||||
data[offset + 3] = palette[indexInPalette * 4 + 3];
|
||||
offset += 4;
|
||||
shift-=bitsPerPixel;
|
||||
}
|
||||
if (shift<8-bitsPerPixel && y<tileh-1) {
|
||||
encoded = this._inflator.inflate(1)[0];
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
_decodeRLETile(tileSize) {
|
||||
const data = this._tileBuffer;
|
||||
let i = 0;
|
||||
while (i < tileSize) {
|
||||
const pixel = this._readPixels(1);
|
||||
const length = this._readRLELength();
|
||||
for (let j = 0; j < length; j++) {
|
||||
data[i * 4] = pixel[0];
|
||||
data[i * 4 + 1] = pixel[1];
|
||||
data[i * 4 + 2] = pixel[2];
|
||||
data[i * 4 + 3] = pixel[3];
|
||||
i++;
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
_decodeRLEPaletteTile(paletteSize, tileSize) {
|
||||
const data = this._tileBuffer;
|
||||
|
||||
// palette
|
||||
const palette = this._readPixels(paletteSize);
|
||||
|
||||
let offset = 0;
|
||||
while (offset < tileSize) {
|
||||
let indexInPalette = this._inflator.inflate(1)[0];
|
||||
let length = 1;
|
||||
if (indexInPalette >= 128) {
|
||||
indexInPalette -= 128;
|
||||
length = this._readRLELength();
|
||||
}
|
||||
if (indexInPalette > paletteSize) {
|
||||
throw new Error('Too big index in palette: ' + indexInPalette + ', palette size: ' + paletteSize);
|
||||
}
|
||||
if (offset + length > tileSize) {
|
||||
throw new Error('Too big rle length in palette mode: ' + length + ', allowed length is: ' + (tileSize - offset));
|
||||
}
|
||||
|
||||
for (let j = 0; j < length; j++) {
|
||||
data[offset * 4] = palette[indexInPalette * 4];
|
||||
data[offset * 4 + 1] = palette[indexInPalette * 4 + 1];
|
||||
data[offset * 4 + 2] = palette[indexInPalette * 4 + 2];
|
||||
data[offset * 4 + 3] = palette[indexInPalette * 4 + 3];
|
||||
offset++;
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
_readRLELength() {
|
||||
let length = 0;
|
||||
let current = 0;
|
||||
do {
|
||||
current = this._inflator.inflate(1)[0];
|
||||
length += current;
|
||||
} while (current === 255);
|
||||
return length + 1;
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import { deflateInit, deflate } from "../vendor/pako/lib/zlib/deflate.js";
|
||||
import { Z_FULL_FLUSH } from "../vendor/pako/lib/zlib/deflate.js";
|
||||
import { Z_FULL_FLUSH, Z_DEFAULT_COMPRESSION } from "../vendor/pako/lib/zlib/deflate.js";
|
||||
import ZStream from "../vendor/pako/lib/zlib/zstream.js";
|
||||
|
||||
export default class Deflator {
|
||||
@@ -15,9 +15,8 @@ export default class Deflator {
|
||||
this.strm = new ZStream();
|
||||
this.chunkSize = 1024 * 10 * 10;
|
||||
this.outputBuffer = new Uint8Array(this.chunkSize);
|
||||
this.windowBits = 5;
|
||||
|
||||
deflateInit(this.strm, this.windowBits);
|
||||
deflateInit(this.strm, Z_DEFAULT_COMPRESSION);
|
||||
}
|
||||
|
||||
deflate(inData) {
|
||||
|
||||
@@ -15,7 +15,7 @@ export default class Display {
|
||||
this._drawCtx = null;
|
||||
|
||||
this._renderQ = []; // queue drawing actions for in-oder rendering
|
||||
this._flushing = false;
|
||||
this._flushPromise = null;
|
||||
|
||||
// the full frame buffer (logical canvas) size
|
||||
this._fbWidth = 0;
|
||||
@@ -61,10 +61,6 @@ export default class Display {
|
||||
|
||||
this._scale = 1.0;
|
||||
this._clipViewport = false;
|
||||
|
||||
// ===== EVENT HANDLERS =====
|
||||
|
||||
this.onflush = () => {}; // A flush request has finished
|
||||
}
|
||||
|
||||
// ===== PROPERTIES =====
|
||||
@@ -224,6 +220,18 @@ export default class Display {
|
||||
this.viewportChangePos(0, 0);
|
||||
}
|
||||
|
||||
getImageData() {
|
||||
return this._drawCtx.getImageData(0, 0, this.width, this.height);
|
||||
}
|
||||
|
||||
toDataURL(type, encoderOptions) {
|
||||
return this._backbuffer.toDataURL(type, encoderOptions);
|
||||
}
|
||||
|
||||
toBlob(callback, type, quality) {
|
||||
return this._backbuffer.toBlob(callback, type, quality);
|
||||
}
|
||||
|
||||
// Track what parts of the visible canvas that need updating
|
||||
_damage(x, y, w, h) {
|
||||
if (x < this._damageBounds.left) {
|
||||
@@ -294,9 +302,14 @@ export default class Display {
|
||||
|
||||
flush() {
|
||||
if (this._renderQ.length === 0) {
|
||||
this.onflush();
|
||||
return Promise.resolve();
|
||||
} else {
|
||||
this._flushing = true;
|
||||
if (this._flushPromise === null) {
|
||||
this._flushPromise = new Promise((resolve) => {
|
||||
this._flushResolve = resolve;
|
||||
});
|
||||
}
|
||||
return this._flushPromise;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -505,9 +518,11 @@ export default class Display {
|
||||
}
|
||||
}
|
||||
|
||||
if (this._renderQ.length === 0 && this._flushing) {
|
||||
this._flushing = false;
|
||||
this.onflush();
|
||||
if (this._renderQ.length === 0 &&
|
||||
this._flushPromise !== null) {
|
||||
this._flushResolve();
|
||||
this._flushPromise = null;
|
||||
this._flushResolve = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,9 @@ export const encodings = {
|
||||
encodingRRE: 2,
|
||||
encodingHextile: 5,
|
||||
encodingTight: 7,
|
||||
encodingZRLE: 16,
|
||||
encodingTightPNG: -260,
|
||||
encodingJPEG: 21,
|
||||
|
||||
pseudoEncodingQualityLevel9: -23,
|
||||
pseudoEncodingQualityLevel0: -32,
|
||||
@@ -20,6 +22,7 @@ export const encodings = {
|
||||
pseudoEncodingLastRect: -224,
|
||||
pseudoEncodingCursor: -239,
|
||||
pseudoEncodingQEMUExtendedKeyEvent: -258,
|
||||
pseudoEncodingQEMULedEvent: -261,
|
||||
pseudoEncodingDesktopName: -307,
|
||||
pseudoEncodingExtendedDesktopSize: -308,
|
||||
pseudoEncodingXvp: -309,
|
||||
@@ -38,7 +41,9 @@ export function encodingName(num) {
|
||||
case encodings.encodingRRE: return "RRE";
|
||||
case encodings.encodingHextile: return "Hextile";
|
||||
case encodings.encodingTight: return "Tight";
|
||||
case encodings.encodingZRLE: return "ZRLE";
|
||||
case encodings.encodingTightPNG: return "TightPNG";
|
||||
case encodings.encodingJPEG: return "JPEG";
|
||||
default: return "[unknown encoding " + num + "]";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,9 +14,8 @@ export default class Inflate {
|
||||
this.strm = new ZStream();
|
||||
this.chunkSize = 1024 * 10 * 10;
|
||||
this.strm.output = new Uint8Array(this.chunkSize);
|
||||
this.windowBits = 5;
|
||||
|
||||
inflateInit(this.strm, this.windowBits);
|
||||
inflateInit(this.strm);
|
||||
}
|
||||
|
||||
setInput(data) {
|
||||
|
||||
@@ -36,7 +36,7 @@ export default class Keyboard {
|
||||
|
||||
// ===== PRIVATE METHODS =====
|
||||
|
||||
_sendKeyEvent(keysym, code, down) {
|
||||
_sendKeyEvent(keysym, code, down, numlock = null, capslock = null) {
|
||||
if (down) {
|
||||
this._keyDownList[code] = keysym;
|
||||
} else {
|
||||
@@ -48,8 +48,8 @@ export default class Keyboard {
|
||||
}
|
||||
|
||||
Log.Debug("onkeyevent " + (down ? "down" : "up") +
|
||||
", keysym: " + keysym, ", code: " + code);
|
||||
this.onkeyevent(keysym, code, down);
|
||||
", keysym: " + keysym, ", code: " + code, + ", numlock: " + numlock + ", capslock: " + capslock);
|
||||
this.onkeyevent(keysym, code, down, numlock, capslock);
|
||||
}
|
||||
|
||||
_getKeyCode(e) {
|
||||
@@ -86,6 +86,14 @@ export default class Keyboard {
|
||||
_handleKeyDown(e) {
|
||||
const code = this._getKeyCode(e);
|
||||
let keysym = KeyboardUtil.getKeysym(e);
|
||||
let numlock = e.getModifierState('NumLock');
|
||||
let capslock = e.getModifierState('CapsLock');
|
||||
|
||||
// getModifierState for NumLock is not supported on mac and ios and always returns false.
|
||||
// Set to null to indicate unknown/unsupported instead.
|
||||
if (browser.isMac() || browser.isIOS()) {
|
||||
numlock = null;
|
||||
}
|
||||
|
||||
// Windows doesn't have a proper AltGr, but handles it using
|
||||
// fake Ctrl+Alt. However the remote end might not be Windows,
|
||||
@@ -107,7 +115,7 @@ export default class Keyboard {
|
||||
// key to "AltGraph".
|
||||
keysym = KeyTable.XK_ISO_Level3_Shift;
|
||||
} else {
|
||||
this._sendKeyEvent(KeyTable.XK_Control_L, "ControlLeft", true);
|
||||
this._sendKeyEvent(KeyTable.XK_Control_L, "ControlLeft", true, numlock, capslock);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,8 +126,8 @@ export default class Keyboard {
|
||||
// If it's a virtual keyboard then it should be
|
||||
// sufficient to just send press and release right
|
||||
// after each other
|
||||
this._sendKeyEvent(keysym, code, true);
|
||||
this._sendKeyEvent(keysym, code, false);
|
||||
this._sendKeyEvent(keysym, code, true, numlock, capslock);
|
||||
this._sendKeyEvent(keysym, code, false, numlock, capslock);
|
||||
}
|
||||
|
||||
stopEvent(e);
|
||||
@@ -153,13 +161,23 @@ export default class Keyboard {
|
||||
keysym = this._keyDownList[code];
|
||||
}
|
||||
|
||||
// macOS doesn't send proper key releases if a key is pressed
|
||||
// while meta is held down
|
||||
if ((browser.isMac() || browser.isIOS()) &&
|
||||
(e.metaKey && code !== 'MetaLeft' && code !== 'MetaRight')) {
|
||||
this._sendKeyEvent(keysym, code, true, numlock, capslock);
|
||||
this._sendKeyEvent(keysym, code, false, numlock, capslock);
|
||||
stopEvent(e);
|
||||
return;
|
||||
}
|
||||
|
||||
// macOS doesn't send proper key events for modifiers, only
|
||||
// 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() || browser.isIOS()) && (code === 'CapsLock')) {
|
||||
this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', true);
|
||||
this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', false);
|
||||
this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', true, numlock, capslock);
|
||||
this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', false, numlock, capslock);
|
||||
stopEvent(e);
|
||||
return;
|
||||
}
|
||||
@@ -172,8 +190,8 @@ export default class Keyboard {
|
||||
KeyTable.XK_Hiragana,
|
||||
KeyTable.XK_Romaji ];
|
||||
if (browser.isWindows() && jpBadKeys.includes(keysym)) {
|
||||
this._sendKeyEvent(keysym, code, true);
|
||||
this._sendKeyEvent(keysym, code, false);
|
||||
this._sendKeyEvent(keysym, code, true, numlock, capslock);
|
||||
this._sendKeyEvent(keysym, code, false, numlock, capslock);
|
||||
stopEvent(e);
|
||||
return;
|
||||
}
|
||||
@@ -189,7 +207,7 @@ export default class Keyboard {
|
||||
return;
|
||||
}
|
||||
|
||||
this._sendKeyEvent(keysym, code, true);
|
||||
this._sendKeyEvent(keysym, code, true, numlock, capslock);
|
||||
}
|
||||
|
||||
_handleKeyUp(e) {
|
||||
|
||||
@@ -0,0 +1,312 @@
|
||||
import { encodeUTF8 } from './util/strings.js';
|
||||
import EventTargetMixin from './util/eventtarget.js';
|
||||
import legacyCrypto from './crypto/crypto.js';
|
||||
|
||||
class RA2Cipher {
|
||||
constructor() {
|
||||
this._cipher = null;
|
||||
this._counter = new Uint8Array(16);
|
||||
}
|
||||
|
||||
async setKey(key) {
|
||||
this._cipher = await legacyCrypto.importKey(
|
||||
"raw", key, { name: "AES-EAX" }, false, ["encrypt, decrypt"]);
|
||||
}
|
||||
|
||||
async makeMessage(message) {
|
||||
const ad = new Uint8Array([(message.length & 0xff00) >>> 8, message.length & 0xff]);
|
||||
const encrypted = await legacyCrypto.encrypt({
|
||||
name: "AES-EAX",
|
||||
iv: this._counter,
|
||||
additionalData: ad,
|
||||
}, this._cipher, message);
|
||||
for (let i = 0; i < 16 && this._counter[i]++ === 255; i++);
|
||||
const res = new Uint8Array(message.length + 2 + 16);
|
||||
res.set(ad);
|
||||
res.set(encrypted, 2);
|
||||
return res;
|
||||
}
|
||||
|
||||
async receiveMessage(length, encrypted) {
|
||||
const ad = new Uint8Array([(length & 0xff00) >>> 8, length & 0xff]);
|
||||
const res = await legacyCrypto.decrypt({
|
||||
name: "AES-EAX",
|
||||
iv: this._counter,
|
||||
additionalData: ad,
|
||||
}, this._cipher, encrypted);
|
||||
for (let i = 0; i < 16 && this._counter[i]++ === 255; i++);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
export default class RSAAESAuthenticationState extends EventTargetMixin {
|
||||
constructor(sock, getCredentials) {
|
||||
super();
|
||||
this._hasStarted = false;
|
||||
this._checkSock = null;
|
||||
this._checkCredentials = null;
|
||||
this._approveServerResolve = null;
|
||||
this._sockReject = null;
|
||||
this._credentialsReject = null;
|
||||
this._approveServerReject = null;
|
||||
this._sock = sock;
|
||||
this._getCredentials = getCredentials;
|
||||
}
|
||||
|
||||
_waitSockAsync(len) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const hasData = () => !this._sock.rQwait('RA2', len);
|
||||
if (hasData()) {
|
||||
resolve();
|
||||
} else {
|
||||
this._checkSock = () => {
|
||||
if (hasData()) {
|
||||
resolve();
|
||||
this._checkSock = null;
|
||||
this._sockReject = null;
|
||||
}
|
||||
};
|
||||
this._sockReject = reject;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_waitApproveKeyAsync() {
|
||||
return new Promise((resolve, reject) => {
|
||||
this._approveServerResolve = resolve;
|
||||
this._approveServerReject = reject;
|
||||
});
|
||||
}
|
||||
|
||||
_waitCredentialsAsync(subtype) {
|
||||
const hasCredentials = () => {
|
||||
if (subtype === 1 && this._getCredentials().username !== undefined &&
|
||||
this._getCredentials().password !== undefined) {
|
||||
return true;
|
||||
} else if (subtype === 2 && this._getCredentials().password !== undefined) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
return new Promise((resolve, reject) => {
|
||||
if (hasCredentials()) {
|
||||
resolve();
|
||||
} else {
|
||||
this._checkCredentials = () => {
|
||||
if (hasCredentials()) {
|
||||
resolve();
|
||||
this._checkCredentials = null;
|
||||
this._credentialsReject = null;
|
||||
}
|
||||
};
|
||||
this._credentialsReject = reject;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
checkInternalEvents() {
|
||||
if (this._checkSock !== null) {
|
||||
this._checkSock();
|
||||
}
|
||||
if (this._checkCredentials !== null) {
|
||||
this._checkCredentials();
|
||||
}
|
||||
}
|
||||
|
||||
approveServer() {
|
||||
if (this._approveServerResolve !== null) {
|
||||
this._approveServerResolve();
|
||||
this._approveServerResolve = null;
|
||||
}
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
if (this._sockReject !== null) {
|
||||
this._sockReject(new Error("disconnect normally"));
|
||||
this._sockReject = null;
|
||||
}
|
||||
if (this._credentialsReject !== null) {
|
||||
this._credentialsReject(new Error("disconnect normally"));
|
||||
this._credentialsReject = null;
|
||||
}
|
||||
if (this._approveServerReject !== null) {
|
||||
this._approveServerReject(new Error("disconnect normally"));
|
||||
this._approveServerReject = null;
|
||||
}
|
||||
}
|
||||
|
||||
async negotiateRA2neAuthAsync() {
|
||||
this._hasStarted = true;
|
||||
// 1: Receive server public key
|
||||
await this._waitSockAsync(4);
|
||||
const serverKeyLengthBuffer = this._sock.rQpeekBytes(4);
|
||||
const serverKeyLength = this._sock.rQshift32();
|
||||
if (serverKeyLength < 1024) {
|
||||
throw new Error("RA2: server public key is too short: " + serverKeyLength);
|
||||
} else if (serverKeyLength > 8192) {
|
||||
throw new Error("RA2: server public key is too long: " + serverKeyLength);
|
||||
}
|
||||
const serverKeyBytes = Math.ceil(serverKeyLength / 8);
|
||||
await this._waitSockAsync(serverKeyBytes * 2);
|
||||
const serverN = this._sock.rQshiftBytes(serverKeyBytes);
|
||||
const serverE = this._sock.rQshiftBytes(serverKeyBytes);
|
||||
const serverRSACipher = await legacyCrypto.importKey(
|
||||
"raw", { n: serverN, e: serverE }, { name: "RSA-PKCS1-v1_5" }, false, ["encrypt"]);
|
||||
const serverPublickey = new Uint8Array(4 + serverKeyBytes * 2);
|
||||
serverPublickey.set(serverKeyLengthBuffer);
|
||||
serverPublickey.set(serverN, 4);
|
||||
serverPublickey.set(serverE, 4 + serverKeyBytes);
|
||||
|
||||
// verify server public key
|
||||
let approveKey = this._waitApproveKeyAsync();
|
||||
this.dispatchEvent(new CustomEvent("serververification", {
|
||||
detail: { type: "RSA", publickey: serverPublickey }
|
||||
}));
|
||||
await approveKey;
|
||||
|
||||
// 2: Send client public key
|
||||
const clientKeyLength = 2048;
|
||||
const clientKeyBytes = Math.ceil(clientKeyLength / 8);
|
||||
const clientRSACipher = (await legacyCrypto.generateKey({
|
||||
name: "RSA-PKCS1-v1_5",
|
||||
modulusLength: clientKeyLength,
|
||||
publicExponent: new Uint8Array([1, 0, 1]),
|
||||
}, true, ["encrypt"])).privateKey;
|
||||
const clientExportedRSAKey = await legacyCrypto.exportKey("raw", clientRSACipher);
|
||||
const clientN = clientExportedRSAKey.n;
|
||||
const clientE = clientExportedRSAKey.e;
|
||||
const clientPublicKey = new Uint8Array(4 + clientKeyBytes * 2);
|
||||
clientPublicKey[0] = (clientKeyLength & 0xff000000) >>> 24;
|
||||
clientPublicKey[1] = (clientKeyLength & 0xff0000) >>> 16;
|
||||
clientPublicKey[2] = (clientKeyLength & 0xff00) >>> 8;
|
||||
clientPublicKey[3] = clientKeyLength & 0xff;
|
||||
clientPublicKey.set(clientN, 4);
|
||||
clientPublicKey.set(clientE, 4 + clientKeyBytes);
|
||||
this._sock.sQpushBytes(clientPublicKey);
|
||||
this._sock.flush();
|
||||
|
||||
// 3: Send client random
|
||||
const clientRandom = new Uint8Array(16);
|
||||
window.crypto.getRandomValues(clientRandom);
|
||||
const clientEncryptedRandom = await legacyCrypto.encrypt(
|
||||
{ name: "RSA-PKCS1-v1_5" }, serverRSACipher, clientRandom);
|
||||
const clientRandomMessage = new Uint8Array(2 + serverKeyBytes);
|
||||
clientRandomMessage[0] = (serverKeyBytes & 0xff00) >>> 8;
|
||||
clientRandomMessage[1] = serverKeyBytes & 0xff;
|
||||
clientRandomMessage.set(clientEncryptedRandom, 2);
|
||||
this._sock.sQpushBytes(clientRandomMessage);
|
||||
this._sock.flush();
|
||||
|
||||
// 4: Receive server random
|
||||
await this._waitSockAsync(2);
|
||||
if (this._sock.rQshift16() !== clientKeyBytes) {
|
||||
throw new Error("RA2: wrong encrypted message length");
|
||||
}
|
||||
const serverEncryptedRandom = this._sock.rQshiftBytes(clientKeyBytes);
|
||||
const serverRandom = await legacyCrypto.decrypt(
|
||||
{ name: "RSA-PKCS1-v1_5" }, clientRSACipher, serverEncryptedRandom);
|
||||
if (serverRandom === null || serverRandom.length !== 16) {
|
||||
throw new Error("RA2: corrupted server encrypted random");
|
||||
}
|
||||
|
||||
// 5: Compute session keys and set ciphers
|
||||
let clientSessionKey = new Uint8Array(32);
|
||||
let serverSessionKey = new Uint8Array(32);
|
||||
clientSessionKey.set(serverRandom);
|
||||
clientSessionKey.set(clientRandom, 16);
|
||||
serverSessionKey.set(clientRandom);
|
||||
serverSessionKey.set(serverRandom, 16);
|
||||
clientSessionKey = await window.crypto.subtle.digest("SHA-1", clientSessionKey);
|
||||
clientSessionKey = new Uint8Array(clientSessionKey).slice(0, 16);
|
||||
serverSessionKey = await window.crypto.subtle.digest("SHA-1", serverSessionKey);
|
||||
serverSessionKey = new Uint8Array(serverSessionKey).slice(0, 16);
|
||||
const clientCipher = new RA2Cipher();
|
||||
await clientCipher.setKey(clientSessionKey);
|
||||
const serverCipher = new RA2Cipher();
|
||||
await serverCipher.setKey(serverSessionKey);
|
||||
|
||||
// 6: Compute and exchange hashes
|
||||
let serverHash = new Uint8Array(8 + serverKeyBytes * 2 + clientKeyBytes * 2);
|
||||
let clientHash = new Uint8Array(8 + serverKeyBytes * 2 + clientKeyBytes * 2);
|
||||
serverHash.set(serverPublickey);
|
||||
serverHash.set(clientPublicKey, 4 + serverKeyBytes * 2);
|
||||
clientHash.set(clientPublicKey);
|
||||
clientHash.set(serverPublickey, 4 + clientKeyBytes * 2);
|
||||
serverHash = await window.crypto.subtle.digest("SHA-1", serverHash);
|
||||
clientHash = await window.crypto.subtle.digest("SHA-1", clientHash);
|
||||
serverHash = new Uint8Array(serverHash);
|
||||
clientHash = new Uint8Array(clientHash);
|
||||
this._sock.sQpushBytes(await clientCipher.makeMessage(clientHash));
|
||||
this._sock.flush();
|
||||
await this._waitSockAsync(2 + 20 + 16);
|
||||
if (this._sock.rQshift16() !== 20) {
|
||||
throw new Error("RA2: wrong server hash");
|
||||
}
|
||||
const serverHashReceived = await serverCipher.receiveMessage(
|
||||
20, this._sock.rQshiftBytes(20 + 16));
|
||||
if (serverHashReceived === null) {
|
||||
throw new Error("RA2: failed to authenticate the message");
|
||||
}
|
||||
for (let i = 0; i < 20; i++) {
|
||||
if (serverHashReceived[i] !== serverHash[i]) {
|
||||
throw new Error("RA2: wrong server hash");
|
||||
}
|
||||
}
|
||||
|
||||
// 7: Receive subtype
|
||||
await this._waitSockAsync(2 + 1 + 16);
|
||||
if (this._sock.rQshift16() !== 1) {
|
||||
throw new Error("RA2: wrong subtype");
|
||||
}
|
||||
let subtype = (await serverCipher.receiveMessage(
|
||||
1, this._sock.rQshiftBytes(1 + 16)));
|
||||
if (subtype === null) {
|
||||
throw new Error("RA2: failed to authenticate the message");
|
||||
}
|
||||
subtype = subtype[0];
|
||||
let waitCredentials = this._waitCredentialsAsync(subtype);
|
||||
if (subtype === 1) {
|
||||
if (this._getCredentials().username === undefined ||
|
||||
this._getCredentials().password === undefined) {
|
||||
this.dispatchEvent(new CustomEvent(
|
||||
"credentialsrequired",
|
||||
{ detail: { types: ["username", "password"] } }));
|
||||
}
|
||||
} else if (subtype === 2) {
|
||||
if (this._getCredentials().password === undefined) {
|
||||
this.dispatchEvent(new CustomEvent(
|
||||
"credentialsrequired",
|
||||
{ detail: { types: ["password"] } }));
|
||||
}
|
||||
} else {
|
||||
throw new Error("RA2: wrong subtype");
|
||||
}
|
||||
await waitCredentials;
|
||||
let username;
|
||||
if (subtype === 1) {
|
||||
username = encodeUTF8(this._getCredentials().username).slice(0, 255);
|
||||
} else {
|
||||
username = "";
|
||||
}
|
||||
const password = encodeUTF8(this._getCredentials().password).slice(0, 255);
|
||||
const credentials = new Uint8Array(username.length + password.length + 2);
|
||||
credentials[0] = username.length;
|
||||
credentials[username.length + 1] = password.length;
|
||||
for (let i = 0; i < username.length; i++) {
|
||||
credentials[i + 1] = username.charCodeAt(i);
|
||||
}
|
||||
for (let i = 0; i < password.length; i++) {
|
||||
credentials[username.length + 2 + i] = password.charCodeAt(i);
|
||||
}
|
||||
this._sock.sQpushBytes(await clientCipher.makeMessage(credentials));
|
||||
this._sock.flush();
|
||||
}
|
||||
|
||||
get hasStarted() {
|
||||
return this._hasStarted;
|
||||
}
|
||||
|
||||
set hasStarted(s) {
|
||||
this._hasStarted = s;
|
||||
}
|
||||
}
|
||||
@@ -77,27 +77,76 @@ export const hasScrollbarGutter = _hasScrollbarGutter;
|
||||
* It's better to use feature detection than platform detection.
|
||||
*/
|
||||
|
||||
/* OS */
|
||||
|
||||
export function isMac() {
|
||||
return navigator && !!(/mac/i).exec(navigator.platform);
|
||||
return !!(/mac/i).exec(navigator.platform);
|
||||
}
|
||||
|
||||
export function isWindows() {
|
||||
return navigator && !!(/win/i).exec(navigator.platform);
|
||||
return !!(/win/i).exec(navigator.platform);
|
||||
}
|
||||
|
||||
export function isIOS() {
|
||||
return navigator &&
|
||||
(!!(/ipad/i).exec(navigator.platform) ||
|
||||
return (!!(/ipad/i).exec(navigator.platform) ||
|
||||
!!(/iphone/i).exec(navigator.platform) ||
|
||||
!!(/ipod/i).exec(navigator.platform));
|
||||
}
|
||||
|
||||
export function isAndroid() {
|
||||
/* Android sets navigator.platform to Linux :/ */
|
||||
return !!navigator.userAgent.match('Android ');
|
||||
}
|
||||
|
||||
export function isChromeOS() {
|
||||
/* ChromeOS sets navigator.platform to Linux :/ */
|
||||
return !!navigator.userAgent.match(' CrOS ');
|
||||
}
|
||||
|
||||
/* Browser */
|
||||
|
||||
export function isSafari() {
|
||||
return navigator && (navigator.userAgent.indexOf('Safari') !== -1 &&
|
||||
navigator.userAgent.indexOf('Chrome') === -1);
|
||||
return !!navigator.userAgent.match('Safari/...') &&
|
||||
!navigator.userAgent.match('Chrome/...') &&
|
||||
!navigator.userAgent.match('Chromium/...') &&
|
||||
!navigator.userAgent.match('Epiphany/...');
|
||||
}
|
||||
|
||||
export function isFirefox() {
|
||||
return navigator && !!(/firefox/i).exec(navigator.userAgent);
|
||||
return !!navigator.userAgent.match('Firefox/...') &&
|
||||
!navigator.userAgent.match('Seamonkey/...');
|
||||
}
|
||||
|
||||
export function isChrome() {
|
||||
return !!navigator.userAgent.match('Chrome/...') &&
|
||||
!navigator.userAgent.match('Chromium/...') &&
|
||||
!navigator.userAgent.match('Edg/...') &&
|
||||
!navigator.userAgent.match('OPR/...');
|
||||
}
|
||||
|
||||
export function isChromium() {
|
||||
return !!navigator.userAgent.match('Chromium/...');
|
||||
}
|
||||
|
||||
export function isOpera() {
|
||||
return !!navigator.userAgent.match('OPR/...');
|
||||
}
|
||||
|
||||
export function isEdge() {
|
||||
return !!navigator.userAgent.match('Edg/...');
|
||||
}
|
||||
|
||||
/* Engine */
|
||||
|
||||
export function isGecko() {
|
||||
return !!navigator.userAgent.match('Gecko/...');
|
||||
}
|
||||
|
||||
export function isWebKit() {
|
||||
return !!navigator.userAgent.match('AppleWebKit/...') &&
|
||||
!navigator.userAgent.match('Chrome/...');
|
||||
}
|
||||
|
||||
export function isBlink() {
|
||||
return !!navigator.userAgent.match('Chrome/...');
|
||||
}
|
||||
|
||||
@@ -18,6 +18,10 @@ export default class Cursor {
|
||||
this._canvas.style.position = 'fixed';
|
||||
this._canvas.style.zIndex = '65535';
|
||||
this._canvas.style.pointerEvents = 'none';
|
||||
// Safari on iOS can select the cursor image
|
||||
// https://bugs.webkit.org/show_bug.cgi?id=249223
|
||||
this._canvas.style.userSelect = 'none';
|
||||
this._canvas.style.WebkitUserSelect = 'none';
|
||||
// Can't use "display" because of Firefox bug #1445997
|
||||
this._canvas.style.visibility = 'hidden';
|
||||
}
|
||||
|
||||
@@ -94,27 +94,7 @@ export default class Websock {
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
get sQ() {
|
||||
return this._sQ;
|
||||
}
|
||||
|
||||
get rQ() {
|
||||
return this._rQ;
|
||||
}
|
||||
|
||||
get rQi() {
|
||||
return this._rQi;
|
||||
}
|
||||
|
||||
set rQi(val) {
|
||||
this._rQi = val;
|
||||
}
|
||||
|
||||
// Receive Queue
|
||||
get rQlen() {
|
||||
return this._rQlen - this._rQi;
|
||||
}
|
||||
|
||||
rQpeek8() {
|
||||
return this._rQ[this._rQi];
|
||||
}
|
||||
@@ -141,42 +121,47 @@ export default class Websock {
|
||||
for (let byte = bytes - 1; byte >= 0; byte--) {
|
||||
res += this._rQ[this._rQi++] << (byte * 8);
|
||||
}
|
||||
return res;
|
||||
return res >>> 0;
|
||||
}
|
||||
|
||||
rQshiftStr(len) {
|
||||
if (typeof(len) === 'undefined') { len = this.rQlen; }
|
||||
let str = "";
|
||||
// Handle large arrays in steps to avoid long strings on the stack
|
||||
for (let i = 0; i < len; i += 4096) {
|
||||
let part = this.rQshiftBytes(Math.min(4096, len - i));
|
||||
let part = this.rQshiftBytes(Math.min(4096, len - i), false);
|
||||
str += String.fromCharCode.apply(null, part);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
rQshiftBytes(len) {
|
||||
if (typeof(len) === 'undefined') { len = this.rQlen; }
|
||||
rQshiftBytes(len, copy=true) {
|
||||
this._rQi += len;
|
||||
return new Uint8Array(this._rQ.buffer, this._rQi - len, len);
|
||||
if (copy) {
|
||||
return this._rQ.slice(this._rQi - len, this._rQi);
|
||||
} else {
|
||||
return this._rQ.subarray(this._rQi - len, this._rQi);
|
||||
}
|
||||
}
|
||||
|
||||
rQshiftTo(target, len) {
|
||||
if (len === undefined) { len = this.rQlen; }
|
||||
// TODO: make this just use set with views when using a ArrayBuffer to store the rQ
|
||||
target.set(new Uint8Array(this._rQ.buffer, this._rQi, len));
|
||||
this._rQi += len;
|
||||
}
|
||||
|
||||
rQslice(start, end = this.rQlen) {
|
||||
return new Uint8Array(this._rQ.buffer, this._rQi + start, end - start);
|
||||
rQpeekBytes(len, copy=true) {
|
||||
if (copy) {
|
||||
return this._rQ.slice(this._rQi, this._rQi + len);
|
||||
} else {
|
||||
return this._rQ.subarray(this._rQi, this._rQi + len);
|
||||
}
|
||||
}
|
||||
|
||||
// Check to see if we must wait for 'num' bytes (default to FBU.bytes)
|
||||
// to be available in the receive queue. Return true if we need to
|
||||
// wait (and possibly print a debug message), otherwise false.
|
||||
rQwait(msg, num, goback) {
|
||||
if (this.rQlen < num) {
|
||||
if (this._rQlen - this._rQi < num) {
|
||||
if (goback) {
|
||||
if (this._rQi < goback) {
|
||||
throw new Error("rQwait cannot backup " + goback + " bytes");
|
||||
@@ -190,21 +175,56 @@ export default class Websock {
|
||||
|
||||
// Send Queue
|
||||
|
||||
sQpush8(num) {
|
||||
this._sQensureSpace(1);
|
||||
this._sQ[this._sQlen++] = num;
|
||||
}
|
||||
|
||||
sQpush16(num) {
|
||||
this._sQensureSpace(2);
|
||||
this._sQ[this._sQlen++] = (num >> 8) & 0xff;
|
||||
this._sQ[this._sQlen++] = (num >> 0) & 0xff;
|
||||
}
|
||||
|
||||
sQpush32(num) {
|
||||
this._sQensureSpace(4);
|
||||
this._sQ[this._sQlen++] = (num >> 24) & 0xff;
|
||||
this._sQ[this._sQlen++] = (num >> 16) & 0xff;
|
||||
this._sQ[this._sQlen++] = (num >> 8) & 0xff;
|
||||
this._sQ[this._sQlen++] = (num >> 0) & 0xff;
|
||||
}
|
||||
|
||||
sQpushString(str) {
|
||||
let bytes = str.split('').map(chr => chr.charCodeAt(0));
|
||||
this.sQpushBytes(new Uint8Array(bytes));
|
||||
}
|
||||
|
||||
sQpushBytes(bytes) {
|
||||
for (let offset = 0;offset < bytes.length;) {
|
||||
this._sQensureSpace(1);
|
||||
|
||||
let chunkSize = this._sQbufferSize - this._sQlen;
|
||||
if (chunkSize > bytes.length - offset) {
|
||||
chunkSize = bytes.length - offset;
|
||||
}
|
||||
|
||||
this._sQ.set(bytes.subarray(offset, chunkSize), this._sQlen);
|
||||
this._sQlen += chunkSize;
|
||||
offset += chunkSize;
|
||||
}
|
||||
}
|
||||
|
||||
flush() {
|
||||
if (this._sQlen > 0 && this.readyState === 'open') {
|
||||
this._websocket.send(this._encodeMessage());
|
||||
this._websocket.send(new Uint8Array(this._sQ.buffer, 0, this._sQlen));
|
||||
this._sQlen = 0;
|
||||
}
|
||||
}
|
||||
|
||||
send(arr) {
|
||||
this._sQ.set(arr, this._sQlen);
|
||||
this._sQlen += arr.length;
|
||||
this.flush();
|
||||
}
|
||||
|
||||
sendString(str) {
|
||||
this.send(str.split('').map(chr => chr.charCodeAt(0)));
|
||||
_sQensureSpace(bytes) {
|
||||
if (this._sQbufferSize - this._sQlen < bytes) {
|
||||
this.flush();
|
||||
}
|
||||
}
|
||||
|
||||
// Event Handlers
|
||||
@@ -283,17 +303,12 @@ export default class Websock {
|
||||
}
|
||||
|
||||
// private methods
|
||||
_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);
|
||||
}
|
||||
|
||||
// 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.
|
||||
// unnecessary 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
|
||||
@@ -309,7 +324,7 @@ export default class Websock {
|
||||
// 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 < minFit) {
|
||||
if (this._rQbufferSize - (this._rQlen - this._rQi) < minFit) {
|
||||
throw new Error("Receive Queue buffer exceeded " + MAX_RQ_GROW_SIZE + " bytes, and the new message could not fit");
|
||||
}
|
||||
}
|
||||
@@ -327,25 +342,22 @@ export default class Websock {
|
||||
}
|
||||
|
||||
// push arraybuffer values onto the end of the receive que
|
||||
_DecodeMessage(data) {
|
||||
const u8 = new Uint8Array(data);
|
||||
_recvMessage(e) {
|
||||
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;
|
||||
}
|
||||
const u8 = new Uint8Array(e.data);
|
||||
if (u8.length > this._rQbufferSize - this._rQlen) {
|
||||
this._expandCompactRQ(u8.length);
|
||||
}
|
||||
this._rQ.set(u8, this._rQlen);
|
||||
this._rQlen += u8.length;
|
||||
}
|
||||
|
||||
_recvMessage(e) {
|
||||
this._DecodeMessage(e.data);
|
||||
if (this.rQlen > 0) {
|
||||
if (this._rQlen - this._rQi > 0) {
|
||||
this._eventHandlers.message();
|
||||
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 {
|
||||
Log.Debug("Ignoring empty message");
|
||||
}
|
||||
|
||||
@@ -81,9 +81,3 @@ None
|
||||
| blitImage | (x, y, width, height, arr, offset, from_queue) | Blit pixels (of R,G,B,A) to the display
|
||||
| drawImage | (img, x, y) | Draw image and track damage
|
||||
| autoscale | (containerWidth, containerHeight) | Scale the display
|
||||
|
||||
### 2.2.3 Callbacks
|
||||
|
||||
| name | parameters | description
|
||||
| ------- | ---------- | ------------
|
||||
| onflush | () | A display flush has been requested and we are now ready to resume FBU processing
|
||||
|
||||
@@ -16,60 +16,13 @@ protocol stream.
|
||||
|
||||
### Properties
|
||||
|
||||
`viewOnly`
|
||||
- Is a `boolean` indicating if any events (e.g. key presses or mouse
|
||||
movement) should be prevented from being sent to the server.
|
||||
Disabled by default.
|
||||
|
||||
`focusOnClick`
|
||||
- Is a `boolean` indicating if keyboard focus should automatically be
|
||||
moved to the remote session when a `mousedown` or `touchstart`
|
||||
event is received. Enabled by default.
|
||||
|
||||
`clipViewport`
|
||||
- Is a `boolean` indicating if the remote session should be clipped
|
||||
to its container. When disabled scrollbars will be shown to handle
|
||||
the resulting overflow. Disabled by default.
|
||||
|
||||
`dragViewport`
|
||||
- Is a `boolean` indicating if mouse events should control the
|
||||
relative position of a clipped remote session. Only relevant if
|
||||
`clipViewport` is enabled. Disabled by default.
|
||||
|
||||
`scaleViewport`
|
||||
- Is a `boolean` indicating if the remote session should be scaled
|
||||
locally so it fits its container. When disabled it will be centered
|
||||
if the remote session is smaller than its container, or handled
|
||||
according to `clipViewport` if it is larger. Disabled by default.
|
||||
|
||||
`resizeSession`
|
||||
- Is a `boolean` indicating if a request to resize the remote session
|
||||
should be sent whenever the container changes dimensions. Disabled
|
||||
by default.
|
||||
|
||||
`showDotCursor`
|
||||
- Is a `boolean` indicating whether a dot cursor should be shown
|
||||
instead of a zero-sized or fully-transparent cursor if the server
|
||||
sets such invisible cursor. Disabled by default.
|
||||
|
||||
`background`
|
||||
- Is a valid CSS [background](https://developer.mozilla.org/en-US/docs/Web/CSS/background)
|
||||
style value indicating which background style should be applied
|
||||
to the element containing the remote session screen. The default value is `rgb(40, 40, 40)`
|
||||
(solid gray color).
|
||||
- Is a valid CSS [background][mdn-bg] style value indicating which
|
||||
background style should be applied to the element containing the
|
||||
remote session screen. The default value is `rgb(40, 40, 40)` (solid
|
||||
gray color).
|
||||
|
||||
`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`.
|
||||
[mdn-bg]: https://developer.mozilla.org/en-US/docs/Web/CSS/background
|
||||
|
||||
`capabilities` *Read only*
|
||||
- Is an `Object` indicating which optional extensions are available
|
||||
@@ -80,62 +33,122 @@ protocol stream.
|
||||
| -------- | --------- | -----------
|
||||
| `power` | `boolean` | Machine power control is available
|
||||
|
||||
`clippingViewport` *Read only*
|
||||
- Is a `boolean` indicating if the remote session is currently being
|
||||
clipped to its container. Only relevant if `clipViewport` is
|
||||
enabled.
|
||||
|
||||
`clipViewport`
|
||||
- Is a `boolean` indicating if the remote session should be clipped
|
||||
to its container. When disabled scrollbars will be shown to handle
|
||||
the resulting overflow. Disabled by default.
|
||||
|
||||
`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`.
|
||||
|
||||
`dragViewport`
|
||||
- Is a `boolean` indicating if mouse events should control the
|
||||
relative position of a clipped remote session. Only relevant if
|
||||
`clipViewport` is enabled. Disabled by default.
|
||||
|
||||
`focusOnClick`
|
||||
- Is a `boolean` indicating if keyboard focus should automatically be
|
||||
moved to the remote session when a `mousedown` or `touchstart`
|
||||
event is received. Enabled by default.
|
||||
|
||||
`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`.
|
||||
|
||||
`resizeSession`
|
||||
- Is a `boolean` indicating if a request to resize the remote session
|
||||
should be sent whenever the container changes dimensions. Disabled
|
||||
by default.
|
||||
|
||||
`scaleViewport`
|
||||
- Is a `boolean` indicating if the remote session should be scaled
|
||||
locally so it fits its container. When disabled it will be centered
|
||||
if the remote session is smaller than its container, or handled
|
||||
according to `clipViewport` if it is larger. Disabled by default.
|
||||
|
||||
`showDotCursor`
|
||||
- Is a `boolean` indicating whether a dot cursor should be shown
|
||||
instead of a zero-sized or fully-transparent cursor if the server
|
||||
sets such invisible cursor. Disabled by default.
|
||||
|
||||
`viewOnly`
|
||||
- Is a `boolean` indicating if any events (e.g. key presses or mouse
|
||||
movement) should be prevented from being sent to the server.
|
||||
Disabled by default.
|
||||
|
||||
### Events
|
||||
|
||||
[`connect`](#connect)
|
||||
- The `connect` event is fired when the `RFB` object has completed
|
||||
the connection and handshaking with the server.
|
||||
|
||||
[`disconnect`](#disconnected)
|
||||
- The `disconnect` event is fired when the `RFB` object disconnects.
|
||||
|
||||
[`credentialsrequired`](#credentialsrequired)
|
||||
- The `credentialsrequired` event is fired when more credentials must
|
||||
be given to continue.
|
||||
|
||||
[`securityfailure`](#securityfailure)
|
||||
- The `securityfailure` event is fired when the security negotiation
|
||||
with the server fails.
|
||||
|
||||
[`clipboard`](#clipboard)
|
||||
- The `clipboard` event is fired when clipboard data is received from
|
||||
the server.
|
||||
|
||||
[`bell`](#bell)
|
||||
- The `bell` event is fired when a audible bell request is received
|
||||
from the server.
|
||||
|
||||
[`desktopname`](#desktopname)
|
||||
- The `desktopname` event is fired when the remote desktop name
|
||||
changes.
|
||||
|
||||
[`capabilities`](#capabilities)
|
||||
- The `capabilities` event is fired when `RFB.capabilities` is
|
||||
updated.
|
||||
|
||||
[`clipboard`](#clipboard)
|
||||
- The `clipboard` event is fired when clipboard data is received from
|
||||
the server.
|
||||
|
||||
[`clippingviewport`](#clippingviewport)
|
||||
- The `clippingviewport` event is fired when `RFB.clippingViewport` is
|
||||
updated.
|
||||
|
||||
[`connect`](#connect)
|
||||
- The `connect` event is fired when the `RFB` object has completed
|
||||
the connection and handshaking with the server.
|
||||
|
||||
[`credentialsrequired`](#credentialsrequired)
|
||||
- The `credentialsrequired` event is fired when more credentials must
|
||||
be given to continue.
|
||||
|
||||
[`desktopname`](#desktopname)
|
||||
- The `desktopname` event is fired when the remote desktop name
|
||||
changes.
|
||||
|
||||
[`disconnect`](#disconnect)
|
||||
- The `disconnect` event is fired when the `RFB` object disconnects.
|
||||
|
||||
[`securityfailure`](#securityfailure)
|
||||
- The `securityfailure` event is fired when the security negotiation
|
||||
with the server fails.
|
||||
|
||||
[`serververification`](#serververification)
|
||||
- The `serververification` event is fired when the server identity
|
||||
must be confirmed by the user.
|
||||
|
||||
### Methods
|
||||
|
||||
[`RFB.disconnect()`](#rfbdisconnect)
|
||||
- Disconnect from the server.
|
||||
|
||||
[`RFB.sendCredentials()`](#rfbsendcredentials)
|
||||
- Send credentials to server. Should be called after the
|
||||
[`credentialsrequired`](#credentialsrequired) event has fired.
|
||||
|
||||
[`RFB.sendKey()`](#rfbsendKey)
|
||||
- Send a key event.
|
||||
|
||||
[`RFB.sendCtrlAltDel()`](#rfbsendctrlaltdel)
|
||||
- Send Ctrl-Alt-Del key sequence.
|
||||
|
||||
[`RFB.focus()`](#rfbfocus)
|
||||
- Move keyboard focus to the remote session.
|
||||
[`RFB.approveServer()`](#rfbapproveserver)
|
||||
- Proceed connecting to the server. Should be called after the
|
||||
[`serververification`](#serververification) event has fired and the
|
||||
user has verified the identity of the server.
|
||||
|
||||
[`RFB.blur()`](#rfbblur)
|
||||
- Remove keyboard focus from the remote session.
|
||||
|
||||
[`RFB.machineShutdown()`](#rfbmachineshutdown)
|
||||
- Request a shutdown of the remote machine.
|
||||
[`RFB.clipboardPasteFrom()`](#rfbclipboardpastefrom)
|
||||
- Send clipboard contents to server.
|
||||
|
||||
[`RFB.disconnect()`](#rfbdisconnect)
|
||||
- Disconnect from the server.
|
||||
|
||||
[`RFB.focus()`](#rfbfocus)
|
||||
- Move keyboard focus to the remote session.
|
||||
|
||||
[`RFB.getImageData()`](#rfbgetimagedata)
|
||||
- Return the current content of the screen as an ImageData array.
|
||||
|
||||
[`RFB.machineReboot()`](#rfbmachinereboot)
|
||||
- Request a reboot of the remote machine.
|
||||
@@ -143,8 +156,24 @@ protocol stream.
|
||||
[`RFB.machineReset()`](#rfbmachinereset)
|
||||
- Request a reset of the remote machine.
|
||||
|
||||
[`RFB.clipboardPasteFrom()`](#rfbclipboardPasteFrom)
|
||||
- Send clipboard contents to server.
|
||||
[`RFB.machineShutdown()`](#rfbmachineshutdown)
|
||||
- Request a shutdown of the remote machine.
|
||||
|
||||
[`RFB.sendCredentials()`](#rfbsendcredentials)
|
||||
- Send credentials to server. Should be called after the
|
||||
[`credentialsrequired`](#credentialsrequired) event has fired.
|
||||
|
||||
[`RFB.sendCtrlAltDel()`](#rfbsendctrlaltdel)
|
||||
- Send Ctrl-Alt-Del key sequence.
|
||||
|
||||
[`RFB.sendKey()`](#rfbsendkey)
|
||||
- Send a key event.
|
||||
|
||||
[`RFB.toBlob()`](#rfbtoblob)
|
||||
- Return the current content of the screen as Blob encoded image file.
|
||||
|
||||
[`RFB.toDataURL()`](#rfbtodataurl)
|
||||
- Return the current content of the screen as data-url encoded image file.
|
||||
|
||||
### Details
|
||||
|
||||
@@ -155,17 +184,22 @@ connection to a specified VNC server.
|
||||
|
||||
##### Syntax
|
||||
|
||||
let rfb = new RFB( target, url [, options] );
|
||||
```js
|
||||
new RFB(target, urlOrChannel);
|
||||
new RFB(target, urlOrChannel, options);
|
||||
```
|
||||
|
||||
###### Parameters
|
||||
|
||||
**`target`**
|
||||
- A block [`HTMLElement`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement)
|
||||
that specifies where the `RFB` object should attach itself. The
|
||||
existing contents of the `HTMLElement` will be untouched, but new
|
||||
elements will be added during the lifetime of the `RFB` object.
|
||||
- A block [`HTMLElement`][mdn-elem] that specifies where the `RFB`
|
||||
object should attach itself. The existing contents of the
|
||||
`HTMLElement` will be untouched, but new elements will be added
|
||||
during the lifetime of the `RFB` object.
|
||||
|
||||
**`urlOrDataChannel`**
|
||||
[mdn-elem]: https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement
|
||||
|
||||
**`urlOrChannel`**
|
||||
- A `DOMString` specifying the VNC server to connect to. This must be
|
||||
a valid WebSocket URL. This can also be a `WebSocket` or `RTCDataChannel`.
|
||||
|
||||
@@ -198,12 +232,48 @@ connection to a specified VNC server.
|
||||
- An `Array` of `DOMString`s specifying the sub-protocols to use
|
||||
in the WebSocket connection. Empty by default.
|
||||
|
||||
#### bell
|
||||
|
||||
The `bell` event is fired when the server has requested an audible
|
||||
bell.
|
||||
|
||||
#### capabilities
|
||||
|
||||
The `capabilities` event is fired whenever an entry is added or removed
|
||||
from `RFB.capabilities`. The `detail` property is an `Object` with the
|
||||
property `capabilities` containing the new value of `RFB.capabilities`.
|
||||
|
||||
#### clippingviewport
|
||||
|
||||
The `clippingviewport` event is fired whenever `RFB.clippingViewport`
|
||||
changes between `true` and `false`. The `detail` property is a `boolean`
|
||||
with the new value of `RFB.clippingViewport`.
|
||||
|
||||
#### clipboard
|
||||
|
||||
The `clipboard` event is fired when the server has sent clipboard data.
|
||||
The `detail` property is an `Object` containing the property `text`
|
||||
which is a `DOMString` with the clipboard data.
|
||||
|
||||
#### credentialsrequired
|
||||
|
||||
The `credentialsrequired` event is fired when the server requests more
|
||||
credentials than were specified to [`RFB()`](#rfb-1). The `detail`
|
||||
property is an `Object` containing the property `types` which is an
|
||||
`Array` of `DOMString` listing the credentials that are required.
|
||||
|
||||
#### connect
|
||||
|
||||
The `connect` event is fired after all the handshaking with the server
|
||||
is completed and the connection is fully established. After this event
|
||||
the `RFB` object is ready to recieve graphics updates and to send input.
|
||||
|
||||
#### desktopname
|
||||
|
||||
The `desktopname` event is fired when the name of the remote desktop
|
||||
changes. The `detail` property is an `Object` with the property `name`
|
||||
which is a `DOMString` specifying the new name.
|
||||
|
||||
#### disconnect
|
||||
|
||||
The `disconnect` event is fired when the connection has been
|
||||
@@ -212,13 +282,6 @@ property `clean`. `clean` is a `boolean` indicating if the termination
|
||||
was clean or not. In the event of an unexpected termination or an error
|
||||
`clean` will be set to false.
|
||||
|
||||
#### credentialsrequired
|
||||
|
||||
The `credentialsrequired` event is fired when the server requests more
|
||||
credentials than were specified to [`RFB()`](#rfb-1). The `detail`
|
||||
property is an `Object` containing the property `types` which is an
|
||||
`Array` of `DOMString` listing the credentials that are required.
|
||||
|
||||
#### securityfailure
|
||||
|
||||
The `securityfailure` event is fired when the handshaking process with
|
||||
@@ -230,8 +293,7 @@ property is an `Object` containing the following properties:
|
||||
| `status` | `long` | The failure status code
|
||||
| `reason` | `DOMString` | The **optional** reason for the failure
|
||||
|
||||
The property `status` corresponds to the
|
||||
[SecurityResult](https://github.com/rfbproto/rfbproto/blob/master/rfbproto.rst#securityresult)
|
||||
The property `status` corresponds to the [SecurityResult][rfb-secresult]
|
||||
status code in cases of failure. A status of zero will not be sent in
|
||||
this event since that indicates a successful security handshaking
|
||||
process. The optional property `reason` is provided by the server and
|
||||
@@ -239,96 +301,33 @@ thus the language of the string is not known. However most servers will
|
||||
probably send English strings. The server can choose to not send a
|
||||
reason and in these cases the `reason` property will be omitted.
|
||||
|
||||
#### clipboard
|
||||
[rfb-secresult]: https://github.com/rfbproto/rfbproto/blob/master/rfbproto.rst#securityresult
|
||||
|
||||
The `clipboard` event is fired when the server has sent clipboard data.
|
||||
The `detail` property is an `Object` containing the property `text`
|
||||
which is a `DOMString` with the clipboard data.
|
||||
#### serververification
|
||||
|
||||
#### bell
|
||||
The `serververification` event is fired when the server provides
|
||||
information that allows the user to verify that it is the correct server
|
||||
and protect against a man-in-the-middle attack. The `detail` property is
|
||||
an `Object` containing the property `type` which is a `DOMString`
|
||||
specifying which type of information the server has provided. Other
|
||||
properties are also available, depending on the value of `type`:
|
||||
|
||||
The `bell` event is fired when the server has requested an audible
|
||||
bell.
|
||||
`"RSA"`
|
||||
- The server identity is verified using just a RSA key. The property
|
||||
`publickey` is a `Uint8Array` containing the public key in a unsigned
|
||||
big endian representation.
|
||||
|
||||
#### desktopname
|
||||
#### RFB.approveServer()
|
||||
|
||||
The `desktopname` event is fired when the name of the remote desktop
|
||||
changes. The `detail` property is an `Object` with the property `name`
|
||||
which is a `DOMString` specifying the new name.
|
||||
|
||||
#### capabilities
|
||||
|
||||
The `capabilities` event is fired whenever an entry is added or removed
|
||||
from `RFB.capabilities`. The `detail` property is an `Object` with the
|
||||
property `capabilities` containing the new value of `RFB.capabilities`.
|
||||
|
||||
#### RFB.disconnect()
|
||||
|
||||
The `RFB.disconnect()` method is used to disconnect from the currently
|
||||
connected server.
|
||||
The `RFB.approveServer()` method is used to signal that the user has
|
||||
verified the server identity provided in a `serververification` event
|
||||
and that the connection can continue.
|
||||
|
||||
##### Syntax
|
||||
|
||||
RFB.disconnect( );
|
||||
|
||||
#### RFB.sendCredentials()
|
||||
|
||||
The `RFB.sendCredentials()` method is used to provide the missing
|
||||
credentials after a `credentialsrequired` event has been fired.
|
||||
|
||||
##### Syntax
|
||||
|
||||
RFB.sendCredentials( credentials );
|
||||
|
||||
###### Parameters
|
||||
|
||||
**`credentials`**
|
||||
- An `Object` specifying the credentials to provide to the server
|
||||
when authenticating. See [`RFB()`](#rfb-1) for details.
|
||||
|
||||
#### RFB.sendKey()
|
||||
|
||||
The `RFB.sendKey()` method is used to send a key event to the server.
|
||||
|
||||
##### Syntax
|
||||
|
||||
RFB.sendKey( keysym, code [, down] );
|
||||
|
||||
###### Parameters
|
||||
|
||||
**`keysym`**
|
||||
- A `long` specifying the RFB keysym to send. Can be `0` if a valid
|
||||
**`code`** is specified.
|
||||
|
||||
**`code`**
|
||||
- A `DOMString` specifying the physical key to send. Valid values are
|
||||
those that can be specified to
|
||||
[`KeyboardEvent.code`](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code).
|
||||
If the physical key cannot be determined then `null` shall be
|
||||
specified.
|
||||
|
||||
**`down`** *Optional*
|
||||
- A `boolean` specifying if a press or a release event should be
|
||||
sent. If omitted then both a press and release event are sent.
|
||||
|
||||
#### RFB.sendCtrlAltDel()
|
||||
|
||||
The `RFB.sendCtrlAltDel()` method is used to send the key sequence
|
||||
*left Control*, *left Alt*, *Delete*. This is a convenience wrapper
|
||||
around [`RFB.sendKey()`](#rfbsendkey).
|
||||
|
||||
##### Syntax
|
||||
|
||||
RFB.sendCtrlAltDel( );
|
||||
|
||||
#### RFB.focus()
|
||||
|
||||
The `RFB.focus()` method sets the keyboard focus on the remote session.
|
||||
Keyboard events will be sent to the remote server after this point.
|
||||
|
||||
##### Syntax
|
||||
|
||||
RFB.focus( );
|
||||
```js
|
||||
RFB.approveServer();
|
||||
```
|
||||
|
||||
#### RFB.blur()
|
||||
|
||||
@@ -338,17 +337,70 @@ point.
|
||||
|
||||
##### Syntax
|
||||
|
||||
RFB.blur( );
|
||||
```js
|
||||
RFB.blur();
|
||||
```
|
||||
|
||||
#### RFB.machineShutdown()
|
||||
#### RFB.clipboardPasteFrom()
|
||||
|
||||
The `RFB.machineShutdown()` method is used to request to shut down the
|
||||
remote machine. The capability `power` must be set for this method to
|
||||
have any effect.
|
||||
The `RFB.clipboardPasteFrom()` method is used to send clipboard data
|
||||
to the remote server.
|
||||
|
||||
##### Syntax
|
||||
|
||||
RFB.machineShutdown( );
|
||||
```js
|
||||
RFB.clipboardPasteFrom(text);
|
||||
```
|
||||
|
||||
###### Parameters
|
||||
|
||||
**`text`**
|
||||
- A `DOMString` specifying the clipboard data to send.
|
||||
|
||||
#### RFB.disconnect()
|
||||
|
||||
The `RFB.disconnect()` method is used to disconnect from the currently
|
||||
connected server.
|
||||
|
||||
##### Syntax
|
||||
|
||||
```js
|
||||
RFB.disconnect();
|
||||
```
|
||||
|
||||
#### RFB.focus()
|
||||
|
||||
The `RFB.focus()` method sets the keyboard focus on the remote session.
|
||||
Keyboard events will be sent to the remote server after this point.
|
||||
|
||||
##### Syntax
|
||||
|
||||
```js
|
||||
RFB.focus();
|
||||
RFB.focus(options);
|
||||
```
|
||||
|
||||
###### Parameters
|
||||
|
||||
**`options`** *Optional*
|
||||
- A `object` providing options to control how the focus will be
|
||||
performed. Please see [`HTMLElement.focus()`][mdn-focus] for
|
||||
available options.
|
||||
|
||||
[mdn-focus]: https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus
|
||||
|
||||
#### RFB.getImageData()
|
||||
|
||||
The `RFB.getImageData()` method is used to return the current content of
|
||||
the screen encoded as [`ImageData`][mdn-imagedata].
|
||||
|
||||
[mdn-imagedata]: https://developer.mozilla.org/en-US/docs/Web/API/ImageData
|
||||
|
||||
##### Syntax
|
||||
|
||||
```js
|
||||
RFB.getImageData();
|
||||
```
|
||||
|
||||
#### RFB.machineReboot()
|
||||
|
||||
@@ -358,7 +410,9 @@ to have any effect.
|
||||
|
||||
##### Syntax
|
||||
|
||||
RFB.machineReboot( );
|
||||
```js
|
||||
RFB.machineReboot();
|
||||
```
|
||||
|
||||
#### RFB.machineReset()
|
||||
|
||||
@@ -368,18 +422,125 @@ to have any effect.
|
||||
|
||||
##### Syntax
|
||||
|
||||
RFB.machineReset( );
|
||||
```js
|
||||
RFB.machineReset();
|
||||
```
|
||||
|
||||
#### RFB.clipboardPasteFrom()
|
||||
#### RFB.machineShutdown()
|
||||
|
||||
The `RFB.clipboardPasteFrom()` method is used to send clipboard data
|
||||
to the remote server.
|
||||
The `RFB.machineShutdown()` method is used to request to shut down the
|
||||
remote machine. The capability `power` must be set for this method to
|
||||
have any effect.
|
||||
|
||||
##### Syntax
|
||||
|
||||
RFB.clipboardPasteFrom( text );
|
||||
```js
|
||||
RFB.machineShutdown();
|
||||
```
|
||||
|
||||
#### RFB.sendCredentials()
|
||||
|
||||
The `RFB.sendCredentials()` method is used to provide the missing
|
||||
credentials after a `credentialsrequired` event has been fired.
|
||||
|
||||
##### Syntax
|
||||
|
||||
```js
|
||||
RFB.sendCredentials(credentials);
|
||||
```
|
||||
|
||||
###### Parameters
|
||||
|
||||
**`text`**
|
||||
- A `DOMString` specifying the clipboard data to send.
|
||||
**`credentials`**
|
||||
- An `Object` specifying the credentials to provide to the server
|
||||
when authenticating. See [`RFB()`](#rfb-1) for details.
|
||||
|
||||
#### RFB.sendCtrlAltDel()
|
||||
|
||||
The `RFB.sendCtrlAltDel()` method is used to send the key sequence
|
||||
*left Control*, *left Alt*, *Delete*. This is a convenience wrapper
|
||||
around [`RFB.sendKey()`](#rfbsendkey).
|
||||
|
||||
##### Syntax
|
||||
|
||||
```js
|
||||
RFB.sendCtrlAltDel();
|
||||
```
|
||||
|
||||
#### RFB.sendKey()
|
||||
|
||||
The `RFB.sendKey()` method is used to send a key event to the server.
|
||||
|
||||
##### Syntax
|
||||
|
||||
```js
|
||||
RFB.sendKey(keysym, code);
|
||||
RFB.sendKey(keysym, code, down);
|
||||
```
|
||||
|
||||
###### Parameters
|
||||
|
||||
**`keysym`**
|
||||
- A `long` specifying the RFB keysym to send. Can be `0` if a valid
|
||||
**`code`** is specified.
|
||||
|
||||
**`code`**
|
||||
- A `DOMString` specifying the physical key to send. Valid values are
|
||||
those that can be specified to [`KeyboardEvent.code`][mdn-keycode].
|
||||
If the physical key cannot be determined then `null` shall be
|
||||
specified.
|
||||
|
||||
[mdn-keycode]: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code
|
||||
|
||||
**`down`** *Optional*
|
||||
- A `boolean` specifying if a press or a release event should be
|
||||
sent. If omitted then both a press and release event are sent.
|
||||
|
||||
#### RFB.toBlob()
|
||||
|
||||
The `RFB.toBlob()` method is used to return the current content of the
|
||||
screen encoded as [`Blob`][mdn-blob].
|
||||
|
||||
[mdn-blob]: https://developer.mozilla.org/en-US/docs/Web/API/Blob
|
||||
|
||||
##### Syntax
|
||||
|
||||
```js
|
||||
RFB.toBlob(callback);
|
||||
RFB.toBlob(callback, type);
|
||||
RFB.toBlob(callback, type, quality);
|
||||
```
|
||||
|
||||
###### Parameters
|
||||
|
||||
**`callback`**
|
||||
- A callback function which will receive the resulting
|
||||
[`Blob`][mdn-blob] as the single argument
|
||||
|
||||
**`type`** *Optional*
|
||||
- A string indicating the requested MIME type of the image
|
||||
|
||||
**`quality`** *Optional*
|
||||
- A number between 0 and 1 indicating the image quality.
|
||||
|
||||
#### RFB.toDataURL()
|
||||
|
||||
The `RFB.toDataURL()` method is used to return the current content of the
|
||||
screen encoded as a data URL that could for example be put in the `src` attribute
|
||||
of an `img` tag.
|
||||
|
||||
##### Syntax
|
||||
|
||||
```js
|
||||
RFB.toDataURL();
|
||||
RFB.toDataURL(type);
|
||||
RFB.toDataURL(type, encoderOptions);
|
||||
```
|
||||
|
||||
###### Parameters
|
||||
|
||||
**`type`** *Optional*
|
||||
- A string indicating the requested MIME type of the image
|
||||
|
||||
**`encoderOptions`** *Optional*
|
||||
- A number between 0 and 1 indicating the image quality.
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
.SH NAME
|
||||
novnc_proxy - noVNC proxy server
|
||||
.SH SYNOPSIS
|
||||
.B novnc_proxy [--listen PORT] [--vnc VNC_HOST:PORT] [--cert CERT] [--ssl-only]
|
||||
.B novnc_proxy [--listen [HOST:]PORT] [--vnc VNC_HOST:PORT] [--cert CERT] [--ssl-only]
|
||||
|
||||
Starts the WebSockets proxy and a mini-webserver and
|
||||
provides a cut-and-paste URL to go to.
|
||||
|
||||
--listen PORT Port for proxy/webserver to listen on
|
||||
Default: 6080
|
||||
--listen [HOST:]PORT Port for proxy/webserver to listen on
|
||||
Default: 6080 (on all interfaces)
|
||||
--vnc VNC_HOST:PORT VNC server host:port proxy target
|
||||
Default: localhost:5900
|
||||
--cert CERT Path to combined cert/key file, or just
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@novnc/novnc",
|
||||
"version": "1.3.0-beta",
|
||||
"version": "1.4.0",
|
||||
"description": "An HTML5 VNC client",
|
||||
"browser": "lib/rfb",
|
||||
"directories": {
|
||||
@@ -21,7 +21,7 @@
|
||||
"scripts": {
|
||||
"lint": "eslint app core po/po2js po/xgettext-html tests utils",
|
||||
"test": "karma start karma.conf.js",
|
||||
"prepublish": "node ./utils/use_require.js --clean"
|
||||
"prepublish": "node ./utils/convert.js --clean"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -29,8 +29,6 @@
|
||||
},
|
||||
"author": "Joel Martin <github@martintribe.org> (https://github.com/kanaka)",
|
||||
"contributors": [
|
||||
"Solly Ross <sross@redhat.com> (https://github.com/directxman12)",
|
||||
"Peter Åstrand <astrand@cendio.se> (https://github.com/astrand)",
|
||||
"Samuel Mannehed <samuel@cendio.se> (https://github.com/samhed)",
|
||||
"Pierre Ossman <ossman@cendio.se> (https://github.com/CendioOssman)"
|
||||
],
|
||||
@@ -40,39 +38,30 @@
|
||||
},
|
||||
"homepage": "https://github.com/novnc/noVNC",
|
||||
"devDependencies": {
|
||||
"@babel/core": "*",
|
||||
"@babel/plugin-syntax-dynamic-import": "*",
|
||||
"@babel/plugin-transform-modules-commonjs": "*",
|
||||
"@babel/preset-env": "*",
|
||||
"@babel/cli": "*",
|
||||
"babel-plugin-import-redirect": "*",
|
||||
"browserify": "*",
|
||||
"babelify": "*",
|
||||
"core-js": "*",
|
||||
"chai": "*",
|
||||
"commander": "*",
|
||||
"es-module-loader": "*",
|
||||
"eslint": "*",
|
||||
"fs-extra": "*",
|
||||
"jsdom": "*",
|
||||
"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": "*",
|
||||
"rollup": "*",
|
||||
"rollup-plugin-node-resolve": "*",
|
||||
"sinon": "*",
|
||||
"sinon-chai": "*"
|
||||
"@babel/core": "latest",
|
||||
"@babel/preset-env": "latest",
|
||||
"babel-plugin-import-redirect": "latest",
|
||||
"browserify": "latest",
|
||||
"chai": "latest",
|
||||
"commander": "latest",
|
||||
"eslint": "latest",
|
||||
"fs-extra": "latest",
|
||||
"jsdom": "latest",
|
||||
"karma": "latest",
|
||||
"karma-mocha": "latest",
|
||||
"karma-chrome-launcher": "latest",
|
||||
"@chiragrupani/karma-chromium-edge-launcher": "latest",
|
||||
"karma-firefox-launcher": "latest",
|
||||
"karma-ie-launcher": "latest",
|
||||
"karma-mocha-reporter": "latest",
|
||||
"karma-safari-launcher": "latest",
|
||||
"karma-script-launcher": "latest",
|
||||
"karma-sinon-chai": "latest",
|
||||
"mocha": "latest",
|
||||
"node-getopt": "latest",
|
||||
"po2json": "latest",
|
||||
"sinon": "latest",
|
||||
"sinon-chai": "latest"
|
||||
},
|
||||
"dependencies": {},
|
||||
"keywords": [
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
all:
|
||||
.PHONY: update-po update-js update-pot
|
||||
.PHONY: FORCE
|
||||
|
||||
LINGUAS := cs de el es fr ja ko nl pl pt_BR ru sv tr zh_CN zh_TW
|
||||
LINGUAS := cs de el es fr it ja ko nl pl pt_BR ru sv tr zh_CN zh_TW
|
||||
|
||||
VERSION := $(shell grep '"version"' ../package.json | cut -d '"' -f 4)
|
||||
|
||||
@@ -11,10 +12,10 @@ JSONFILES := $(addprefix ../app/locale/,$(addsuffix .json,$(LINGUAS)))
|
||||
update-po: $(POFILES)
|
||||
update-js: $(JSONFILES)
|
||||
|
||||
%.po: noVNC.pot
|
||||
msgmerge --update --lang=$* $@ $<
|
||||
../app/locale/%.json: %.po
|
||||
./po2js $< $@
|
||||
%.po: FORCE
|
||||
msgmerge --update --lang=$* $@ noVNC.pot
|
||||
../app/locale/%.json: FORCE
|
||||
./po2js $*.po $@
|
||||
|
||||
update-pot:
|
||||
xgettext --output=noVNC.js.pot \
|
||||
|
||||
@@ -3,14 +3,15 @@
|
||||
# Copyright (C) 2021 The noVNC Authors
|
||||
# This file is distributed under the same license as the noVNC package.
|
||||
# Jose <jose.matsuda@canada.ca>, 2021.
|
||||
# Lowxorx <lowxorx@lahan.fr>, 2022.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: noVNC 1.2.0\n"
|
||||
"Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
|
||||
"POT-Creation-Date: 2020-07-03 16:11+0200\n"
|
||||
"PO-Revision-Date: 2021-05-05 20:19-0400\n"
|
||||
"Last-Translator: Jose <jose.matsuda@canada.ca>\n"
|
||||
"PO-Revision-Date: 2022-04-25 23:40+0200\n"
|
||||
"Last-Translator: Lowxorx <lowxorx@lahan.fr>\n"
|
||||
"Language-Team: French\n"
|
||||
"Language: fr\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
@@ -40,15 +41,15 @@ msgstr "Doit définir l'hôte"
|
||||
|
||||
#: ../app/ui.js:1090
|
||||
msgid "Connected (encrypted) to "
|
||||
msgstr "Connecté (crypté) à "
|
||||
msgstr "Connecté (chiffré) à "
|
||||
|
||||
#: ../app/ui.js:1092
|
||||
msgid "Connected (unencrypted) to "
|
||||
msgstr "Connecté (non crypté) à "
|
||||
msgstr "Connecté (non chiffré) à "
|
||||
|
||||
#: ../app/ui.js:1115
|
||||
msgid "Something went wrong, connection is closed"
|
||||
msgstr "Quelque chose est arrivé, la connexion est fermée"
|
||||
msgstr "Quelque chose s'est mal passé, la connexion a été fermée"
|
||||
|
||||
#: ../app/ui.js:1118
|
||||
msgid "Failed to connect to server"
|
||||
@@ -60,7 +61,7 @@ msgstr "Déconnecté"
|
||||
|
||||
#: ../app/ui.js:1143
|
||||
msgid "New connection has been rejected with reason: "
|
||||
msgstr "Une nouvelle connexion a été rejetée avec raison: "
|
||||
msgstr "Une nouvelle connexion a été rejetée avec motif : "
|
||||
|
||||
#: ../app/ui.js:1146
|
||||
msgid "New connection has been rejected"
|
||||
@@ -72,7 +73,7 @@ msgstr "Les identifiants sont requis"
|
||||
|
||||
#: ../vnc.html:74
|
||||
msgid "noVNC encountered an error:"
|
||||
msgstr "noVNC a rencontré une erreur:"
|
||||
msgstr "noVNC a rencontré une erreur :"
|
||||
|
||||
#: ../vnc.html:84
|
||||
msgid "Hide/Show the control bar"
|
||||
@@ -84,7 +85,7 @@ msgstr "Faire glisser"
|
||||
|
||||
#: ../vnc.html:91
|
||||
msgid "Move/Drag Viewport"
|
||||
msgstr "Déplacer/faire glisser Viewport"
|
||||
msgstr "Déplacer/faire glisser le Viewport"
|
||||
|
||||
#: ../vnc.html:97
|
||||
msgid "Keyboard"
|
||||
@@ -204,7 +205,7 @@ msgstr "Clip à fenêtre"
|
||||
|
||||
#: ../vnc.html:185
|
||||
msgid "Scaling Mode:"
|
||||
msgstr "Mode mise à l'échelle:"
|
||||
msgstr "Mode mise à l'échelle :"
|
||||
|
||||
#: ../vnc.html:187
|
||||
msgid "None"
|
||||
@@ -224,15 +225,15 @@ msgstr "Avancé"
|
||||
|
||||
#: ../vnc.html:197
|
||||
msgid "Quality:"
|
||||
msgstr "Qualité:"
|
||||
msgstr "Qualité :"
|
||||
|
||||
#: ../vnc.html:201
|
||||
msgid "Compression level:"
|
||||
msgstr "Niveau de compression:"
|
||||
msgstr "Niveau de compression :"
|
||||
|
||||
#: ../vnc.html:206
|
||||
msgid "Repeater ID:"
|
||||
msgstr "ID Répéteur:"
|
||||
msgstr "ID Répéteur :"
|
||||
|
||||
#: ../vnc.html:210
|
||||
msgid "WebSocket"
|
||||
@@ -240,19 +241,19 @@ msgstr "WebSocket"
|
||||
|
||||
#: ../vnc.html:213
|
||||
msgid "Encrypt"
|
||||
msgstr "Crypter"
|
||||
msgstr "Chiffrer"
|
||||
|
||||
#: ../vnc.html:216
|
||||
msgid "Host:"
|
||||
msgstr "Hôte:"
|
||||
msgstr "Hôte :"
|
||||
|
||||
#: ../vnc.html:220
|
||||
msgid "Port:"
|
||||
msgstr "Port:"
|
||||
msgstr "Port :"
|
||||
|
||||
#: ../vnc.html:224
|
||||
msgid "Path:"
|
||||
msgstr "Chemin:"
|
||||
msgstr "Chemin :"
|
||||
|
||||
#: ../vnc.html:231
|
||||
msgid "Automatic Reconnect"
|
||||
@@ -260,7 +261,7 @@ msgstr "Reconnecter automatiquemen"
|
||||
|
||||
#: ../vnc.html:234
|
||||
msgid "Reconnect Delay (ms):"
|
||||
msgstr "Délai de reconnexion (ms):"
|
||||
msgstr "Délai de reconnexion (ms) :"
|
||||
|
||||
#: ../vnc.html:239
|
||||
msgid "Show Dot when No Cursor"
|
||||
@@ -268,11 +269,11 @@ msgstr "Afficher le point lorsqu'il n'y a pas de curseur"
|
||||
|
||||
#: ../vnc.html:244
|
||||
msgid "Logging:"
|
||||
msgstr "Se connecter:"
|
||||
msgstr "Se connecter :"
|
||||
|
||||
#: ../vnc.html:253
|
||||
msgid "Version:"
|
||||
msgstr "Version:"
|
||||
msgstr "Version :"
|
||||
|
||||
#: ../vnc.html:261
|
||||
msgid "Disconnect"
|
||||
@@ -284,11 +285,11 @@ msgstr "Connecter"
|
||||
|
||||
#: ../vnc.html:290
|
||||
msgid "Username:"
|
||||
msgstr "Nom d'utilisateur:"
|
||||
msgstr "Nom d'utilisateur :"
|
||||
|
||||
#: ../vnc.html:294
|
||||
msgid "Password:"
|
||||
msgstr "Mot de passe:"
|
||||
msgstr "Mot de passe :"
|
||||
|
||||
#: ../vnc.html:298
|
||||
msgid "Send Credentials"
|
||||
|
||||
@@ -0,0 +1,300 @@
|
||||
# Italian translations for noVNC
|
||||
# Traduzione italiana di noVNC
|
||||
# Copyright (C) 2022 The noVNC Authors
|
||||
# This file is distributed under the same license as the noVNC package.
|
||||
# Fabio Fantoni <fabio.fantoni@m2r.biz>, 2022.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: noVNC 1.3.0\n"
|
||||
"Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
|
||||
"POT-Creation-Date: 2021-08-27 16:03+0200\n"
|
||||
"PO-Revision-Date: 2022-09-08 13:27+0200\n"
|
||||
"Last-Translator: Fabio Fantoni <fabio.fantoni@m2r.biz>\n"
|
||||
"Language-Team: Italian\n"
|
||||
"Language: it\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Generator: Poedit 3.1.1\n"
|
||||
|
||||
#: ../app/ui.js:400
|
||||
msgid "Connecting..."
|
||||
msgstr "Connessione in corso..."
|
||||
|
||||
#: ../app/ui.js:407
|
||||
msgid "Disconnecting..."
|
||||
msgstr "Disconnessione..."
|
||||
|
||||
#: ../app/ui.js:413
|
||||
msgid "Reconnecting..."
|
||||
msgstr "Riconnessione..."
|
||||
|
||||
#: ../app/ui.js:418
|
||||
msgid "Internal error"
|
||||
msgstr "Errore interno"
|
||||
|
||||
#: ../app/ui.js:1009
|
||||
msgid "Must set host"
|
||||
msgstr "Devi impostare l'host"
|
||||
|
||||
#: ../app/ui.js:1091
|
||||
msgid "Connected (encrypted) to "
|
||||
msgstr "Connesso (crittografato) a "
|
||||
|
||||
#: ../app/ui.js:1093
|
||||
msgid "Connected (unencrypted) to "
|
||||
msgstr "Connesso (non crittografato) a"
|
||||
|
||||
#: ../app/ui.js:1116
|
||||
msgid "Something went wrong, connection is closed"
|
||||
msgstr "Qualcosa è andato storto, la connessione è stata chiusa"
|
||||
|
||||
#: ../app/ui.js:1119
|
||||
msgid "Failed to connect to server"
|
||||
msgstr "Impossibile connettersi al server"
|
||||
|
||||
#: ../app/ui.js:1129
|
||||
msgid "Disconnected"
|
||||
msgstr "Disconnesso"
|
||||
|
||||
#: ../app/ui.js:1144
|
||||
msgid "New connection has been rejected with reason: "
|
||||
msgstr "La nuova connessione è stata rifiutata con motivo: "
|
||||
|
||||
#: ../app/ui.js:1147
|
||||
msgid "New connection has been rejected"
|
||||
msgstr "La nuova connessione è stata rifiutata"
|
||||
|
||||
#: ../app/ui.js:1182
|
||||
msgid "Credentials are required"
|
||||
msgstr "Le credenziali sono obbligatorie"
|
||||
|
||||
#: ../vnc.html:61
|
||||
msgid "noVNC encountered an error:"
|
||||
msgstr "noVNC ha riscontrato un errore:"
|
||||
|
||||
#: ../vnc.html:71
|
||||
msgid "Hide/Show the control bar"
|
||||
msgstr "Nascondi/Mostra la barra di controllo"
|
||||
|
||||
#: ../vnc.html:78
|
||||
msgid "Drag"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:78
|
||||
msgid "Move/Drag Viewport"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:84
|
||||
msgid "Keyboard"
|
||||
msgstr "Tastiera"
|
||||
|
||||
#: ../vnc.html:84
|
||||
msgid "Show Keyboard"
|
||||
msgstr "Mostra tastiera"
|
||||
|
||||
#: ../vnc.html:89
|
||||
msgid "Extra keys"
|
||||
msgstr "Tasti Aggiuntivi"
|
||||
|
||||
#: ../vnc.html:89
|
||||
msgid "Show Extra Keys"
|
||||
msgstr "Mostra Tasti Aggiuntivi"
|
||||
|
||||
#: ../vnc.html:94
|
||||
msgid "Ctrl"
|
||||
msgstr "Ctrl"
|
||||
|
||||
#: ../vnc.html:94
|
||||
msgid "Toggle Ctrl"
|
||||
msgstr "Tieni premuto Ctrl"
|
||||
|
||||
#: ../vnc.html:97
|
||||
msgid "Alt"
|
||||
msgstr "Alt"
|
||||
|
||||
#: ../vnc.html:97
|
||||
msgid "Toggle Alt"
|
||||
msgstr "Tieni premuto Alt"
|
||||
|
||||
#: ../vnc.html:100
|
||||
msgid "Toggle Windows"
|
||||
msgstr "Tieni premuto Windows"
|
||||
|
||||
#: ../vnc.html:100
|
||||
msgid "Windows"
|
||||
msgstr "Windows"
|
||||
|
||||
#: ../vnc.html:103
|
||||
msgid "Send Tab"
|
||||
msgstr "Invia Tab"
|
||||
|
||||
#: ../vnc.html:103
|
||||
msgid "Tab"
|
||||
msgstr "Tab"
|
||||
|
||||
#: ../vnc.html:106
|
||||
msgid "Esc"
|
||||
msgstr "Esc"
|
||||
|
||||
#: ../vnc.html:106
|
||||
msgid "Send Escape"
|
||||
msgstr "Invia Esc"
|
||||
|
||||
#: ../vnc.html:109
|
||||
msgid "Ctrl+Alt+Del"
|
||||
msgstr "Ctrl+Alt+Canc"
|
||||
|
||||
#: ../vnc.html:109
|
||||
msgid "Send Ctrl-Alt-Del"
|
||||
msgstr "Invia Ctrl-Alt-Canc"
|
||||
|
||||
#: ../vnc.html:116
|
||||
msgid "Shutdown/Reboot"
|
||||
msgstr "Spegnimento/Riavvio"
|
||||
|
||||
#: ../vnc.html:116
|
||||
msgid "Shutdown/Reboot..."
|
||||
msgstr "Spegnimento/Riavvio..."
|
||||
|
||||
#: ../vnc.html:122
|
||||
msgid "Power"
|
||||
msgstr "Alimentazione"
|
||||
|
||||
#: ../vnc.html:124
|
||||
msgid "Shutdown"
|
||||
msgstr "Spegnimento"
|
||||
|
||||
#: ../vnc.html:125
|
||||
msgid "Reboot"
|
||||
msgstr "Riavvio"
|
||||
|
||||
#: ../vnc.html:126
|
||||
msgid "Reset"
|
||||
msgstr "Reset"
|
||||
|
||||
#: ../vnc.html:131 ../vnc.html:137
|
||||
msgid "Clipboard"
|
||||
msgstr "Clipboard"
|
||||
|
||||
#: ../vnc.html:141
|
||||
msgid "Clear"
|
||||
msgstr "Pulisci"
|
||||
|
||||
#: ../vnc.html:147
|
||||
msgid "Fullscreen"
|
||||
msgstr "Schermo intero"
|
||||
|
||||
#: ../vnc.html:152 ../vnc.html:159
|
||||
msgid "Settings"
|
||||
msgstr "Impostazioni"
|
||||
|
||||
#: ../vnc.html:162
|
||||
msgid "Shared Mode"
|
||||
msgstr "Modalità condivisa"
|
||||
|
||||
#: ../vnc.html:165
|
||||
msgid "View Only"
|
||||
msgstr "Sola Visualizzazione"
|
||||
|
||||
#: ../vnc.html:169
|
||||
msgid "Clip to Window"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:172
|
||||
msgid "Scaling Mode:"
|
||||
msgstr "Modalità di ridimensionamento:"
|
||||
|
||||
#: ../vnc.html:174
|
||||
msgid "None"
|
||||
msgstr "Nessuna"
|
||||
|
||||
#: ../vnc.html:175
|
||||
msgid "Local Scaling"
|
||||
msgstr "Ridimensionamento Locale"
|
||||
|
||||
#: ../vnc.html:176
|
||||
msgid "Remote Resizing"
|
||||
msgstr "Ridimensionamento Remoto"
|
||||
|
||||
#: ../vnc.html:181
|
||||
msgid "Advanced"
|
||||
msgstr "Avanzate"
|
||||
|
||||
#: ../vnc.html:184
|
||||
msgid "Quality:"
|
||||
msgstr "Qualità:"
|
||||
|
||||
#: ../vnc.html:188
|
||||
msgid "Compression level:"
|
||||
msgstr "Livello Compressione:"
|
||||
|
||||
#: ../vnc.html:193
|
||||
msgid "Repeater ID:"
|
||||
msgstr "ID Ripetitore:"
|
||||
|
||||
#: ../vnc.html:197
|
||||
msgid "WebSocket"
|
||||
msgstr "WebSocket"
|
||||
|
||||
#: ../vnc.html:200
|
||||
msgid "Encrypt"
|
||||
msgstr "Crittografa"
|
||||
|
||||
#: ../vnc.html:203
|
||||
msgid "Host:"
|
||||
msgstr "Host:"
|
||||
|
||||
#: ../vnc.html:207
|
||||
msgid "Port:"
|
||||
msgstr "Porta:"
|
||||
|
||||
#: ../vnc.html:211
|
||||
msgid "Path:"
|
||||
msgstr "Percorso:"
|
||||
|
||||
#: ../vnc.html:218
|
||||
msgid "Automatic Reconnect"
|
||||
msgstr "Riconnessione Automatica"
|
||||
|
||||
#: ../vnc.html:221
|
||||
msgid "Reconnect Delay (ms):"
|
||||
msgstr "Ritardo Riconnessione (ms):"
|
||||
|
||||
#: ../vnc.html:226
|
||||
msgid "Show Dot when No Cursor"
|
||||
msgstr "Mostra Punto quando Nessun Cursore"
|
||||
|
||||
#: ../vnc.html:231
|
||||
msgid "Logging:"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:240
|
||||
msgid "Version:"
|
||||
msgstr "Versione:"
|
||||
|
||||
#: ../vnc.html:248
|
||||
msgid "Disconnect"
|
||||
msgstr "Disconnetti"
|
||||
|
||||
#: ../vnc.html:267
|
||||
msgid "Connect"
|
||||
msgstr "Connetti"
|
||||
|
||||
#: ../vnc.html:277
|
||||
msgid "Username:"
|
||||
msgstr "Utente:"
|
||||
|
||||
#: ../vnc.html:281
|
||||
msgid "Password:"
|
||||
msgstr "Password:"
|
||||
|
||||
#: ../vnc.html:285
|
||||
msgid "Send Credentials"
|
||||
msgstr "Invia Credenziale"
|
||||
|
||||
#: ../vnc.html:295
|
||||
msgid "Cancel"
|
||||
msgstr "Annulla"
|
||||
@@ -8,8 +8,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: noVNC 1.1.0\n"
|
||||
"Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
|
||||
"POT-Creation-Date: 2020-07-03 16:11+0200\n"
|
||||
"PO-Revision-Date: 2021-01-15 12:37+0900\n"
|
||||
"POT-Creation-Date: 2022-12-27 15:24+0100\n"
|
||||
"PO-Revision-Date: 2023-03-21 12:42+0900\n"
|
||||
"Last-Translator: nnn1590 <nnn1590@nnn1590.org>\n"
|
||||
"Language-Team: Japanese\n"
|
||||
"Language: ja\n"
|
||||
@@ -19,286 +19,325 @@ msgstr ""
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
"X-Generator: Poedit 2.3\n"
|
||||
|
||||
#: ../app/ui.js:394
|
||||
#: ../app/ui.js:69
|
||||
msgid "HTTPS is required for full functionality"
|
||||
msgstr "すべての機能を使用するにはHTTPS接続が必要です"
|
||||
|
||||
#: ../app/ui.js:410
|
||||
msgid "Connecting..."
|
||||
msgstr "接続しています..."
|
||||
|
||||
#: ../app/ui.js:401
|
||||
#: ../app/ui.js:417
|
||||
msgid "Disconnecting..."
|
||||
msgstr "切断しています..."
|
||||
|
||||
#: ../app/ui.js:407
|
||||
#: ../app/ui.js:423
|
||||
msgid "Reconnecting..."
|
||||
msgstr "再接続しています..."
|
||||
|
||||
#: ../app/ui.js:412
|
||||
#: ../app/ui.js:428
|
||||
msgid "Internal error"
|
||||
msgstr "内部エラー"
|
||||
|
||||
#: ../app/ui.js:1008
|
||||
#: ../app/ui.js:1026
|
||||
msgid "Must set host"
|
||||
msgstr "ホストを設定する必要があります"
|
||||
|
||||
#: ../app/ui.js:1090
|
||||
#: ../app/ui.js:1110
|
||||
msgid "Connected (encrypted) to "
|
||||
msgstr "接続しました (暗号化済み): "
|
||||
|
||||
#: ../app/ui.js:1092
|
||||
#: ../app/ui.js:1112
|
||||
msgid "Connected (unencrypted) to "
|
||||
msgstr "接続しました (暗号化されていません): "
|
||||
|
||||
#: ../app/ui.js:1115
|
||||
#: ../app/ui.js:1135
|
||||
msgid "Something went wrong, connection is closed"
|
||||
msgstr "何らかの問題で、接続が閉じられました"
|
||||
|
||||
#: ../app/ui.js:1118
|
||||
#: ../app/ui.js:1138
|
||||
msgid "Failed to connect to server"
|
||||
msgstr "サーバーへの接続に失敗しました"
|
||||
|
||||
#: ../app/ui.js:1128
|
||||
#: ../app/ui.js:1150
|
||||
msgid "Disconnected"
|
||||
msgstr "切断しました"
|
||||
|
||||
#: ../app/ui.js:1143
|
||||
#: ../app/ui.js:1165
|
||||
msgid "New connection has been rejected with reason: "
|
||||
msgstr "新規接続は次の理由で拒否されました: "
|
||||
|
||||
#: ../app/ui.js:1146
|
||||
#: ../app/ui.js:1168
|
||||
msgid "New connection has been rejected"
|
||||
msgstr "新規接続は拒否されました"
|
||||
|
||||
#: ../app/ui.js:1181
|
||||
#: ../app/ui.js:1234
|
||||
msgid "Credentials are required"
|
||||
msgstr "資格情報が必要です"
|
||||
|
||||
#: ../vnc.html:74
|
||||
#: ../vnc.html:57
|
||||
msgid "noVNC encountered an error:"
|
||||
msgstr "noVNC でエラーが発生しました:"
|
||||
|
||||
#: ../vnc.html:84
|
||||
#: ../vnc.html:67
|
||||
msgid "Hide/Show the control bar"
|
||||
msgstr "コントロールバーを隠す/表示する"
|
||||
|
||||
#: ../vnc.html:91
|
||||
#: ../vnc.html:76
|
||||
msgid "Drag"
|
||||
msgstr "ドラッグ"
|
||||
|
||||
#: ../vnc.html:91
|
||||
#: ../vnc.html:76
|
||||
msgid "Move/Drag Viewport"
|
||||
msgstr "ビューポートを移動/ドラッグ"
|
||||
|
||||
#: ../vnc.html:97
|
||||
#: ../vnc.html:82
|
||||
msgid "Keyboard"
|
||||
msgstr "キーボード"
|
||||
|
||||
#: ../vnc.html:97
|
||||
#: ../vnc.html:82
|
||||
msgid "Show Keyboard"
|
||||
msgstr "キーボードを表示"
|
||||
|
||||
#: ../vnc.html:102
|
||||
#: ../vnc.html:87
|
||||
msgid "Extra keys"
|
||||
msgstr "追加キー"
|
||||
|
||||
#: ../vnc.html:102
|
||||
#: ../vnc.html:87
|
||||
msgid "Show Extra Keys"
|
||||
msgstr "追加キーを表示"
|
||||
|
||||
#: ../vnc.html:107
|
||||
#: ../vnc.html:92
|
||||
msgid "Ctrl"
|
||||
msgstr "Ctrl"
|
||||
|
||||
#: ../vnc.html:107
|
||||
#: ../vnc.html:92
|
||||
msgid "Toggle Ctrl"
|
||||
msgstr "Ctrl キーを切り替え"
|
||||
msgstr "Ctrl キーをトグル"
|
||||
|
||||
#: ../vnc.html:110
|
||||
#: ../vnc.html:95
|
||||
msgid "Alt"
|
||||
msgstr "Alt"
|
||||
|
||||
#: ../vnc.html:110
|
||||
#: ../vnc.html:95
|
||||
msgid "Toggle Alt"
|
||||
msgstr "Alt キーを切り替え"
|
||||
msgstr "Alt キーをトグル"
|
||||
|
||||
#: ../vnc.html:113
|
||||
#: ../vnc.html:98
|
||||
msgid "Toggle Windows"
|
||||
msgstr "Windows キーを切り替え"
|
||||
msgstr "Windows キーをトグル"
|
||||
|
||||
#: ../vnc.html:113
|
||||
#: ../vnc.html:98
|
||||
msgid "Windows"
|
||||
msgstr "Windows"
|
||||
|
||||
#: ../vnc.html:116
|
||||
#: ../vnc.html:101
|
||||
msgid "Send Tab"
|
||||
msgstr "Tab キーを送信"
|
||||
|
||||
#: ../vnc.html:116
|
||||
#: ../vnc.html:101
|
||||
msgid "Tab"
|
||||
msgstr "Tab"
|
||||
|
||||
#: ../vnc.html:119
|
||||
#: ../vnc.html:104
|
||||
msgid "Esc"
|
||||
msgstr "Esc"
|
||||
|
||||
#: ../vnc.html:119
|
||||
#: ../vnc.html:104
|
||||
msgid "Send Escape"
|
||||
msgstr "Escape キーを送信"
|
||||
|
||||
#: ../vnc.html:122
|
||||
#: ../vnc.html:107
|
||||
msgid "Ctrl+Alt+Del"
|
||||
msgstr "Ctrl+Alt+Del"
|
||||
|
||||
#: ../vnc.html:122
|
||||
#: ../vnc.html:107
|
||||
msgid "Send Ctrl-Alt-Del"
|
||||
msgstr "Ctrl-Alt-Del を送信"
|
||||
|
||||
#: ../vnc.html:129
|
||||
#: ../vnc.html:114
|
||||
msgid "Shutdown/Reboot"
|
||||
msgstr "シャットダウン/再起動"
|
||||
|
||||
#: ../vnc.html:129
|
||||
#: ../vnc.html:114
|
||||
msgid "Shutdown/Reboot..."
|
||||
msgstr "シャットダウン/再起動..."
|
||||
|
||||
#: ../vnc.html:135
|
||||
#: ../vnc.html:120
|
||||
msgid "Power"
|
||||
msgstr "電源"
|
||||
|
||||
#: ../vnc.html:137
|
||||
#: ../vnc.html:122
|
||||
msgid "Shutdown"
|
||||
msgstr "シャットダウン"
|
||||
|
||||
#: ../vnc.html:138
|
||||
#: ../vnc.html:123
|
||||
msgid "Reboot"
|
||||
msgstr "再起動"
|
||||
|
||||
#: ../vnc.html:139
|
||||
#: ../vnc.html:124
|
||||
msgid "Reset"
|
||||
msgstr "リセット"
|
||||
|
||||
#: ../vnc.html:144 ../vnc.html:150
|
||||
#: ../vnc.html:129 ../vnc.html:135
|
||||
msgid "Clipboard"
|
||||
msgstr "クリップボード"
|
||||
|
||||
#: ../vnc.html:154
|
||||
msgid "Clear"
|
||||
msgstr "クリア"
|
||||
#: ../vnc.html:137
|
||||
msgid "Edit clipboard content in the textarea below."
|
||||
msgstr "以下の入力欄からクリップボードの内容を編集できます。"
|
||||
|
||||
#: ../vnc.html:160
|
||||
msgid "Fullscreen"
|
||||
#: ../vnc.html:145
|
||||
msgid "Full Screen"
|
||||
msgstr "全画面表示"
|
||||
|
||||
#: ../vnc.html:165 ../vnc.html:172
|
||||
#: ../vnc.html:150 ../vnc.html:156
|
||||
msgid "Settings"
|
||||
msgstr "設定"
|
||||
|
||||
#: ../vnc.html:175
|
||||
#: ../vnc.html:160
|
||||
msgid "Shared Mode"
|
||||
msgstr "共有モード"
|
||||
|
||||
#: ../vnc.html:178
|
||||
#: ../vnc.html:163
|
||||
msgid "View Only"
|
||||
msgstr "表示のみ"
|
||||
msgstr "表示専用"
|
||||
|
||||
#: ../vnc.html:182
|
||||
#: ../vnc.html:167
|
||||
msgid "Clip to Window"
|
||||
msgstr "ウィンドウにクリップ"
|
||||
|
||||
#: ../vnc.html:185
|
||||
#: ../vnc.html:170
|
||||
msgid "Scaling Mode:"
|
||||
msgstr "スケーリングモード:"
|
||||
|
||||
#: ../vnc.html:187
|
||||
#: ../vnc.html:172
|
||||
msgid "None"
|
||||
msgstr "なし"
|
||||
|
||||
#: ../vnc.html:188
|
||||
#: ../vnc.html:173
|
||||
msgid "Local Scaling"
|
||||
msgstr "ローカルスケーリング"
|
||||
|
||||
#: ../vnc.html:189
|
||||
#: ../vnc.html:174
|
||||
msgid "Remote Resizing"
|
||||
msgstr "リモートでリサイズ"
|
||||
|
||||
#: ../vnc.html:194
|
||||
#: ../vnc.html:179
|
||||
msgid "Advanced"
|
||||
msgstr "高度"
|
||||
|
||||
#: ../vnc.html:197
|
||||
#: ../vnc.html:182
|
||||
msgid "Quality:"
|
||||
msgstr "品質:"
|
||||
|
||||
#: ../vnc.html:201
|
||||
#: ../vnc.html:186
|
||||
msgid "Compression level:"
|
||||
msgstr "圧縮レベル:"
|
||||
|
||||
#: ../vnc.html:206
|
||||
#: ../vnc.html:191
|
||||
msgid "Repeater ID:"
|
||||
msgstr "リピーター ID:"
|
||||
|
||||
#: ../vnc.html:210
|
||||
#: ../vnc.html:195
|
||||
msgid "WebSocket"
|
||||
msgstr "WebSocket"
|
||||
|
||||
#: ../vnc.html:213
|
||||
#: ../vnc.html:198
|
||||
msgid "Encrypt"
|
||||
msgstr "暗号化"
|
||||
|
||||
#: ../vnc.html:216
|
||||
#: ../vnc.html:201
|
||||
msgid "Host:"
|
||||
msgstr "ホスト:"
|
||||
|
||||
#: ../vnc.html:220
|
||||
#: ../vnc.html:205
|
||||
msgid "Port:"
|
||||
msgstr "ポート:"
|
||||
|
||||
#: ../vnc.html:224
|
||||
#: ../vnc.html:209
|
||||
msgid "Path:"
|
||||
msgstr "パス:"
|
||||
|
||||
#: ../vnc.html:231
|
||||
#: ../vnc.html:216
|
||||
msgid "Automatic Reconnect"
|
||||
msgstr "自動再接続"
|
||||
|
||||
#: ../vnc.html:234
|
||||
#: ../vnc.html:219
|
||||
msgid "Reconnect Delay (ms):"
|
||||
msgstr "再接続する遅延 (ミリ秒):"
|
||||
|
||||
#: ../vnc.html:239
|
||||
#: ../vnc.html:224
|
||||
msgid "Show Dot when No Cursor"
|
||||
msgstr "カーソルがないときにドットを表示"
|
||||
msgstr "カーソルがないときにドットを表示する"
|
||||
|
||||
#: ../vnc.html:244
|
||||
#: ../vnc.html:229
|
||||
msgid "Logging:"
|
||||
msgstr "ロギング:"
|
||||
|
||||
#: ../vnc.html:253
|
||||
#: ../vnc.html:238
|
||||
msgid "Version:"
|
||||
msgstr "バージョン:"
|
||||
|
||||
#: ../vnc.html:261
|
||||
#: ../vnc.html:246
|
||||
msgid "Disconnect"
|
||||
msgstr "切断"
|
||||
|
||||
#: ../vnc.html:280
|
||||
#: ../vnc.html:269
|
||||
msgid "Connect"
|
||||
msgstr "接続"
|
||||
|
||||
#: ../vnc.html:290
|
||||
#: ../vnc.html:278
|
||||
msgid "Server identity"
|
||||
msgstr "サーバーの識別情報"
|
||||
|
||||
#: ../vnc.html:281
|
||||
msgid "The server has provided the following identifying information:"
|
||||
msgstr "サーバーは以下の識別情報を提供しています:"
|
||||
|
||||
#: ../vnc.html:285
|
||||
msgid "Fingerprint:"
|
||||
msgstr "フィンガープリント:"
|
||||
|
||||
#: ../vnc.html:288
|
||||
msgid ""
|
||||
"Please verify that the information is correct and press \"Approve\". "
|
||||
"Otherwise press \"Reject\"."
|
||||
msgstr ""
|
||||
"この情報が正しい場合は「承認」を、そうでない場合は「拒否」を押してく"
|
||||
"ださい。"
|
||||
|
||||
#: ../vnc.html:293
|
||||
msgid "Approve"
|
||||
msgstr "承認"
|
||||
|
||||
#: ../vnc.html:294
|
||||
msgid "Reject"
|
||||
msgstr "拒否"
|
||||
|
||||
#: ../vnc.html:302
|
||||
msgid "Credentials"
|
||||
msgstr "資格情報"
|
||||
|
||||
#: ../vnc.html:306
|
||||
msgid "Username:"
|
||||
msgstr "ユーザー名:"
|
||||
|
||||
#: ../vnc.html:294
|
||||
#: ../vnc.html:310
|
||||
msgid "Password:"
|
||||
msgstr "パスワード:"
|
||||
|
||||
#: ../vnc.html:298
|
||||
#: ../vnc.html:314
|
||||
msgid "Send Credentials"
|
||||
msgstr "資格情報を送信"
|
||||
|
||||
#: ../vnc.html:308
|
||||
#: ../vnc.html:323
|
||||
msgid "Cancel"
|
||||
msgstr "キャンセル"
|
||||
|
||||
#~ msgid "Clear"
|
||||
#~ msgstr "クリア"
|
||||
|
||||
#~ msgid "Password is required"
|
||||
#~ msgstr "パスワードが必要です"
|
||||
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: noVNC 1.3.0\n"
|
||||
"Project-Id-Version: noVNC 1.4.0\n"
|
||||
"Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
|
||||
"POT-Creation-Date: 2021-08-27 16:03+0200\n"
|
||||
"POT-Creation-Date: 2022-12-27 15:24+0100\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,282 +17,316 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=CHARSET\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: ../app/ui.js:400
|
||||
#: ../app/ui.js:69
|
||||
msgid "HTTPS is required for full functionality"
|
||||
msgstr ""
|
||||
|
||||
#: ../app/ui.js:410
|
||||
msgid "Connecting..."
|
||||
msgstr ""
|
||||
|
||||
#: ../app/ui.js:407
|
||||
#: ../app/ui.js:417
|
||||
msgid "Disconnecting..."
|
||||
msgstr ""
|
||||
|
||||
#: ../app/ui.js:413
|
||||
#: ../app/ui.js:423
|
||||
msgid "Reconnecting..."
|
||||
msgstr ""
|
||||
|
||||
#: ../app/ui.js:418
|
||||
#: ../app/ui.js:428
|
||||
msgid "Internal error"
|
||||
msgstr ""
|
||||
|
||||
#: ../app/ui.js:1009
|
||||
#: ../app/ui.js:1026
|
||||
msgid "Must set host"
|
||||
msgstr ""
|
||||
|
||||
#: ../app/ui.js:1091
|
||||
#: ../app/ui.js:1110
|
||||
msgid "Connected (encrypted) to "
|
||||
msgstr ""
|
||||
|
||||
#: ../app/ui.js:1093
|
||||
#: ../app/ui.js:1112
|
||||
msgid "Connected (unencrypted) to "
|
||||
msgstr ""
|
||||
|
||||
#: ../app/ui.js:1116
|
||||
#: ../app/ui.js:1135
|
||||
msgid "Something went wrong, connection is closed"
|
||||
msgstr ""
|
||||
|
||||
#: ../app/ui.js:1119
|
||||
#: ../app/ui.js:1138
|
||||
msgid "Failed to connect to server"
|
||||
msgstr ""
|
||||
|
||||
#: ../app/ui.js:1129
|
||||
#: ../app/ui.js:1150
|
||||
msgid "Disconnected"
|
||||
msgstr ""
|
||||
|
||||
#: ../app/ui.js:1144
|
||||
#: ../app/ui.js:1165
|
||||
msgid "New connection has been rejected with reason: "
|
||||
msgstr ""
|
||||
|
||||
#: ../app/ui.js:1147
|
||||
#: ../app/ui.js:1168
|
||||
msgid "New connection has been rejected"
|
||||
msgstr ""
|
||||
|
||||
#: ../app/ui.js:1182
|
||||
#: ../app/ui.js:1234
|
||||
msgid "Credentials are required"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:61
|
||||
#: ../vnc.html:57
|
||||
msgid "noVNC encountered an error:"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:71
|
||||
#: ../vnc.html:67
|
||||
msgid "Hide/Show the control bar"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:78
|
||||
#: ../vnc.html:76
|
||||
msgid "Drag"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:78
|
||||
#: ../vnc.html:76
|
||||
msgid "Move/Drag Viewport"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:84
|
||||
#: ../vnc.html:82
|
||||
msgid "Keyboard"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:84
|
||||
#: ../vnc.html:82
|
||||
msgid "Show Keyboard"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:89
|
||||
#: ../vnc.html:87
|
||||
msgid "Extra keys"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:89
|
||||
#: ../vnc.html:87
|
||||
msgid "Show Extra Keys"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:94
|
||||
#: ../vnc.html:92
|
||||
msgid "Ctrl"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:94
|
||||
#: ../vnc.html:92
|
||||
msgid "Toggle Ctrl"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:97
|
||||
#: ../vnc.html:95
|
||||
msgid "Alt"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:97
|
||||
#: ../vnc.html:95
|
||||
msgid "Toggle Alt"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:100
|
||||
#: ../vnc.html:98
|
||||
msgid "Toggle Windows"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:100
|
||||
#: ../vnc.html:98
|
||||
msgid "Windows"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:103
|
||||
#: ../vnc.html:101
|
||||
msgid "Send Tab"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:103
|
||||
#: ../vnc.html:101
|
||||
msgid "Tab"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:106
|
||||
#: ../vnc.html:104
|
||||
msgid "Esc"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:106
|
||||
#: ../vnc.html:104
|
||||
msgid "Send Escape"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:109
|
||||
#: ../vnc.html:107
|
||||
msgid "Ctrl+Alt+Del"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:109
|
||||
#: ../vnc.html:107
|
||||
msgid "Send Ctrl-Alt-Del"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:116
|
||||
#: ../vnc.html:114
|
||||
msgid "Shutdown/Reboot"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:116
|
||||
#: ../vnc.html:114
|
||||
msgid "Shutdown/Reboot..."
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:122
|
||||
#: ../vnc.html:120
|
||||
msgid "Power"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:124
|
||||
#: ../vnc.html:122
|
||||
msgid "Shutdown"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:125
|
||||
#: ../vnc.html:123
|
||||
msgid "Reboot"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:126
|
||||
#: ../vnc.html:124
|
||||
msgid "Reset"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:131 ../vnc.html:137
|
||||
#: ../vnc.html:129 ../vnc.html:135
|
||||
msgid "Clipboard"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:141
|
||||
msgid "Clear"
|
||||
#: ../vnc.html:137
|
||||
msgid "Edit clipboard content in the textarea below."
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:147
|
||||
msgid "Fullscreen"
|
||||
#: ../vnc.html:145
|
||||
msgid "Full Screen"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:152 ../vnc.html:159
|
||||
#: ../vnc.html:150 ../vnc.html:156
|
||||
msgid "Settings"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:162
|
||||
#: ../vnc.html:160
|
||||
msgid "Shared Mode"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:165
|
||||
#: ../vnc.html:163
|
||||
msgid "View Only"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:169
|
||||
#: ../vnc.html:167
|
||||
msgid "Clip to Window"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:172
|
||||
#: ../vnc.html:170
|
||||
msgid "Scaling Mode:"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:174
|
||||
#: ../vnc.html:172
|
||||
msgid "None"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:175
|
||||
#: ../vnc.html:173
|
||||
msgid "Local Scaling"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:176
|
||||
#: ../vnc.html:174
|
||||
msgid "Remote Resizing"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:181
|
||||
#: ../vnc.html:179
|
||||
msgid "Advanced"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:184
|
||||
#: ../vnc.html:182
|
||||
msgid "Quality:"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:188
|
||||
#: ../vnc.html:186
|
||||
msgid "Compression level:"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:193
|
||||
#: ../vnc.html:191
|
||||
msgid "Repeater ID:"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:197
|
||||
#: ../vnc.html:195
|
||||
msgid "WebSocket"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:200
|
||||
#: ../vnc.html:198
|
||||
msgid "Encrypt"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:203
|
||||
#: ../vnc.html:201
|
||||
msgid "Host:"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:207
|
||||
#: ../vnc.html:205
|
||||
msgid "Port:"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:211
|
||||
#: ../vnc.html:209
|
||||
msgid "Path:"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:218
|
||||
#: ../vnc.html:216
|
||||
msgid "Automatic Reconnect"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:221
|
||||
#: ../vnc.html:219
|
||||
msgid "Reconnect Delay (ms):"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:226
|
||||
#: ../vnc.html:224
|
||||
msgid "Show Dot when No Cursor"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:231
|
||||
#: ../vnc.html:229
|
||||
msgid "Logging:"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:240
|
||||
#: ../vnc.html:238
|
||||
msgid "Version:"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:248
|
||||
#: ../vnc.html:246
|
||||
msgid "Disconnect"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:267
|
||||
#: ../vnc.html:269
|
||||
msgid "Connect"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:277
|
||||
msgid "Username:"
|
||||
#: ../vnc.html:278
|
||||
msgid "Server identity"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:281
|
||||
msgid "Password:"
|
||||
msgid "The server has provided the following identifying information:"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:285
|
||||
msgid "Fingerprint:"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:288
|
||||
msgid ""
|
||||
"Please verify that the information is correct and press \"Approve\". "
|
||||
"Otherwise press \"Reject\"."
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:293
|
||||
msgid "Approve"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:294
|
||||
msgid "Reject"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:302
|
||||
msgid "Credentials"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:306
|
||||
msgid "Username:"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:310
|
||||
msgid "Password:"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:314
|
||||
msgid "Send Credentials"
|
||||
msgstr ""
|
||||
|
||||
#: ../vnc.html:295
|
||||
#: ../vnc.html:323
|
||||
msgid "Cancel"
|
||||
msgstr ""
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: noVNC 1.1.0\n"
|
||||
"Project-Id-Version: noVNC 1.3.0\n"
|
||||
"Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
|
||||
"POT-Creation-Date: 2019-02-26 14:53+0400\n"
|
||||
"PO-Revision-Date: 2019-02-17 17:29+0400\n"
|
||||
"Last-Translator: Dmitriy Shweew <shweew@it-advisor.ru>\n"
|
||||
"POT-Creation-Date: 2021-08-27 16:03+0200\n"
|
||||
"PO-Revision-Date: 2021-09-09 10:29+0400\n"
|
||||
"Last-Translator: Nia Remez <nia.remez@cendio.com>\n"
|
||||
"Language-Team: Russian\n"
|
||||
"Language: ru\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
@@ -21,286 +21,282 @@ msgstr ""
|
||||
"X-Generator: Poedit 2.2.1\n"
|
||||
"X-Poedit-Flags-xgettext: --add-comments\n"
|
||||
|
||||
#: ../app/ui.js:387
|
||||
#: ../app/ui.js:400
|
||||
msgid "Connecting..."
|
||||
msgstr "Подключение..."
|
||||
|
||||
#: ../app/ui.js:394
|
||||
#: ../app/ui.js:407
|
||||
msgid "Disconnecting..."
|
||||
msgstr "Отключение..."
|
||||
|
||||
#: ../app/ui.js:400
|
||||
#: ../app/ui.js:413
|
||||
msgid "Reconnecting..."
|
||||
msgstr "Переподключение..."
|
||||
|
||||
#: ../app/ui.js:405
|
||||
#: ../app/ui.js:418
|
||||
msgid "Internal error"
|
||||
msgstr "Внутренняя ошибка"
|
||||
|
||||
#: ../app/ui.js:995
|
||||
#: ../app/ui.js:1009
|
||||
msgid "Must set host"
|
||||
msgstr "Задайте имя сервера или IP"
|
||||
|
||||
#: ../app/ui.js:1077
|
||||
#: ../app/ui.js:1091
|
||||
msgid "Connected (encrypted) to "
|
||||
msgstr "Подключено (с шифрованием) к "
|
||||
|
||||
#: ../app/ui.js:1079
|
||||
#: ../app/ui.js:1093
|
||||
msgid "Connected (unencrypted) to "
|
||||
msgstr "Подключено (без шифрования) к "
|
||||
|
||||
#: ../app/ui.js:1102
|
||||
#: ../app/ui.js:1116
|
||||
msgid "Something went wrong, connection is closed"
|
||||
msgstr "Что-то пошло не так, подключение разорвано"
|
||||
|
||||
#: ../app/ui.js:1105
|
||||
#: ../app/ui.js:1119
|
||||
msgid "Failed to connect to server"
|
||||
msgstr "Ошибка подключения к серверу"
|
||||
|
||||
#: ../app/ui.js:1115
|
||||
#: ../app/ui.js:1129
|
||||
msgid "Disconnected"
|
||||
msgstr "Отключено"
|
||||
|
||||
#: ../app/ui.js:1128
|
||||
#: ../app/ui.js:1144
|
||||
msgid "New connection has been rejected with reason: "
|
||||
msgstr "Подключиться не удалось: "
|
||||
msgstr "Новое соединение отклонено по причине: "
|
||||
|
||||
#: ../app/ui.js:1131
|
||||
#: ../app/ui.js:1147
|
||||
msgid "New connection has been rejected"
|
||||
msgstr "Подключиться не удалось"
|
||||
msgstr "Новое соединение отклонено"
|
||||
|
||||
#: ../app/ui.js:1151
|
||||
msgid "Password is required"
|
||||
msgstr "Требуется пароль"
|
||||
#: ../app/ui.js:1182
|
||||
msgid "Credentials are required"
|
||||
msgstr "Требуются учетные данные"
|
||||
|
||||
#: ../vnc.html:84
|
||||
#: ../vnc.html:61
|
||||
msgid "noVNC encountered an error:"
|
||||
msgstr "Ошибка noVNC: "
|
||||
|
||||
#: ../vnc.html:94
|
||||
#: ../vnc.html:71
|
||||
msgid "Hide/Show the control bar"
|
||||
msgstr "Скрыть/Показать контрольную панель"
|
||||
|
||||
#: ../vnc.html:101
|
||||
#: ../vnc.html:78
|
||||
msgid "Drag"
|
||||
msgstr "Переместить"
|
||||
|
||||
#: ../vnc.html:78
|
||||
msgid "Move/Drag Viewport"
|
||||
msgstr "Переместить окно"
|
||||
|
||||
#: ../vnc.html:101
|
||||
msgid "viewport drag"
|
||||
msgstr "Переместить окно"
|
||||
|
||||
#: ../vnc.html:107 ../vnc.html:110 ../vnc.html:113 ../vnc.html:116
|
||||
msgid "Active Mouse Button"
|
||||
msgstr "Активировать кнопки мыши"
|
||||
|
||||
#: ../vnc.html:107
|
||||
msgid "No mousebutton"
|
||||
msgstr "Отключить кнопки мыши"
|
||||
|
||||
#: ../vnc.html:110
|
||||
msgid "Left mousebutton"
|
||||
msgstr "Левая кнопка мыши"
|
||||
|
||||
#: ../vnc.html:113
|
||||
msgid "Middle mousebutton"
|
||||
msgstr "Средняя кнопка мыши"
|
||||
|
||||
#: ../vnc.html:116
|
||||
msgid "Right mousebutton"
|
||||
msgstr "Правая кнопка мыши"
|
||||
|
||||
#: ../vnc.html:119
|
||||
#: ../vnc.html:84
|
||||
msgid "Keyboard"
|
||||
msgstr "Клавиатура"
|
||||
|
||||
#: ../vnc.html:119
|
||||
#: ../vnc.html:84
|
||||
msgid "Show Keyboard"
|
||||
msgstr "Показать клавиатуру"
|
||||
|
||||
#: ../vnc.html:126
|
||||
#: ../vnc.html:89
|
||||
msgid "Extra keys"
|
||||
msgstr "Доп. кнопки"
|
||||
msgstr "Дополнительные Кнопки"
|
||||
|
||||
#: ../vnc.html:126
|
||||
#: ../vnc.html:89
|
||||
msgid "Show Extra Keys"
|
||||
msgstr "Показать дополнительные кнопки"
|
||||
msgstr "Показать Дополнительные Кнопки"
|
||||
|
||||
#: ../vnc.html:131
|
||||
#: ../vnc.html:94
|
||||
msgid "Ctrl"
|
||||
msgstr "Ctrl"
|
||||
|
||||
#: ../vnc.html:131
|
||||
#: ../vnc.html:94
|
||||
msgid "Toggle Ctrl"
|
||||
msgstr "Передать нажатие Ctrl"
|
||||
msgstr "Переключение нажатия Ctrl"
|
||||
|
||||
#: ../vnc.html:134
|
||||
#: ../vnc.html:97
|
||||
msgid "Alt"
|
||||
msgstr "Alt"
|
||||
|
||||
#: ../vnc.html:134
|
||||
#: ../vnc.html:97
|
||||
msgid "Toggle Alt"
|
||||
msgstr "Передать нажатие Alt"
|
||||
msgstr "Переключение нажатия Alt"
|
||||
|
||||
#: ../vnc.html:137
|
||||
#: ../vnc.html:100
|
||||
msgid "Toggle Windows"
|
||||
msgstr "Переключение вкладок"
|
||||
|
||||
#: ../vnc.html:137
|
||||
#: ../vnc.html:100
|
||||
msgid "Windows"
|
||||
msgstr "Вкладка"
|
||||
|
||||
#: ../vnc.html:140
|
||||
#: ../vnc.html:103
|
||||
msgid "Send Tab"
|
||||
msgstr "Передать нажатие Tab"
|
||||
|
||||
#: ../vnc.html:140
|
||||
#: ../vnc.html:103
|
||||
msgid "Tab"
|
||||
msgstr "Tab"
|
||||
|
||||
#: ../vnc.html:143
|
||||
#: ../vnc.html:106
|
||||
msgid "Esc"
|
||||
msgstr "Esc"
|
||||
|
||||
#: ../vnc.html:143
|
||||
#: ../vnc.html:106
|
||||
msgid "Send Escape"
|
||||
msgstr "Передать нажатие Escape"
|
||||
|
||||
#: ../vnc.html:146
|
||||
#: ../vnc.html:109
|
||||
msgid "Ctrl+Alt+Del"
|
||||
msgstr "Ctrl+Alt+Del"
|
||||
|
||||
#: ../vnc.html:146
|
||||
#: ../vnc.html:109
|
||||
msgid "Send Ctrl-Alt-Del"
|
||||
msgstr "Передать нажатие Ctrl-Alt-Del"
|
||||
|
||||
#: ../vnc.html:154
|
||||
#: ../vnc.html:116
|
||||
msgid "Shutdown/Reboot"
|
||||
msgstr "Выключить/Перезагрузить"
|
||||
|
||||
#: ../vnc.html:154
|
||||
#: ../vnc.html:116
|
||||
msgid "Shutdown/Reboot..."
|
||||
msgstr "Выключить/Перезагрузить..."
|
||||
|
||||
#: ../vnc.html:160
|
||||
#: ../vnc.html:122
|
||||
msgid "Power"
|
||||
msgstr "Питание"
|
||||
|
||||
#: ../vnc.html:162
|
||||
#: ../vnc.html:124
|
||||
msgid "Shutdown"
|
||||
msgstr "Выключить"
|
||||
|
||||
#: ../vnc.html:163
|
||||
#: ../vnc.html:125
|
||||
msgid "Reboot"
|
||||
msgstr "Перезагрузить"
|
||||
|
||||
#: ../vnc.html:164
|
||||
#: ../vnc.html:126
|
||||
msgid "Reset"
|
||||
msgstr "Сброс"
|
||||
|
||||
#: ../vnc.html:169 ../vnc.html:175
|
||||
#: ../vnc.html:131 ../vnc.html:137
|
||||
msgid "Clipboard"
|
||||
msgstr "Буфер обмена"
|
||||
|
||||
#: ../vnc.html:179
|
||||
#: ../vnc.html:141
|
||||
msgid "Clear"
|
||||
msgstr "Очистить"
|
||||
|
||||
#: ../vnc.html:185
|
||||
#: ../vnc.html:147
|
||||
msgid "Fullscreen"
|
||||
msgstr "Во весь экран"
|
||||
|
||||
#: ../vnc.html:190 ../vnc.html:197
|
||||
#: ../vnc.html:152 ../vnc.html:159
|
||||
msgid "Settings"
|
||||
msgstr "Настройки"
|
||||
|
||||
#: ../vnc.html:200
|
||||
#: ../vnc.html:162
|
||||
msgid "Shared Mode"
|
||||
msgstr "Общий режим"
|
||||
|
||||
#: ../vnc.html:203
|
||||
#: ../vnc.html:165
|
||||
msgid "View Only"
|
||||
msgstr "Просмотр"
|
||||
msgstr "Только Просмотр"
|
||||
|
||||
#: ../vnc.html:207
|
||||
#: ../vnc.html:169
|
||||
msgid "Clip to Window"
|
||||
msgstr "В окно"
|
||||
|
||||
#: ../vnc.html:210
|
||||
#: ../vnc.html:172
|
||||
msgid "Scaling Mode:"
|
||||
msgstr "Масштаб:"
|
||||
|
||||
#: ../vnc.html:212
|
||||
#: ../vnc.html:174
|
||||
msgid "None"
|
||||
msgstr "Нет"
|
||||
|
||||
#: ../vnc.html:213
|
||||
#: ../vnc.html:175
|
||||
msgid "Local Scaling"
|
||||
msgstr "Локльный масштаб"
|
||||
|
||||
#: ../vnc.html:214
|
||||
#: ../vnc.html:176
|
||||
msgid "Remote Resizing"
|
||||
msgstr "Удаленный масштаб"
|
||||
msgstr "Удаленная перенастройка размера"
|
||||
|
||||
#: ../vnc.html:219
|
||||
#: ../vnc.html:181
|
||||
msgid "Advanced"
|
||||
msgstr "Дополнительно"
|
||||
|
||||
#: ../vnc.html:222
|
||||
#: ../vnc.html:184
|
||||
msgid "Quality:"
|
||||
msgstr "Качество"
|
||||
|
||||
#: ../vnc.html:188
|
||||
msgid "Compression level:"
|
||||
msgstr "Уровень Сжатия"
|
||||
|
||||
#: ../vnc.html:193
|
||||
msgid "Repeater ID:"
|
||||
msgstr "Идентификатор ID:"
|
||||
|
||||
#: ../vnc.html:226
|
||||
#: ../vnc.html:197
|
||||
msgid "WebSocket"
|
||||
msgstr "WebSocket"
|
||||
|
||||
#: ../vnc.html:229
|
||||
#: ../vnc.html:200
|
||||
msgid "Encrypt"
|
||||
msgstr "Шифрование"
|
||||
|
||||
#: ../vnc.html:232
|
||||
#: ../vnc.html:203
|
||||
msgid "Host:"
|
||||
msgstr "Сервер:"
|
||||
|
||||
#: ../vnc.html:236
|
||||
#: ../vnc.html:207
|
||||
msgid "Port:"
|
||||
msgstr "Порт:"
|
||||
|
||||
#: ../vnc.html:240
|
||||
#: ../vnc.html:211
|
||||
msgid "Path:"
|
||||
msgstr "Путь:"
|
||||
|
||||
#: ../vnc.html:247
|
||||
#: ../vnc.html:218
|
||||
msgid "Automatic Reconnect"
|
||||
msgstr "Автоматическое переподключение"
|
||||
|
||||
#: ../vnc.html:250
|
||||
#: ../vnc.html:221
|
||||
msgid "Reconnect Delay (ms):"
|
||||
msgstr "Задержка переподключения (мс):"
|
||||
|
||||
#: ../vnc.html:255
|
||||
#: ../vnc.html:226
|
||||
msgid "Show Dot when No Cursor"
|
||||
msgstr "Показать точку вместо курсора"
|
||||
|
||||
#: ../vnc.html:260
|
||||
#: ../vnc.html:231
|
||||
msgid "Logging:"
|
||||
msgstr "Лог:"
|
||||
|
||||
#: ../vnc.html:272
|
||||
#: ../vnc.html:240
|
||||
msgid "Version:"
|
||||
msgstr "Версия"
|
||||
|
||||
#: ../vnc.html:248
|
||||
msgid "Disconnect"
|
||||
msgstr "Отключение"
|
||||
|
||||
#: ../vnc.html:291
|
||||
#: ../vnc.html:267
|
||||
msgid "Connect"
|
||||
msgstr "Подключение"
|
||||
|
||||
#: ../vnc.html:301
|
||||
#: ../vnc.html:277
|
||||
msgid "Username:"
|
||||
msgstr "Имя Пользователя"
|
||||
|
||||
#: ../vnc.html:281
|
||||
msgid "Password:"
|
||||
msgstr "Пароль:"
|
||||
|
||||
#: ../vnc.html:305
|
||||
msgid "Send Password"
|
||||
msgstr "Пароль: "
|
||||
#: ../vnc.html:285
|
||||
msgid "Send Credentials"
|
||||
msgstr "Передача Учетных Данных"
|
||||
|
||||
#: ../vnc.html:315
|
||||
#: ../vnc.html:295
|
||||
msgid "Cancel"
|
||||
msgstr "Выход"
|
||||
|
||||
@@ -8,8 +8,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: noVNC 1.3.0\n"
|
||||
"Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
|
||||
"POT-Creation-Date: 2021-08-27 16:03+0200\n"
|
||||
"PO-Revision-Date: 2021-08-27 16:18+0200\n"
|
||||
"POT-Creation-Date: 2023-01-20 12:54+0100\n"
|
||||
"PO-Revision-Date: 2023-01-20 12:58+0100\n"
|
||||
"Last-Translator: Samuel Mannehed <samuel@cendio.se>\n"
|
||||
"Language-Team: none\n"
|
||||
"Language: sv\n"
|
||||
@@ -17,265 +17,269 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Generator: Poedit 2.0.3\n"
|
||||
"X-Generator: Poedit 3.2.2\n"
|
||||
|
||||
#: ../app/ui.js:400
|
||||
#: ../app/ui.js:69
|
||||
msgid "HTTPS is required for full functionality"
|
||||
msgstr "HTTPS krävs för full funktionalitet"
|
||||
|
||||
#: ../app/ui.js:410
|
||||
msgid "Connecting..."
|
||||
msgstr "Ansluter..."
|
||||
|
||||
#: ../app/ui.js:407
|
||||
#: ../app/ui.js:417
|
||||
msgid "Disconnecting..."
|
||||
msgstr "Kopplar ner..."
|
||||
|
||||
#: ../app/ui.js:413
|
||||
#: ../app/ui.js:423
|
||||
msgid "Reconnecting..."
|
||||
msgstr "Återansluter..."
|
||||
|
||||
#: ../app/ui.js:418
|
||||
#: ../app/ui.js:428
|
||||
msgid "Internal error"
|
||||
msgstr "Internt fel"
|
||||
|
||||
#: ../app/ui.js:1009
|
||||
#: ../app/ui.js:1026
|
||||
msgid "Must set host"
|
||||
msgstr "Du måste specifiera en värd"
|
||||
|
||||
#: ../app/ui.js:1091
|
||||
#: ../app/ui.js:1110
|
||||
msgid "Connected (encrypted) to "
|
||||
msgstr "Ansluten (krypterat) till "
|
||||
|
||||
#: ../app/ui.js:1093
|
||||
#: ../app/ui.js:1112
|
||||
msgid "Connected (unencrypted) to "
|
||||
msgstr "Ansluten (okrypterat) till "
|
||||
|
||||
#: ../app/ui.js:1116
|
||||
#: ../app/ui.js:1135
|
||||
msgid "Something went wrong, connection is closed"
|
||||
msgstr "Något gick fel, anslutningen avslutades"
|
||||
|
||||
#: ../app/ui.js:1119
|
||||
#: ../app/ui.js:1138
|
||||
msgid "Failed to connect to server"
|
||||
msgstr "Misslyckades att ansluta till servern"
|
||||
|
||||
#: ../app/ui.js:1129
|
||||
#: ../app/ui.js:1150
|
||||
msgid "Disconnected"
|
||||
msgstr "Frånkopplad"
|
||||
|
||||
#: ../app/ui.js:1144
|
||||
#: ../app/ui.js:1165
|
||||
msgid "New connection has been rejected with reason: "
|
||||
msgstr "Ny anslutning har blivit nekad med följande skäl: "
|
||||
|
||||
#: ../app/ui.js:1147
|
||||
#: ../app/ui.js:1168
|
||||
msgid "New connection has been rejected"
|
||||
msgstr "Ny anslutning har blivit nekad"
|
||||
|
||||
#: ../app/ui.js:1182
|
||||
#: ../app/ui.js:1234
|
||||
msgid "Credentials are required"
|
||||
msgstr "Användaruppgifter krävs"
|
||||
|
||||
#: ../vnc.html:61
|
||||
#: ../vnc.html:55
|
||||
msgid "noVNC encountered an error:"
|
||||
msgstr "noVNC stötte på ett problem:"
|
||||
|
||||
#: ../vnc.html:71
|
||||
#: ../vnc.html:65
|
||||
msgid "Hide/Show the control bar"
|
||||
msgstr "Göm/Visa kontrollbaren"
|
||||
|
||||
#: ../vnc.html:78
|
||||
#: ../vnc.html:74
|
||||
msgid "Drag"
|
||||
msgstr "Dra"
|
||||
|
||||
#: ../vnc.html:78
|
||||
#: ../vnc.html:74
|
||||
msgid "Move/Drag Viewport"
|
||||
msgstr "Flytta/Dra Vyn"
|
||||
|
||||
#: ../vnc.html:84
|
||||
#: ../vnc.html:80
|
||||
msgid "Keyboard"
|
||||
msgstr "Tangentbord"
|
||||
|
||||
#: ../vnc.html:84
|
||||
#: ../vnc.html:80
|
||||
msgid "Show Keyboard"
|
||||
msgstr "Visa Tangentbord"
|
||||
|
||||
#: ../vnc.html:89
|
||||
#: ../vnc.html:85
|
||||
msgid "Extra keys"
|
||||
msgstr "Extraknappar"
|
||||
|
||||
#: ../vnc.html:89
|
||||
#: ../vnc.html:85
|
||||
msgid "Show Extra Keys"
|
||||
msgstr "Visa Extraknappar"
|
||||
|
||||
#: ../vnc.html:94
|
||||
#: ../vnc.html:90
|
||||
msgid "Ctrl"
|
||||
msgstr "Ctrl"
|
||||
|
||||
#: ../vnc.html:94
|
||||
#: ../vnc.html:90
|
||||
msgid "Toggle Ctrl"
|
||||
msgstr "Växla Ctrl"
|
||||
|
||||
#: ../vnc.html:97
|
||||
#: ../vnc.html:93
|
||||
msgid "Alt"
|
||||
msgstr "Alt"
|
||||
|
||||
#: ../vnc.html:97
|
||||
#: ../vnc.html:93
|
||||
msgid "Toggle Alt"
|
||||
msgstr "Växla Alt"
|
||||
|
||||
#: ../vnc.html:100
|
||||
#: ../vnc.html:96
|
||||
msgid "Toggle Windows"
|
||||
msgstr "Växla Windows"
|
||||
|
||||
#: ../vnc.html:100
|
||||
#: ../vnc.html:96
|
||||
msgid "Windows"
|
||||
msgstr "Windows"
|
||||
|
||||
#: ../vnc.html:103
|
||||
#: ../vnc.html:99
|
||||
msgid "Send Tab"
|
||||
msgstr "Skicka Tab"
|
||||
|
||||
#: ../vnc.html:103
|
||||
#: ../vnc.html:99
|
||||
msgid "Tab"
|
||||
msgstr "Tab"
|
||||
|
||||
#: ../vnc.html:106
|
||||
#: ../vnc.html:102
|
||||
msgid "Esc"
|
||||
msgstr "Esc"
|
||||
|
||||
#: ../vnc.html:106
|
||||
#: ../vnc.html:102
|
||||
msgid "Send Escape"
|
||||
msgstr "Skicka Escape"
|
||||
|
||||
#: ../vnc.html:109
|
||||
#: ../vnc.html:105
|
||||
msgid "Ctrl+Alt+Del"
|
||||
msgstr "Ctrl+Alt+Del"
|
||||
|
||||
#: ../vnc.html:109
|
||||
#: ../vnc.html:105
|
||||
msgid "Send Ctrl-Alt-Del"
|
||||
msgstr "Skicka Ctrl-Alt-Del"
|
||||
|
||||
#: ../vnc.html:116
|
||||
#: ../vnc.html:112
|
||||
msgid "Shutdown/Reboot"
|
||||
msgstr "Stäng av/Boota om"
|
||||
|
||||
#: ../vnc.html:116
|
||||
#: ../vnc.html:112
|
||||
msgid "Shutdown/Reboot..."
|
||||
msgstr "Stäng av/Boota om..."
|
||||
|
||||
#: ../vnc.html:122
|
||||
#: ../vnc.html:118
|
||||
msgid "Power"
|
||||
msgstr "Ström"
|
||||
|
||||
#: ../vnc.html:124
|
||||
#: ../vnc.html:120
|
||||
msgid "Shutdown"
|
||||
msgstr "Stäng av"
|
||||
|
||||
#: ../vnc.html:125
|
||||
#: ../vnc.html:121
|
||||
msgid "Reboot"
|
||||
msgstr "Boota om"
|
||||
|
||||
#: ../vnc.html:126
|
||||
#: ../vnc.html:122
|
||||
msgid "Reset"
|
||||
msgstr "Återställ"
|
||||
|
||||
#: ../vnc.html:131 ../vnc.html:137
|
||||
#: ../vnc.html:127 ../vnc.html:133
|
||||
msgid "Clipboard"
|
||||
msgstr "Urklipp"
|
||||
|
||||
#: ../vnc.html:141
|
||||
msgid "Clear"
|
||||
msgstr "Rensa"
|
||||
#: ../vnc.html:135
|
||||
msgid "Edit clipboard content in the textarea below."
|
||||
msgstr "Redigera urklippets innehåll i fältet nedan."
|
||||
|
||||
#: ../vnc.html:147
|
||||
msgid "Fullscreen"
|
||||
#: ../vnc.html:143
|
||||
msgid "Full Screen"
|
||||
msgstr "Fullskärm"
|
||||
|
||||
#: ../vnc.html:152 ../vnc.html:159
|
||||
#: ../vnc.html:148 ../vnc.html:154
|
||||
msgid "Settings"
|
||||
msgstr "Inställningar"
|
||||
|
||||
#: ../vnc.html:162
|
||||
#: ../vnc.html:158
|
||||
msgid "Shared Mode"
|
||||
msgstr "Delat Läge"
|
||||
|
||||
#: ../vnc.html:165
|
||||
#: ../vnc.html:161
|
||||
msgid "View Only"
|
||||
msgstr "Endast Visning"
|
||||
|
||||
#: ../vnc.html:169
|
||||
#: ../vnc.html:165
|
||||
msgid "Clip to Window"
|
||||
msgstr "Begränsa till Fönster"
|
||||
|
||||
#: ../vnc.html:172
|
||||
#: ../vnc.html:168
|
||||
msgid "Scaling Mode:"
|
||||
msgstr "Skalningsläge:"
|
||||
|
||||
#: ../vnc.html:174
|
||||
#: ../vnc.html:170
|
||||
msgid "None"
|
||||
msgstr "Ingen"
|
||||
|
||||
#: ../vnc.html:175
|
||||
#: ../vnc.html:171
|
||||
msgid "Local Scaling"
|
||||
msgstr "Lokal Skalning"
|
||||
|
||||
#: ../vnc.html:176
|
||||
#: ../vnc.html:172
|
||||
msgid "Remote Resizing"
|
||||
msgstr "Ändra Storlek"
|
||||
|
||||
#: ../vnc.html:181
|
||||
#: ../vnc.html:177
|
||||
msgid "Advanced"
|
||||
msgstr "Avancerat"
|
||||
|
||||
#: ../vnc.html:184
|
||||
#: ../vnc.html:180
|
||||
msgid "Quality:"
|
||||
msgstr "Kvalitet:"
|
||||
|
||||
#: ../vnc.html:188
|
||||
#: ../vnc.html:184
|
||||
msgid "Compression level:"
|
||||
msgstr "Kompressionsnivå:"
|
||||
|
||||
#: ../vnc.html:193
|
||||
#: ../vnc.html:189
|
||||
msgid "Repeater ID:"
|
||||
msgstr "Repeater-ID:"
|
||||
|
||||
#: ../vnc.html:197
|
||||
#: ../vnc.html:193
|
||||
msgid "WebSocket"
|
||||
msgstr "WebSocket"
|
||||
|
||||
#: ../vnc.html:200
|
||||
#: ../vnc.html:196
|
||||
msgid "Encrypt"
|
||||
msgstr "Kryptera"
|
||||
|
||||
#: ../vnc.html:203
|
||||
#: ../vnc.html:199
|
||||
msgid "Host:"
|
||||
msgstr "Värd:"
|
||||
|
||||
#: ../vnc.html:207
|
||||
#: ../vnc.html:203
|
||||
msgid "Port:"
|
||||
msgstr "Port:"
|
||||
|
||||
#: ../vnc.html:211
|
||||
#: ../vnc.html:207
|
||||
msgid "Path:"
|
||||
msgstr "Sökväg:"
|
||||
|
||||
#: ../vnc.html:218
|
||||
#: ../vnc.html:214
|
||||
msgid "Automatic Reconnect"
|
||||
msgstr "Automatisk Återanslutning"
|
||||
|
||||
#: ../vnc.html:221
|
||||
#: ../vnc.html:217
|
||||
msgid "Reconnect Delay (ms):"
|
||||
msgstr "Fördröjning (ms):"
|
||||
|
||||
#: ../vnc.html:226
|
||||
#: ../vnc.html:222
|
||||
msgid "Show Dot when No Cursor"
|
||||
msgstr "Visa prick när ingen muspekare finns"
|
||||
|
||||
#: ../vnc.html:231
|
||||
#: ../vnc.html:227
|
||||
msgid "Logging:"
|
||||
msgstr "Loggning:"
|
||||
|
||||
#: ../vnc.html:240
|
||||
#: ../vnc.html:236
|
||||
msgid "Version:"
|
||||
msgstr "Version:"
|
||||
|
||||
#: ../vnc.html:248
|
||||
#: ../vnc.html:244
|
||||
msgid "Disconnect"
|
||||
msgstr "Koppla från"
|
||||
|
||||
@@ -283,18 +287,53 @@ msgstr "Koppla från"
|
||||
msgid "Connect"
|
||||
msgstr "Anslut"
|
||||
|
||||
#: ../vnc.html:277
|
||||
#: ../vnc.html:276
|
||||
msgid "Server identity"
|
||||
msgstr "Server-identitet"
|
||||
|
||||
#: ../vnc.html:279
|
||||
msgid "The server has provided the following identifying information:"
|
||||
msgstr "Servern har gett följande identifierande information:"
|
||||
|
||||
#: ../vnc.html:283
|
||||
msgid "Fingerprint:"
|
||||
msgstr "Fingeravtryck:"
|
||||
|
||||
#: ../vnc.html:286
|
||||
msgid ""
|
||||
"Please verify that the information is correct and press \"Approve\". "
|
||||
"Otherwise press \"Reject\"."
|
||||
msgstr ""
|
||||
"Kontrollera att informationen är korrekt och tryck sedan "
|
||||
"\"Godkänn\". Tryck annars \"Neka\"."
|
||||
|
||||
#: ../vnc.html:291
|
||||
msgid "Approve"
|
||||
msgstr "Godkänn"
|
||||
|
||||
#: ../vnc.html:292
|
||||
msgid "Reject"
|
||||
msgstr "Neka"
|
||||
|
||||
#: ../vnc.html:300
|
||||
msgid "Credentials"
|
||||
msgstr "Användaruppgifter"
|
||||
|
||||
#: ../vnc.html:304
|
||||
msgid "Username:"
|
||||
msgstr "Användarnamn:"
|
||||
|
||||
#: ../vnc.html:281
|
||||
#: ../vnc.html:308
|
||||
msgid "Password:"
|
||||
msgstr "Lösenord:"
|
||||
|
||||
#: ../vnc.html:285
|
||||
#: ../vnc.html:312
|
||||
msgid "Send Credentials"
|
||||
msgstr "Skicka Användaruppgifter"
|
||||
|
||||
#: ../vnc.html:295
|
||||
#: ../vnc.html:321
|
||||
msgid "Cancel"
|
||||
msgstr "Avbryt"
|
||||
|
||||
#~ msgid "Clear"
|
||||
#~ msgstr "Rensa"
|
||||
|
||||
@@ -17,6 +17,10 @@ const opt = getopt.create([
|
||||
const strings = {};
|
||||
|
||||
function addString(str, location) {
|
||||
// We assume surrounding whitespace, and whitespace around line
|
||||
// breaks, is just for source formatting
|
||||
str = str.split("\n").map(s => s.trim()).join(" ").trim();
|
||||
|
||||
if (str.length == 0) {
|
||||
return;
|
||||
}
|
||||
@@ -78,7 +82,7 @@ function process(elem, locator, enabled) {
|
||||
if (node.nodeType === node.ELEMENT_NODE) {
|
||||
process(node, locator, enabled);
|
||||
} else if (node.nodeType === node.TEXT_NODE && enabled) {
|
||||
addString(node.data.trim(), locator(node));
|
||||
addString(node.data, locator(node));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
name: novnc
|
||||
base: core18 # the base snap is the execution environment for this snap
|
||||
version: '@VERSION@'
|
||||
base: core22 # the base snap is the execution environment for this snap
|
||||
version: git
|
||||
summary: Open Source VNC client using HTML5 (WebSockets, Canvas)
|
||||
description: |
|
||||
Open Source VNC client using HTML5 (WebSockets, Canvas).
|
||||
@@ -23,6 +23,9 @@ parts:
|
||||
- core/**/*.js
|
||||
- vendor/**/*.js
|
||||
- novnc_proxy
|
||||
|
||||
novnc-deps:
|
||||
plugin: nil
|
||||
stage-packages:
|
||||
- bash
|
||||
|
||||
@@ -31,12 +34,15 @@ parts:
|
||||
plugin: dump
|
||||
stage:
|
||||
- svc_wrapper.sh
|
||||
|
||||
svc-script-deps:
|
||||
plugin: nil
|
||||
stage-packages:
|
||||
- bash
|
||||
- jq
|
||||
|
||||
websockify:
|
||||
source: https://github.com/novnc/websockify/archive/v0.9.0.tar.gz
|
||||
source: https://github.com/novnc/websockify/archive/v0.11.0.tar.gz
|
||||
plugin: python
|
||||
stage-packages:
|
||||
- python3-numpy
|
||||
|
||||
@@ -29,12 +29,6 @@ chai.use(function (_chai, utils) {
|
||||
|
||||
_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),
|
||||
_sQ: new Uint8Array(obj._sQ.buffer, 0, obj._sQlen) };
|
||||
res.prototype = obj;
|
||||
return res;
|
||||
};
|
||||
const data = obj._websocket._getSentData();
|
||||
let same = true;
|
||||
if (data.length != targetData.length) {
|
||||
|
||||
@@ -42,7 +42,7 @@ export default class FakeWebSocket {
|
||||
}
|
||||
|
||||
_getSentData() {
|
||||
const res = new Uint8Array(this._sendQueue.buffer, 0, this.bufferedAmount);
|
||||
const res = this._sendQueue.slice(0, this.bufferedAmount);
|
||||
this.bufferedAmount = 0;
|
||||
return res;
|
||||
}
|
||||
@@ -55,11 +55,15 @@ export default class FakeWebSocket {
|
||||
}
|
||||
|
||||
_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(new MessageEvent("message", { 'data': buf }));
|
||||
if (data.length < 4096) {
|
||||
// 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.slice(i, i+1);
|
||||
this.onmessage(new MessageEvent("message", { 'data': buf.buffer }));
|
||||
}
|
||||
} else {
|
||||
this.onmessage(new MessageEvent("message", { 'data': data.buffer }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ function loadFile() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const script = document.createElement("script");
|
||||
script.onload = resolve;
|
||||
script.onerror = reject;
|
||||
script.onerror = () => { reject("Failed to load " + fname); };
|
||||
document.body.appendChild(script);
|
||||
script.src = "../recordings/" + fname;
|
||||
});
|
||||
@@ -200,6 +200,9 @@ function start() {
|
||||
player.onrfbdisconnected = (evt) => {
|
||||
if (!evt.detail.clean) {
|
||||
message(`noVNC sent disconnected during iteration ${evt.detail.iteration} frame ${evt.detail.frame}`);
|
||||
|
||||
document.getElementById('startButton').disabled = false;
|
||||
document.getElementById('startButton').value = "Start";
|
||||
}
|
||||
};
|
||||
player.onfinish = (evt) => {
|
||||
|
||||
@@ -131,12 +131,10 @@ export default class RecordingPlayer {
|
||||
_doPacket() {
|
||||
// Avoid having excessive queue buildup in non-realtime mode
|
||||
if (this._trafficManagement && this._rfb._flushing) {
|
||||
const orig = this._rfb._display.onflush;
|
||||
this._rfb._display.onflush = () => {
|
||||
this._rfb._display.onflush = orig;
|
||||
this._rfb._onFlush();
|
||||
this._doPacket();
|
||||
};
|
||||
this._rfb.flush()
|
||||
.then(() => {
|
||||
this._doPacket();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -150,13 +148,8 @@ export default class RecordingPlayer {
|
||||
|
||||
_finish() {
|
||||
if (this._rfb._display.pending()) {
|
||||
this._rfb._display.onflush = () => {
|
||||
if (this._rfb._flushing) {
|
||||
this._rfb._onFlush();
|
||||
}
|
||||
this._finish();
|
||||
};
|
||||
this._rfb._display.flush();
|
||||
this._rfb._display.flush()
|
||||
.then(() => { this._finish(); });
|
||||
} else {
|
||||
this._running = false;
|
||||
this._ws.onclose({code: 1000, reason: ""});
|
||||
|
||||
@@ -0,0 +1,244 @@
|
||||
/* eslint-disable no-console */
|
||||
const expect = chai.expect;
|
||||
|
||||
import { isMac, isWindows, isIOS, isAndroid, isChromeOS,
|
||||
isSafari, isFirefox, isChrome, isChromium, isOpera, isEdge,
|
||||
isGecko, isWebKit, isBlink } from '../core/util/browser.js';
|
||||
|
||||
describe('OS detection', function () {
|
||||
let origNavigator;
|
||||
beforeEach(function () {
|
||||
// window.navigator is a protected read-only property in many
|
||||
// environments, so we need to redefine it whilst running these
|
||||
// tests.
|
||||
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
|
||||
|
||||
Object.defineProperty(window, "navigator", {value: {}});
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
Object.defineProperty(window, "navigator", origNavigator);
|
||||
});
|
||||
|
||||
it('should handle macOS', function () {
|
||||
const platforms = [
|
||||
"MacIntel",
|
||||
"MacPPC",
|
||||
];
|
||||
|
||||
navigator.userAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 12_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.6 Safari/605.1.15";
|
||||
platforms.forEach((platform) => {
|
||||
navigator.platform = platform;
|
||||
expect(isMac()).to.be.true;
|
||||
expect(isWindows()).to.be.false;
|
||||
expect(isIOS()).to.be.false;
|
||||
expect(isAndroid()).to.be.false;
|
||||
expect(isChromeOS()).to.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle Windows', function () {
|
||||
const platforms = [
|
||||
"Win32",
|
||||
"Win64",
|
||||
];
|
||||
|
||||
navigator.userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36";
|
||||
platforms.forEach((platform) => {
|
||||
navigator.platform = platform;
|
||||
expect(isMac()).to.be.false;
|
||||
expect(isWindows()).to.be.true;
|
||||
expect(isIOS()).to.be.false;
|
||||
expect(isAndroid()).to.be.false;
|
||||
expect(isChromeOS()).to.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle iOS', function () {
|
||||
const platforms = [
|
||||
"iPhone",
|
||||
"iPod",
|
||||
"iPad",
|
||||
];
|
||||
|
||||
navigator.userAgent = "Mozilla/5.0 (iPhone; CPU iPhone OS 16_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.1 Mobile/15E148 Safari/604.1";
|
||||
platforms.forEach((platform) => {
|
||||
navigator.platform = platform;
|
||||
expect(isMac()).to.be.false;
|
||||
expect(isWindows()).to.be.false;
|
||||
expect(isIOS()).to.be.true;
|
||||
expect(isAndroid()).to.be.false;
|
||||
expect(isChromeOS()).to.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle Android', function () {
|
||||
let userAgents = [
|
||||
"Mozilla/5.0 (Linux; Android 13; SM-G960U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.5359.128 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Android 13; Mobile; LG-M255; rv:108.0) Gecko/108.0 Firefox/108.0",
|
||||
];
|
||||
|
||||
navigator.platform = "Linux x86_64";
|
||||
userAgents.forEach((ua) => {
|
||||
navigator.userAgent = ua;
|
||||
expect(isMac()).to.be.false;
|
||||
expect(isWindows()).to.be.false;
|
||||
expect(isIOS()).to.be.false;
|
||||
expect(isAndroid()).to.be.true;
|
||||
expect(isChromeOS()).to.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle ChromeOS', function () {
|
||||
let userAgents = [
|
||||
"Mozilla/5.0 (X11; CrOS x86_64 15183.59.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.5359.75 Safari/537.36",
|
||||
"Mozilla/5.0 (X11; CrOS aarch64 15183.59.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.5359.75 Safari/537.36",
|
||||
];
|
||||
|
||||
navigator.platform = "Linux x86_64";
|
||||
userAgents.forEach((ua) => {
|
||||
navigator.userAgent = ua;
|
||||
expect(isMac()).to.be.false;
|
||||
expect(isWindows()).to.be.false;
|
||||
expect(isIOS()).to.be.false;
|
||||
expect(isAndroid()).to.be.false;
|
||||
expect(isChromeOS()).to.be.true;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Browser detection', function () {
|
||||
let origNavigator;
|
||||
beforeEach(function () {
|
||||
// window.navigator is a protected read-only property in many
|
||||
// environments, so we need to redefine it whilst running these
|
||||
// tests.
|
||||
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
|
||||
|
||||
Object.defineProperty(window, "navigator", {value: {}});
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
Object.defineProperty(window, "navigator", origNavigator);
|
||||
});
|
||||
|
||||
it('should handle Chrome', function () {
|
||||
navigator.userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36";
|
||||
|
||||
expect(isSafari()).to.be.false;
|
||||
expect(isFirefox()).to.be.false;
|
||||
expect(isChrome()).to.be.true;
|
||||
expect(isChromium()).to.be.false;
|
||||
expect(isOpera()).to.be.false;
|
||||
expect(isEdge()).to.be.false;
|
||||
|
||||
expect(isGecko()).to.be.false;
|
||||
expect(isWebKit()).to.be.false;
|
||||
expect(isBlink()).to.be.true;
|
||||
});
|
||||
|
||||
it('should handle Chromium', function () {
|
||||
navigator.userAgent = "Mozilla/5.0 (X11; Linux armv7l) AppleWebKit/537.36 (KHTML, like Gecko) Raspbian Chromium/74.0.3729.157 Chrome/74.0.3729.157 Safari/537.36";
|
||||
|
||||
expect(isSafari()).to.be.false;
|
||||
expect(isFirefox()).to.be.false;
|
||||
expect(isChrome()).to.be.false;
|
||||
expect(isChromium()).to.be.true;
|
||||
expect(isOpera()).to.be.false;
|
||||
expect(isEdge()).to.be.false;
|
||||
|
||||
expect(isGecko()).to.be.false;
|
||||
expect(isWebKit()).to.be.false;
|
||||
expect(isBlink()).to.be.true;
|
||||
});
|
||||
|
||||
it('should handle Firefox', function () {
|
||||
navigator.userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:105.0) Gecko/20100101 Firefox/105.0";
|
||||
|
||||
expect(isSafari()).to.be.false;
|
||||
expect(isFirefox()).to.be.true;
|
||||
expect(isChrome()).to.be.false;
|
||||
expect(isChromium()).to.be.false;
|
||||
expect(isOpera()).to.be.false;
|
||||
expect(isEdge()).to.be.false;
|
||||
|
||||
expect(isGecko()).to.be.true;
|
||||
expect(isWebKit()).to.be.false;
|
||||
expect(isBlink()).to.be.false;
|
||||
});
|
||||
|
||||
it('should handle Seamonkey', function () {
|
||||
navigator.userAgent = "Mozilla/5.0 (Windows NT 6.1; rv:36.0) Gecko/20100101 Firefox/36.0 Seamonkey/2.33.1";
|
||||
|
||||
expect(isSafari()).to.be.false;
|
||||
expect(isFirefox()).to.be.false;
|
||||
expect(isChrome()).to.be.false;
|
||||
expect(isChromium()).to.be.false;
|
||||
expect(isOpera()).to.be.false;
|
||||
expect(isEdge()).to.be.false;
|
||||
|
||||
expect(isGecko()).to.be.true;
|
||||
expect(isWebKit()).to.be.false;
|
||||
expect(isBlink()).to.be.false;
|
||||
});
|
||||
|
||||
it('should handle Safari', function () {
|
||||
navigator.userAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 12_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.6 Safari/605.1.15";
|
||||
|
||||
expect(isSafari()).to.be.true;
|
||||
expect(isFirefox()).to.be.false;
|
||||
expect(isChrome()).to.be.false;
|
||||
expect(isChromium()).to.be.false;
|
||||
expect(isOpera()).to.be.false;
|
||||
expect(isEdge()).to.be.false;
|
||||
|
||||
expect(isGecko()).to.be.false;
|
||||
expect(isWebKit()).to.be.true;
|
||||
expect(isBlink()).to.be.false;
|
||||
});
|
||||
|
||||
it('should handle Edge', function () {
|
||||
navigator.userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36 Edg/106.0.1370.34";
|
||||
|
||||
expect(isSafari()).to.be.false;
|
||||
expect(isFirefox()).to.be.false;
|
||||
expect(isChrome()).to.be.false;
|
||||
expect(isChromium()).to.be.false;
|
||||
expect(isOpera()).to.be.false;
|
||||
expect(isEdge()).to.be.true;
|
||||
|
||||
expect(isGecko()).to.be.false;
|
||||
expect(isWebKit()).to.be.false;
|
||||
expect(isBlink()).to.be.true;
|
||||
});
|
||||
|
||||
it('should handle Opera', function () {
|
||||
navigator.userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36 OPR/91.0.4516.20";
|
||||
|
||||
expect(isSafari()).to.be.false;
|
||||
expect(isFirefox()).to.be.false;
|
||||
expect(isChrome()).to.be.false;
|
||||
expect(isChromium()).to.be.false;
|
||||
expect(isOpera()).to.be.true;
|
||||
expect(isEdge()).to.be.false;
|
||||
|
||||
expect(isGecko()).to.be.false;
|
||||
expect(isWebKit()).to.be.false;
|
||||
expect(isBlink()).to.be.true;
|
||||
});
|
||||
|
||||
it('should handle Epiphany', function () {
|
||||
navigator.userAgent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.0 Safari/605.1.15 Epiphany/605.1.15";
|
||||
|
||||
expect(isSafari()).to.be.false;
|
||||
expect(isFirefox()).to.be.false;
|
||||
expect(isChrome()).to.be.false;
|
||||
expect(isChromium()).to.be.false;
|
||||
expect(isOpera()).to.be.false;
|
||||
expect(isEdge()).to.be.false;
|
||||
|
||||
expect(isGecko()).to.be.false;
|
||||
expect(isWebKit()).to.be.true;
|
||||
expect(isBlink()).to.be.false;
|
||||
});
|
||||
});
|
||||
@@ -9,23 +9,26 @@ import FakeWebSocket from './fake.websocket.js';
|
||||
|
||||
function testDecodeRect(decoder, x, y, width, height, data, display, depth) {
|
||||
let sock;
|
||||
let done = false;
|
||||
|
||||
sock = new Websock;
|
||||
sock.open("ws://example.com");
|
||||
|
||||
sock.on('message', () => {
|
||||
decoder.decodeRect(x, y, width, height, sock, display, depth);
|
||||
done = decoder.decodeRect(x, y, width, height, sock, display, depth);
|
||||
});
|
||||
|
||||
// Empty messages are filtered at multiple layers, so we need to
|
||||
// do a direct call
|
||||
if (data.length === 0) {
|
||||
decoder.decodeRect(x, y, width, height, sock, display, depth);
|
||||
done = decoder.decodeRect(x, y, width, height, sock, display, depth);
|
||||
} else {
|
||||
sock._websocket._receiveData(new Uint8Array(data));
|
||||
}
|
||||
|
||||
display.flip();
|
||||
|
||||
return done;
|
||||
}
|
||||
|
||||
describe('CopyRect Decoder', function () {
|
||||
@@ -47,12 +50,15 @@ describe('CopyRect Decoder', function () {
|
||||
display.fillRect(0, 0, 2, 2, [ 0x00, 0x00, 0xff ]);
|
||||
display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);
|
||||
|
||||
testDecodeRect(decoder, 0, 2, 2, 2,
|
||||
[0x00, 0x02, 0x00, 0x00],
|
||||
display, 24);
|
||||
testDecodeRect(decoder, 2, 2, 2, 2,
|
||||
[0x00, 0x00, 0x00, 0x00],
|
||||
display, 24);
|
||||
let done;
|
||||
done = testDecodeRect(decoder, 0, 2, 2, 2,
|
||||
[0x00, 0x02, 0x00, 0x00],
|
||||
display, 24);
|
||||
expect(done).to.be.true;
|
||||
done = testDecodeRect(decoder, 2, 2, 2, 2,
|
||||
[0x00, 0x00, 0x00, 0x00],
|
||||
display, 24);
|
||||
expect(done).to.be.true;
|
||||
|
||||
let targetData = new Uint8Array([
|
||||
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
||||
@@ -69,7 +75,9 @@ describe('CopyRect Decoder', function () {
|
||||
display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);
|
||||
display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]);
|
||||
|
||||
testDecodeRect(decoder, 1, 2, 0, 0, [0x00, 0x00, 0x00, 0x00], display, 24);
|
||||
let done = testDecodeRect(decoder, 1, 2, 0, 0,
|
||||
[0x00, 0x00, 0x00, 0x00],
|
||||
display, 24);
|
||||
|
||||
let targetData = new Uint8Array([
|
||||
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
||||
@@ -78,6 +86,7 @@ describe('CopyRect Decoder', function () {
|
||||
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
|
||||
]);
|
||||
|
||||
expect(done).to.be.true;
|
||||
expect(display).to.have.displayed(targetData);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -298,14 +298,11 @@ describe('Display/Canvas Helper', function () {
|
||||
expect(display).to.have.displayed(checkedData);
|
||||
});
|
||||
|
||||
it('should support drawing images via #imageRect', function (done) {
|
||||
it('should support drawing images via #imageRect', async function () {
|
||||
display.imageRect(0, 0, 4, 4, "image/png", makeImagePng(checkedData, 4, 4));
|
||||
display.flip();
|
||||
display.onflush = () => {
|
||||
expect(display).to.have.displayed(checkedData);
|
||||
done();
|
||||
};
|
||||
display.flush();
|
||||
await display.flush();
|
||||
expect(display).to.have.displayed(checkedData);
|
||||
});
|
||||
|
||||
it('should support blit images with true color via #blitImage', function () {
|
||||
@@ -360,12 +357,11 @@ describe('Display/Canvas Helper', function () {
|
||||
expect(img.addEventListener).to.have.been.calledOnce;
|
||||
});
|
||||
|
||||
it('should call callback when queue is flushed', function () {
|
||||
display.onflush = sinon.spy();
|
||||
it('should resolve promise when queue is flushed', async function () {
|
||||
display.fillRect(0, 0, 4, 4, [0, 0xff, 0]);
|
||||
expect(display.onflush).to.not.have.been.called;
|
||||
display.flush();
|
||||
expect(display.onflush).to.have.been.calledOnce;
|
||||
let promise = display.flush();
|
||||
expect(promise).to.be.an.instanceOf(Promise);
|
||||
await promise;
|
||||
});
|
||||
|
||||
it('should draw a blit image on type "blit"', function () {
|
||||
|
||||
@@ -71,18 +71,10 @@ describe('Helpers', function () {
|
||||
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
|
||||
|
||||
Object.defineProperty(window, "navigator", {value: {}});
|
||||
if (window.navigator.platform !== undefined) {
|
||||
// Object.defineProperty() doesn't work properly in old
|
||||
// versions of Chrome
|
||||
this.skip();
|
||||
}
|
||||
|
||||
window.navigator.platform = "Mac x86_64";
|
||||
});
|
||||
afterEach(function () {
|
||||
if (origNavigator !== undefined) {
|
||||
Object.defineProperty(window, "navigator", origNavigator);
|
||||
}
|
||||
Object.defineProperty(window, "navigator", origNavigator);
|
||||
});
|
||||
|
||||
it('should respect ContextMenu on modern browser', function () {
|
||||
@@ -196,19 +188,11 @@ describe('Helpers', function () {
|
||||
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
|
||||
|
||||
Object.defineProperty(window, "navigator", {value: {}});
|
||||
if (window.navigator.platform !== undefined) {
|
||||
// Object.defineProperty() doesn't work properly in old
|
||||
// versions of Chrome
|
||||
this.skip();
|
||||
}
|
||||
|
||||
window.navigator.platform = "Windows";
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
if (origNavigator !== undefined) {
|
||||
Object.defineProperty(window, "navigator", origNavigator);
|
||||
}
|
||||
Object.defineProperty(window, "navigator", origNavigator);
|
||||
});
|
||||
|
||||
const keys = { 'Zenkaku': 0xff2a, 'Hankaku': 0xff2a,
|
||||
|
||||
@@ -9,23 +9,26 @@ import FakeWebSocket from './fake.websocket.js';
|
||||
|
||||
function testDecodeRect(decoder, x, y, width, height, data, display, depth) {
|
||||
let sock;
|
||||
let done = false;
|
||||
|
||||
sock = new Websock;
|
||||
sock.open("ws://example.com");
|
||||
|
||||
sock.on('message', () => {
|
||||
decoder.decodeRect(x, y, width, height, sock, display, depth);
|
||||
done = decoder.decodeRect(x, y, width, height, sock, display, depth);
|
||||
});
|
||||
|
||||
// Empty messages are filtered at multiple layers, so we need to
|
||||
// do a direct call
|
||||
if (data.length === 0) {
|
||||
decoder.decodeRect(x, y, width, height, sock, display, depth);
|
||||
done = decoder.decodeRect(x, y, width, height, sock, display, depth);
|
||||
} else {
|
||||
sock._websocket._receiveData(new Uint8Array(data));
|
||||
}
|
||||
|
||||
display.flip();
|
||||
|
||||
return done;
|
||||
}
|
||||
|
||||
function push32(arr, num) {
|
||||
@@ -62,7 +65,7 @@ describe('Hextile Decoder', function () {
|
||||
data.push(2 | (2 << 4)); // x: 2, y: 2
|
||||
data.push(1 | (1 << 4)); // width: 2, height: 2
|
||||
|
||||
testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
|
||||
let done = testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
|
||||
|
||||
let targetData = new Uint8Array([
|
||||
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
||||
@@ -71,6 +74,7 @@ describe('Hextile Decoder', function () {
|
||||
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
|
||||
]);
|
||||
|
||||
expect(done).to.be.true;
|
||||
expect(display).to.have.displayed(targetData);
|
||||
});
|
||||
|
||||
@@ -92,8 +96,9 @@ describe('Hextile Decoder', function () {
|
||||
data.push(0);
|
||||
}
|
||||
|
||||
testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
|
||||
let done = testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
|
||||
|
||||
expect(done).to.be.true;
|
||||
expect(display).to.have.displayed(targetData);
|
||||
});
|
||||
|
||||
@@ -102,13 +107,14 @@ describe('Hextile Decoder', function () {
|
||||
data.push(0x02);
|
||||
push32(data, 0x00ff0000); // becomes 00ff0000 --> #00FF00 bg color
|
||||
|
||||
testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
|
||||
let done = testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
|
||||
|
||||
let expected = [];
|
||||
for (let i = 0; i < 16; i++) {
|
||||
push32(expected, 0x00ff00ff);
|
||||
}
|
||||
|
||||
expect(done).to.be.true;
|
||||
expect(display).to.have.displayed(new Uint8Array(expected));
|
||||
});
|
||||
|
||||
@@ -125,7 +131,7 @@ describe('Hextile Decoder', function () {
|
||||
// send an empty frame
|
||||
data.push(0x00);
|
||||
|
||||
testDecodeRect(decoder, 0, 0, 32, 4, data, display, 24);
|
||||
let done = testDecodeRect(decoder, 0, 0, 32, 4, data, display, 24);
|
||||
|
||||
let expected = [];
|
||||
for (let i = 0; i < 16; i++) {
|
||||
@@ -135,6 +141,7 @@ describe('Hextile Decoder', function () {
|
||||
push32(expected, 0x00ff00ff); // rect 2: same bkground color
|
||||
}
|
||||
|
||||
expect(done).to.be.true;
|
||||
expect(display).to.have.displayed(new Uint8Array(expected));
|
||||
});
|
||||
|
||||
@@ -156,7 +163,7 @@ describe('Hextile Decoder', function () {
|
||||
data.push(2 | (2 << 4)); // x: 2, y: 2
|
||||
data.push(1 | (1 << 4)); // width: 2, height: 2
|
||||
|
||||
testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
|
||||
let done = testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
|
||||
|
||||
let targetData = new Uint8Array([
|
||||
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
||||
@@ -165,6 +172,7 @@ describe('Hextile Decoder', function () {
|
||||
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
|
||||
]);
|
||||
|
||||
expect(done).to.be.true;
|
||||
expect(display).to.have.displayed(targetData);
|
||||
});
|
||||
|
||||
@@ -190,7 +198,7 @@ describe('Hextile Decoder', function () {
|
||||
data.push(0); // x: 0, y: 0
|
||||
data.push(1 | (1 << 4)); // width: 2, height: 2
|
||||
|
||||
testDecodeRect(decoder, 0, 0, 4, 17, data, display, 24);
|
||||
let done = testDecodeRect(decoder, 0, 0, 4, 17, data, display, 24);
|
||||
|
||||
let targetData = [
|
||||
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
||||
@@ -205,6 +213,7 @@ describe('Hextile Decoder', function () {
|
||||
}
|
||||
expected = expected.concat(targetData.slice(0, 16));
|
||||
|
||||
expect(done).to.be.true;
|
||||
expect(display).to.have.displayed(new Uint8Array(expected));
|
||||
});
|
||||
|
||||
@@ -218,7 +227,7 @@ describe('Hextile Decoder', function () {
|
||||
display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);
|
||||
display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]);
|
||||
|
||||
testDecodeRect(decoder, 1, 2, 0, 0, [], display, 24);
|
||||
let done = testDecodeRect(decoder, 1, 2, 0, 0, [], display, 24);
|
||||
|
||||
let targetData = new Uint8Array([
|
||||
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
||||
@@ -227,6 +236,7 @@ describe('Hextile Decoder', function () {
|
||||
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
|
||||
]);
|
||||
|
||||
expect(done).to.be.true;
|
||||
expect(display).to.have.displayed(targetData);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
/* eslint-disable no-console */
|
||||
const expect = chai.expect;
|
||||
|
||||
import { deflateInit, deflate, Z_FULL_FLUSH } from "../vendor/pako/lib/zlib/deflate.js";
|
||||
import ZStream from "../vendor/pako/lib/zlib/zstream.js";
|
||||
import Inflator from "../core/inflator.js";
|
||||
|
||||
function _deflator(data) {
|
||||
let strm = new ZStream();
|
||||
|
||||
deflateInit(strm, 5);
|
||||
|
||||
/* eslint-disable camelcase */
|
||||
strm.input = data;
|
||||
strm.avail_in = strm.input.length;
|
||||
strm.next_in = 0;
|
||||
/* eslint-enable camelcase */
|
||||
|
||||
let chunks = [];
|
||||
let totalLen = 0;
|
||||
while (strm.avail_in > 0) {
|
||||
/* eslint-disable camelcase */
|
||||
strm.output = new Uint8Array(1024 * 10 * 10);
|
||||
strm.avail_out = strm.output.length;
|
||||
strm.next_out = 0;
|
||||
/* eslint-enable camelcase */
|
||||
|
||||
let ret = deflate(strm, Z_FULL_FLUSH);
|
||||
|
||||
// Check that return code is not an error
|
||||
expect(ret).to.be.greaterThan(-1);
|
||||
|
||||
let chunk = new Uint8Array(strm.output.buffer, 0, strm.next_out);
|
||||
totalLen += chunk.length;
|
||||
chunks.push(chunk);
|
||||
}
|
||||
|
||||
// Combine chunks into a single data
|
||||
|
||||
let outData = new Uint8Array(totalLen);
|
||||
let offset = 0;
|
||||
|
||||
for (let i = 0; i < chunks.length; i++) {
|
||||
outData.set(chunks[i], offset);
|
||||
offset += chunks[i].length;
|
||||
}
|
||||
|
||||
return outData;
|
||||
}
|
||||
|
||||
describe('Inflate data', function () {
|
||||
|
||||
it('should be able to inflate messages', function () {
|
||||
let inflator = new Inflator();
|
||||
|
||||
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(preText);
|
||||
|
||||
inflator.setInput(compText);
|
||||
let inflatedText = inflator.inflate(preText.length);
|
||||
|
||||
expect(inflatedText).to.array.equal(preText);
|
||||
|
||||
});
|
||||
|
||||
it('should be able to inflate large messages', function () {
|
||||
let inflator = new Inflator();
|
||||
|
||||
/* 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(preText);
|
||||
|
||||
//Check that the compressed size is expected size
|
||||
expect(compText.length).to.be.greaterThan((1024 * 10 * 10) * 2);
|
||||
|
||||
inflator.setInput(compText);
|
||||
let inflatedText = inflator.inflate(preText.length);
|
||||
|
||||
expect(inflatedText).to.array.equal(preText);
|
||||
});
|
||||
|
||||
it('should throw an error on insufficient data', function () {
|
||||
let inflator = new Inflator();
|
||||
|
||||
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(preText);
|
||||
|
||||
inflator.setInput(compText);
|
||||
expect(() => inflator.inflate(preText.length * 2)).to.throw();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,292 @@
|
||||
const expect = chai.expect;
|
||||
|
||||
import Websock from '../core/websock.js';
|
||||
import Display from '../core/display.js';
|
||||
|
||||
import JPEGDecoder from '../core/decoders/jpeg.js';
|
||||
|
||||
import FakeWebSocket from './fake.websocket.js';
|
||||
|
||||
function testDecodeRect(decoder, x, y, width, height, data, display, depth) {
|
||||
let sock;
|
||||
let done = false;
|
||||
|
||||
sock = new Websock;
|
||||
sock.open("ws://example.com");
|
||||
|
||||
sock.on('message', () => {
|
||||
done = decoder.decodeRect(x, y, width, height, sock, display, depth);
|
||||
});
|
||||
|
||||
// Empty messages are filtered at multiple layers, so we need to
|
||||
// do a direct call
|
||||
if (data.length === 0) {
|
||||
done = decoder.decodeRect(x, y, width, height, sock, display, depth);
|
||||
} else {
|
||||
sock._websocket._receiveData(new Uint8Array(data));
|
||||
}
|
||||
|
||||
display.flip();
|
||||
|
||||
return done;
|
||||
}
|
||||
|
||||
describe('JPEG Decoder', function () {
|
||||
let decoder;
|
||||
let display;
|
||||
|
||||
before(FakeWebSocket.replace);
|
||||
after(FakeWebSocket.restore);
|
||||
|
||||
beforeEach(function () {
|
||||
decoder = new JPEGDecoder();
|
||||
display = new Display(document.createElement('canvas'));
|
||||
display.resize(4, 4);
|
||||
});
|
||||
|
||||
it('should handle JPEG rects', async function () {
|
||||
let data = [
|
||||
// JPEG data
|
||||
0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46,
|
||||
0x49, 0x46, 0x00, 0x01, 0x01, 0x01, 0x01, 0x2c,
|
||||
0x01, 0x2c, 0x00, 0x42, 0xff, 0xdb, 0x00, 0x43,
|
||||
0x00, 0x03, 0x02, 0x02, 0x03, 0x02, 0x02, 0x03,
|
||||
0x03, 0x03, 0x03, 0x04, 0x03, 0x03, 0x04, 0x05,
|
||||
0x08, 0x05, 0x05, 0x04, 0x04, 0x05, 0x0a, 0x07,
|
||||
0x07, 0x06, 0x08, 0x0c, 0x0a, 0x0c, 0x0c, 0x0b,
|
||||
0x0a, 0x0b, 0x0b, 0x0d, 0x0e, 0x12, 0x10, 0x0d,
|
||||
0x0e, 0x11, 0x0e, 0x0b, 0x0b, 0x10, 0x16, 0x10,
|
||||
0x11, 0x13, 0x14, 0x15, 0x15, 0x15, 0x0c, 0x0f,
|
||||
0x17, 0x18, 0x16, 0x14, 0x18, 0x12, 0x14, 0x15,
|
||||
0x14, 0xff, 0xdb, 0x00, 0x43, 0x01, 0x03, 0x04,
|
||||
0x04, 0x05, 0x04, 0x05, 0x09, 0x05, 0x05, 0x09,
|
||||
0x14, 0x0d, 0x0b, 0x0d, 0x14, 0x14, 0x14, 0x14,
|
||||
0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14,
|
||||
0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14,
|
||||
0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14,
|
||||
0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14,
|
||||
0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14,
|
||||
0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0xff, 0xc0,
|
||||
0x00, 0x11, 0x08, 0x00, 0x04, 0x00, 0x04, 0x03,
|
||||
0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11,
|
||||
0x01, 0xff, 0xc4, 0x00, 0x1f, 0x00, 0x00, 0x01,
|
||||
0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
|
||||
0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
|
||||
0x0a, 0x0b, 0xff, 0xc4, 0x00, 0xb5, 0x10, 0x00,
|
||||
0x02, 0x01, 0x03, 0x03, 0x02, 0x04, 0x03, 0x05,
|
||||
0x05, 0x04, 0x04, 0x00, 0x00, 0x01, 0x7d, 0x01,
|
||||
0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21,
|
||||
0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, 0x22,
|
||||
0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, 0x23,
|
||||
0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0, 0x24,
|
||||
0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, 0x17,
|
||||
0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28, 0x29,
|
||||
0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a,
|
||||
0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a,
|
||||
0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a,
|
||||
0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a,
|
||||
0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a,
|
||||
0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a,
|
||||
0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99,
|
||||
0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8,
|
||||
0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
|
||||
0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6,
|
||||
0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5,
|
||||
0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2, 0xe3,
|
||||
0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1,
|
||||
0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9,
|
||||
0xfa, 0xff, 0xc4, 0x00, 0x1f, 0x01, 0x00, 0x03,
|
||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
||||
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
|
||||
0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
|
||||
0x0a, 0x0b, 0xff, 0xc4, 0x00, 0xb5, 0x11, 0x00,
|
||||
0x02, 0x01, 0x02, 0x04, 0x04, 0x03, 0x04, 0x07,
|
||||
0x05, 0x04, 0x04, 0x00, 0x01, 0x02, 0x77, 0x00,
|
||||
0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31,
|
||||
0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, 0x13,
|
||||
0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xa1,
|
||||
0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15,
|
||||
0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1,
|
||||
0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26, 0x27,
|
||||
0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38, 0x39,
|
||||
0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49,
|
||||
0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59,
|
||||
0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
|
||||
0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79,
|
||||
0x7a, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88,
|
||||
0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,
|
||||
0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6,
|
||||
0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5,
|
||||
0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4,
|
||||
0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3,
|
||||
0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe2,
|
||||
0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea,
|
||||
0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9,
|
||||
0xfa, 0xff, 0xda, 0x00, 0x0c, 0x03, 0x01, 0x00,
|
||||
0x02, 0x11, 0x03, 0x11, 0x00, 0x3f, 0x00, 0xf9,
|
||||
0xf7, 0xfb, 0x67, 0x56, 0xff, 0x00, 0x9f, 0xf8,
|
||||
0x3f, 0xf0, 0x51, 0xa7, 0xff, 0x00, 0xf2, 0x3d,
|
||||
0x7e, 0x6f, 0xfd, 0xab, 0x94, 0x7f, 0xd0, 0x9a,
|
||||
0x8f, 0xfe, 0x0d, 0xc7, 0x7f, 0xf3, 0x61, 0xfd,
|
||||
0xa7, 0xff, 0x00, 0x10, 0x77, 0x0d, 0xff, 0x00,
|
||||
0x43, 0xec, 0xcf, 0xff, 0x00, 0x0b, 0xab, 0x1f,
|
||||
0xff, 0xd9,
|
||||
];
|
||||
|
||||
let decodeDone = testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
|
||||
expect(decodeDone).to.be.true;
|
||||
|
||||
let targetData = new Uint8Array([
|
||||
0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255,
|
||||
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
||||
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
|
||||
0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255
|
||||
]);
|
||||
|
||||
// Browsers have rounding errors, so we need an approximate
|
||||
// comparing function
|
||||
function almost(a, b) {
|
||||
let diff = Math.abs(a - b);
|
||||
return diff < 5;
|
||||
}
|
||||
|
||||
await display.flush();
|
||||
expect(display).to.have.displayed(targetData, almost);
|
||||
});
|
||||
|
||||
it('should handle JPEG rects without Huffman and quantification tables', async function () {
|
||||
let data1 = [
|
||||
// JPEG data
|
||||
0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46,
|
||||
0x49, 0x46, 0x00, 0x01, 0x01, 0x01, 0x01, 0x2c,
|
||||
0x01, 0x2c, 0x00, 0x42, 0xff, 0xdb, 0x00, 0x43,
|
||||
0x00, 0x03, 0x02, 0x02, 0x03, 0x02, 0x02, 0x03,
|
||||
0x03, 0x03, 0x03, 0x04, 0x03, 0x03, 0x04, 0x05,
|
||||
0x08, 0x05, 0x05, 0x04, 0x04, 0x05, 0x0a, 0x07,
|
||||
0x07, 0x06, 0x08, 0x0c, 0x0a, 0x0c, 0x0c, 0x0b,
|
||||
0x0a, 0x0b, 0x0b, 0x0d, 0x0e, 0x12, 0x10, 0x0d,
|
||||
0x0e, 0x11, 0x0e, 0x0b, 0x0b, 0x10, 0x16, 0x10,
|
||||
0x11, 0x13, 0x14, 0x15, 0x15, 0x15, 0x0c, 0x0f,
|
||||
0x17, 0x18, 0x16, 0x14, 0x18, 0x12, 0x14, 0x15,
|
||||
0x14, 0xff, 0xdb, 0x00, 0x43, 0x01, 0x03, 0x04,
|
||||
0x04, 0x05, 0x04, 0x05, 0x09, 0x05, 0x05, 0x09,
|
||||
0x14, 0x0d, 0x0b, 0x0d, 0x14, 0x14, 0x14, 0x14,
|
||||
0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14,
|
||||
0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14,
|
||||
0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14,
|
||||
0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14,
|
||||
0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14,
|
||||
0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0xff, 0xc0,
|
||||
0x00, 0x11, 0x08, 0x00, 0x04, 0x00, 0x04, 0x03,
|
||||
0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11,
|
||||
0x01, 0xff, 0xc4, 0x00, 0x1f, 0x00, 0x00, 0x01,
|
||||
0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
|
||||
0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
|
||||
0x0a, 0x0b, 0xff, 0xc4, 0x00, 0xb5, 0x10, 0x00,
|
||||
0x02, 0x01, 0x03, 0x03, 0x02, 0x04, 0x03, 0x05,
|
||||
0x05, 0x04, 0x04, 0x00, 0x00, 0x01, 0x7d, 0x01,
|
||||
0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21,
|
||||
0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, 0x22,
|
||||
0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, 0x23,
|
||||
0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0, 0x24,
|
||||
0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, 0x17,
|
||||
0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28, 0x29,
|
||||
0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a,
|
||||
0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a,
|
||||
0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a,
|
||||
0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a,
|
||||
0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a,
|
||||
0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a,
|
||||
0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99,
|
||||
0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8,
|
||||
0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
|
||||
0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6,
|
||||
0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5,
|
||||
0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2, 0xe3,
|
||||
0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1,
|
||||
0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9,
|
||||
0xfa, 0xff, 0xc4, 0x00, 0x1f, 0x01, 0x00, 0x03,
|
||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
||||
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
|
||||
0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
|
||||
0x0a, 0x0b, 0xff, 0xc4, 0x00, 0xb5, 0x11, 0x00,
|
||||
0x02, 0x01, 0x02, 0x04, 0x04, 0x03, 0x04, 0x07,
|
||||
0x05, 0x04, 0x04, 0x00, 0x01, 0x02, 0x77, 0x00,
|
||||
0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31,
|
||||
0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, 0x13,
|
||||
0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xa1,
|
||||
0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15,
|
||||
0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1,
|
||||
0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26, 0x27,
|
||||
0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38, 0x39,
|
||||
0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49,
|
||||
0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59,
|
||||
0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
|
||||
0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79,
|
||||
0x7a, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88,
|
||||
0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,
|
||||
0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6,
|
||||
0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5,
|
||||
0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4,
|
||||
0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3,
|
||||
0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe2,
|
||||
0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea,
|
||||
0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9,
|
||||
0xfa, 0xff, 0xda, 0x00, 0x0c, 0x03, 0x01, 0x00,
|
||||
0x02, 0x11, 0x03, 0x11, 0x00, 0x3f, 0x00, 0xf9,
|
||||
0xf7, 0xfb, 0x67, 0x56, 0xff, 0x00, 0x9f, 0xf8,
|
||||
0x3f, 0xf0, 0x51, 0xa7, 0xff, 0x00, 0xf2, 0x3d,
|
||||
0x7e, 0x6f, 0xfd, 0xab, 0x94, 0x7f, 0xd0, 0x9a,
|
||||
0x8f, 0xfe, 0x0d, 0xc7, 0x7f, 0xf3, 0x61, 0xfd,
|
||||
0xa7, 0xff, 0x00, 0x10, 0x77, 0x0d, 0xff, 0x00,
|
||||
0x43, 0xec, 0xcf, 0xff, 0x00, 0x0b, 0xab, 0x1f,
|
||||
0xff, 0xd9,
|
||||
];
|
||||
|
||||
let decodeDone;
|
||||
|
||||
decodeDone = testDecodeRect(decoder, 0, 0, 4, 4, data1, display, 24);
|
||||
expect(decodeDone).to.be.true;
|
||||
|
||||
display.fillRect(0, 0, 4, 4, [128, 128, 128, 255]);
|
||||
|
||||
let data2 = [
|
||||
// JPEG data
|
||||
0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46,
|
||||
0x49, 0x46, 0x00, 0x01, 0x01, 0x01, 0x01, 0x2c,
|
||||
0x01, 0x2c, 0x00, 0x73, 0xff, 0xc0, 0x00, 0x11,
|
||||
0x08, 0x00, 0x04, 0x00, 0x04, 0x03, 0x01, 0x11,
|
||||
0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xff,
|
||||
0xda, 0x00, 0x0c, 0x03, 0x01, 0x00, 0x02, 0x11,
|
||||
0x03, 0x11, 0x00, 0x3f, 0x00, 0xf9, 0xf7, 0xfb,
|
||||
0x67, 0x56, 0xff, 0x00, 0x9f, 0xf8, 0x3f, 0xf0,
|
||||
0x51, 0xa7, 0xff, 0x00, 0xf2, 0x3d, 0x7e, 0x6f,
|
||||
0xfd, 0xab, 0x94, 0x7f, 0xd0, 0x9a, 0x8f, 0xfe,
|
||||
0x0d, 0xc7, 0x7f, 0xf3, 0x61, 0xfd, 0xa7, 0xff,
|
||||
0x00, 0x10, 0x77, 0x0d, 0xff, 0x00, 0x43, 0xec,
|
||||
0xcf, 0xff, 0x00, 0x0b, 0xab, 0x1f, 0xff, 0xd9,
|
||||
];
|
||||
|
||||
decodeDone = testDecodeRect(decoder, 0, 0, 4, 4, data2, display, 24);
|
||||
expect(decodeDone).to.be.true;
|
||||
|
||||
let targetData = new Uint8Array([
|
||||
0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255,
|
||||
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
||||
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
|
||||
0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255
|
||||
]);
|
||||
|
||||
// Browsers have rounding errors, so we need an approximate
|
||||
// comparing function
|
||||
function almost(a, b) {
|
||||
let diff = Math.abs(a - b);
|
||||
return diff < 5;
|
||||
}
|
||||
|
||||
await display.flush();
|
||||
expect(display).to.have.displayed(targetData, almost);
|
||||
});
|
||||
});
|
||||
@@ -14,6 +14,10 @@ describe('Key Event Handling', function () {
|
||||
}
|
||||
e.stopPropagation = sinon.spy();
|
||||
e.preventDefault = sinon.spy();
|
||||
e.getModifierState = function (key) {
|
||||
return e[key];
|
||||
};
|
||||
|
||||
return e;
|
||||
}
|
||||
|
||||
@@ -144,18 +148,10 @@ describe('Key Event Handling', function () {
|
||||
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
|
||||
|
||||
Object.defineProperty(window, "navigator", {value: {}});
|
||||
if (window.navigator.platform !== undefined) {
|
||||
// Object.defineProperty() doesn't work properly in old
|
||||
// versions of Chrome
|
||||
this.skip();
|
||||
}
|
||||
|
||||
window.navigator.platform = "Mac x86_64";
|
||||
});
|
||||
afterEach(function () {
|
||||
if (origNavigator !== undefined) {
|
||||
Object.defineProperty(window, "navigator", origNavigator);
|
||||
}
|
||||
Object.defineProperty(window, "navigator", origNavigator);
|
||||
});
|
||||
|
||||
it('should change Alt to AltGraph', function () {
|
||||
@@ -197,7 +193,7 @@ describe('Key Event Handling', function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Caps Lock on iOS and macOS', function () {
|
||||
describe('Meta key combination on iOS and macOS', function () {
|
||||
let origNavigator;
|
||||
beforeEach(function () {
|
||||
// window.navigator is a protected read-only property in many
|
||||
@@ -219,6 +215,60 @@ describe('Key Event Handling', function () {
|
||||
}
|
||||
});
|
||||
|
||||
it('should send keyup when meta key is pressed on iOS', function () {
|
||||
window.navigator.platform = "iPad";
|
||||
const kbd = new Keyboard(document);
|
||||
kbd.onkeyevent = sinon.spy();
|
||||
|
||||
kbd._handleKeyDown(keyevent('keydown', {code: 'MetaRight', key: 'Meta', location: 2, metaKey: true}));
|
||||
expect(kbd.onkeyevent).to.have.been.calledOnce;
|
||||
kbd.onkeyevent.resetHistory();
|
||||
|
||||
kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a', metaKey: true}));
|
||||
expect(kbd.onkeyevent).to.have.been.calledTwice;
|
||||
expect(kbd.onkeyevent).to.have.been.calledWith(0x61, "KeyA", true);
|
||||
expect(kbd.onkeyevent).to.have.been.calledWith(0x61, "KeyA", false);
|
||||
kbd.onkeyevent.resetHistory();
|
||||
|
||||
kbd._handleKeyUp(keyevent('keyup', {code: 'MetaRight', key: 'Meta', location: 2, metaKey: true}));
|
||||
expect(kbd.onkeyevent).to.have.been.calledOnce;
|
||||
});
|
||||
|
||||
it('should send keyup when meta key is pressed on macOS', function () {
|
||||
window.navigator.platform = "Mac";
|
||||
const kbd = new Keyboard(document);
|
||||
kbd.onkeyevent = sinon.spy();
|
||||
|
||||
kbd._handleKeyDown(keyevent('keydown', {code: 'MetaRight', key: 'Meta', location: 2, metaKey: true}));
|
||||
expect(kbd.onkeyevent).to.have.been.calledOnce;
|
||||
kbd.onkeyevent.resetHistory();
|
||||
|
||||
kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a', metaKey: true}));
|
||||
expect(kbd.onkeyevent).to.have.been.calledTwice;
|
||||
expect(kbd.onkeyevent).to.have.been.calledWith(0x61, "KeyA", true);
|
||||
expect(kbd.onkeyevent).to.have.been.calledWith(0x61, "KeyA", false);
|
||||
kbd.onkeyevent.resetHistory();
|
||||
|
||||
kbd._handleKeyUp(keyevent('keyup', {code: 'MetaRight', key: 'Meta', location: 2, metaKey: true}));
|
||||
expect(kbd.onkeyevent).to.have.been.calledOnce;
|
||||
});
|
||||
});
|
||||
|
||||
describe('Caps Lock on iOS and macOS', function () {
|
||||
let origNavigator;
|
||||
beforeEach(function () {
|
||||
// window.navigator is a protected read-only property in many
|
||||
// environments, so we need to redefine it whilst running these
|
||||
// tests.
|
||||
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
|
||||
|
||||
Object.defineProperty(window, "navigator", {value: {}});
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
Object.defineProperty(window, "navigator", origNavigator);
|
||||
});
|
||||
|
||||
it('should toggle caps lock on key press on iOS', function () {
|
||||
window.navigator.platform = "iPad";
|
||||
const kbd = new Keyboard(document);
|
||||
@@ -264,6 +314,50 @@ describe('Key Event Handling', function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Modifier status info', function () {
|
||||
let origNavigator;
|
||||
beforeEach(function () {
|
||||
// window.navigator is a protected read-only property in many
|
||||
// environments, so we need to redefine it whilst running these
|
||||
// tests.
|
||||
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
|
||||
|
||||
Object.defineProperty(window, "navigator", {value: {}});
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
Object.defineProperty(window, "navigator", origNavigator);
|
||||
});
|
||||
|
||||
it('should provide caps lock state', function () {
|
||||
const kbd = new Keyboard(document);
|
||||
kbd.onkeyevent = sinon.spy();
|
||||
kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'A', NumLock: false, CapsLock: true}));
|
||||
|
||||
expect(kbd.onkeyevent).to.have.been.calledOnce;
|
||||
expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0x41, "KeyA", true, false, true);
|
||||
});
|
||||
|
||||
it('should provide num lock state', function () {
|
||||
const kbd = new Keyboard(document);
|
||||
kbd.onkeyevent = sinon.spy();
|
||||
kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'A', NumLock: true, CapsLock: false}));
|
||||
|
||||
expect(kbd.onkeyevent).to.have.been.calledOnce;
|
||||
expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0x41, "KeyA", true, true, false);
|
||||
});
|
||||
|
||||
it('should have no num lock state on mac', function () {
|
||||
window.navigator.platform = "Mac";
|
||||
const kbd = new Keyboard(document);
|
||||
kbd.onkeyevent = sinon.spy();
|
||||
kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'A', NumLock: false, CapsLock: true}));
|
||||
|
||||
expect(kbd.onkeyevent).to.have.been.calledOnce;
|
||||
expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0x41, "KeyA", true, null, true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Japanese IM keys on Windows', function () {
|
||||
let origNavigator;
|
||||
beforeEach(function () {
|
||||
@@ -273,19 +367,11 @@ describe('Key Event Handling', function () {
|
||||
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
|
||||
|
||||
Object.defineProperty(window, "navigator", {value: {}});
|
||||
if (window.navigator.platform !== undefined) {
|
||||
// Object.defineProperty() doesn't work properly in old
|
||||
// versions of Chrome
|
||||
this.skip();
|
||||
}
|
||||
|
||||
window.navigator.platform = "Windows";
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
if (origNavigator !== undefined) {
|
||||
Object.defineProperty(window, "navigator", origNavigator);
|
||||
}
|
||||
Object.defineProperty(window, "navigator", origNavigator);
|
||||
});
|
||||
|
||||
const keys = { 'Zenkaku': 0xff2a, 'Hankaku': 0xff2a,
|
||||
@@ -314,20 +400,12 @@ describe('Key Event Handling', function () {
|
||||
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
|
||||
|
||||
Object.defineProperty(window, "navigator", {value: {}});
|
||||
if (window.navigator.platform !== undefined) {
|
||||
// Object.defineProperty() doesn't work properly in old
|
||||
// versions of Chrome
|
||||
this.skip();
|
||||
}
|
||||
|
||||
window.navigator.platform = "Windows x86_64";
|
||||
|
||||
this.clock = sinon.useFakeTimers();
|
||||
});
|
||||
afterEach(function () {
|
||||
if (origNavigator !== undefined) {
|
||||
Object.defineProperty(window, "navigator", origNavigator);
|
||||
}
|
||||
Object.defineProperty(window, "navigator", origNavigator);
|
||||
if (this.clock !== undefined) {
|
||||
this.clock.restore();
|
||||
}
|
||||
@@ -459,20 +537,12 @@ describe('Key Event Handling', function () {
|
||||
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
|
||||
|
||||
Object.defineProperty(window, "navigator", {value: {}});
|
||||
if (window.navigator.platform !== undefined) {
|
||||
// Object.defineProperty() doesn't work properly in old
|
||||
// versions of Chrome
|
||||
this.skip();
|
||||
}
|
||||
|
||||
window.navigator.platform = "Windows x86_64";
|
||||
|
||||
this.clock = sinon.useFakeTimers();
|
||||
});
|
||||
afterEach(function () {
|
||||
if (origNavigator !== undefined) {
|
||||
Object.defineProperty(window, "navigator", origNavigator);
|
||||
}
|
||||
Object.defineProperty(window, "navigator", origNavigator);
|
||||
if (this.clock !== undefined) {
|
||||
this.clock.restore();
|
||||
}
|
||||
|
||||
@@ -1,69 +1,146 @@
|
||||
const expect = chai.expect;
|
||||
import { l10n } from '../app/localization.js';
|
||||
import _, { Localizer, l10n } from '../app/localization.js';
|
||||
|
||||
describe('Localization', function () {
|
||||
"use strict";
|
||||
|
||||
let origNavigator;
|
||||
let fetch;
|
||||
|
||||
beforeEach(function () {
|
||||
// window.navigator is a protected read-only property in many
|
||||
// environments, so we need to redefine it whilst running these
|
||||
// tests.
|
||||
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
|
||||
|
||||
Object.defineProperty(window, "navigator", {value: {}});
|
||||
window.navigator.languages = [];
|
||||
|
||||
fetch = sinon.stub(window, "fetch");
|
||||
fetch.resolves(new Response("{}"));
|
||||
});
|
||||
afterEach(function () {
|
||||
fetch.restore();
|
||||
|
||||
Object.defineProperty(window, "navigator", origNavigator);
|
||||
});
|
||||
|
||||
describe('Singleton', function () {
|
||||
it('should export a singleton object', function () {
|
||||
expect(l10n).to.be.instanceOf(Localizer);
|
||||
});
|
||||
it('should export a singleton translation function', async function () {
|
||||
// FIXME: Can we use some spy instead?
|
||||
window.navigator.languages = ["de"];
|
||||
fetch.resolves(new Response(JSON.stringify({ "Foobar": "gazonk" })));
|
||||
await l10n.setup(["de"]);
|
||||
expect(_("Foobar")).to.equal("gazonk");
|
||||
});
|
||||
});
|
||||
|
||||
describe('language selection', function () {
|
||||
let origNavigator;
|
||||
beforeEach(function () {
|
||||
// window.navigator is a protected read-only property in many
|
||||
// environments, so we need to redefine it whilst running these
|
||||
// tests.
|
||||
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
|
||||
|
||||
Object.defineProperty(window, "navigator", {value: {}});
|
||||
if (window.navigator.languages !== undefined) {
|
||||
// Object.defineProperty() doesn't work properly in old
|
||||
// versions of Chrome
|
||||
this.skip();
|
||||
}
|
||||
|
||||
window.navigator.languages = [];
|
||||
});
|
||||
afterEach(function () {
|
||||
if (origNavigator !== undefined) {
|
||||
Object.defineProperty(window, "navigator", origNavigator);
|
||||
}
|
||||
});
|
||||
|
||||
it('should use English by default', function () {
|
||||
expect(l10n.language).to.equal('en');
|
||||
let lclz = new Localizer();
|
||||
expect(lclz.language).to.equal('en');
|
||||
});
|
||||
it('should use English if no user language matches', function () {
|
||||
it('should use English if no user language matches', async function () {
|
||||
window.navigator.languages = ["nl", "de"];
|
||||
l10n.setup(["es", "fr"]);
|
||||
expect(l10n.language).to.equal('en');
|
||||
let lclz = new Localizer();
|
||||
await lclz.setup(["es", "fr"]);
|
||||
expect(lclz.language).to.equal('en');
|
||||
});
|
||||
it('should use the most preferred user language', function () {
|
||||
it('should fall back to generic English for other English', async function () {
|
||||
window.navigator.languages = ["en-AU", "de"];
|
||||
let lclz = new Localizer();
|
||||
await lclz.setup(["de", "fr", "en-GB"]);
|
||||
expect(lclz.language).to.equal('en');
|
||||
});
|
||||
it('should prefer specific English over generic', async function () {
|
||||
window.navigator.languages = ["en-GB", "de"];
|
||||
let lclz = new Localizer();
|
||||
await lclz.setup(["de", "en-AU", "en-GB"]);
|
||||
expect(lclz.language).to.equal('en-GB');
|
||||
});
|
||||
it('should use the most preferred user language', async function () {
|
||||
window.navigator.languages = ["nl", "de", "fr"];
|
||||
l10n.setup(["es", "fr", "de"]);
|
||||
expect(l10n.language).to.equal('de');
|
||||
let lclz = new Localizer();
|
||||
await lclz.setup(["es", "fr", "de"]);
|
||||
expect(lclz.language).to.equal('de');
|
||||
});
|
||||
it('should prefer sub-languages languages', function () {
|
||||
it('should prefer sub-languages languages', async function () {
|
||||
window.navigator.languages = ["pt-BR"];
|
||||
l10n.setup(["pt", "pt-BR"]);
|
||||
expect(l10n.language).to.equal('pt-BR');
|
||||
let lclz = new Localizer();
|
||||
await lclz.setup(["pt", "pt-BR"]);
|
||||
expect(lclz.language).to.equal('pt-BR');
|
||||
});
|
||||
it('should fall back to language "parents"', function () {
|
||||
it('should fall back to language "parents"', async function () {
|
||||
window.navigator.languages = ["pt-BR"];
|
||||
l10n.setup(["fr", "pt", "de"]);
|
||||
expect(l10n.language).to.equal('pt');
|
||||
let lclz = new Localizer();
|
||||
await lclz.setup(["fr", "pt", "de"]);
|
||||
expect(lclz.language).to.equal('pt');
|
||||
});
|
||||
it('should not use specific language when user asks for a generic language', function () {
|
||||
it('should not use specific language when user asks for a generic language', async function () {
|
||||
window.navigator.languages = ["pt", "de"];
|
||||
l10n.setup(["fr", "pt-BR", "de"]);
|
||||
expect(l10n.language).to.equal('de');
|
||||
let lclz = new Localizer();
|
||||
await lclz.setup(["fr", "pt-BR", "de"]);
|
||||
expect(lclz.language).to.equal('de');
|
||||
});
|
||||
it('should handle underscore as a separator', function () {
|
||||
it('should handle underscore as a separator', async function () {
|
||||
window.navigator.languages = ["pt-BR"];
|
||||
l10n.setup(["pt_BR"]);
|
||||
expect(l10n.language).to.equal('pt_BR');
|
||||
let lclz = new Localizer();
|
||||
await lclz.setup(["pt_BR"]);
|
||||
expect(lclz.language).to.equal('pt_BR');
|
||||
});
|
||||
it('should handle difference in case', function () {
|
||||
it('should handle difference in case', async function () {
|
||||
window.navigator.languages = ["pt-br"];
|
||||
l10n.setup(["pt-BR"]);
|
||||
expect(l10n.language).to.equal('pt-BR');
|
||||
let lclz = new Localizer();
|
||||
await lclz.setup(["pt-BR"]);
|
||||
expect(lclz.language).to.equal('pt-BR');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Translation loading', function () {
|
||||
it('should not fetch a translation for English', async function () {
|
||||
window.navigator.languages = [];
|
||||
let lclz = new Localizer();
|
||||
await lclz.setup([]);
|
||||
expect(fetch).to.not.have.been.called;
|
||||
});
|
||||
it('should fetch dictionary relative base URL', async function () {
|
||||
window.navigator.languages = ["de", "fr"];
|
||||
fetch.resolves(new Response('{ "Foobar": "gazonk" }'));
|
||||
let lclz = new Localizer();
|
||||
await lclz.setup(["ru", "fr"], "/some/path/");
|
||||
expect(fetch).to.have.been.calledOnceWith("/some/path/fr.json");
|
||||
expect(lclz.get("Foobar")).to.equal("gazonk");
|
||||
});
|
||||
it('should handle base URL without trailing slash', async function () {
|
||||
window.navigator.languages = ["de", "fr"];
|
||||
fetch.resolves(new Response('{ "Foobar": "gazonk" }'));
|
||||
let lclz = new Localizer();
|
||||
await lclz.setup(["ru", "fr"], "/some/path");
|
||||
expect(fetch).to.have.been.calledOnceWith("/some/path/fr.json");
|
||||
expect(lclz.get("Foobar")).to.equal("gazonk");
|
||||
});
|
||||
it('should handle current base URL', async function () {
|
||||
window.navigator.languages = ["de", "fr"];
|
||||
fetch.resolves(new Response('{ "Foobar": "gazonk" }'));
|
||||
let lclz = new Localizer();
|
||||
await lclz.setup(["ru", "fr"]);
|
||||
expect(fetch).to.have.been.calledOnceWith("fr.json");
|
||||
expect(lclz.get("Foobar")).to.equal("gazonk");
|
||||
});
|
||||
it('should fail if dictionary cannot be found', async function () {
|
||||
window.navigator.languages = ["de", "fr"];
|
||||
fetch.resolves(new Response('{}', { status: 404 }));
|
||||
let lclz = new Localizer();
|
||||
let ok = false;
|
||||
try {
|
||||
await lclz.setup(["ru", "fr"], "/some/path/");
|
||||
} catch (e) {
|
||||
ok = true;
|
||||
}
|
||||
expect(ok).to.be.true;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,23 +9,26 @@ import FakeWebSocket from './fake.websocket.js';
|
||||
|
||||
function testDecodeRect(decoder, x, y, width, height, data, display, depth) {
|
||||
let sock;
|
||||
let done = false;
|
||||
|
||||
sock = new Websock;
|
||||
sock.open("ws://example.com");
|
||||
|
||||
sock.on('message', () => {
|
||||
decoder.decodeRect(x, y, width, height, sock, display, depth);
|
||||
done = decoder.decodeRect(x, y, width, height, sock, display, depth);
|
||||
});
|
||||
|
||||
// Empty messages are filtered at multiple layers, so we need to
|
||||
// do a direct call
|
||||
if (data.length === 0) {
|
||||
decoder.decodeRect(x, y, width, height, sock, display, depth);
|
||||
done = decoder.decodeRect(x, y, width, height, sock, display, depth);
|
||||
} else {
|
||||
sock._websocket._receiveData(new Uint8Array(data));
|
||||
}
|
||||
|
||||
display.flip();
|
||||
|
||||
return done;
|
||||
}
|
||||
|
||||
describe('Raw Decoder', function () {
|
||||
@@ -42,22 +45,36 @@ describe('Raw Decoder', function () {
|
||||
});
|
||||
|
||||
it('should handle the Raw encoding', function () {
|
||||
testDecodeRect(decoder, 0, 0, 2, 2,
|
||||
[0xff, 0x00, 0x00, 0, 0x00, 0xff, 0x00, 0,
|
||||
0x00, 0xff, 0x00, 0, 0xff, 0x00, 0x00, 0],
|
||||
display, 24);
|
||||
testDecodeRect(decoder, 2, 0, 2, 2,
|
||||
[0x00, 0x00, 0xff, 0, 0x00, 0x00, 0xff, 0,
|
||||
0x00, 0x00, 0xff, 0, 0x00, 0x00, 0xff, 0],
|
||||
display, 24);
|
||||
testDecodeRect(decoder, 0, 2, 4, 1,
|
||||
[0xee, 0x00, 0xff, 0, 0x00, 0xee, 0xff, 0,
|
||||
0xaa, 0xee, 0xff, 0, 0xab, 0xee, 0xff, 0],
|
||||
display, 24);
|
||||
testDecodeRect(decoder, 0, 3, 4, 1,
|
||||
[0xee, 0x00, 0xff, 0, 0x00, 0xee, 0xff, 0,
|
||||
0xaa, 0xee, 0xff, 0, 0xab, 0xee, 0xff, 0],
|
||||
display, 24);
|
||||
let done;
|
||||
|
||||
done = testDecodeRect(decoder, 0, 0, 2, 2,
|
||||
[0xff, 0x00, 0x00, 0,
|
||||
0x00, 0xff, 0x00, 0,
|
||||
0x00, 0xff, 0x00, 0,
|
||||
0xff, 0x00, 0x00, 0],
|
||||
display, 24);
|
||||
expect(done).to.be.true;
|
||||
done = testDecodeRect(decoder, 2, 0, 2, 2,
|
||||
[0x00, 0x00, 0xff, 0,
|
||||
0x00, 0x00, 0xff, 0,
|
||||
0x00, 0x00, 0xff, 0,
|
||||
0x00, 0x00, 0xff, 0],
|
||||
display, 24);
|
||||
expect(done).to.be.true;
|
||||
done = testDecodeRect(decoder, 0, 2, 4, 1,
|
||||
[0xee, 0x00, 0xff, 0,
|
||||
0x00, 0xee, 0xff, 0,
|
||||
0xaa, 0xee, 0xff, 0,
|
||||
0xab, 0xee, 0xff, 0],
|
||||
display, 24);
|
||||
expect(done).to.be.true;
|
||||
done = testDecodeRect(decoder, 0, 3, 4, 1,
|
||||
[0xee, 0x00, 0xff, 0,
|
||||
0x00, 0xee, 0xff, 0,
|
||||
0xaa, 0xee, 0xff, 0,
|
||||
0xab, 0xee, 0xff, 0],
|
||||
display, 24);
|
||||
expect(done).to.be.true;
|
||||
|
||||
let targetData = new Uint8Array([
|
||||
0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
|
||||
@@ -70,18 +87,24 @@ describe('Raw Decoder', function () {
|
||||
});
|
||||
|
||||
it('should handle the Raw encoding in low colour mode', function () {
|
||||
testDecodeRect(decoder, 0, 0, 2, 2,
|
||||
[0x30, 0x30, 0x30, 0x30],
|
||||
display, 8);
|
||||
testDecodeRect(decoder, 2, 0, 2, 2,
|
||||
[0x0c, 0x0c, 0x0c, 0x0c],
|
||||
display, 8);
|
||||
testDecodeRect(decoder, 0, 2, 4, 1,
|
||||
[0x0c, 0x0c, 0x30, 0x30],
|
||||
display, 8);
|
||||
testDecodeRect(decoder, 0, 3, 4, 1,
|
||||
[0x0c, 0x0c, 0x30, 0x30],
|
||||
display, 8);
|
||||
let done;
|
||||
|
||||
done = testDecodeRect(decoder, 0, 0, 2, 2,
|
||||
[0x30, 0x30, 0x30, 0x30],
|
||||
display, 8);
|
||||
expect(done).to.be.true;
|
||||
done = testDecodeRect(decoder, 2, 0, 2, 2,
|
||||
[0x0c, 0x0c, 0x0c, 0x0c],
|
||||
display, 8);
|
||||
expect(done).to.be.true;
|
||||
done = testDecodeRect(decoder, 0, 2, 4, 1,
|
||||
[0x0c, 0x0c, 0x30, 0x30],
|
||||
display, 8);
|
||||
expect(done).to.be.true;
|
||||
done = testDecodeRect(decoder, 0, 3, 4, 1,
|
||||
[0x0c, 0x0c, 0x30, 0x30],
|
||||
display, 8);
|
||||
expect(done).to.be.true;
|
||||
|
||||
let targetData = new Uint8Array([
|
||||
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
||||
@@ -98,7 +121,7 @@ describe('Raw Decoder', function () {
|
||||
display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);
|
||||
display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]);
|
||||
|
||||
testDecodeRect(decoder, 1, 2, 0, 0, [], display, 24);
|
||||
let done = testDecodeRect(decoder, 1, 2, 0, 0, [], display, 24);
|
||||
|
||||
let targetData = new Uint8Array([
|
||||
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
||||
@@ -107,6 +130,7 @@ describe('Raw Decoder', function () {
|
||||
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
|
||||
]);
|
||||
|
||||
expect(done).to.be.true;
|
||||
expect(display).to.have.displayed(targetData);
|
||||
});
|
||||
|
||||
@@ -115,7 +139,7 @@ describe('Raw Decoder', function () {
|
||||
display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);
|
||||
display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]);
|
||||
|
||||
testDecodeRect(decoder, 1, 2, 0, 0, [], display, 8);
|
||||
let done = testDecodeRect(decoder, 1, 2, 0, 0, [], display, 8);
|
||||
|
||||
let targetData = new Uint8Array([
|
||||
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
||||
@@ -124,6 +148,7 @@ describe('Raw Decoder', function () {
|
||||
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
|
||||
]);
|
||||
|
||||
expect(done).to.be.true;
|
||||
expect(display).to.have.displayed(targetData);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,23 +9,26 @@ import FakeWebSocket from './fake.websocket.js';
|
||||
|
||||
function testDecodeRect(decoder, x, y, width, height, data, display, depth) {
|
||||
let sock;
|
||||
let done = false;
|
||||
|
||||
sock = new Websock;
|
||||
sock.open("ws://example.com");
|
||||
|
||||
sock.on('message', () => {
|
||||
decoder.decodeRect(x, y, width, height, sock, display, depth);
|
||||
done = decoder.decodeRect(x, y, width, height, sock, display, depth);
|
||||
});
|
||||
|
||||
// Empty messages are filtered at multiple layers, so we need to
|
||||
// do a direct call
|
||||
if (data.length === 0) {
|
||||
decoder.decodeRect(x, y, width, height, sock, display, depth);
|
||||
done = decoder.decodeRect(x, y, width, height, sock, display, depth);
|
||||
} else {
|
||||
sock._websocket._receiveData(new Uint8Array(data));
|
||||
}
|
||||
|
||||
display.flip();
|
||||
|
||||
return done;
|
||||
}
|
||||
|
||||
function push16(arr, num) {
|
||||
@@ -76,7 +79,7 @@ describe('RRE Decoder', function () {
|
||||
push16(data, 2); // width: 2
|
||||
push16(data, 2); // height: 2
|
||||
|
||||
testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
|
||||
let done = testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
|
||||
|
||||
let targetData = new Uint8Array([
|
||||
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
||||
@@ -85,6 +88,7 @@ describe('RRE Decoder', function () {
|
||||
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
|
||||
]);
|
||||
|
||||
expect(done).to.be.true;
|
||||
expect(display).to.have.displayed(targetData);
|
||||
});
|
||||
|
||||
@@ -93,7 +97,10 @@ describe('RRE Decoder', function () {
|
||||
display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);
|
||||
display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]);
|
||||
|
||||
testDecodeRect(decoder, 1, 2, 0, 0, [ 0x00, 0xff, 0xff, 0xff, 0xff ], display, 24);
|
||||
let done = testDecodeRect(decoder, 1, 2, 0, 0,
|
||||
[ 0x00, 0x00, 0x00, 0x00,
|
||||
0xff, 0xff, 0xff, 0xff ],
|
||||
display, 24);
|
||||
|
||||
let targetData = new Uint8Array([
|
||||
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
||||
@@ -102,6 +109,7 @@ describe('RRE Decoder', function () {
|
||||
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
|
||||
]);
|
||||
|
||||
expect(done).to.be.true;
|
||||
expect(display).to.have.displayed(targetData);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,23 +9,26 @@ import FakeWebSocket from './fake.websocket.js';
|
||||
|
||||
function testDecodeRect(decoder, x, y, width, height, data, display, depth) {
|
||||
let sock;
|
||||
let done = false;
|
||||
|
||||
sock = new Websock;
|
||||
sock.open("ws://example.com");
|
||||
|
||||
sock.on('message', () => {
|
||||
decoder.decodeRect(x, y, width, height, sock, display, depth);
|
||||
done = decoder.decodeRect(x, y, width, height, sock, display, depth);
|
||||
});
|
||||
|
||||
// Empty messages are filtered at multiple layers, so we need to
|
||||
// do a direct call
|
||||
if (data.length === 0) {
|
||||
decoder.decodeRect(x, y, width, height, sock, display, depth);
|
||||
done = decoder.decodeRect(x, y, width, height, sock, display, depth);
|
||||
} else {
|
||||
sock._websocket._receiveData(new Uint8Array(data));
|
||||
}
|
||||
|
||||
display.flip();
|
||||
|
||||
return done;
|
||||
}
|
||||
|
||||
describe('Tight Decoder', function () {
|
||||
@@ -42,9 +45,9 @@ describe('Tight Decoder', function () {
|
||||
});
|
||||
|
||||
it('should handle fill rects', function () {
|
||||
testDecodeRect(decoder, 0, 0, 4, 4,
|
||||
[0x80, 0xff, 0x88, 0x44],
|
||||
display, 24);
|
||||
let done = testDecodeRect(decoder, 0, 0, 4, 4,
|
||||
[0x80, 0xff, 0x88, 0x44],
|
||||
display, 24);
|
||||
|
||||
let targetData = new Uint8Array([
|
||||
0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255,
|
||||
@@ -53,21 +56,31 @@ describe('Tight Decoder', function () {
|
||||
0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255,
|
||||
]);
|
||||
|
||||
expect(done).to.be.true;
|
||||
expect(display).to.have.displayed(targetData);
|
||||
});
|
||||
|
||||
it('should handle uncompressed copy rects', function () {
|
||||
let done;
|
||||
let blueData = [ 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0xff ];
|
||||
let greenData = [ 0x00, 0x00, 0xff, 0x00, 0x00, 0xff, 0x00 ];
|
||||
|
||||
testDecodeRect(decoder, 0, 0, 2, 1, blueData, display, 24);
|
||||
testDecodeRect(decoder, 0, 1, 2, 1, blueData, display, 24);
|
||||
testDecodeRect(decoder, 2, 0, 2, 1, greenData, display, 24);
|
||||
testDecodeRect(decoder, 2, 1, 2, 1, greenData, display, 24);
|
||||
testDecodeRect(decoder, 0, 2, 2, 1, greenData, display, 24);
|
||||
testDecodeRect(decoder, 0, 3, 2, 1, greenData, display, 24);
|
||||
testDecodeRect(decoder, 2, 2, 2, 1, blueData, display, 24);
|
||||
testDecodeRect(decoder, 2, 3, 2, 1, blueData, display, 24);
|
||||
done = testDecodeRect(decoder, 0, 0, 2, 1, blueData, display, 24);
|
||||
expect(done).to.be.true;
|
||||
done = testDecodeRect(decoder, 0, 1, 2, 1, blueData, display, 24);
|
||||
expect(done).to.be.true;
|
||||
done = testDecodeRect(decoder, 2, 0, 2, 1, greenData, display, 24);
|
||||
expect(done).to.be.true;
|
||||
done = testDecodeRect(decoder, 2, 1, 2, 1, greenData, display, 24);
|
||||
expect(done).to.be.true;
|
||||
done = testDecodeRect(decoder, 0, 2, 2, 1, greenData, display, 24);
|
||||
expect(done).to.be.true;
|
||||
done = testDecodeRect(decoder, 0, 3, 2, 1, greenData, display, 24);
|
||||
expect(done).to.be.true;
|
||||
done = testDecodeRect(decoder, 2, 2, 2, 1, blueData, display, 24);
|
||||
expect(done).to.be.true;
|
||||
done = testDecodeRect(decoder, 2, 3, 2, 1, blueData, display, 24);
|
||||
expect(done).to.be.true;
|
||||
|
||||
let targetData = new Uint8Array([
|
||||
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
||||
@@ -89,7 +102,7 @@ describe('Tight Decoder', function () {
|
||||
0x60, 0x82, 0x01, 0x99, 0x8d, 0x29, 0x02, 0xa6,
|
||||
0x00, 0x7e, 0xbf, 0x0f, 0xf1 ];
|
||||
|
||||
testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
|
||||
let done = testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
|
||||
|
||||
let targetData = new Uint8Array([
|
||||
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
||||
@@ -98,6 +111,7 @@ describe('Tight Decoder', function () {
|
||||
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
|
||||
]);
|
||||
|
||||
expect(done).to.be.true;
|
||||
expect(display).to.have.displayed(targetData);
|
||||
});
|
||||
|
||||
@@ -110,7 +124,7 @@ describe('Tight Decoder', function () {
|
||||
// Pixels
|
||||
0x30, 0x30, 0xc0, 0xc0 ];
|
||||
|
||||
testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
|
||||
let done = testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
|
||||
|
||||
let targetData = new Uint8Array([
|
||||
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
||||
@@ -119,6 +133,7 @@ describe('Tight Decoder', function () {
|
||||
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
|
||||
]);
|
||||
|
||||
expect(done).to.be.true;
|
||||
expect(display).to.have.displayed(targetData);
|
||||
});
|
||||
|
||||
@@ -135,7 +150,7 @@ describe('Tight Decoder', function () {
|
||||
0x78, 0x9c, 0x33, 0x30, 0x38, 0x70, 0xc0, 0x00,
|
||||
0x8a, 0x01, 0x21, 0x3c, 0x05, 0xa1 ];
|
||||
|
||||
testDecodeRect(decoder, 0, 0, 4, 12, data, display, 24);
|
||||
let done = testDecodeRect(decoder, 0, 0, 4, 12, data, display, 24);
|
||||
|
||||
let targetData = new Uint8Array([
|
||||
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
||||
@@ -152,10 +167,12 @@ describe('Tight Decoder', function () {
|
||||
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
|
||||
]);
|
||||
|
||||
expect(done).to.be.true;
|
||||
expect(display).to.have.displayed(targetData);
|
||||
});
|
||||
|
||||
it('should handle uncompressed palette rects', function () {
|
||||
let done;
|
||||
let data1 = [
|
||||
// Control bytes
|
||||
0x40, 0x01,
|
||||
@@ -171,8 +188,10 @@ describe('Tight Decoder', function () {
|
||||
// Pixels
|
||||
0x01, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00 ];
|
||||
|
||||
testDecodeRect(decoder, 0, 0, 4, 2, data1, display, 24);
|
||||
testDecodeRect(decoder, 0, 2, 4, 2, data2, display, 24);
|
||||
done = testDecodeRect(decoder, 0, 0, 4, 2, data1, display, 24);
|
||||
expect(done).to.be.true;
|
||||
done = testDecodeRect(decoder, 0, 2, 4, 2, data2, display, 24);
|
||||
expect(done).to.be.true;
|
||||
|
||||
let targetData = new Uint8Array([
|
||||
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
||||
@@ -196,7 +215,7 @@ describe('Tight Decoder', function () {
|
||||
0x62, 0x08, 0xc9, 0xc0, 0x00, 0x00, 0x00, 0x54,
|
||||
0x00, 0x09 ];
|
||||
|
||||
testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
|
||||
let done = testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
|
||||
|
||||
let targetData = new Uint8Array([
|
||||
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
||||
@@ -205,6 +224,7 @@ describe('Tight Decoder', function () {
|
||||
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
|
||||
]);
|
||||
|
||||
expect(done).to.be.true;
|
||||
expect(display).to.have.displayed(targetData);
|
||||
});
|
||||
|
||||
@@ -221,7 +241,7 @@ describe('Tight Decoder', function () {
|
||||
display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);
|
||||
display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]);
|
||||
|
||||
testDecodeRect(decoder, 1, 2, 0, 0, [ 0x00 ], display, 24);
|
||||
let done = testDecodeRect(decoder, 1, 2, 0, 0, [ 0x00 ], display, 24);
|
||||
|
||||
let targetData = new Uint8Array([
|
||||
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
||||
@@ -230,6 +250,7 @@ describe('Tight Decoder', function () {
|
||||
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
|
||||
]);
|
||||
|
||||
expect(done).to.be.true;
|
||||
expect(display).to.have.displayed(targetData);
|
||||
});
|
||||
|
||||
@@ -238,10 +259,10 @@ describe('Tight Decoder', function () {
|
||||
display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);
|
||||
display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]);
|
||||
|
||||
testDecodeRect(decoder, 1, 2, 0, 0,
|
||||
[ 0x40, 0x01, 0x01,
|
||||
0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff ], display, 24);
|
||||
let done = testDecodeRect(decoder, 1, 2, 0, 0,
|
||||
[ 0x40, 0x01, 0x01,
|
||||
0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff ], display, 24);
|
||||
|
||||
let targetData = new Uint8Array([
|
||||
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
||||
@@ -250,6 +271,7 @@ describe('Tight Decoder', function () {
|
||||
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
|
||||
]);
|
||||
|
||||
expect(done).to.be.true;
|
||||
expect(display).to.have.displayed(targetData);
|
||||
});
|
||||
|
||||
@@ -258,8 +280,9 @@ describe('Tight Decoder', function () {
|
||||
display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);
|
||||
display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]);
|
||||
|
||||
testDecodeRect(decoder, 1, 2, 0, 0,
|
||||
[ 0x80, 0xff, 0xff, 0xff ], display, 24);
|
||||
let done = testDecodeRect(decoder, 1, 2, 0, 0,
|
||||
[ 0x80, 0xff, 0xff, 0xff ],
|
||||
display, 24);
|
||||
|
||||
let targetData = new Uint8Array([
|
||||
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
||||
@@ -268,10 +291,11 @@ describe('Tight Decoder', function () {
|
||||
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
|
||||
]);
|
||||
|
||||
expect(done).to.be.true;
|
||||
expect(display).to.have.displayed(targetData);
|
||||
});
|
||||
|
||||
it('should handle JPEG rects', function (done) {
|
||||
it('should handle JPEG rects', async function () {
|
||||
let data = [
|
||||
// Control bytes
|
||||
0x90, 0xd6, 0x05,
|
||||
@@ -369,7 +393,8 @@ describe('Tight Decoder', function () {
|
||||
0x3f, 0xeb, 0xff, 0x00, 0xff, 0xd9,
|
||||
];
|
||||
|
||||
testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
|
||||
let decodeDone = testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
|
||||
expect(decodeDone).to.be.true;
|
||||
|
||||
let targetData = new Uint8Array([
|
||||
0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
||||
@@ -385,10 +410,7 @@ describe('Tight Decoder', function () {
|
||||
return diff < 5;
|
||||
}
|
||||
|
||||
display.onflush = () => {
|
||||
expect(display).to.have.displayed(targetData, almost);
|
||||
done();
|
||||
};
|
||||
display.flush();
|
||||
await display.flush();
|
||||
expect(display).to.have.displayed(targetData, almost);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,23 +9,26 @@ import FakeWebSocket from './fake.websocket.js';
|
||||
|
||||
function testDecodeRect(decoder, x, y, width, height, data, display, depth) {
|
||||
let sock;
|
||||
let done = false;
|
||||
|
||||
sock = new Websock;
|
||||
sock.open("ws://example.com");
|
||||
|
||||
sock.on('message', () => {
|
||||
decoder.decodeRect(x, y, width, height, sock, display, depth);
|
||||
done = decoder.decodeRect(x, y, width, height, sock, display, depth);
|
||||
});
|
||||
|
||||
// Empty messages are filtered at multiple layers, so we need to
|
||||
// do a direct call
|
||||
if (data.length === 0) {
|
||||
decoder.decodeRect(x, y, width, height, sock, display, depth);
|
||||
done = decoder.decodeRect(x, y, width, height, sock, display, depth);
|
||||
} else {
|
||||
sock._websocket._receiveData(new Uint8Array(data));
|
||||
}
|
||||
|
||||
display.flip();
|
||||
|
||||
return done;
|
||||
}
|
||||
|
||||
describe('TightPng Decoder', function () {
|
||||
@@ -41,7 +44,7 @@ describe('TightPng Decoder', function () {
|
||||
display.resize(4, 4);
|
||||
});
|
||||
|
||||
it('should handle the TightPng encoding', function (done) {
|
||||
it('should handle the TightPng encoding', async function () {
|
||||
let data = [
|
||||
// Control bytes
|
||||
0xa0, 0xb4, 0x04,
|
||||
@@ -119,7 +122,8 @@ describe('TightPng Decoder', function () {
|
||||
0xae, 0x42, 0x60, 0x82,
|
||||
];
|
||||
|
||||
testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
|
||||
let decodeDone = testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
|
||||
expect(decodeDone).to.be.true;
|
||||
|
||||
let targetData = new Uint8Array([
|
||||
0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
||||
@@ -135,10 +139,7 @@ describe('TightPng Decoder', function () {
|
||||
return diff < 30;
|
||||
}
|
||||
|
||||
display.onflush = () => {
|
||||
expect(display).to.have.displayed(targetData, almost);
|
||||
done();
|
||||
};
|
||||
display.flush();
|
||||
await display.flush();
|
||||
expect(display).to.have.displayed(targetData, almost);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,92 +6,64 @@ import FakeWebSocket from './fake.websocket.js';
|
||||
describe('Websock', function () {
|
||||
"use strict";
|
||||
|
||||
describe('Queue methods', function () {
|
||||
let sock;
|
||||
const RQ_TEMPLATE = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7]);
|
||||
describe('Receive queue methods', function () {
|
||||
let sock, websock;
|
||||
|
||||
beforeEach(function () {
|
||||
sock = new Websock();
|
||||
// skip init
|
||||
sock._allocateBuffers();
|
||||
sock._rQ.set(RQ_TEMPLATE);
|
||||
sock._rQlen = RQ_TEMPLATE.length;
|
||||
});
|
||||
describe('rQlen', function () {
|
||||
it('should return the length of the receive queue', function () {
|
||||
sock.rQi = 0;
|
||||
|
||||
expect(sock.rQlen).to.equal(RQ_TEMPLATE.length);
|
||||
});
|
||||
|
||||
it("should return the proper length if we read some from the receive queue", function () {
|
||||
sock.rQi = 1;
|
||||
|
||||
expect(sock.rQlen).to.equal(RQ_TEMPLATE.length - 1);
|
||||
});
|
||||
websock = new FakeWebSocket();
|
||||
websock._open();
|
||||
sock.attach(websock);
|
||||
});
|
||||
|
||||
describe('rQpeek8', function () {
|
||||
it('should peek at the next byte without poping it off the queue', function () {
|
||||
const befLen = sock.rQlen;
|
||||
const peek = sock.rQpeek8();
|
||||
expect(sock.rQpeek8()).to.equal(peek);
|
||||
expect(sock.rQlen).to.equal(befLen);
|
||||
websock._receiveData(new Uint8Array([0xab, 0xcd]));
|
||||
expect(sock.rQpeek8()).to.equal(0xab);
|
||||
expect(sock.rQpeek8()).to.equal(0xab);
|
||||
});
|
||||
});
|
||||
|
||||
describe('rQshift8()', function () {
|
||||
it('should pop a single byte from the receive queue', function () {
|
||||
const peek = sock.rQpeek8();
|
||||
const befLen = sock.rQlen;
|
||||
expect(sock.rQshift8()).to.equal(peek);
|
||||
expect(sock.rQlen).to.equal(befLen - 1);
|
||||
websock._receiveData(new Uint8Array([0xab, 0xcd]));
|
||||
expect(sock.rQshift8()).to.equal(0xab);
|
||||
expect(sock.rQshift8()).to.equal(0xcd);
|
||||
});
|
||||
});
|
||||
|
||||
describe('rQshift16()', function () {
|
||||
it('should pop two bytes from the receive queue and return a single number', function () {
|
||||
const befLen = sock.rQlen;
|
||||
const expected = (RQ_TEMPLATE[0] << 8) + RQ_TEMPLATE[1];
|
||||
expect(sock.rQshift16()).to.equal(expected);
|
||||
expect(sock.rQlen).to.equal(befLen - 2);
|
||||
websock._receiveData(new Uint8Array([0xab, 0xcd, 0x12, 0x34]));
|
||||
expect(sock.rQshift16()).to.equal(0xabcd);
|
||||
expect(sock.rQshift16()).to.equal(0x1234);
|
||||
});
|
||||
});
|
||||
|
||||
describe('rQshift32()', function () {
|
||||
it('should pop four bytes from the receive queue and return a single number', function () {
|
||||
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(befLen - 4);
|
||||
websock._receiveData(new Uint8Array([0xab, 0xcd, 0x12, 0x34,
|
||||
0x88, 0xee, 0x11, 0x33]));
|
||||
expect(sock.rQshift32()).to.equal(0xabcd1234);
|
||||
expect(sock.rQshift32()).to.equal(0x88ee1133);
|
||||
});
|
||||
});
|
||||
|
||||
describe('rQshiftStr', function () {
|
||||
it('should shift the given number of bytes off of the receive queue and return a string', function () {
|
||||
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, 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 () {
|
||||
sock.rQshiftStr();
|
||||
expect(sock.rQlen).to.equal(0);
|
||||
websock._receiveData(new Uint8Array([0xab, 0xcd, 0x12, 0x34,
|
||||
0x88, 0xee, 0x11, 0x33]));
|
||||
expect(sock.rQshiftStr(4)).to.equal('\xab\xcd\x12\x34');
|
||||
expect(sock.rQshiftStr(4)).to.equal('\x88\xee\x11\x33');
|
||||
});
|
||||
|
||||
it('should be able to handle very large strings', function () {
|
||||
const BIG_LEN = 500000;
|
||||
const RQ_BIG = new Uint8Array(BIG_LEN);
|
||||
const incoming = new Uint8Array(BIG_LEN);
|
||||
let expected = "";
|
||||
let letterCode = 'a'.charCodeAt(0);
|
||||
for (let i = 0; i < BIG_LEN; i++) {
|
||||
RQ_BIG[i] = letterCode;
|
||||
incoming[i] = letterCode;
|
||||
expected += String.fromCharCode(letterCode);
|
||||
|
||||
if (letterCode < 'z'.charCodeAt(0)) {
|
||||
@@ -100,146 +72,282 @@ describe('Websock', function () {
|
||||
letterCode = 'a'.charCodeAt(0);
|
||||
}
|
||||
}
|
||||
sock._rQ.set(RQ_BIG);
|
||||
sock._rQlen = RQ_BIG.length;
|
||||
websock._receiveData(incoming);
|
||||
|
||||
const shifted = sock.rQshiftStr();
|
||||
const shifted = sock.rQshiftStr(BIG_LEN);
|
||||
|
||||
expect(shifted).to.be.equal(expected);
|
||||
expect(sock.rQlen).to.equal(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('rQshiftBytes', function () {
|
||||
it('should shift the given number of bytes of the receive queue and return an array', function () {
|
||||
const 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, befRQi, 3));
|
||||
expect(sock.rQlen).to.equal(befLen - 3);
|
||||
websock._receiveData(new Uint8Array([0xab, 0xcd, 0x12, 0x34,
|
||||
0x88, 0xee, 0x11, 0x33]));
|
||||
expect(sock.rQshiftBytes(4)).to.array.equal(new Uint8Array([0xab, 0xcd, 0x12, 0x34]));
|
||||
expect(sock.rQshiftBytes(4)).to.array.equal(new Uint8Array([0x88, 0xee, 0x11, 0x33]));
|
||||
});
|
||||
|
||||
it('should shift the entire rest of the queue off if no length is given', function () {
|
||||
sock.rQshiftBytes();
|
||||
expect(sock.rQlen).to.equal(0);
|
||||
it('should return a shared array if requested', function () {
|
||||
websock._receiveData(new Uint8Array([0xab, 0xcd, 0x12, 0x34,
|
||||
0x88, 0xee, 0x11, 0x33]));
|
||||
const bytes = sock.rQshiftBytes(4, false);
|
||||
expect(bytes).to.array.equal(new Uint8Array([0xab, 0xcd, 0x12, 0x34]));
|
||||
expect(bytes.buffer.byteLength).to.not.equal(bytes.length);
|
||||
});
|
||||
});
|
||||
|
||||
describe('rQslice', function () {
|
||||
beforeEach(function () {
|
||||
sock.rQi = 0;
|
||||
});
|
||||
|
||||
describe('rQpeekBytes', function () {
|
||||
it('should not modify the receive queue', function () {
|
||||
const befLen = sock.rQlen;
|
||||
sock.rQslice(0, 2);
|
||||
expect(sock.rQlen).to.equal(befLen);
|
||||
websock._receiveData(new Uint8Array([0xab, 0xcd, 0x12, 0x34,
|
||||
0x88, 0xee, 0x11, 0x33]));
|
||||
expect(sock.rQpeekBytes(4)).to.array.equal(new Uint8Array([0xab, 0xcd, 0x12, 0x34]));
|
||||
expect(sock.rQpeekBytes(4)).to.array.equal(new Uint8Array([0xab, 0xcd, 0x12, 0x34]));
|
||||
});
|
||||
|
||||
it('should return an array containing the given slice of the receive queue', function () {
|
||||
const sl = sock.rQslice(0, 2);
|
||||
expect(sl).to.be.an.instanceof(Uint8Array);
|
||||
expect(sl).to.array.equal(new Uint8Array(RQ_TEMPLATE.buffer, 0, 2));
|
||||
});
|
||||
|
||||
it('should use the rest of the receive queue if no end is given', function () {
|
||||
const sl = sock.rQslice(1);
|
||||
expect(sl).to.have.length(RQ_TEMPLATE.length - 1);
|
||||
expect(sl).to.array.equal(new Uint8Array(RQ_TEMPLATE.buffer, 1));
|
||||
});
|
||||
|
||||
it('should take the current rQi in to account', function () {
|
||||
sock.rQi = 1;
|
||||
expect(sock.rQslice(0, 2)).to.array.equal(new Uint8Array(RQ_TEMPLATE.buffer, 1, 2));
|
||||
it('should return a shared array if requested', function () {
|
||||
websock._receiveData(new Uint8Array([0xab, 0xcd, 0x12, 0x34,
|
||||
0x88, 0xee, 0x11, 0x33]));
|
||||
const bytes = sock.rQpeekBytes(4, false);
|
||||
expect(bytes).to.array.equal(new Uint8Array([0xab, 0xcd, 0x12, 0x34]));
|
||||
expect(bytes.buffer.byteLength).to.not.equal(bytes.length);
|
||||
});
|
||||
});
|
||||
|
||||
describe('rQwait', function () {
|
||||
beforeEach(function () {
|
||||
sock.rQi = 0;
|
||||
websock._receiveData(new Uint8Array([0xab, 0xcd, 0x12, 0x34,
|
||||
0x88, 0xee, 0x11, 0x33]));
|
||||
});
|
||||
|
||||
it('should return true if there are not enough bytes in the receive queue', function () {
|
||||
expect(sock.rQwait('hi', RQ_TEMPLATE.length + 1)).to.be.true;
|
||||
expect(sock.rQwait('hi', 9)).to.be.true;
|
||||
});
|
||||
|
||||
it('should return false if there are enough bytes in the receive queue', function () {
|
||||
expect(sock.rQwait('hi', RQ_TEMPLATE.length)).to.be.false;
|
||||
expect(sock.rQwait('hi', 8)).to.be.false;
|
||||
});
|
||||
|
||||
it('should return true and reduce rQi by "goback" if there are not enough bytes', function () {
|
||||
sock.rQi = 5;
|
||||
expect(sock.rQwait('hi', RQ_TEMPLATE.length, 4)).to.be.true;
|
||||
expect(sock.rQi).to.equal(1);
|
||||
expect(sock.rQshift32()).to.equal(0xabcd1234);
|
||||
expect(sock.rQwait('hi', 8, 2)).to.be.true;
|
||||
expect(sock.rQshift32()).to.equal(0x123488ee);
|
||||
});
|
||||
|
||||
it('should raise an error if we try to go back more than possible', function () {
|
||||
sock.rQi = 5;
|
||||
expect(() => sock.rQwait('hi', RQ_TEMPLATE.length, 6)).to.throw(Error);
|
||||
expect(sock.rQshift32()).to.equal(0xabcd1234);
|
||||
expect(() => sock.rQwait('hi', 8, 6)).to.throw(Error);
|
||||
});
|
||||
|
||||
it('should not reduce rQi if there are enough bytes', function () {
|
||||
sock.rQi = 5;
|
||||
sock.rQwait('hi', 1, 6);
|
||||
expect(sock.rQi).to.equal(5);
|
||||
expect(sock.rQshift32()).to.equal(0xabcd1234);
|
||||
expect(sock.rQwait('hi', 4, 2)).to.be.false;
|
||||
expect(sock.rQshift32()).to.equal(0x88ee1133);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Send queue methods', function () {
|
||||
let sock;
|
||||
|
||||
const bufferSize = 10 * 1024;
|
||||
|
||||
beforeEach(function () {
|
||||
let websock = new FakeWebSocket();
|
||||
websock._open();
|
||||
sock = new Websock();
|
||||
sock.attach(websock);
|
||||
});
|
||||
|
||||
describe('sQpush8()', function () {
|
||||
it('should send a single byte', function () {
|
||||
sock.sQpush8(42);
|
||||
sock.flush();
|
||||
expect(sock).to.have.sent(new Uint8Array([42]));
|
||||
});
|
||||
it('should not send any data until flushing', function () {
|
||||
sock.sQpush8(42);
|
||||
expect(sock).to.have.sent(new Uint8Array([]));
|
||||
});
|
||||
it('should implicitly flush if the queue is full', function () {
|
||||
for (let i = 0;i <= bufferSize;i++) {
|
||||
sock.sQpush8(42);
|
||||
}
|
||||
|
||||
let expected = [];
|
||||
for (let i = 0;i < bufferSize;i++) {
|
||||
expected.push(42);
|
||||
}
|
||||
|
||||
expect(sock).to.have.sent(new Uint8Array(expected));
|
||||
});
|
||||
});
|
||||
|
||||
describe('sQpush16()', function () {
|
||||
it('should send a number as two bytes', function () {
|
||||
sock.sQpush16(420);
|
||||
sock.flush();
|
||||
expect(sock).to.have.sent(new Uint8Array([1, 164]));
|
||||
});
|
||||
it('should not send any data until flushing', function () {
|
||||
sock.sQpush16(420);
|
||||
expect(sock).to.have.sent(new Uint8Array([]));
|
||||
});
|
||||
it('should implicitly flush if the queue is full', function () {
|
||||
for (let i = 0;i <= bufferSize/2;i++) {
|
||||
sock.sQpush16(420);
|
||||
}
|
||||
|
||||
let expected = [];
|
||||
for (let i = 0;i < bufferSize/2;i++) {
|
||||
expected.push(1);
|
||||
expected.push(164);
|
||||
}
|
||||
|
||||
expect(sock).to.have.sent(new Uint8Array(expected));
|
||||
});
|
||||
});
|
||||
|
||||
describe('sQpush32()', function () {
|
||||
it('should send a number as two bytes', function () {
|
||||
sock.sQpush32(420420);
|
||||
sock.flush();
|
||||
expect(sock).to.have.sent(new Uint8Array([0, 6, 106, 68]));
|
||||
});
|
||||
it('should not send any data until flushing', function () {
|
||||
sock.sQpush32(420420);
|
||||
expect(sock).to.have.sent(new Uint8Array([]));
|
||||
});
|
||||
it('should implicitly flush if the queue is full', function () {
|
||||
for (let i = 0;i <= bufferSize/4;i++) {
|
||||
sock.sQpush32(420420);
|
||||
}
|
||||
|
||||
let expected = [];
|
||||
for (let i = 0;i < bufferSize/4;i++) {
|
||||
expected.push(0);
|
||||
expected.push(6);
|
||||
expected.push(106);
|
||||
expected.push(68);
|
||||
}
|
||||
|
||||
expect(sock).to.have.sent(new Uint8Array(expected));
|
||||
});
|
||||
});
|
||||
|
||||
describe('sQpushString()', function () {
|
||||
it('should send a string buffer', function () {
|
||||
sock.sQpushString('\x12\x34\x56\x78\x90');
|
||||
sock.flush();
|
||||
expect(sock).to.have.sent(new Uint8Array([0x12, 0x34, 0x56, 0x78, 0x90]));
|
||||
});
|
||||
it('should not send any data until flushing', function () {
|
||||
sock.sQpushString('\x12\x34\x56\x78\x90');
|
||||
expect(sock).to.have.sent(new Uint8Array([]));
|
||||
});
|
||||
it('should implicitly flush if the queue is full', function () {
|
||||
for (let i = 0;i <= bufferSize/5;i++) {
|
||||
sock.sQpushString('\x12\x34\x56\x78\x90');
|
||||
}
|
||||
|
||||
let expected = [];
|
||||
for (let i = 0;i < bufferSize/5;i++) {
|
||||
expected.push(0x12);
|
||||
expected.push(0x34);
|
||||
expected.push(0x56);
|
||||
expected.push(0x78);
|
||||
expected.push(0x90);
|
||||
}
|
||||
|
||||
expect(sock).to.have.sent(new Uint8Array(expected));
|
||||
});
|
||||
it('should implicitly split a large buffer', function () {
|
||||
let str = '';
|
||||
for (let i = 0;i <= bufferSize/5;i++) {
|
||||
str += '\x12\x34\x56\x78\x90';
|
||||
}
|
||||
|
||||
sock.sQpushString(str);
|
||||
|
||||
let expected = [];
|
||||
for (let i = 0;i < bufferSize/5;i++) {
|
||||
expected.push(0x12);
|
||||
expected.push(0x34);
|
||||
expected.push(0x56);
|
||||
expected.push(0x78);
|
||||
expected.push(0x90);
|
||||
}
|
||||
|
||||
expect(sock).to.have.sent(new Uint8Array(expected));
|
||||
});
|
||||
});
|
||||
|
||||
describe('sQpushBytes()', function () {
|
||||
it('should send a byte buffer', function () {
|
||||
sock.sQpushBytes(new Uint8Array([0x12, 0x34, 0x56, 0x78, 0x90]));
|
||||
sock.flush();
|
||||
expect(sock).to.have.sent(new Uint8Array([0x12, 0x34, 0x56, 0x78, 0x90]));
|
||||
});
|
||||
it('should not send any data until flushing', function () {
|
||||
sock.sQpushBytes(new Uint8Array([0x12, 0x34, 0x56, 0x78, 0x90]));
|
||||
expect(sock).to.have.sent(new Uint8Array([]));
|
||||
});
|
||||
it('should implicitly flush if the queue is full', function () {
|
||||
for (let i = 0;i <= bufferSize/5;i++) {
|
||||
sock.sQpushBytes(new Uint8Array([0x12, 0x34, 0x56, 0x78, 0x90]));
|
||||
}
|
||||
|
||||
let expected = [];
|
||||
for (let i = 0;i < bufferSize/5;i++) {
|
||||
expected.push(0x12);
|
||||
expected.push(0x34);
|
||||
expected.push(0x56);
|
||||
expected.push(0x78);
|
||||
expected.push(0x90);
|
||||
}
|
||||
|
||||
expect(sock).to.have.sent(new Uint8Array(expected));
|
||||
});
|
||||
it('should implicitly split a large buffer', function () {
|
||||
let buffer = [];
|
||||
for (let i = 0;i <= bufferSize/5;i++) {
|
||||
buffer.push(0x12);
|
||||
buffer.push(0x34);
|
||||
buffer.push(0x56);
|
||||
buffer.push(0x78);
|
||||
buffer.push(0x90);
|
||||
}
|
||||
|
||||
sock.sQpushBytes(new Uint8Array(buffer));
|
||||
|
||||
let expected = [];
|
||||
for (let i = 0;i < bufferSize/5;i++) {
|
||||
expected.push(0x12);
|
||||
expected.push(0x34);
|
||||
expected.push(0x56);
|
||||
expected.push(0x78);
|
||||
expected.push(0x90);
|
||||
}
|
||||
|
||||
expect(sock).to.have.sent(new Uint8Array(expected));
|
||||
});
|
||||
});
|
||||
|
||||
describe('flush', function () {
|
||||
beforeEach(function () {
|
||||
sock._websocket = {
|
||||
send: sinon.spy()
|
||||
};
|
||||
});
|
||||
|
||||
it('should actually send on the websocket', function () {
|
||||
sock._websocket.bufferedAmount = 8;
|
||||
sock._websocket.readyState = WebSocket.OPEN;
|
||||
sock._sQ = new Uint8Array([1, 2, 3]);
|
||||
sock._sQlen = 3;
|
||||
const encoded = sock._encodeMessage();
|
||||
|
||||
sock.flush();
|
||||
expect(sock._websocket.send).to.have.been.calledOnce;
|
||||
expect(sock._websocket.send).to.have.been.calledWith(encoded);
|
||||
expect(sock).to.have.sent(new Uint8Array([1, 2, 3]));
|
||||
});
|
||||
|
||||
it('should not call send if we do not have anything queued up', function () {
|
||||
sock._sQlen = 0;
|
||||
sock._websocket.bufferedAmount = 8;
|
||||
|
||||
sock.flush();
|
||||
|
||||
expect(sock._websocket.send).not.to.have.been.called;
|
||||
});
|
||||
});
|
||||
|
||||
describe('send', function () {
|
||||
beforeEach(function () {
|
||||
sock.flush = sinon.spy();
|
||||
});
|
||||
|
||||
it('should add to the send queue', function () {
|
||||
sock.send([1, 2, 3]);
|
||||
const sq = sock.sQ;
|
||||
expect(new Uint8Array(sq.buffer, sock._sQlen - 3, 3)).to.array.equal(new Uint8Array([1, 2, 3]));
|
||||
});
|
||||
|
||||
it('should call flush', function () {
|
||||
sock.send([1, 2, 3]);
|
||||
expect(sock.flush).to.have.been.calledOnce;
|
||||
});
|
||||
});
|
||||
|
||||
describe('sendString', function () {
|
||||
beforeEach(function () {
|
||||
sock.send = sinon.spy();
|
||||
});
|
||||
|
||||
it('should call send after converting the string to an array', function () {
|
||||
sock.sendString("\x01\x02\x03");
|
||||
expect(sock.send).to.have.been.calledWith([1, 2, 3]);
|
||||
expect(sock).to.have.sent(new Uint8Array([]));
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -450,9 +558,8 @@ describe('Websock', function () {
|
||||
sock._allocateBuffers();
|
||||
});
|
||||
|
||||
it('should support adding binary Uint8Array data to the receive queue', function () {
|
||||
it('should support adding data to the receive queue', function () {
|
||||
const msg = { data: new Uint8Array([1, 2, 3]) };
|
||||
sock._mode = 'binary';
|
||||
sock._recvMessage(msg);
|
||||
expect(sock.rQshiftStr(3)).to.equal('\x01\x02\x03');
|
||||
});
|
||||
@@ -473,80 +580,49 @@ describe('Websock', function () {
|
||||
expect(sock._eventHandlers.message).not.to.have.been.called;
|
||||
});
|
||||
|
||||
it('should compact the receive queue when a message handler empties it', function () {
|
||||
sock._eventHandlers.message = () => { sock.rQi = sock._rQlen; };
|
||||
it('should compact the receive queue when fully read', function () {
|
||||
sock._rQ = new Uint8Array([0, 1, 2, 3, 4, 5, 0, 0, 0, 0]);
|
||||
sock._rQlen = 6;
|
||||
sock.rQi = 6;
|
||||
sock._rQi = 6;
|
||||
const msg = { data: new Uint8Array([1, 2, 3]).buffer };
|
||||
sock._mode = 'binary';
|
||||
sock._recvMessage(msg);
|
||||
expect(sock._rQlen).to.equal(0);
|
||||
expect(sock.rQi).to.equal(0);
|
||||
expect(sock._rQlen).to.equal(3);
|
||||
expect(sock._rQi).to.equal(0);
|
||||
});
|
||||
|
||||
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;
|
||||
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);
|
||||
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._rQi = 0;
|
||||
sock._rQbufferSize = 20;
|
||||
const msg = { data: new Uint8Array(30).buffer };
|
||||
sock._mode = 'binary';
|
||||
sock._recvMessage(msg);
|
||||
expect(sock._rQlen).to.equal(30);
|
||||
expect(sock.rQi).to.equal(0);
|
||||
expect(sock._rQi).to.equal(0);
|
||||
expect(sock._rQ.length).to.equal(240); // keep the invariant that rQbufferSize / 8 >= rQlen
|
||||
});
|
||||
|
||||
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._rQi = 15;
|
||||
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 () {
|
||||
before(function () { FakeWebSocket.replace(); });
|
||||
after(function () { FakeWebSocket.restore(); });
|
||||
|
||||
describe('as binary data', function () {
|
||||
let sock;
|
||||
beforeEach(function () {
|
||||
sock = new Websock();
|
||||
sock.open('ws://', 'binary');
|
||||
sock._websocket._open();
|
||||
});
|
||||
|
||||
it('should only send the send queue up to the send queue length', function () {
|
||||
sock._sQ = new Uint8Array([1, 2, 3, 4, 5]);
|
||||
sock._sQlen = 3;
|
||||
const res = sock._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._getSentData()).to.array.equal(new Uint8Array([1, 2, 3]));
|
||||
});
|
||||
expect(sock._rQlen).to.equal(7);
|
||||
expect(sock._rQi).to.equal(0);
|
||||
expect(sock._rQ.length).to.equal(56);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,39 +8,48 @@ describe('WebUtil', function () {
|
||||
"use strict";
|
||||
|
||||
describe('config variables', function () {
|
||||
let origState, origHref;
|
||||
beforeEach(function () {
|
||||
origState = history.state;
|
||||
origHref = location.href;
|
||||
});
|
||||
afterEach(function () {
|
||||
history.replaceState(origState, '', origHref);
|
||||
});
|
||||
|
||||
it('should parse query string variables', function () {
|
||||
// history.pushState() will not cause the browser to attempt loading
|
||||
// the URL, this is exactly what we want here for the tests.
|
||||
history.pushState({}, '', "test?myvar=myval");
|
||||
history.replaceState({}, '', "test?myvar=myval");
|
||||
expect(WebUtil.getConfigVar("myvar")).to.be.equal("myval");
|
||||
});
|
||||
it('should return default value when no query match', function () {
|
||||
history.pushState({}, '', "test?myvar=myval");
|
||||
history.replaceState({}, '', "test?myvar=myval");
|
||||
expect(WebUtil.getConfigVar("other", "def")).to.be.equal("def");
|
||||
});
|
||||
it('should handle no query match and no default value', function () {
|
||||
history.pushState({}, '', "test?myvar=myval");
|
||||
history.replaceState({}, '', "test?myvar=myval");
|
||||
expect(WebUtil.getConfigVar("other")).to.be.equal(null);
|
||||
});
|
||||
it('should parse fragment variables', function () {
|
||||
history.pushState({}, '', "test#myvar=myval");
|
||||
history.replaceState({}, '', "test#myvar=myval");
|
||||
expect(WebUtil.getConfigVar("myvar")).to.be.equal("myval");
|
||||
});
|
||||
it('should return default value when no fragment match', function () {
|
||||
history.pushState({}, '', "test#myvar=myval");
|
||||
history.replaceState({}, '', "test#myvar=myval");
|
||||
expect(WebUtil.getConfigVar("other", "def")).to.be.equal("def");
|
||||
});
|
||||
it('should handle no fragment match and no default value', function () {
|
||||
history.pushState({}, '', "test#myvar=myval");
|
||||
history.replaceState({}, '', "test#myvar=myval");
|
||||
expect(WebUtil.getConfigVar("other")).to.be.equal(null);
|
||||
});
|
||||
it('should handle both query and fragment', function () {
|
||||
history.pushState({}, '', "test?myquery=1#myhash=2");
|
||||
history.replaceState({}, '', "test?myquery=1#myhash=2");
|
||||
expect(WebUtil.getConfigVar("myquery")).to.be.equal("1");
|
||||
expect(WebUtil.getConfigVar("myhash")).to.be.equal("2");
|
||||
});
|
||||
it('should prioritize fragment if both provide same var', function () {
|
||||
history.pushState({}, '', "test?myvar=1#myvar=2");
|
||||
history.replaceState({}, '', "test?myvar=1#myvar=2");
|
||||
expect(WebUtil.getConfigVar("myvar")).to.be.equal("2");
|
||||
});
|
||||
});
|
||||
@@ -66,11 +75,6 @@ describe('WebUtil', function () {
|
||||
origLocalStorage = Object.getOwnPropertyDescriptor(window, "localStorage");
|
||||
|
||||
Object.defineProperty(window, "localStorage", {value: {}});
|
||||
if (window.localStorage.setItem !== undefined) {
|
||||
// Object.defineProperty() doesn't work properly in old
|
||||
// versions of Chrome
|
||||
this.skip();
|
||||
}
|
||||
|
||||
window.localStorage.setItem = sinon.stub();
|
||||
window.localStorage.getItem = sinon.stub();
|
||||
@@ -79,9 +83,7 @@ describe('WebUtil', function () {
|
||||
return WebUtil.initSettings();
|
||||
});
|
||||
afterEach(function () {
|
||||
if (origLocalStorage !== undefined) {
|
||||
Object.defineProperty(window, "localStorage", origLocalStorage);
|
||||
}
|
||||
Object.defineProperty(window, "localStorage", origLocalStorage);
|
||||
});
|
||||
|
||||
describe('writeSetting', function () {
|
||||
@@ -90,6 +92,11 @@ describe('WebUtil', function () {
|
||||
expect(window.localStorage.setItem).to.have.been.calledWithExactly('test', 'value');
|
||||
expect(WebUtil.readSetting('test')).to.equal('value');
|
||||
});
|
||||
|
||||
it('should not crash when local storage save fails', function () {
|
||||
localStorage.setItem.throws(new DOMException());
|
||||
expect(WebUtil.writeSetting('test', 'value')).to.not.throw;
|
||||
});
|
||||
});
|
||||
|
||||
describe('setSetting', function () {
|
||||
@@ -135,6 +142,11 @@ describe('WebUtil', function () {
|
||||
WebUtil.writeSetting('test', 'something else');
|
||||
expect(WebUtil.readSetting('test')).to.equal('something else');
|
||||
});
|
||||
|
||||
it('should not crash when local storage read fails', function () {
|
||||
localStorage.getItem.throws(new DOMException());
|
||||
expect(WebUtil.readSetting('test')).to.not.throw;
|
||||
});
|
||||
});
|
||||
|
||||
// this doesn't appear to be used anywhere
|
||||
@@ -143,6 +155,11 @@ describe('WebUtil', function () {
|
||||
WebUtil.eraseSetting('test');
|
||||
expect(window.localStorage.removeItem).to.have.been.calledWithExactly('test');
|
||||
});
|
||||
|
||||
it('should not crash when local storage remove fails', function () {
|
||||
localStorage.removeItem.throws(new DOMException());
|
||||
expect(WebUtil.eraseSetting('test')).to.not.throw;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
const expect = chai.expect;
|
||||
|
||||
import Websock from '../core/websock.js';
|
||||
import Display from '../core/display.js';
|
||||
|
||||
import ZRLEDecoder from '../core/decoders/zrle.js';
|
||||
|
||||
import FakeWebSocket from './fake.websocket.js';
|
||||
|
||||
function testDecodeRect(decoder, x, y, width, height, data, display, depth) {
|
||||
let sock;
|
||||
let done = false;
|
||||
|
||||
sock = new Websock;
|
||||
sock.open("ws://example.com");
|
||||
|
||||
sock.on('message', () => {
|
||||
done = decoder.decodeRect(x, y, width, height, sock, display, depth);
|
||||
});
|
||||
|
||||
// Empty messages are filtered at multiple layers, so we need to
|
||||
// do a direct call
|
||||
if (data.length === 0) {
|
||||
done = decoder.decodeRect(x, y, width, height, sock, display, depth);
|
||||
} else {
|
||||
sock._websocket._receiveData(new Uint8Array(data));
|
||||
}
|
||||
|
||||
display.flip();
|
||||
|
||||
return done;
|
||||
}
|
||||
|
||||
describe('ZRLE Decoder', function () {
|
||||
let decoder;
|
||||
let display;
|
||||
|
||||
before(FakeWebSocket.replace);
|
||||
after(FakeWebSocket.restore);
|
||||
|
||||
beforeEach(function () {
|
||||
decoder = new ZRLEDecoder();
|
||||
display = new Display(document.createElement('canvas'));
|
||||
display.resize(4, 4);
|
||||
});
|
||||
|
||||
it('should handle the Raw subencoding', function () {
|
||||
let done = testDecodeRect(decoder, 0, 0, 4, 4,
|
||||
[0x00, 0x00, 0x00, 0x0e, 0x78, 0x5e,
|
||||
0x62, 0x60, 0x60, 0xf8, 0x4f, 0x12,
|
||||
0x02, 0x00, 0x00, 0x00, 0xff, 0xff],
|
||||
display, 24);
|
||||
|
||||
let targetData = new Uint8Array([
|
||||
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
|
||||
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
|
||||
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
|
||||
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff
|
||||
]);
|
||||
|
||||
expect(done).to.be.true;
|
||||
expect(display).to.have.displayed(targetData);
|
||||
});
|
||||
|
||||
it('should handle the Solid subencoding', function () {
|
||||
let done = testDecodeRect(decoder, 0, 0, 4, 4,
|
||||
[0x00, 0x00, 0x00, 0x0c, 0x78, 0x5e,
|
||||
0x62, 0x64, 0x60, 0xf8, 0x0f, 0x00,
|
||||
0x00, 0x00, 0xff, 0xff],
|
||||
display, 24);
|
||||
|
||||
let targetData = new Uint8Array([
|
||||
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
|
||||
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
|
||||
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
|
||||
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff
|
||||
]);
|
||||
|
||||
expect(done).to.be.true;
|
||||
expect(display).to.have.displayed(targetData);
|
||||
});
|
||||
|
||||
|
||||
it('should handle the Palette Tile subencoding', function () {
|
||||
let done = testDecodeRect(decoder, 0, 0, 4, 4,
|
||||
[0x00, 0x00, 0x00, 0x12, 0x78, 0x5E,
|
||||
0x62, 0x62, 0x60, 248, 0xff, 0x9F,
|
||||
0x01, 0x08, 0x3E, 0x7C, 0x00, 0x00,
|
||||
0x00, 0x00, 0xff, 0xff],
|
||||
display, 24);
|
||||
|
||||
let targetData = new Uint8Array([
|
||||
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
|
||||
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
|
||||
0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff,
|
||||
0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff
|
||||
]);
|
||||
|
||||
expect(done).to.be.true;
|
||||
expect(display).to.have.displayed(targetData);
|
||||
});
|
||||
|
||||
it('should handle the RLE Tile subencoding', function () {
|
||||
let done = testDecodeRect(decoder, 0, 0, 4, 4,
|
||||
[0x00, 0x00, 0x00, 0x0d, 0x78, 0x5e,
|
||||
0x6a, 0x60, 0x60, 0xf8, 0x2f, 0x00,
|
||||
0x00, 0x00, 0x00, 0xff, 0xff],
|
||||
display, 24);
|
||||
|
||||
let targetData = new Uint8Array([
|
||||
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
|
||||
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
|
||||
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
|
||||
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff
|
||||
]);
|
||||
|
||||
expect(done).to.be.true;
|
||||
expect(display).to.have.displayed(targetData);
|
||||
});
|
||||
|
||||
it('should handle the RLE Palette Tile subencoding', function () {
|
||||
let done = testDecodeRect(decoder, 0, 0, 4, 4,
|
||||
[0x00, 0x00, 0x00, 0x11, 0x78, 0x5e,
|
||||
0x6a, 0x62, 0x60, 0xf8, 0xff, 0x9f,
|
||||
0x81, 0xa1, 0x81, 0x1f, 0x00, 0x00,
|
||||
0x00, 0xff, 0xff],
|
||||
display, 24);
|
||||
|
||||
let targetData = new Uint8Array([
|
||||
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
|
||||
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
|
||||
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
|
||||
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff
|
||||
]);
|
||||
|
||||
expect(done).to.be.true;
|
||||
expect(display).to.have.displayed(targetData);
|
||||
});
|
||||
|
||||
it('should fail on an invalid subencoding', function () {
|
||||
let data = [0x00, 0x00, 0x00, 0x0c, 0x78, 0x5e, 0x6a, 0x64, 0x60, 0xf8, 0x0f, 0x00, 0x00, 0x00, 0xff, 0xff];
|
||||
expect(() => testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24)).to.throw();
|
||||
});
|
||||
});
|
||||