84 Commits

Author SHA1 Message Date
Samuel Mannehed 0f4a06ffcd noVNC 1.3.0 beta 2021-09-08 16:06:42 +02:00
Samuel Mannehed f69d55c02f Fix parsing of query string variables
This space that was added here was added to the parsed value of the
query variable. This broke any comparisons with the value, for example
"myvar=true" resulted in a value of "true ".

This was broken by f796b05e42

The commit also adds unit tests for webutil.getConfigVar() that will
detect problems like this in the future.
2021-09-08 15:37:42 +02:00
Samuel Mannehed 7841037618 Merge pull request #1365 from baleeds/feature/detect-parent-resize
feature: Detect parent resize
2021-09-03 17:51:18 +02:00
Samuel Mannehed 1afa18f09e Increase browser version requirements
Now that we use ResizeObserver we know that we require more modern
browsers. The most notable ones here are Firefox and Safari.

With regards to Firefox, while the desktop version has had support
since 69, the Android app requires 79. At the time of writing the
current ESR of Firefox is 78, but the concept of ESR doesn't seem to
exist for Android.

The Safari 13 requirement means we no longer support for example iPhone
5S or the 4th generation of the iPad. These are devices from 2013~2014.
2021-09-03 16:52:20 +02:00
Samuel Mannehed 375f36c575 Modify unit tests to work with ResizeObserver 2021-09-03 16:52:20 +02:00
Benjamin Leeds a9c2ff30b6 Replace window.onresize with ResizeObserver
Fixes an issue where if the screen div resizes for a reason other than
window resize, the canvas wouldn't redraw.
2021-09-01 23:45:55 +02:00
Samuel Mannehed fcb95821b7 Merge pull request #1573 from yatru/security-privacy-url-patch
Security privacy to url parameters
2021-09-01 16:00:19 +02:00
yatru f796b05e42 Add support for URL fragment parameters
Passing parameters as part of the fragment could be considered
benifical from a security or privacy standpoint when compared to query
string parameters. The URL fragment parameters are not sent to the
server.
2021-09-01 14:49:37 +02:00
Samuel Mannehed 0a8ced2cfe Update Swedish translation 2021-08-27 16:12:02 +02:00
Samuel Mannehed 7a76fbb767 Remove duplicate translation string from es.po 2021-08-27 16:04:27 +02:00
Samuel Mannehed cb56f35fab Update the translation template file for v1.3.0 2021-08-27 16:04:27 +02:00
Pierre Ossman 7485e82b72 Update playback test to use new API
Hooking in to the underlying WebSocket after it has been created no
longer works, so clean things up and use the new method of passing an
existing object to the RFB constructor.
2021-07-22 16:56:49 +02:00
Samuel Mannehed d44ddbe186 Merge pull request #1449 from JanZerebecki/manpage
add Man page for launch.sh and rename to novnc_proxy
2021-06-30 16:04:01 +02:00
Jan Zerebecki 89e206c146 add Man page and rename launch.sh to novnc_proxy.
Co-Authored-By: Adam Young <ayoung@redhat.com>
2021-06-25 13:54:04 +02:00
Jose (Ito) Matsuda dd20b17d49 feat: add French localization strings 2021-05-06 22:35:18 -04:00
Samuel Mannehed 8ab18125f9 Merge pull request #1545 from ascillato/patch-2
Update Spanish Translation
2021-04-25 01:20:02 +02:00
Adrian Scillato e283f08d04 Update Spanish Translation
Update Spanish Translation.

Added missing translations. Fixed some typos.
Also, some changes were made to use a more formal and international spanish.
2021-04-23 12:10:53 -03:00
Pierre Ossman dbd519558c Initiate connection from RFB constructor
We need to do this in order to safely attach to existing WebSocket
objects. There may be message events already pending so we must set up
our event handlers before returning.

This means we will now throw errors instead of generating "disconnect"
events on problems as the caller no longer has the opportunity to set up
event handlers.

This might have been the correct approach from the start as it mimics
how e.g. the WebSocket constructor works.
2021-04-18 14:27:57 +02:00
Pierre Ossman de9fc9508c Don't fake open events in Websock
We don't know if the caller is prepared to receive those events right
now as normally they would get them on a fresh new stack later. We also
can't delay delivery since then we might deliver the event after any
pending "message" events.

Better to push the problem one layer up to the caller, which now needs
to be more aware of the state of the WebSocket object it is trying to
use.
2021-04-18 14:26:30 +02:00
Pierre Ossman 9376191fc4 Refuse to use already closed WebSocket objects
We can't do anything useful with them anyway.
2021-04-18 14:26:28 +02:00
Pierre Ossman b7b7e4e26b Provide readyState for Websock objects
It mainly reports the state of the underlying object in consistent
manner.
2021-04-18 14:26:05 +02:00
Pierre Ossman 2244f53774 Move Websock event handlers to own methods
Avoid cluttering up the RFB constructor.
2021-04-18 14:26:02 +02:00
Pierre Ossman 42100e8233 Add unit tests for connect/attach errors 2021-04-18 14:25:59 +02:00
Pierre Ossman ae3c01f782 Add unit tests for passing WebSocket objects 2021-04-18 14:25:03 +02:00
Pierre Ossman f0e4908dec Stop explicitly testing connection states
These are internal and we should be testing the externally observable
behaviour.
2021-04-18 14:24:05 +02:00
Pierre Ossman ae5f3f6909 Revert "Fixed a race condition when attaching to an existing socket"
This reverts commit ef27628c6d. By
bypassing setTimeout() it creates other race conditions so this is not
the proper fix for the issue.
2021-04-16 13:28:47 +02:00
Samuel Mannehed 84f102d6a9 Merge pull request #1537 from TimSBSquare/bugfix/existing-channel-race-condition
Fixed a race condition when attaching to an existing socket
2021-03-30 15:59:37 +02:00
Tim Stableford ef27628c6d Fixed a race condition when attaching to an existing socket
This is an error that presents itself with RTCDataChannel's, I suspect this could not
happen with a pre-existing WebSocket.

If the remote connection creates a data channel then the local (VNC) side gets a channel
created callback. It may also be the case that in that very same tick the socket is also
opened and buffered data received. This meant that (in my tests) about 1/3 of the time
noVNC would fail to respond to the initial message from the server because it was received
and subsequently not handled during that initial tick.

Also made the documentation reflect this new behaviour and document the existing behaviour.
2021-03-30 14:32:04 +01:00
Liddack 89f9ac0016 Add Portuguese (Brazil) translation 2021-03-16 13:30:23 +01:00
lhchavez f9a8c4ccd5 Add VeNCrypt Plain authentication tests
This change adds tests for the VeNCrypt Plain authentication. In doing
that, this also fixes a typo that was introduced in a recent change.
2021-03-13 05:40:38 -08:00
Pierre Ossman 9ca337d3a8 Merge branch 'homogenize-credentials-testing' of https://github.com/lhchavez/noVNC 2021-03-11 16:47:35 +01:00
Pierre Ossman 4c96d4b7bd Merge branch 'feature/support-existing-rtcdatachannel-or-websocket-squashed' of https://github.com/TimSBSquare/noVNC 2021-03-11 16:30:22 +01:00
lhchavez 0c55c64757 Normalize the credentials presence check
Most places that check for the presence / absence of credentials compare
them against `undefined`, except the one for Plain authentication.

This change makes the very last place to use the same pattern (instead
of checking for falsiness) for consistency. Additionally, there are ways
to configure PAM to accept empty passwords, so it's possible for a user
to legitimately send an empty string as password.
2021-03-11 06:42:11 -08:00
Tim Stableford 44d384b99c Added support for RTCDataChannel
This work is originally by Ryan Castner <castner.rr@gmail.com> and
submitted as a PR here https://github.com/novnc/noVNC/pull/1362

Architecturally it is much the same except it doesn't rename a lot
of variables to make this more reviewable. It also avoids unrelated
changes such as replacing .onclose with an event listener, which
caused numerous test failures.

It also adds in ppoffice's fix to initialise the buffers.

Like the original author I don't have enough time available to
refactor this project to the new style event listeners.

Review cleanup for RTCDataChannel support (see below)

* More descriptive error when url or channel not set.
* Moved websocket property check to WebSock.
  This had unintended consequences in the tests that required some
  fixup. Mostly due to some tests not always passing FakeWebsocket.
  FakeWebsocket also needs to set the listeners to null to be compatible
  with what is in thw browser and expected by the property check code.
  The property check code now also takes into account class prototypes
  for test compatibility.
* Removed unreachable code.
* Reverted comment.
* Cleanup raw channel reference in rfb on websock close.
* Use readyState to check whether a socket is open rather than assuming.
* Updated RFB constructor documentation

Removed an unused boolean passed to attach
2021-03-04 18:55:06 +00:00
lhchavez 18593154d3 Allow longer passwords in Plain authentication
Some people have longer passwords than 256 characters (hooray for
password managers!). Server implementations also allow longer passwords:
TigerVNC allows up to 1024 characters.
2021-03-03 17:34:02 -08:00
Pierre Ossman 5a0cceb815 Change phrasing for discussion group link
It's listed with the issue templates, so make sure it follows the same
style in phrasing.
2021-01-15 16:16:38 +01:00
Pierre Ossman d56e042fee Add link to discussion group from issues
So that people can easily find it and not file bug reports for
things that are just questions.
2021-01-15 16:14:40 +01:00
Pierre Ossman babd665c03 Hide link to create blank issues
We want users to use the templates so we don't miss any relevant
information.
2021-01-15 16:08:13 +01:00
NNN1590 199910e63b Update Japanese translation 2021-01-15 14:04:49 +09:00
Pierre Ossman 4a319c414d Merge branch 'more_noie' of https://github.com/CendioOssman/noVNC 2020-12-30 16:13:52 +01:00
Pierre Ossman adfb99e7ec Update conversion documentation for Node.js
We now only support conversion to CommonJS, in order to support Node.js
older than version 15.
2020-12-30 15:57:02 +01:00
Pierre Ossman 32222304f4 Remove documentation about converting the app
This is no longer possible as we now require browser support for
modules.
2020-12-30 15:56:42 +01:00
Pierre Ossman 4a8efa6bc9 Use snap actions instead of the broken container
The container didn't work properly for our base snap anyway.
2020-12-30 15:46:03 +01:00
Pierre Ossman cd9f535eb3 Package files directly in snapcraft.yaml
We don't need to convert things anymore, so reference files directly in
the snap yaml file.
2020-12-30 15:46:03 +01:00
Pierre Ossman 23249c7263 Store result from NPM and snap builds
To ease debugging.
2020-12-30 15:46:03 +01:00
Pierre Ossman 76aa3d1256 Run NPM and snap builds on every push
To make sure these things still build. That means we need to make the
actual deploy parts optional.
2020-12-30 13:40:03 +01:00
Quentin Dreyer 6784bb312f chore: sync with W3C documentation
https://www.w3.org/TR/uievents-key/
2020-12-22 10:50:23 +01:00
Pierre Ossman 67ac9f9c0d Merge branch 'jp' of https://github.com/CendioOssman/noVNC 2020-12-10 10:23:10 +01:00
Pierre Ossman 4ae9d3e75a Remove some unnecessary use of done argument 2020-12-10 10:21:21 +01:00
Pierre Ossman 0cdf2962c0 Fake key releases for some Japanese IM keys
Windows behaves very oddly for some Japanese IM keys in that it won't
send a key release event when the key is released. In some keys it never
sends the event, and in some cases it sends the release as the key is
pressed the subsequent time.
2020-12-10 10:21:21 +01:00
Pierre Ossman 146258291a Send combination keysyms for some Japanese keys
Windows doesn't give us stable symbols for a bunch of Japanese IM keys,
instead alternating between two symbols. This state is not synchronised
with the IM running on the remote server so to have stable behaviour we
have to collapse these multiple symbols in to a single keysym.
2020-12-10 10:00:44 +01:00
Pierre Ossman 3e55d5d71a Fix typo for ZenkakuHankaku key 2020-12-10 09:43:08 +01:00
Pierre Ossman bd1bb2ed75 Use toggle keysym for Eisu key
This matches how the key behaves on a Linux system.
2020-12-10 09:42:19 +01:00
Pierre Ossman dc9da4a042 Merge branch 'noie' of https://github.com/CendioOssman/noVNC 2020-12-07 14:30:05 +01:00
Pierre Ossman 6a4c411976 Remove createEvent() fallbacks
We can now rely on proper constructors for our events.
2020-12-07 10:10:53 +01:00
Pierre Ossman 27496941a0 Remove createImageData() fallback
All our browsers should be new enough now that we can rely on the
ImageData constructor.
2020-12-07 10:10:53 +01:00
Pierre Ossman 5b5b747494 Remove many small, obsolete, old browser hacks
These are for browsers no longer supported anyway.
2020-12-07 10:10:53 +01:00
Pierre Ossman 6cd9bacf8b Use Fetch API for getting JSON data
We no longer need to support Internet Explorer so we can use a more
proper API here.
2020-12-04 16:43:44 +01:00
Pierre Ossman 273acf3e89 Remove unused injectParamIfMissing()
It should have been removed in 58fc267b2b
with the caller.
2020-12-04 16:43:04 +01:00
Pierre Ossman 60c7518f8c Update keycode mappings to latest version
This update fixes Korean layouts.
2020-11-16 13:44:27 +01:00
Pierre Ossman 90456dbeed Merge branches 'logs' and 'timeout' of https://github.com/jcpunk/noVNC 2020-11-10 13:21:15 +01:00
Pat Riehecky 5dbacc5e41 Make timeouts/heartbeats easy to setup 2020-11-09 13:25:29 -06:00
Pat Riehecky 18256baad0 Permit setting syslog on websockify 2020-10-29 13:47:07 -05:00
Pierre Ossman 1f7e1c7572 Remove keypress handling
We no longer support any browser that requires this legacy handling.
2020-10-15 18:53:51 +02:00
Pierre Ossman dccf6facdc Drop support for legacy Edge 2020-10-15 18:53:51 +02:00
Pierre Ossman 890cff921d Remove legacy conversion of modules
We no longer support Internet Explorer so we can now require that
browsers support modules.

Some conversion to commonjs still remains for nodejs.
2020-10-15 18:53:51 +02:00
Pierre Ossman c01eb5e74d Drop support for Internet Explorer 2020-10-15 18:53:51 +02:00
Pierre Ossman 499eee4d06 Include current websockify in noVNC snap
The one from the system is too old to support current versions of noVNC,
so we need to bundle our own.
2020-10-08 16:55:12 +02:00
Pierre Ossman c1281b136d Add workaround for Firefox PNG rounding bug 2020-09-28 12:24:56 +02:00
Pierre Ossman 20e4f1b3f8 Remove Firefox Alt workaround
The bug got fixed way back in Firefox 63, and it is also misbehaving
with modern Firefox as they no longer consider AltGr an Alt-key.
2020-09-28 11:12:16 +02:00
Pierre Ossman b91b1e8edc Handle empty rects in RAW decoder as well
It was overlooked in the previous commit because we couldn't feed
empty data messages through the test framework.
2020-09-07 12:58:52 +02:00
Samuel Mannehed 3037eb16f7 Ignore recording variables in our linter 2020-09-05 11:08:16 +02:00
Pierre Ossman 3762300399 Approximate comparison of JPEG result
The browsers have various rounding errors so we need to compare the
output data only approximately and not exactly.
2020-09-04 16:48:44 +02:00
Pierre Ossman 113fa27ebc Handle empty rects from the server
These are very pointless for the server to send, but not a violation of
the protocol so we need to be able to handle them. We've seen this
happen in real world scenarios a few times.
2020-09-04 16:16:44 +02:00
Pierre Ossman 0630352e19 Merge branch 'rgbx' of https://github.com/CendioOssman/noVNC 2020-09-04 13:40:23 +02:00
Samuel Mannehed 7ce1b071ec Fix call to _recvMessage()
Missed to rename this one in commit ea858bfa27
2020-09-04 10:40:42 +02:00
Samuel Mannehed ecdd075672 Fix names of recording variables
The name of these variables must match how they were set when the
recording was created.

Reverts part of 95632e413d
2020-09-04 10:40:37 +02:00
Pierre Ossman 6a19390baa Switch to RGBx pixel format
This is what the browser wants so it avoids having to spend time
converting everything. Unfortunately it usually means the server instead
needs to convert it for us, but we assume it has more power than we do.
2020-06-08 07:57:17 +02:00
Pierre Ossman f5b5767c98 Standardise on a single blit function
Keep everything simpler by always blitting in the same pixel format.
It's up to the decoders to convert if they need to.
2020-06-08 07:53:41 +02:00
Pierre Ossman 34f52a8f41 Fix bad BasicCompression check in Tight decoder 2020-06-08 07:53:41 +02:00
Pierre Ossman 18a68dfac1 Test correct handling of alpha
The forth byte of a pixel is undefined in most encodings, so make sure
the decoders don't leak that through as an alpha channel.
2020-06-08 07:53:16 +02:00
Pierre Ossman 15cfa13563 Add tests for the Tight decoders 2020-06-08 07:48:20 +02:00
Pierre Ossman 111225fa41 Split decoder tests to separate files 2020-06-08 07:46:42 +02:00
Pierre Ossman 224f95f997 Move tile handling to Hextile decoder
It is only used there so no need for it to be in the general
Display class.
2020-06-06 13:23:05 +02:00
76 changed files with 3105 additions and 125785 deletions
+5
View File
@@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Question or discussion
url: https://groups.google.com/forum/?fromgroups#!forum/novnc
about: Ask a question or start a discussion
+26 -19
View File
@@ -1,6 +1,8 @@
name: Publish
on:
push:
pull_request:
release:
types: [published]
@@ -14,36 +16,41 @@ jobs:
# Needs to be explicitly specified for auth to work
registry-url: 'https://registry.npmjs.org'
- run: npm install
- uses: actions/upload-artifact@v2
with:
name: npm
path: lib
- run: npm publish --access public
env:
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
if: ${{ !github.event.release.prerelease }}
if: ${{ github.event_name == 'release' && !github.event.release.prerelease }}
- run: npm publish --access public --tag beta
env:
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
if: ${{ github.event.release.prerelease }}
if: ${{ github.event_name == 'release' && github.event.release.prerelease }}
snap:
runs-on: ubuntu-latest
container: snapcore/snapcraft
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
- run: npm install
- run: ./utils/use_require.js --clean --as commonjs --with-app
- run: |
cp utils/launch.sh build/launch.sh
cp snap/local/svc_wrapper.sh build/svc_wrapper.sh
- run: |
VERSION=$(grep '"version"' package.json | cut -d '"' -f 4)
echo $VERSION
sed -i "s/@VERSION@/$VERSION/g" snap/snapcraft.yaml
- run: snapcraft
- run: |
mkdir .snapcraft
echo ${SNAPCRAFT_LOGIN} | base64 --decode --ignore-garbage > .snapcraft/snapcraft.cfg
env:
SNAPCRAFT_LOGIN: ${{secrets.SNAPCRAFT_LOGIN}}
- run: snapcraft push --release=stable *.snap
if: ${{ !github.event.release.prerelease }}
- run: snapcraft push --release=beta *.snap
if: ${{ github.event.release.prerelease }}
- uses: snapcore/action-build@v1
id: snapcraft
- uses: actions/upload-artifact@v2
with:
name: snap
path: ${{ steps.snapcraft.outputs.snap }}
- uses: snapcore/action-publish@v1
with:
store_login: ${{ secrets.SNAPCRAFT_LOGIN }}
snap: ${{ steps.build.outputs.snap }}
release: stable
if: ${{ github.event_name == 'release' && !github.event.release.prerelease }}
- uses: snapcore/action-publish@v1
with:
store_login: ${{ secrets.SNAPCRAFT_LOGIN }}
snap: ${{ steps.build.outputs.snap }}
release: beta
if: ${{ github.event_name == 'release' && github.event.release.prerelease }}
-2
View File
@@ -17,8 +17,6 @@ jobs:
browser: Safari
- os: windows-latest
browser: EdgeHeadless
- os: windows-latest
browser: IE
fail-fast: false
runs-on: ${{ matrix.os }}
steps:
-6
View File
@@ -42,12 +42,6 @@ licenses (all MPL 2.0 compatible):
vendor/pako/ : MIT
vendor/browser-es-module-loader/src/ : MIT
vendor/browser-es-module-loader/dist/ : Various BSD style licenses
vendor/promise.js : MIT
Any other files not mentioned above are typically marked with
a copyright/license header at the top of the file. The default noVNC
license is MPL-2.0.
+4 -4
View File
@@ -91,7 +91,7 @@ noVNC uses many modern web technologies so a formal requirement list is
not available. However these are the minimum versions we are currently
aware of:
* Chrome 49, Firefox 44, Safari 11, Opera 36, IE 11, Edge 12
* Chrome 64, Firefox 79, Safari 13.4, Opera 51, Edge 79
### Server Requirements
@@ -108,13 +108,13 @@ proxy.
### Quick Start
* Use the launch script to automatically download and start websockify, which
* Use the `novnc_proxy` script to automatically download and start websockify, which
includes a mini-webserver and the WebSockets proxy. The `--vnc` option is
used to specify the location of a running VNC server:
`./utils/launch.sh --vnc localhost:5901`
`./utils/novnc_proxy --vnc localhost:5901`
* Point your browser to the cut-and-paste URL that is output by the launch
* Point your browser to the cut-and-paste URL that is output by the `novnc_procy`
script. Hit the Connect button, enter a password if the VNC server has one
configured, and enjoy!
+8 -9
View File
@@ -6,21 +6,16 @@
"Must set host": "ホストを設定する必要があります",
"Connected (encrypted) to ": "接続しました (暗号化済み): ",
"Connected (unencrypted) to ": "接続しました (暗号化されていません): ",
"Something went wrong, connection is closed": "何かが問題で、接続が閉じられました",
"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": "パスワードが必要です",
"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": "追加キー",
@@ -55,6 +50,8 @@
"Local Scaling": "ローカルスケーリング",
"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": "キャンセル"
}
+72
View File
@@ -0,0 +1,72 @@
{
"Connecting...": "Conectando...",
"Disconnecting...": "Desconectando...",
"Reconnecting...": "Reconectando...",
"Internal error": "Erro interno",
"Must set host": "É necessário definir o host",
"Connected (encrypted) to ": "Conectado (com criptografia) a ",
"Connected (unencrypted) to ": "Conectado (sem criptografia) a ",
"Something went wrong, connection is closed": "Algo deu errado. A conexão foi encerrada.",
"Failed to connect to server": "Falha ao conectar-se ao servidor",
"Disconnected": "Desconectado",
"New connection has been rejected with reason: ": "A nova conexão foi rejeitada pelo motivo: ",
"New connection has been rejected": "A nova conexão foi rejeitada",
"Credentials are required": "Credenciais são obrigatórias",
"noVNC encountered an error:": "O noVNC encontrou um erro:",
"Hide/Show the control bar": "Esconder/mostrar a barra de controles",
"Drag": "Arrastar",
"Move/Drag Viewport": "Mover/arrastar a janela",
"Keyboard": "Teclado",
"Show Keyboard": "Mostrar teclado",
"Extra keys": "Teclas adicionais",
"Show Extra Keys": "Mostar teclas adicionais",
"Ctrl": "Ctrl",
"Toggle Ctrl": "Pressionar/soltar Ctrl",
"Alt": "Alt",
"Toggle Alt": "Pressionar/soltar Alt",
"Toggle Windows": "Pressionar/soltar Windows",
"Windows": "Windows",
"Send Tab": "Enviar Tab",
"Tab": "Tab",
"Esc": "Esc",
"Send Escape": "Enviar Esc",
"Ctrl+Alt+Del": "Ctrl+Alt+Del",
"Send Ctrl-Alt-Del": "Enviar Ctrl-Alt-Del",
"Shutdown/Reboot": "Desligar/reiniciar",
"Shutdown/Reboot...": "Desligar/reiniciar...",
"Power": "Ligar",
"Shutdown": "Desligar",
"Reboot": "Reiniciar",
"Reset": "Reiniciar (forçado)",
"Clipboard": "Área de transferência",
"Clear": "Limpar",
"Fullscreen": "Tela cheia",
"Settings": "Configurações",
"Shared Mode": "Modo compartilhado",
"View Only": "Apenas visualizar",
"Clip to Window": "Recortar à janela",
"Scaling Mode:": "Modo de dimensionamento:",
"None": "Nenhum",
"Local Scaling": "Local",
"Remote Resizing": "Remoto",
"Advanced": "Avançado",
"Quality:": "Qualidade:",
"Compression level:": "Nível de compressão:",
"Repeater ID:": "ID do repetidor:",
"WebSocket": "WebSocket",
"Encrypt": "Criptografar",
"Host:": "Host:",
"Port:": "Porta:",
"Path:": "Caminho:",
"Automatic Reconnect": "Reconexão automática",
"Reconnect Delay (ms):": "Atraso da reconexão (ms)",
"Show Dot when No Cursor": "Mostrar ponto quando não há cursor",
"Logging:": "Registros:",
"Version:": "Versão:",
"Disconnect": "Desconectar",
"Connect": "Conectar",
"Username:": "Nome de usuário:",
"Password:": "Senha:",
"Send Credentials": "Enviar credenciais",
"Cancel": "Cancelar"
}
+15 -8
View File
@@ -61,7 +61,13 @@ const UI = {
// Translate the DOM
l10n.translateDOM();
WebUtil.fetchJSON('./package.json')
fetch('./package.json')
.then((response) => {
if (!response.ok) {
throw Error("" + response.status + " " + response.statusText);
}
return response.json();
})
.then((packageInfo) => {
Array.from(document.getElementsByClassName('noVNC_version')).forEach(el => el.innerText = packageInfo.version);
})
@@ -755,11 +761,6 @@ const UI = {
}
}
} else {
/*Weird IE9 error leads to 'null' appearring
in textboxes instead of ''.*/
if (value === null) {
value = "";
}
ctrl.value = value;
}
},
@@ -1694,12 +1695,18 @@ const UI = {
};
// Set up translations
const LINGUAS = ["cs", "de", "el", "es", "ja", "ko", "nl", "pl", "ru", "sv", "tr", "zh_CN", "zh_TW"];
const LINGUAS = ["cs", "de", "el", "es", "fr", "ja", "ko", "nl", "pl", "pt_BR", "ru", "sv", "tr", "zh_CN", "zh_TW"];
l10n.setup(LINGUAS);
if (l10n.language === "en" || l10n.dictionary !== undefined) {
UI.prime();
} else {
WebUtil.fetchJSON('app/locale/' + l10n.language + '.json')
fetch('app/locale/' + l10n.language + '.json')
.then((response) => {
if (!response.ok) {
throw Error("" + response.status + " " + response.statusText);
}
return response.json();
})
.then((translations) => { l10n.dictionary = translations; })
.catch(err => Log.Error("Failed to load translations: " + err))
.then(UI.prime);
+10 -63
View File
@@ -20,10 +20,19 @@ export function initLogging(level) {
}
// Read a query string variable
// A URL with a query parameter can look like this (But will most probably get logged on the http server):
// https://www.example.com?myqueryparam=myvalue
//
// For privacy (Using a hastag #, the parameters will not be sent to the server)
// the url can be requested in the following way:
// https://www.example.com#myqueryparam=myvalue&password=secreatvalue
//
// Even Mixing public and non public parameters will work:
// https://www.example.com?nonsecretparam=example.com#password=secreatvalue
export function getQueryVar(name, defVal) {
"use strict";
const re = new RegExp('.*[?&]' + name + '=([^&#]*)'),
match = document.location.href.match(re);
match = ''.concat(document.location.href, window.location.hash).match(re);
if (typeof defVal === 'undefined') { defVal = null; }
if (match) {
@@ -175,65 +184,3 @@ export function eraseSetting(name) {
localStorage.removeItem(name);
}
}
export function injectParamIfMissing(path, param, value) {
// force pretend that we're dealing with a relative path
// (assume that we wanted an extra if we pass one in)
path = "/" + path;
const elem = document.createElement('a');
elem.href = path;
const paramEq = encodeURIComponent(param) + "=";
let query;
if (elem.search) {
query = elem.search.slice(1).split('&');
} else {
query = [];
}
if (!query.some(v => v.startsWith(paramEq))) {
query.push(paramEq + encodeURIComponent(value));
elem.search = "?" + query.join("&");
}
// some browsers (e.g. IE11) may occasionally omit the leading slash
// in the elem.pathname string. Handle that case gracefully.
if (elem.pathname.charAt(0) == "/") {
return elem.pathname.slice(1) + elem.search + elem.hash;
}
return elem.pathname + elem.search + elem.hash;
}
// sadly, we can't use the Fetch API until we decide to drop
// IE11 support or polyfill promises and fetch in IE11.
// resolve will receive an object on success, while reject
// will receive either an event or an error on failure.
export function fetchJSON(path) {
return new Promise((resolve, reject) => {
// NB: IE11 doesn't support JSON as a responseType
const req = new XMLHttpRequest();
req.open('GET', path);
req.onload = () => {
if (req.status === 200) {
let resObj;
try {
resObj = JSON.parse(req.responseText);
} catch (err) {
reject(err);
}
resolve(resObj);
} else {
reject(new Error("XHR got non-200 status while trying to load '" + path + "': " + req.status));
}
};
req.onerror = evt => reject(new Error("XHR encountered an error while trying to load '" + path + "': " + evt.message));
req.ontimeout = evt => reject(new Error("XHR timed out while trying to load '" + path + "'"));
req.send();
});
}
+5
View File
@@ -15,6 +15,11 @@ export default class CopyRectDecoder {
let deltaX = sock.rQshift16();
let deltaY = sock.rQshift16();
if ((width === 0) || (height === 0)) {
return true;
}
display.copyImage(deltaX, deltaY, x, y, width, height);
return true;
+57 -3
View File
@@ -13,6 +13,7 @@ export default class HextileDecoder {
constructor() {
this._tiles = 0;
this._lastsubencoding = 0;
this._tileBuffer = new Uint8Array(16 * 16 * 4);
}
decodeRect(x, y, width, height, sock, display, depth) {
@@ -87,6 +88,11 @@ export default class HextileDecoder {
display.fillRect(tx, ty, tw, th, this._background);
}
} else if (subencoding & 0x01) { // Raw
let pixels = tw * th;
// Max sure the image is fully opaque
for (let i = 0;i < pixels;i++) {
rQ[rQi + i * 4 + 3] = 255;
}
display.blitImage(tx, ty, tw, th, rQ, rQi);
rQi += bytes - 1;
} else {
@@ -99,7 +105,7 @@ export default class HextileDecoder {
rQi += 4;
}
display.startTile(tx, ty, tw, th, this._background);
this._startTile(tx, ty, tw, th, this._background);
if (subencoding & 0x08) { // AnySubrects
let subrects = rQ[rQi];
rQi++;
@@ -122,10 +128,10 @@ export default class HextileDecoder {
const sw = (wh >> 4) + 1;
const sh = (wh & 0x0f) + 1;
display.subTile(sx, sy, sw, sh, color);
this._subTile(sx, sy, sw, sh, color);
}
}
display.finishTile();
this._finishTile(display);
}
sock.rQi = rQi;
this._lastsubencoding = subencoding;
@@ -134,4 +140,52 @@ export default class HextileDecoder {
return true;
}
// start updating a tile
_startTile(x, y, width, height, color) {
this._tileX = x;
this._tileY = y;
this._tileW = width;
this._tileH = height;
const red = color[0];
const green = color[1];
const blue = color[2];
const data = this._tileBuffer;
for (let i = 0; i < width * height * 4; i += 4) {
data[i] = red;
data[i + 1] = green;
data[i + 2] = blue;
data[i + 3] = 255;
}
}
// update sub-rectangle of the current tile
_subTile(x, y, w, h, color) {
const red = color[0];
const green = color[1];
const blue = color[2];
const xend = x + w;
const yend = y + h;
const data = this._tileBuffer;
const width = this._tileW;
for (let j = y; j < yend; j++) {
for (let i = x; i < xend; i++) {
const p = (i + (j * width)) * 4;
data[p] = red;
data[p + 1] = green;
data[p + 2] = blue;
data[p + 3] = 255;
}
}
}
// draw the current tile to the screen
_finishTile(display) {
display.blitImage(this._tileX, this._tileY,
this._tileW, this._tileH,
this._tileBuffer, 0);
}
}
+12 -2
View File
@@ -13,6 +13,10 @@ export default class RawDecoder {
}
decodeRect(x, y, width, height, sock, display, depth) {
if ((width === 0) || (height === 0)) {
return true;
}
if (this._lines === 0) {
this._lines = height;
}
@@ -27,23 +31,29 @@ export default class RawDecoder {
const curY = y + (height - this._lines);
const currHeight = Math.min(this._lines,
Math.floor(sock.rQlen / bytesPerLine));
const pixels = width * currHeight;
let data = sock.rQ;
let index = sock.rQi;
// Convert data if needed
if (depth == 8) {
const pixels = width * currHeight;
const newdata = new Uint8Array(pixels * 4);
for (let i = 0; i < pixels; i++) {
newdata[i * 4 + 0] = ((data[index + i] >> 0) & 0x3) * 255 / 3;
newdata[i * 4 + 1] = ((data[index + i] >> 2) & 0x3) * 255 / 3;
newdata[i * 4 + 2] = ((data[index + i] >> 4) & 0x3) * 255 / 3;
newdata[i * 4 + 4] = 0;
newdata[i * 4 + 3] = 255;
}
data = newdata;
index = 0;
}
// Max sure the image is fully opaque
for (let i = 0; i < pixels; i++) {
data[i * 4 + 3] = 255;
}
display.blitImage(x, curY, width, currHeight, data, index);
sock.rQskipBytes(currHeight * bytesPerLine);
this._lines -= currHeight;
+24 -8
View File
@@ -56,7 +56,7 @@ export default class TightDecoder {
} else if (this._ctl === 0x0A) {
ret = this._pngRect(x, y, width, height,
sock, display, depth);
} else if ((this._ctl & 0x80) == 0) {
} else if ((this._ctl & 0x08) == 0) {
ret = this._basicRect(this._ctl, x, y, width, height,
sock, display, depth);
} else {
@@ -80,7 +80,7 @@ export default class TightDecoder {
const rQ = sock.rQ;
display.fillRect(x, y, width, height,
[rQ[rQi + 2], rQ[rQi + 1], rQ[rQi]], false);
[rQ[rQi], rQ[rQi + 1], rQ[rQi + 2]], false);
sock.rQskipBytes(3);
return true;
@@ -148,6 +148,10 @@ export default class TightDecoder {
const uncompressedSize = width * height * 3;
let data;
if (uncompressedSize === 0) {
return true;
}
if (uncompressedSize < 12) {
if (sock.rQwait("TIGHT", uncompressedSize)) {
return false;
@@ -165,7 +169,15 @@ export default class TightDecoder {
this._zlibs[streamId].setInput(null);
}
display.blitRgbImage(x, y, width, height, data, 0, false);
let rgbx = new Uint8Array(width * height * 4);
for (let i = 0, j = 0; i < width * height * 4; i += 4, j += 3) {
rgbx[i] = data[j];
rgbx[i + 1] = data[j + 1];
rgbx[i + 2] = data[j + 2];
rgbx[i + 3] = 255; // Alpha
}
display.blitImage(x, y, width, height, rgbx, 0, false);
return true;
}
@@ -195,6 +207,10 @@ export default class TightDecoder {
let data;
if (uncompressedSize === 0) {
return true;
}
if (uncompressedSize < 12) {
if (sock.rQwait("TIGHT", uncompressedSize)) {
return false;
@@ -237,7 +253,7 @@ export default class TightDecoder {
for (let b = 7; b >= 0; b--) {
dp = (y * width + x * 8 + 7 - b) * 4;
sp = (data[y * w + x] >> b & 1) * 3;
dest[dp] = palette[sp];
dest[dp] = palette[sp];
dest[dp + 1] = palette[sp + 1];
dest[dp + 2] = palette[sp + 2];
dest[dp + 3] = 255;
@@ -247,14 +263,14 @@ export default class TightDecoder {
for (let b = 7; b >= 8 - width % 8; b--) {
dp = (y * width + x * 8 + 7 - b) * 4;
sp = (data[y * w + x] >> b & 1) * 3;
dest[dp] = palette[sp];
dest[dp] = palette[sp];
dest[dp + 1] = palette[sp + 1];
dest[dp + 2] = palette[sp + 2];
dest[dp + 3] = 255;
}
}
display.blitRgbxImage(x, y, width, height, dest, 0, false);
display.blitImage(x, y, width, height, dest, 0, false);
}
_paletteRect(x, y, width, height, data, palette, display) {
@@ -263,13 +279,13 @@ export default class TightDecoder {
const total = width * height * 4;
for (let i = 0, j = 0; i < total; i += 4, j++) {
const sp = data[j] * 3;
dest[i] = palette[sp];
dest[i] = palette[sp];
dest[i + 1] = palette[sp + 1];
dest[i + 2] = palette[sp + 2];
dest[i + 3] = 255;
}
display.blitRgbxImage(x, y, width, height, dest, 0, false);
display.blitImage(x, y, width, height, dest, 0, false);
}
_gradientFilter(streamId, x, y, width, height, sock, display, depth) {
+9 -151
View File
@@ -8,7 +8,6 @@
import * as Log from './util/logging.js';
import Base64 from "./base64.js";
import { supportsImageMetadata } from './util/browser.js';
import { toSigned32bit } from './util/int.js';
export default class Display {
@@ -23,10 +22,6 @@ export default class Display {
this._fbHeight = 0;
this._prevDrawStyle = "";
this._tile = null;
this._tile16x16 = null;
this._tileX = 0;
this._tileY = 0;
Log.Debug(">> Display.constructor");
@@ -60,12 +55,6 @@ export default class Display {
Log.Debug("User Agent: " + navigator.userAgent);
// Check canvas features
if (!('createImageData' in this._drawCtx)) {
throw new Error("Canvas does not support createImageData");
}
this._tile16x16 = this._drawCtx.createImageData(16, 16);
Log.Debug("<< Display.constructor");
// ===== PROPERTIES =====
@@ -378,57 +367,6 @@ export default class Display {
});
}
// start updating a tile
startTile(x, y, width, height, color) {
this._tileX = x;
this._tileY = y;
if (width === 16 && height === 16) {
this._tile = this._tile16x16;
} else {
this._tile = this._drawCtx.createImageData(width, height);
}
const red = color[2];
const green = color[1];
const blue = color[0];
const data = this._tile.data;
for (let i = 0; i < width * height * 4; i += 4) {
data[i] = red;
data[i + 1] = green;
data[i + 2] = blue;
data[i + 3] = 255;
}
}
// update sub-rectangle of the current tile
subTile(x, y, w, h, color) {
const red = color[2];
const green = color[1];
const blue = color[0];
const xend = x + w;
const yend = y + h;
const data = this._tile.data;
const width = this._tile.width;
for (let j = y; j < yend; j++) {
for (let i = x; i < xend; i++) {
const p = (i + (j * width)) * 4;
data[p] = red;
data[p + 1] = green;
data[p + 2] = blue;
data[p + 3] = 255;
}
}
}
// draw the current tile to the screen
finishTile() {
this._drawCtx.putImageData(this._tile, this._tileX, this._tileY);
this._damage(this._tileX, this._tileY,
this._tile.width, this._tile.height);
}
blitImage(x, y, width, height, arr, offset, fromQueue) {
if (this._renderQ.length !== 0 && !fromQueue) {
// NB(directxman12): it's technically more performant here to use preallocated arrays,
@@ -445,47 +383,13 @@ export default class Display {
'height': height,
});
} else {
this._bgrxImageData(x, y, width, height, arr, offset);
}
}
blitRgbImage(x, y, width, height, arr, offset, fromQueue) {
if (this._renderQ.length !== 0 && !fromQueue) {
// NB(directxman12): it's technically more performant here to use preallocated arrays,
// but it's a lot of extra work for not a lot of payoff -- if we're using the render queue,
// this probably isn't getting called *nearly* as much
const newArr = new Uint8Array(width * height * 3);
newArr.set(new Uint8Array(arr.buffer, 0, newArr.length));
this._renderQPush({
'type': 'blitRgb',
'data': newArr,
'x': x,
'y': y,
'width': width,
'height': height,
});
} else {
this._rgbImageData(x, y, width, height, arr, offset);
}
}
blitRgbxImage(x, y, width, height, arr, offset, fromQueue) {
if (this._renderQ.length !== 0 && !fromQueue) {
// NB(directxman12): it's technically more performant here to use preallocated arrays,
// but it's a lot of extra work for not a lot of payoff -- if we're using the render queue,
// this probably isn't getting called *nearly* as much
const newArr = new Uint8Array(width * height * 4);
newArr.set(new Uint8Array(arr.buffer, 0, newArr.length));
this._renderQPush({
'type': 'blitRgbx',
'data': newArr,
'x': x,
'y': y,
'width': width,
'height': height,
});
} else {
this._rgbxImageData(x, y, width, height, arr, offset);
// NB(directxman12): arr must be an Type Array view
let data = new Uint8ClampedArray(arr.buffer,
arr.byteOffset + offset,
width * height * 4);
let img = new ImageData(data, width, height);
this._drawCtx.putImageData(img, x, y);
this._damage(x, y, width, height);
}
}
@@ -537,52 +441,13 @@ export default class Display {
}
_setFillColor(color) {
const newStyle = 'rgb(' + color[2] + ',' + color[1] + ',' + color[0] + ')';
const newStyle = 'rgb(' + color[0] + ',' + color[1] + ',' + color[2] + ')';
if (newStyle !== this._prevDrawStyle) {
this._drawCtx.fillStyle = newStyle;
this._prevDrawStyle = newStyle;
}
}
_rgbImageData(x, y, width, height, arr, offset) {
const img = this._drawCtx.createImageData(width, height);
const data = img.data;
for (let i = 0, j = offset; i < width * height * 4; i += 4, j += 3) {
data[i] = arr[j];
data[i + 1] = arr[j + 1];
data[i + 2] = arr[j + 2];
data[i + 3] = 255; // Alpha
}
this._drawCtx.putImageData(img, x, y);
this._damage(x, y, img.width, img.height);
}
_bgrxImageData(x, y, width, height, arr, offset) {
const img = this._drawCtx.createImageData(width, height);
const data = img.data;
for (let i = 0, j = offset; i < width * height * 4; i += 4, j += 4) {
data[i] = arr[j + 2];
data[i + 1] = arr[j + 1];
data[i + 2] = arr[j];
data[i + 3] = 255; // Alpha
}
this._drawCtx.putImageData(img, x, y);
this._damage(x, y, img.width, img.height);
}
_rgbxImageData(x, y, width, height, arr, offset) {
// NB(directxman12): arr must be an Type Array view
let img;
if (supportsImageMetadata) {
img = new ImageData(new Uint8ClampedArray(arr.buffer, arr.byteOffset, width * height * 4), width, height);
} else {
img = this._drawCtx.createImageData(width, height);
img.data.set(new Uint8ClampedArray(arr.buffer, arr.byteOffset, width * height * 4));
}
this._drawCtx.putImageData(img, x, y);
this._damage(x, y, img.width, img.height);
}
_renderQPush(action) {
this._renderQ.push(action);
if (this._renderQ.length === 1) {
@@ -616,15 +481,8 @@ export default class Display {
case 'blit':
this.blitImage(a.x, a.y, a.width, a.height, a.data, 0, true);
break;
case 'blitRgb':
this.blitRgbImage(a.x, a.y, a.width, a.height, a.data, 0, true);
break;
case 'blitRgbx':
this.blitRgbxImage(a.x, a.y, a.width, a.height, a.data, 0, true);
break;
case 'img':
/* IE tends to set "complete" prematurely, so check dimensions */
if (a.img.complete && (a.img.width !== 0) && (a.img.height !== 0)) {
if (a.img.complete) {
if (a.img.width !== a.width || a.img.height !== a.height) {
Log.Error("Decoded image has incorrect dimensions. Got " +
a.img.width + "x" + a.img.height + ". Expected " +
+25 -21
View File
@@ -35,7 +35,7 @@ function addNumpad(key, standard, numpad) {
DOMKeyTable[key] = [standard, standard, standard, numpad];
}
// 2.2. Modifier Keys
// 3.2. Modifier Keys
addLeftRight("Alt", KeyTable.XK_Alt_L, KeyTable.XK_Alt_R);
addStandard("AltGraph", KeyTable.XK_ISO_Level3_Shift);
@@ -49,25 +49,27 @@ addStandard("ScrollLock", KeyTable.XK_Scroll_Lock);
addLeftRight("Shift", KeyTable.XK_Shift_L, KeyTable.XK_Shift_R);
// - Symbol
// - SymbolLock
// - Hyper
// - Super
// 2.3. Whitespace Keys
// 3.3. Whitespace Keys
addNumpad("Enter", KeyTable.XK_Return, KeyTable.XK_KP_Enter);
addStandard("Tab", KeyTable.XK_Tab);
addNumpad(" ", KeyTable.XK_space, KeyTable.XK_KP_Space);
// 2.4. Navigation Keys
// 3.4. Navigation Keys
addNumpad("ArrowDown", KeyTable.XK_Down, KeyTable.XK_KP_Down);
addNumpad("ArrowUp", KeyTable.XK_Up, KeyTable.XK_KP_Up);
addNumpad("ArrowLeft", KeyTable.XK_Left, KeyTable.XK_KP_Left);
addNumpad("ArrowRight", KeyTable.XK_Right, KeyTable.XK_KP_Right);
addNumpad("ArrowUp", KeyTable.XK_Up, KeyTable.XK_KP_Up);
addNumpad("End", KeyTable.XK_End, KeyTable.XK_KP_End);
addNumpad("Home", KeyTable.XK_Home, KeyTable.XK_KP_Home);
addNumpad("PageDown", KeyTable.XK_Next, KeyTable.XK_KP_Next);
addNumpad("PageUp", KeyTable.XK_Prior, KeyTable.XK_KP_Prior);
// 2.5. Editing Keys
// 3.5. Editing Keys
addStandard("Backspace", KeyTable.XK_BackSpace);
// Browsers send "Clear" for the numpad 5 without NumLock because
@@ -85,7 +87,7 @@ addStandard("Paste", KeyTable.XF86XK_Paste);
addStandard("Redo", KeyTable.XK_Redo);
addStandard("Undo", KeyTable.XK_Undo);
// 2.6. UI Keys
// 3.6. UI Keys
// - Accept
// - Again (could just be XK_Redo)
@@ -103,7 +105,7 @@ addStandard("Select", KeyTable.XK_Select);
addStandard("ZoomIn", KeyTable.XF86XK_ZoomIn);
addStandard("ZoomOut", KeyTable.XF86XK_ZoomOut);
// 2.7. Device Keys
// 3.7. Device Keys
addStandard("BrightnessDown", KeyTable.XF86XK_MonBrightnessDown);
addStandard("BrightnessUp", KeyTable.XF86XK_MonBrightnessUp);
@@ -116,10 +118,10 @@ addStandard("Hibernate", KeyTable.XF86XK_Hibernate);
addStandard("Standby", KeyTable.XF86XK_Standby);
addStandard("WakeUp", KeyTable.XF86XK_WakeUp);
// 2.8. IME and Composition Keys
// 3.8. IME and Composition Keys
addStandard("AllCandidates", KeyTable.XK_MultipleCandidate);
addStandard("Alphanumeric", KeyTable.XK_Eisu_Shift); // could also be _Eisu_Toggle
addStandard("Alphanumeric", KeyTable.XK_Eisu_toggle);
addStandard("CodeInput", KeyTable.XK_Codeinput);
addStandard("Compose", KeyTable.XK_Multi_key);
addStandard("Convert", KeyTable.XK_Henkan);
@@ -137,7 +139,7 @@ addStandard("PreviousCandidate", KeyTable.XK_PreviousCandidate);
addStandard("SingleCandidate", KeyTable.XK_SingleCandidate);
addStandard("HangulMode", KeyTable.XK_Hangul);
addStandard("HanjaMode", KeyTable.XK_Hangul_Hanja);
addStandard("JunjuaMode", KeyTable.XK_Hangul_Jeonja);
addStandard("JunjaMode", KeyTable.XK_Hangul_Jeonja);
addStandard("Eisu", KeyTable.XK_Eisu_toggle);
addStandard("Hankaku", KeyTable.XK_Hankaku);
addStandard("Hiragana", KeyTable.XK_Hiragana);
@@ -147,9 +149,9 @@ addStandard("KanjiMode", KeyTable.XK_Kanji);
addStandard("Katakana", KeyTable.XK_Katakana);
addStandard("Romaji", KeyTable.XK_Romaji);
addStandard("Zenkaku", KeyTable.XK_Zenkaku);
addStandard("ZenkakuHanaku", KeyTable.XK_Zenkaku_Hankaku);
addStandard("ZenkakuHankaku", KeyTable.XK_Zenkaku_Hankaku);
// 2.9. General-Purpose Function Keys
// 3.9. General-Purpose Function Keys
addStandard("F1", KeyTable.XK_F1);
addStandard("F2", KeyTable.XK_F2);
@@ -188,7 +190,7 @@ addStandard("F34", KeyTable.XK_F34);
addStandard("F35", KeyTable.XK_F35);
// - Soft1...
// 2.10. Multimedia Keys
// 3.10. Multimedia Keys
// - ChannelDown
// - ChannelUp
@@ -200,6 +202,7 @@ addStandard("MailSend", KeyTable.XF86XK_Send);
addStandard("MediaFastForward", KeyTable.XF86XK_AudioForward);
addStandard("MediaPause", KeyTable.XF86XK_AudioPause);
addStandard("MediaPlay", KeyTable.XF86XK_AudioPlay);
// - MediaPlayPause
addStandard("MediaRecord", KeyTable.XF86XK_AudioRecord);
addStandard("MediaRewind", KeyTable.XF86XK_AudioRewind);
addStandard("MediaStop", KeyTable.XF86XK_AudioStop);
@@ -211,12 +214,12 @@ addStandard("Print", KeyTable.XK_Print);
addStandard("Save", KeyTable.XF86XK_Save);
addStandard("SpellCheck", KeyTable.XF86XK_Spell);
// 2.11. Multimedia Numpad Keys
// 3.11. Multimedia Numpad Keys
// - Key11
// - Key12
// 2.12. Audio Keys
// 3.12. Audio Keys
// - AudioBalanceLeft
// - AudioBalanceRight
@@ -236,16 +239,17 @@ addStandard("AudioVolumeMute", KeyTable.XF86XK_AudioMute);
// - MicrophoneVolumeUp
addStandard("MicrophoneVolumeMute", KeyTable.XF86XK_AudioMicMute);
// 2.13. Speech Keys
// 3.13. Speech Keys
// - SpeechCorrectionList
// - SpeechInputToggle
// 2.14. Application Keys
// 3.14. Application Keys
addStandard("LaunchApplication1", KeyTable.XF86XK_MyComputer);
addStandard("LaunchApplication2", KeyTable.XF86XK_Calculator);
addStandard("LaunchCalendar", KeyTable.XF86XK_Calendar);
// - LaunchContacts
addStandard("LaunchMail", KeyTable.XF86XK_Mail);
addStandard("LaunchMediaPlayer", KeyTable.XF86XK_AudioMedia);
addStandard("LaunchMusicPlayer", KeyTable.XF86XK_Music);
@@ -256,7 +260,7 @@ addStandard("LaunchWebBrowser", KeyTable.XF86XK_WWW);
addStandard("LaunchWebCam", KeyTable.XF86XK_WebCam);
addStandard("LaunchWordProcessor", KeyTable.XF86XK_Word);
// 2.15. Browser Keys
// 3.15. Browser Keys
addStandard("BrowserBack", KeyTable.XF86XK_Back);
addStandard("BrowserFavorites", KeyTable.XF86XK_Favorites);
@@ -266,15 +270,15 @@ addStandard("BrowserRefresh", KeyTable.XF86XK_Refresh);
addStandard("BrowserSearch", KeyTable.XF86XK_Search);
addStandard("BrowserStop", KeyTable.XF86XK_Stop);
// 2.16. Mobile Phone Keys
// 3.16. Mobile Phone Keys
// - A whole bunch...
// 2.17. TV Keys
// 3.17. TV Keys
// - A whole bunch...
// 2.18. Media Controller Keys
// 3.18. Media Controller Keys
// - A whole bunch...
addStandard("Dimmer", KeyTable.XF86XK_BrightnessAdjust);
+12 -127
View File
@@ -20,16 +20,13 @@ export default class Keyboard {
this._keyDownList = {}; // List of depressed keys
// (even if they are happy)
this._pendingKey = null; // Key waiting for keypress
this._altGrArmed = false; // Windows AltGr detection
// keep these here so we can refer to them later
this._eventHandlers = {
'keyup': this._handleKeyUp.bind(this),
'keydown': this._handleKeyDown.bind(this),
'keypress': this._handleKeyPress.bind(this),
'blur': this._allKeysUp.bind(this),
'checkalt': this._checkAlt.bind(this),
};
// ===== EVENT HANDLERS =====
@@ -62,9 +59,7 @@ export default class Keyboard {
}
// Unstable, but we don't have anything else to go on
// (don't use it for 'keypress' events thought since
// WebKit sets it to the same as charCode)
if (e.keyCode && (e.type !== 'keypress')) {
if (e.keyCode) {
// 229 is used for composition events
if (e.keyCode !== 229) {
return 'Platform' + e.keyCode;
@@ -169,20 +164,20 @@ export default class Keyboard {
return;
}
// If this is a legacy browser then we'll need to wait for
// a keypress event as well
// (IE and Edge has a broken KeyboardEvent.key, so we can't
// just check for the presence of that field)
if (!keysym && (!e.key || browser.isIE() || browser.isEdge())) {
this._pendingKey = code;
// However we might not get a keypress event if the key
// is non-printable, which needs some special fallback
// handling
setTimeout(this._handleKeyPressTimeout.bind(this), 10, e);
// Windows doesn't send proper key releases for a bunch of
// Japanese IM keys so we have to fake the release right away
const jpBadKeys = [ KeyTable.XK_Zenkaku_Hankaku,
KeyTable.XK_Eisu_toggle,
KeyTable.XK_Katakana,
KeyTable.XK_Hiragana,
KeyTable.XK_Romaji ];
if (browser.isWindows() && jpBadKeys.includes(keysym)) {
this._sendKeyEvent(keysym, code, true);
this._sendKeyEvent(keysym, code, false);
stopEvent(e);
return;
}
this._pendingKey = null;
stopEvent(e);
// Possible start of AltGr sequence? (see above)
@@ -197,69 +192,6 @@ export default class Keyboard {
this._sendKeyEvent(keysym, code, true);
}
// Legacy event for browsers without code/key
_handleKeyPress(e) {
stopEvent(e);
// Are we expecting a keypress?
if (this._pendingKey === null) {
return;
}
let code = this._getKeyCode(e);
const keysym = KeyboardUtil.getKeysym(e);
// The key we were waiting for?
if ((code !== 'Unidentified') && (code != this._pendingKey)) {
return;
}
code = this._pendingKey;
this._pendingKey = null;
if (!keysym) {
Log.Info('keypress with no keysym:', e);
return;
}
this._sendKeyEvent(keysym, code, true);
}
_handleKeyPressTimeout(e) {
// Did someone manage to sort out the key already?
if (this._pendingKey === null) {
return;
}
let keysym;
const code = this._pendingKey;
this._pendingKey = null;
// We have no way of knowing the proper keysym with the
// information given, but the following are true for most
// layouts
if ((e.keyCode >= 0x30) && (e.keyCode <= 0x39)) {
// Digit
keysym = e.keyCode;
} else if ((e.keyCode >= 0x41) && (e.keyCode <= 0x5a)) {
// Character (A-Z)
let char = String.fromCharCode(e.keyCode);
// A feeble attempt at the correct case
if (e.shiftKey) {
char = char.toUpperCase();
} else {
char = char.toLowerCase();
}
keysym = char.charCodeAt();
} else {
// Unknown, give up
keysym = 0;
}
this._sendKeyEvent(keysym, code, true);
}
_handleKeyUp(e) {
stopEvent(e);
@@ -312,30 +244,6 @@ export default class Keyboard {
Log.Debug("<< Keyboard.allKeysUp");
}
// Alt workaround for Firefox on Windows, see below
_checkAlt(e) {
if (e.skipCheckAlt) {
return;
}
if (e.altKey) {
return;
}
const target = this._target;
const downList = this._keyDownList;
['AltLeft', 'AltRight'].forEach((code) => {
if (!(code in downList)) {
return;
}
const event = new KeyboardEvent('keyup',
{ key: downList[code],
code: code });
event.skipCheckAlt = true;
target.dispatchEvent(event);
});
}
// ===== PUBLIC METHODS =====
grab() {
@@ -343,41 +251,18 @@ export default class Keyboard {
this._target.addEventListener('keydown', this._eventHandlers.keydown);
this._target.addEventListener('keyup', this._eventHandlers.keyup);
this._target.addEventListener('keypress', this._eventHandlers.keypress);
// Release (key up) if window loses focus
window.addEventListener('blur', this._eventHandlers.blur);
// Firefox on Windows has broken handling of Alt, so we need to
// poll as best we can for releases (still doesn't prevent the
// menu from popping up though as we can't call
// preventDefault())
if (browser.isWindows() && browser.isFirefox()) {
const handler = this._eventHandlers.checkalt;
['mousedown', 'mouseup', 'mousemove', 'wheel',
'touchstart', 'touchend', 'touchmove',
'keydown', 'keyup'].forEach(type =>
document.addEventListener(type, handler,
{ capture: true,
passive: true }));
}
//Log.Debug("<< Keyboard.grab");
}
ungrab() {
//Log.Debug(">> Keyboard.ungrab");
if (browser.isWindows() && browser.isFirefox()) {
const handler = this._eventHandlers.checkalt;
['mousedown', 'mouseup', 'mousemove', 'wheel',
'touchstart', 'touchend', 'touchmove',
'keydown', 'keyup'].forEach(type => document.removeEventListener(type, handler));
}
this._target.removeEventListener('keydown', this._eventHandlers.keydown);
this._target.removeEventListener('keyup', this._eventHandlers.keyup);
this._target.removeEventListener('keypress', this._eventHandlers.keypress);
window.removeEventListener('blur', this._eventHandlers.blur);
// Release (key up) all keys that are in a down state
+18 -35
View File
@@ -22,9 +22,8 @@ export function getKeycode(evt) {
}
// The de-facto standard is to use Windows Virtual-Key codes
// in the 'keyCode' field for non-printable characters. However
// Webkit sets it to the same as charCode in 'keypress' events.
if ((evt.type !== 'keypress') && (evt.keyCode in vkeys)) {
// in the 'keyCode' field for non-printable characters
if (evt.keyCode in vkeys) {
let code = vkeys[evt.keyCode];
// macOS has messed up this code for some reason
@@ -69,26 +68,6 @@ export function getKeycode(evt) {
export function getKey(evt) {
// Are we getting a proper key value?
if (evt.key !== undefined) {
// IE and Edge use some ancient version of the spec
// https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/8860571/
switch (evt.key) {
case 'Spacebar': return ' ';
case 'Esc': return 'Escape';
case 'Scroll': return 'ScrollLock';
case 'Win': return 'Meta';
case 'Apps': return 'ContextMenu';
case 'Up': return 'ArrowUp';
case 'Left': return 'ArrowLeft';
case 'Right': return 'ArrowRight';
case 'Down': return 'ArrowDown';
case 'Del': return 'Delete';
case 'Divide': return '/';
case 'Multiply': return '*';
case 'Subtract': return '-';
case 'Add': return '+';
case 'Decimal': return evt.char;
}
// Mozilla isn't fully in sync with the spec yet
switch (evt.key) {
case 'OS': return 'Meta';
@@ -110,18 +89,7 @@ export function getKey(evt) {
return 'Delete';
}
// IE and Edge need special handling, but for everyone else we
// can trust the value provided
if (!browser.isIE() && !browser.isEdge()) {
return evt.key;
}
// IE and Edge have broken handling of AltGraph so we can only
// trust them for non-printable characters (and unfortunately
// they also specify 'Unidentified' for some problem keys)
if ((evt.key.length !== 1) && (evt.key !== 'Unidentified')) {
return evt.key;
}
return evt.key;
}
// Try to deduce it based on the physical key
@@ -189,6 +157,21 @@ export function getKeysym(evt) {
}
}
// Windows sends alternating symbols for some keys when using a
// Japanese layout. We have no way of synchronising with the IM
// running on the remote system, so we send some combined keysym
// instead and hope for the best.
if (browser.isWindows()) {
switch (key) {
case 'Zenkaku':
case 'Hankaku':
return KeyTable.XK_Zenkaku_Hankaku;
case 'Romaji':
case 'KanaMode':
return KeyTable.XK_Romaji;
}
}
return DOMKeyTable[key][location];
}
-1
View File
@@ -13,7 +13,6 @@ export default {
0x08: 'Backspace',
0x09: 'Tab',
0x0a: 'NumpadClear',
0x0c: 'Numpad5', // IE11 sends evt.keyCode: 12 when numlock is off
0x0d: 'Enter',
0x10: 'ShiftLeft',
0x11: 'ControlLeft',
+5 -3
View File
@@ -1,8 +1,8 @@
/*
* This file is auto-generated from keymaps.csv on 2017-05-31 16:20
* Database checksum sha256(92fd165507f2a3b8c5b3fa56e425d45788dbcb98cf067a307527d91ce22cab94)
* This file is auto-generated from keymaps.csv
* Database checksum sha256(76d68c10e97d37fe2ea459e210125ae41796253fb217e900bf2983ade13a7920)
* To re-generate, run:
* keymap-gen --lang=js code-map keymaps.csv html atset1
* keymap-gen code-map --lang=js keymaps.csv html atset1
*/
export default {
"Again": 0xe005, /* html:Again (Again) -> linux:129 (KEY_AGAIN) -> atset1:57349 */
@@ -111,6 +111,8 @@ export default {
"KeyX": 0x2d, /* html:KeyX (KeyX) -> linux:45 (KEY_X) -> atset1:45 */
"KeyY": 0x15, /* html:KeyY (KeyY) -> linux:21 (KEY_Y) -> atset1:21 */
"KeyZ": 0x2c, /* html:KeyZ (KeyZ) -> linux:44 (KEY_Z) -> atset1:44 */
"Lang1": 0x72, /* html:Lang1 (Lang1) -> linux:122 (KEY_HANGEUL) -> atset1:114 */
"Lang2": 0x71, /* html:Lang2 (Lang2) -> linux:123 (KEY_HANJA) -> atset1:113 */
"Lang3": 0x78, /* html:Lang3 (Lang3) -> linux:90 (KEY_KATAKANA) -> atset1:120 */
"Lang4": 0x77, /* html:Lang4 (Lang4) -> linux:91 (KEY_HIRAGANA) -> atset1:119 */
"Lang5": 0x76, /* html:Lang5 (Lang5) -> linux:85 (KEY_ZENKAKUHANKAKU) -> atset1:118 */
+113 -106
View File
@@ -25,7 +25,6 @@ import DES from "./des.js";
import KeyTable from "./input/keysym.js";
import XtScancode from "./input/xtscancodes.js";
import { encodings } from "./encodings.js";
import "./util/polyfill.js";
import RawDecoder from "./decoders/raw.js";
import CopyRectDecoder from "./decoders/copyrect.js";
@@ -67,20 +66,25 @@ const extendedClipboardActionPeek = 1 << 26;
const extendedClipboardActionNotify = 1 << 27;
const extendedClipboardActionProvide = 1 << 28;
export default class RFB extends EventTargetMixin {
constructor(target, url, options) {
constructor(target, urlOrChannel, options) {
if (!target) {
throw new Error("Must specify target");
}
if (!url) {
throw new Error("Must specify URL");
if (!urlOrChannel) {
throw new Error("Must specify URL, WebSocket or RTCDataChannel");
}
super();
this._target = target;
this._url = url;
if (typeof urlOrChannel === "string") {
this._url = urlOrChannel;
} else {
this._url = null;
this._rawChannel = urlOrChannel;
}
// Connection details
options = options || {};
@@ -130,6 +134,7 @@ export default class RFB extends EventTargetMixin {
this._flushing = false; // Display flushing state
this._keyboard = null; // Keyboard input handler object
this._gestures = null; // Gesture input handler object
this._resizeObserver = null; // Resize observer object
// Timers
this._disconnTimer = null; // disconnection timer
@@ -167,7 +172,7 @@ export default class RFB extends EventTargetMixin {
// Bound event handlers
this._eventHandlers = {
focusCanvas: this._focusCanvas.bind(this),
windowResize: this._windowResize.bind(this),
handleResize: this._handleResize.bind(this),
handleMouse: this._handleMouse.bind(this),
handleWheel: this._handleWheel.bind(this),
handleGesture: this._handleGesture.bind(this),
@@ -187,8 +192,6 @@ export default class RFB extends EventTargetMixin {
this._canvas.style.margin = 'auto';
// Some browsers add an outline on focus
this._canvas.style.outline = 'none';
// IE miscalculates width without this :(
this._canvas.style.flexShrink = '0';
this._canvas.width = 0;
this._canvas.height = 0;
this._canvas.tabIndex = -1;
@@ -232,58 +235,15 @@ export default class RFB extends EventTargetMixin {
this._gestures = new GestureHandler();
this._sock = new Websock();
this._sock.on('message', () => {
this._handleMessage();
});
this._sock.on('open', () => {
if ((this._rfbConnectionState === 'connecting') &&
(this._rfbInitState === '')) {
this._rfbInitState = 'ProtocolVersion';
Log.Debug("Starting VNC handshake");
} else {
this._fail("Unexpected server connection while " +
this._rfbConnectionState);
}
});
this._sock.on('close', (e) => {
Log.Debug("WebSocket on-close event");
let msg = "";
if (e.code) {
msg = "(code: " + e.code;
if (e.reason) {
msg += ", reason: " + e.reason;
}
msg += ")";
}
switch (this._rfbConnectionState) {
case 'connecting':
this._fail("Connection closed " + msg);
break;
case 'connected':
// Handle disconnects that were initiated server-side
this._updateConnectionState('disconnecting');
this._updateConnectionState('disconnected');
break;
case 'disconnecting':
// Normal disconnection path
this._updateConnectionState('disconnected');
break;
case 'disconnected':
this._fail("Unexpected server disconnect " +
"when already disconnected " + msg);
break;
default:
this._fail("Unexpected server disconnect before connecting " +
msg);
break;
}
this._sock.off('close');
});
this._sock.on('error', e => Log.Warn("WebSocket on-error event"));
this._sock.on('open', this._socketOpen.bind(this));
this._sock.on('close', this._socketClose.bind(this));
this._sock.on('message', this._handleMessage.bind(this));
this._sock.on('error', this._socketError.bind(this));
// Slight delay of the actual connection so that the caller has
// time to set up callbacks
setTimeout(this._updateConnectionState.bind(this, 'connecting'));
this._resizeObserver = new ResizeObserver(this._eventHandlers.handleResize);
// All prepared, kick off the connection
this._updateConnectionState('connecting');
Log.Debug("<< RFB.constructor");
@@ -504,16 +464,22 @@ export default class RFB extends EventTargetMixin {
_connect() {
Log.Debug(">> RFB.connect");
Log.Info("connecting to " + this._url);
try {
// WebSocket.onopen transitions to the RFB init states
if (this._url) {
Log.Info(`connecting to ${this._url}`);
this._sock.open(this._url, this._wsProtocols);
} catch (e) {
if (e.name === 'SyntaxError') {
this._fail("Invalid host or port (" + e + ")");
} else {
this._fail("Error when opening socket (" + e + ")");
} else {
Log.Info(`attaching ${this._rawChannel} to Websock`);
this._sock.attach(this._rawChannel);
if (this._sock.readyState === 'closed') {
throw Error("Cannot use already closed WebSocket/RTCDataChannel");
}
if (this._sock.readyState === 'open') {
// FIXME: _socketOpen() can in theory call _fail(), which
// isn't allowed this early, but I'm not sure that can
// happen without a bug messing up our state variables
this._socketOpen();
}
}
@@ -525,9 +491,8 @@ export default class RFB extends EventTargetMixin {
this._cursor.attach(this._canvas);
this._refreshCursor();
// Monitor size changes of the screen
// FIXME: Use ResizeObserver, or hidden overflow
window.addEventListener('resize', this._eventHandlers.windowResize);
// Monitor size changes of the screen element
this._resizeObserver.observe(this._screen);
// Always grab focus on some kind of click event
this._canvas.addEventListener("mousedown", this._eventHandlers.focusCanvas);
@@ -568,7 +533,7 @@ export default class RFB extends EventTargetMixin {
this._canvas.removeEventListener('contextmenu', this._eventHandlers.handleMouse);
this._canvas.removeEventListener("mousedown", this._eventHandlers.focusCanvas);
this._canvas.removeEventListener("touchstart", this._eventHandlers.focusCanvas);
window.removeEventListener('resize', this._eventHandlers.windowResize);
this._resizeObserver.disconnect();
this._keyboard.ungrab();
this._gestures.detach();
this._sock.close();
@@ -587,6 +552,58 @@ export default class RFB extends EventTargetMixin {
Log.Debug("<< RFB.disconnect");
}
_socketOpen() {
if ((this._rfbConnectionState === 'connecting') &&
(this._rfbInitState === '')) {
this._rfbInitState = 'ProtocolVersion';
Log.Debug("Starting VNC handshake");
} else {
this._fail("Unexpected server connection while " +
this._rfbConnectionState);
}
}
_socketClose(e) {
Log.Debug("WebSocket on-close event");
let msg = "";
if (e.code) {
msg = "(code: " + e.code;
if (e.reason) {
msg += ", reason: " + e.reason;
}
msg += ")";
}
switch (this._rfbConnectionState) {
case 'connecting':
this._fail("Connection closed " + msg);
break;
case 'connected':
// Handle disconnects that were initiated server-side
this._updateConnectionState('disconnecting');
this._updateConnectionState('disconnected');
break;
case 'disconnecting':
// Normal disconnection path
this._updateConnectionState('disconnected');
break;
case 'disconnected':
this._fail("Unexpected server disconnect " +
"when already disconnected " + msg);
break;
default:
this._fail("Unexpected server disconnect before connecting " +
msg);
break;
}
this._sock.off('close');
// Delete reference to raw channel to allow cleanup.
this._rawChannel = null;
}
_socketError(e) {
Log.Warn("WebSocket on-error event");
}
_focusCanvas(event) {
if (!this.focusOnClick) {
return;
@@ -602,7 +619,7 @@ export default class RFB extends EventTargetMixin {
{ detail: { name: this._fbName } }));
}
_windowResize(event) {
_handleResize() {
// If the window resized then our screen element might have
// as well. Update the viewport dimensions.
window.requestAnimationFrame(() => {
@@ -1263,17 +1280,6 @@ export default class RFB extends EventTargetMixin {
}
_negotiateSecurity() {
// Polyfill since IE and PhantomJS doesn't have
// TypedArray.includes()
function includes(item, array) {
for (let i = 0; i < array.length; i++) {
if (array[i] === item) {
return true;
}
}
return false;
}
if (this._rfbVersion >= 3.7) {
// Server sends supported list, client decides
const numTypes = this._sock.rQshift8();
@@ -1290,15 +1296,15 @@ export default class RFB extends EventTargetMixin {
Log.Debug("Server security types: " + types);
// Look for each auth in preferred order
if (includes(1, types)) {
if (types.includes(1)) {
this._rfbAuthScheme = 1; // None
} else if (includes(22, types)) {
} else if (types.includes(22)) {
this._rfbAuthScheme = 22; // XVP
} else if (includes(16, types)) {
} else if (types.includes(16)) {
this._rfbAuthScheme = 16; // Tight
} else if (includes(2, types)) {
} else if (types.includes(2)) {
this._rfbAuthScheme = 2; // VNC Auth
} else if (includes(19, types)) {
} else if (types.includes(19)) {
this._rfbAuthScheme = 19; // VeNCrypt Auth
} else {
return this._fail("Unsupported security types (types: " + types + ")");
@@ -1441,8 +1447,8 @@ export default class RFB extends EventTargetMixin {
// negotiated Plain subtype, server waits for password
if (this._rfbVeNCryptState == 4) {
if (!this._rfbCredentials.username ||
!this._rfbCredentials.password) {
if (this._rfbCredentials.username === undefined ||
this._rfbCredentials.password === undefined) {
this.dispatchEvent(new CustomEvent(
"credentialsrequired",
{ detail: { types: ["username", "password"] } }));
@@ -1452,9 +1458,18 @@ export default class RFB extends EventTargetMixin {
const user = encodeUTF8(this._rfbCredentials.username);
const pass = encodeUTF8(this._rfbCredentials.password);
// XXX we assume lengths are <= 255 (should not be an issue in the real world)
this._sock.send([0, 0, 0, user.length]);
this._sock.send([0, 0, 0, pass.length]);
this._sock.send([
(user.length >> 24) & 0xFF,
(user.length >> 16) & 0xFF,
(user.length >> 8) & 0xFF,
user.length & 0xFF
]);
this._sock.send([
(pass.length >> 24) & 0xFF,
(pass.length >> 16) & 0xFF,
(pass.length >> 8) & 0xFF,
pass.length & 0xFF
]);
this._sock.sendString(user);
this._sock.sendString(pass);
@@ -2183,15 +2198,7 @@ export default class RFB extends EventTargetMixin {
return this._handleCursor();
case encodings.pseudoEncodingQEMUExtendedKeyEvent:
// Old Safari doesn't support creating keyboard events
try {
const keyboardEvent = document.createEvent("keyboardEvent");
if (keyboardEvent.code !== undefined) {
this._qemuExtKeyEventSupported = true;
}
} catch (err) {
// Do nothing
}
this._qemuExtKeyEventSupported = true;
return true;
case encodings.pseudoEncodingDesktopName:
@@ -2882,9 +2889,9 @@ RFB.messages = {
buff[offset + 12] = 0; // blue-max
buff[offset + 13] = (1 << bits) - 1; // blue-max
buff[offset + 14] = bits * 2; // red-shift
buff[offset + 14] = bits * 0; // red-shift
buff[offset + 15] = bits * 1; // green-shift
buff[offset + 16] = bits * 0; // blue-shift
buff[offset + 16] = bits * 2; // blue-shift
buff[offset + 17] = 0; // padding
buff[offset + 18] = 0; // padding
-17
View File
@@ -45,15 +45,6 @@ try {
export const supportsCursorURIs = _supportsCursorURIs;
let _supportsImageMetadata = false;
try {
new ImageData(new Uint8ClampedArray(4), 1, 1);
_supportsImageMetadata = true;
} catch (ex) {
// ignore failure
}
export const supportsImageMetadata = _supportsImageMetadata;
let _hasScrollbarGutter = true;
try {
// Create invisible container
@@ -106,14 +97,6 @@ export function isSafari() {
navigator.userAgent.indexOf('Chrome') === -1);
}
export function isIE() {
return navigator && !!(/trident/i).exec(navigator.userAgent);
}
export function isEdge() {
return navigator && !!(/edge/i).exec(navigator.userAgent);
}
export function isFirefox() {
return navigator && !!(/firefox/i).exec(navigator.userAgent);
}
+1 -11
View File
@@ -43,9 +43,6 @@ export default class Cursor {
if (useFallback) {
document.body.appendChild(this._canvas);
// FIXME: These don't fire properly except for mouse
/// movement in IE. We want to also capture element
// movement, size changes, visibility, etc.
const options = { capture: true, passive: true };
this._target.addEventListener('mouseover', this._eventHandlers.mouseover, options);
this._target.addEventListener('mouseleave', this._eventHandlers.mouseleave, options);
@@ -90,14 +87,7 @@ export default class Cursor {
this._canvas.width = w;
this._canvas.height = h;
let img;
try {
// IE doesn't support this
img = new ImageData(new Uint8ClampedArray(rgba), w, h);
} catch (ex) {
img = ctx.createImageData(w, h);
img.data.set(new Uint8ClampedArray(rgba));
}
let img = new ImageData(new Uint8ClampedArray(rgba), w, h);
ctx.clearRect(0, 0, w, h);
ctx.putImageData(img, 0, 0);
-4
View File
@@ -65,10 +65,6 @@ export function setCapture(target) {
target.setCapture();
document.captureElement = target;
// IE releases capture on 'click' events which might not trigger
target.addEventListener('mouseup', releaseCapture);
} else {
// Release any existing capture in case this method is
// called multiple times without coordination
-61
View File
@@ -1,61 +0,0 @@
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2020 The noVNC Authors
* Licensed under MPL 2.0 or any later version (see LICENSE.txt)
*/
/* Polyfills to provide new APIs in old browsers */
/* Object.assign() (taken from MDN) */
if (typeof Object.assign != 'function') {
// Must be writable: true, enumerable: false, configurable: true
Object.defineProperty(Object, "assign", {
value: function assign(target, varArgs) { // .length of function is 2
'use strict';
if (target == null) { // TypeError if undefined or null
throw new TypeError('Cannot convert undefined or null to object');
}
const to = Object(target);
for (let index = 1; index < arguments.length; index++) {
const nextSource = arguments[index];
if (nextSource != null) { // Skip over if undefined or null
for (let nextKey in nextSource) {
// Avoid bugs when hasOwnProperty is shadowed
if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
to[nextKey] = nextSource[nextKey];
}
}
}
}
return to;
},
writable: true,
configurable: true
});
}
/* CustomEvent constructor (taken from MDN) */
(() => {
function CustomEvent(event, params) {
params = params || { bubbles: false, cancelable: false, detail: undefined };
const evt = document.createEvent( 'CustomEvent' );
evt.initCustomEvent( event, params.bubbles, params.cancelable, params.detail );
return evt;
}
CustomEvent.prototype = window.Event.prototype;
if (typeof window.CustomEvent !== "function") {
window.CustomEvent = CustomEvent;
}
})();
/* Number.isInteger() (taken from MDN) */
Number.isInteger = Number.isInteger || function isInteger(value) {
return typeof value === 'number' &&
isFinite(value) &&
Math.floor(value) === value;
};
+76 -17
View File
@@ -1,10 +1,10 @@
/*
* Websock: high-performance binary WebSockets
* Websock: high-performance buffering wrapper
* Copyright (C) 2019 The noVNC Authors
* Licensed under MPL 2.0 (see LICENSE.txt)
*
* Websock is similar to the standard WebSocket object but with extra
* buffer handling.
* Websock is similar to the standard WebSocket / RTCDataChannel object
* but with extra buffer handling.
*
* Websock has built-in receive queue buffering; the message event
* does not contain actual data but is simply a notification that
@@ -17,14 +17,39 @@ import * as Log from './util/logging.js';
// this has performance issues in some versions Chromium, and
// doesn't gain a tremendous amount of performance increase in Firefox
// at the moment. It may be valuable to turn it on in the future.
// Also copyWithin() for TypedArrays is not supported in IE 11 or
// Safari 13 (at the moment we want to support Safari 11).
const ENABLE_COPYWITHIN = false;
const MAX_RQ_GROW_SIZE = 40 * 1024 * 1024; // 40 MiB
// Constants pulled from RTCDataChannelState enum
// https://developer.mozilla.org/en-US/docs/Web/API/RTCDataChannel/readyState#RTCDataChannelState_enum
const DataChannel = {
CONNECTING: "connecting",
OPEN: "open",
CLOSING: "closing",
CLOSED: "closed"
};
const ReadyStates = {
CONNECTING: [WebSocket.CONNECTING, DataChannel.CONNECTING],
OPEN: [WebSocket.OPEN, DataChannel.OPEN],
CLOSING: [WebSocket.CLOSING, DataChannel.CLOSING],
CLOSED: [WebSocket.CLOSED, DataChannel.CLOSED],
};
// Properties a raw channel must have, WebSocket and RTCDataChannel are two examples
const rawChannelProps = [
"send",
"close",
"binaryType",
"onerror",
"onmessage",
"onopen",
"protocol",
"readyState",
];
export default class Websock {
constructor() {
this._websocket = null; // WebSocket object
this._websocket = null; // WebSocket or RTCDataChannel object
this._rQi = 0; // Receive queue index
this._rQlen = 0; // Next write position in the receive queue
@@ -46,6 +71,29 @@ export default class Websock {
}
// Getters and Setters
get readyState() {
let subState;
if (this._websocket === null) {
return "unused";
}
subState = this._websocket.readyState;
if (ReadyStates.CONNECTING.includes(subState)) {
return "connecting";
} else if (ReadyStates.OPEN.includes(subState)) {
return "open";
} else if (ReadyStates.CLOSING.includes(subState)) {
return "closing";
} else if (ReadyStates.CLOSED.includes(subState)) {
return "closed";
}
return "unknown";
}
get sQ() {
return this._sQ;
}
@@ -143,7 +191,7 @@ export default class Websock {
// Send Queue
flush() {
if (this._sQlen > 0 && this._websocket.readyState === WebSocket.OPEN) {
if (this._sQlen > 0 && this.readyState === 'open') {
this._websocket.send(this._encodeMessage());
this._sQlen = 0;
}
@@ -180,12 +228,25 @@ export default class Websock {
}
open(uri, protocols) {
this.attach(new WebSocket(uri, protocols));
}
attach(rawChannel) {
this.init();
this._websocket = new WebSocket(uri, protocols);
this._websocket.binaryType = 'arraybuffer';
// Must get object and class methods to be compatible with the tests.
const channelProps = [...Object.keys(rawChannel), ...Object.getOwnPropertyNames(Object.getPrototypeOf(rawChannel))];
for (let i = 0; i < rawChannelProps.length; i++) {
const prop = rawChannelProps[i];
if (channelProps.indexOf(prop) < 0) {
throw new Error('Raw channel missing property: ' + prop);
}
}
this._websocket = rawChannel;
this._websocket.binaryType = "arraybuffer";
this._websocket.onmessage = this._recvMessage.bind(this);
this._websocket.onopen = () => {
Log.Debug('>> WebSock.onopen');
if (this._websocket.protocol) {
@@ -195,11 +256,13 @@ export default class Websock {
this._eventHandlers.open();
Log.Debug("<< WebSock.onopen");
};
this._websocket.onclose = (e) => {
Log.Debug(">> WebSock.onclose");
this._eventHandlers.close(e);
Log.Debug("<< WebSock.onclose");
};
this._websocket.onerror = (e) => {
Log.Debug(">> WebSock.onerror: " + e);
this._eventHandlers.error(e);
@@ -209,8 +272,8 @@ export default class Websock {
close() {
if (this._websocket) {
if ((this._websocket.readyState === WebSocket.OPEN) ||
(this._websocket.readyState === WebSocket.CONNECTING)) {
if (this.readyState === 'connecting' ||
this.readyState === 'open') {
Log.Info("Closing WebSocket connection");
this._websocket.close();
}
@@ -256,11 +319,7 @@ export default class Websock {
this._rQ = new Uint8Array(this._rQbufferSize);
this._rQ.set(new Uint8Array(oldRQbuffer, this._rQi, this._rQlen - this._rQi));
} else {
if (ENABLE_COPYWITHIN) {
this._rQ.copyWithin(0, this._rQi, this._rQlen);
} else {
this._rQ.set(new Uint8Array(this._rQ.buffer, this._rQi, this._rQlen - this._rQi));
}
this._rQ.copyWithin(0, this._rQi, this._rQlen);
}
this._rQlen = this._rQlen - this._rQi;
-5
View File
@@ -78,12 +78,7 @@ None
| fillRect | (x, y, width, height, color, from_queue) | Draw a filled in rectangle
| copyImage | (old_x, old_y, new_x, new_y, width, height, from_queue) | Copy a rectangular area
| imageRect | (x, y, width, height, mime, arr) | Draw a rectangle with an image
| startTile | (x, y, width, height, color) | Begin updating a tile
| subTile | (tile, x, y, w, h, color) | Update a sub-rectangle within the given tile
| finishTile | () | Draw the current tile to the display
| blitImage | (x, y, width, height, arr, offset, from_queue) | Blit pixels (of R,G,B,A) to the display
| blitRgbImage | (x, y, width, height, arr, offset, from_queue) | Blit RGB encoded image to display
| blitRgbxImage | (x, y, width, height, arr, offset, from_queue) | Blit RGBX encoded image to display
| drawImage | (img, x, y) | Draw image and track damage
| autoscale | (containerWidth, containerHeight) | Scale the display
+2 -2
View File
@@ -165,9 +165,9 @@ connection to a specified VNC server.
existing contents of the `HTMLElement` will be untouched, but new
elements will be added during the lifetime of the `RFB` object.
**`url`**
**`urlOrDataChannel`**
- A `DOMString` specifying the VNC server to connect to. This must be
a valid WebSocket URL.
a valid WebSocket URL. This can also be a `WebSocket` or `RTCDataChannel`.
**`options`** *Optional*
- An `Object` specifying extra details about how the connection
-18
View File
@@ -71,24 +71,6 @@ query string. Currently the following options are available:
* `logging` - The console log level. Can be one of `error`, `warn`, `info` or
`debug`.
## Pre-conversion of Modules
noVNC is written using ECMAScript 6 modules. Many of the major browsers support
these modules natively, but not all. By default the noVNC application includes
a script that can convert these modules to an older format as they are being
loaded. However this process can be slow and severely increases the load time
for the application.
It is possible to perform this conversion ahead of time, avoiding the extra
load times. To do this please follow these steps:
1. Install Node.js
2. Run `npm install` in the noVNC directory
3. Run `./utils/use_require.js --with-app --as commonjs`
This will produce a `build/` directory that includes everything needed to run
the noVNC application.
## HTTP Serving Considerations
### Browser Cache Issue
+3 -7
View File
@@ -18,18 +18,14 @@ do things.
## Conversion of Modules
noVNC is written using ECMAScript 6 modules. Many of the major browsers support
these modules natively, but not all. They are also not supported by Node.js. To
use noVNC in these places the library must first be converted.
noVNC is written using ECMAScript 6 modules. This is not supported by older
versions of Node.js. To use noVNC with those older versions of Node.js the
library must first be converted.
Fortunately noVNC includes a script to handle this conversion. Please follow
the following steps:
1. Install Node.js
2. Run `npm install` in the noVNC directory
3. Run `./utils/use_require.js --as <module format>`
Several module formats are available. Please run
`./utils/use_require.js --help` to see them all.
The result of the conversion is available in the `lib/` directory.
+37
View File
@@ -0,0 +1,37 @@
.TH novnc_proxy 1 "June 25, 2020" "version 1.2.0" "USER COMMANDS"
.SH NAME
novnc_proxy - noVNC proxy server
.SH SYNOPSIS
.B novnc_proxy [--listen PORT] [--vnc VNC_HOST:PORT] [--cert CERT] [--ssl-only]
Starts the WebSockets proxy and a mini-webserver and
provides a cut-and-paste URL to go to.
--listen PORT Port for proxy/webserver to listen on
Default: 6080
--vnc VNC_HOST:PORT VNC server host:port proxy target
Default: localhost:5900
--cert CERT Path to combined cert/key file, or just
the cert file if used with --key
Default: self.pem
--key KEY Path to key file, when not combined with cert
--web WEB Path to web files (e.g. vnc.html)
Default: ./
--ssl-only Disable non-https connections.
--record FILE Record traffic to FILE.session.js
--syslog SERVER Can be local socket such as /dev/log, or a UDP host:port pair.
--heartbeat SEC send a ping to the client every SEC seconds
--timeout SEC after SEC seconds exit when not connected
--idle-timeout SEC server exits after SEC seconds if there are no
active connections
.SH AUTHOR
The noVNC Authors
https://github.com/novnc/noVNC
.SH SEE ALSO
websockify(1), nova-novncproxy(1)
+7 -10
View File
@@ -31,16 +31,13 @@ module.exports = (config) => {
// list of files / patterns to load in the browser (loaded in order)
files: [
{ pattern: 'app/localization.js', included: false },
{ pattern: 'app/webutil.js', included: false },
{ pattern: 'core/**/*.js', included: false },
{ pattern: 'vendor/pako/**/*.js', included: false },
{ pattern: 'vendor/browser-es-module-loader/dist/*.js*', included: false },
{ pattern: 'tests/test.*.js', included: false },
{ pattern: 'tests/fake.*.js', included: false },
{ pattern: 'tests/assertions.js', included: false },
'vendor/promise.js',
'tests/karma-test-main.js',
{ pattern: 'app/localization.js', included: false, type: 'module' },
{ pattern: 'app/webutil.js', included: false, type: 'module' },
{ pattern: 'core/**/*.js', included: false, type: 'module' },
{ pattern: 'vendor/pako/**/*.js', included: false, type: 'module' },
{ pattern: 'tests/test.*.js', type: 'module' },
{ pattern: 'tests/fake.*.js', included: false, type: 'module' },
{ pattern: 'tests/assertions.js', type: 'module' },
],
client: {
+2 -5
View File
@@ -1,6 +1,6 @@
{
"name": "@novnc/novnc",
"version": "1.2.0",
"version": "1.3.0-beta",
"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 --as commonjs --clean"
"prepublish": "node ./utils/use_require.js --clean"
},
"repository": {
"type": "git",
@@ -42,10 +42,7 @@
"devDependencies": {
"@babel/core": "*",
"@babel/plugin-syntax-dynamic-import": "*",
"@babel/plugin-transform-modules-amd": "*",
"@babel/plugin-transform-modules-commonjs": "*",
"@babel/plugin-transform-modules-systemjs": "*",
"@babel/plugin-transform-modules-umd": "*",
"@babel/preset-env": "*",
"@babel/cli": "*",
"babel-plugin-import-redirect": "*",
+1 -1
View File
@@ -1,7 +1,7 @@
all:
.PHONY: update-po update-js update-pot
LINGUAS := cs de el es ja ko nl pl ru sv tr zh_CN zh_TW
LINGUAS := cs de el es fr ja ko nl pl pt_BR ru sv tr zh_CN zh_TW
VERSION := $(shell grep '"version"' ../package.json | cut -d '"' -f 4)
+17 -16
View File
@@ -3,14 +3,15 @@
# Copyright (C) 2018 The noVNC Authors
# This file is distributed under the same license as the noVNC package.
# Juanjo Diaz <juanjo.diazmo@gmail.com>, 2018.
# Adrian Scillato <ascillato@gmail.com>, 2021.
#
msgid ""
msgstr ""
"Project-Id-Version: noVNC 1.0.0-testing.2\n"
"Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
"POT-Creation-Date: 2017-10-06 10:07+0200\n"
"PO-Revision-Date: 2018-01-30 19:14-0800\n"
"Last-Translator: Juanjo Diaz <juanjo.diazmo@gmail.com>\n"
"PO-Revision-Date: 2021-04-23 12:00-0300\n"
"Last-Translator: Adrian Scillato <ascillato@gmail.com>\n"
"Language-Team: Spanish\n"
"Language: es\n"
"MIME-Version: 1.0\n"
@@ -40,7 +41,7 @@ msgstr "Desconectado"
#: ../app/ui.js:1052 ../core/rfb.js:248
msgid "Must set host"
msgstr "Debes configurar el host"
msgstr "Se debe configurar el host"
#: ../app/ui.js:1101
msgid "Reconnecting..."
@@ -48,7 +49,7 @@ msgstr "Reconectando..."
#: ../app/ui.js:1140
msgid "Password is required"
msgstr "Contraseña es obligatoria"
msgstr "La contraseña es obligatoria"
#: ../core/rfb.js:548
msgid "Disconnect timeout"
@@ -186,6 +187,10 @@ msgstr "Pantalla Completa"
msgid "Settings"
msgstr "Configuraciones"
#: ../vnc.html:200
msgid "Encrypt"
msgstr "Encriptar"
#: ../vnc.html:202
msgid "Shared Mode"
msgstr "Modo Compartido"
@@ -228,27 +233,23 @@ msgstr "Cursor Local"
#: ../vnc.html:229
msgid "Repeater ID:"
msgstr "ID del Repetidor"
msgstr "ID del Repetidor:"
#: ../vnc.html:233
msgid "WebSocket"
msgstr "WebSocket"
#: ../vnc.html:236
msgid "Encrypt"
msgstr ""
#: ../vnc.html:239
msgid "Host:"
msgstr "Host"
msgstr "Host:"
#: ../vnc.html:243
msgid "Port:"
msgstr "Puesto"
msgstr "Puerto:"
#: ../vnc.html:247
msgid "Path:"
msgstr "Ruta"
msgstr "Ruta:"
#: ../vnc.html:254
msgid "Automatic Reconnect"
@@ -256,11 +257,11 @@ msgstr "Reconexión automática"
#: ../vnc.html:257
msgid "Reconnect Delay (ms):"
msgstr "Retraso en la reconexión (ms)"
msgstr "Retraso en la reconexión (ms):"
#: ../vnc.html:263
msgid "Logging:"
msgstr "Logging"
msgstr "Registrando:"
#: ../vnc.html:275
msgid "Disconnect"
@@ -272,7 +273,7 @@ msgstr "Conectar"
#: ../vnc.html:304
msgid "Password:"
msgstr "Contraseña"
msgstr "Contraseña:"
#: ../vnc.html:318
msgid "Cancel"
@@ -280,4 +281,4 @@ msgstr "Cancelar"
#: ../vnc.html:334
msgid "Canvas not supported."
msgstr "Canvas no está soportado"
msgstr "Canvas no soportado."
+299
View File
@@ -0,0 +1,299 @@
# French translations for noVNC package
# Traductions françaises du paquet noVNC.
# Copyright (C) 2021 The noVNC Authors
# This file is distributed under the same license as the noVNC package.
# Jose <jose.matsuda@canada.ca>, 2021.
#
msgid ""
msgstr ""
"Project-Id-Version: noVNC 1.2.0\n"
"Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
"POT-Creation-Date: 2020-07-03 16:11+0200\n"
"PO-Revision-Date: 2021-05-05 20:19-0400\n"
"Last-Translator: Jose <jose.matsuda@canada.ca>\n"
"Language-Team: French\n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#: ../app/ui.js:394
msgid "Connecting..."
msgstr "En cours de connexion..."
#: ../app/ui.js:401
msgid "Disconnecting..."
msgstr "Déconnexion en cours..."
#: ../app/ui.js:407
msgid "Reconnecting..."
msgstr "Reconnexion en cours..."
#: ../app/ui.js:412
msgid "Internal error"
msgstr "Erreur interne"
#: ../app/ui.js:1008
msgid "Must set host"
msgstr "Doit définir l'hôte"
#: ../app/ui.js:1090
msgid "Connected (encrypted) to "
msgstr "Connecté (crypté) à "
#: ../app/ui.js:1092
msgid "Connected (unencrypted) to "
msgstr "Connecté (non crypté) à "
#: ../app/ui.js:1115
msgid "Something went wrong, connection is closed"
msgstr "Quelque chose est arrivé, la connexion est fermée"
#: ../app/ui.js:1118
msgid "Failed to connect to server"
msgstr "Échec de connexion au serveur"
#: ../app/ui.js:1128
msgid "Disconnected"
msgstr "Déconnecté"
#: ../app/ui.js:1143
msgid "New connection has been rejected with reason: "
msgstr "Une nouvelle connexion a été rejetée avec raison: "
#: ../app/ui.js:1146
msgid "New connection has been rejected"
msgstr "Une nouvelle connexion a été rejetée"
#: ../app/ui.js:1181
msgid "Credentials are required"
msgstr "Les identifiants sont requis"
#: ../vnc.html:74
msgid "noVNC encountered an error:"
msgstr "noVNC a rencontré une erreur:"
#: ../vnc.html:84
msgid "Hide/Show the control bar"
msgstr "Masquer/Afficher la barre de contrôle"
#: ../vnc.html:91
msgid "Drag"
msgstr "Faire glisser"
#: ../vnc.html:91
msgid "Move/Drag Viewport"
msgstr "Déplacer/faire glisser Viewport"
#: ../vnc.html:97
msgid "Keyboard"
msgstr "Clavier"
#: ../vnc.html:97
msgid "Show Keyboard"
msgstr "Afficher le clavier"
#: ../vnc.html:102
msgid "Extra keys"
msgstr "Touches supplémentaires"
#: ../vnc.html:102
msgid "Show Extra Keys"
msgstr "Afficher les touches supplémentaires"
#: ../vnc.html:107
msgid "Ctrl"
msgstr "Ctrl"
#: ../vnc.html:107
msgid "Toggle Ctrl"
msgstr "Basculer Ctrl"
#: ../vnc.html:110
msgid "Alt"
msgstr "Alt"
#: ../vnc.html:110
msgid "Toggle Alt"
msgstr "Basculer Alt"
#: ../vnc.html:113
msgid "Toggle Windows"
msgstr "Basculer Windows"
#: ../vnc.html:113
msgid "Windows"
msgstr "Windows"
#: ../vnc.html:116
msgid "Send Tab"
msgstr "Envoyer l'onglet"
#: ../vnc.html:116
msgid "Tab"
msgstr "l'onglet"
#: ../vnc.html:119
msgid "Esc"
msgstr "Esc"
#: ../vnc.html:119
msgid "Send Escape"
msgstr "Envoyer Escape"
#: ../vnc.html:122
msgid "Ctrl+Alt+Del"
msgstr "Ctrl+Alt+Del"
#: ../vnc.html:122
msgid "Send Ctrl-Alt-Del"
msgstr "Envoyer Ctrl-Alt-Del"
#: ../vnc.html:129
msgid "Shutdown/Reboot"
msgstr "Arrêter/Redémarrer"
#: ../vnc.html:129
msgid "Shutdown/Reboot..."
msgstr "Arrêter/Redémarrer..."
#: ../vnc.html:135
msgid "Power"
msgstr "Alimentation"
#: ../vnc.html:137
msgid "Shutdown"
msgstr "Arrêter"
#: ../vnc.html:138
msgid "Reboot"
msgstr "Redémarrer"
#: ../vnc.html:139
msgid "Reset"
msgstr "Réinitialiser"
#: ../vnc.html:144 ../vnc.html:150
msgid "Clipboard"
msgstr "Presse-papiers"
#: ../vnc.html:154
msgid "Clear"
msgstr "Effacer"
#: ../vnc.html:160
msgid "Fullscreen"
msgstr "Plein écran"
#: ../vnc.html:165 ../vnc.html:172
msgid "Settings"
msgstr "Paramètres"
#: ../vnc.html:175
msgid "Shared Mode"
msgstr "Mode partagé"
#: ../vnc.html:178
msgid "View Only"
msgstr "Afficher uniquement"
#: ../vnc.html:182
msgid "Clip to Window"
msgstr "Clip à fenêtre"
#: ../vnc.html:185
msgid "Scaling Mode:"
msgstr "Mode mise à l'échelle:"
#: ../vnc.html:187
msgid "None"
msgstr "Aucun"
#: ../vnc.html:188
msgid "Local Scaling"
msgstr "Mise à l'échelle locale"
#: ../vnc.html:189
msgid "Remote Resizing"
msgstr "Redimensionnement à distance"
#: ../vnc.html:194
msgid "Advanced"
msgstr "Avancé"
#: ../vnc.html:197
msgid "Quality:"
msgstr "Qualité:"
#: ../vnc.html:201
msgid "Compression level:"
msgstr "Niveau de compression:"
#: ../vnc.html:206
msgid "Repeater ID:"
msgstr "ID Répéteur:"
#: ../vnc.html:210
msgid "WebSocket"
msgstr "WebSocket"
#: ../vnc.html:213
msgid "Encrypt"
msgstr "Crypter"
#: ../vnc.html:216
msgid "Host:"
msgstr "Hôte:"
#: ../vnc.html:220
msgid "Port:"
msgstr "Port:"
#: ../vnc.html:224
msgid "Path:"
msgstr "Chemin:"
#: ../vnc.html:231
msgid "Automatic Reconnect"
msgstr "Reconnecter automatiquemen"
#: ../vnc.html:234
msgid "Reconnect Delay (ms):"
msgstr "Délai de reconnexion (ms):"
#: ../vnc.html:239
msgid "Show Dot when No Cursor"
msgstr "Afficher le point lorsqu'il n'y a pas de curseur"
#: ../vnc.html:244
msgid "Logging:"
msgstr "Se connecter:"
#: ../vnc.html:253
msgid "Version:"
msgstr "Version:"
#: ../vnc.html:261
msgid "Disconnect"
msgstr "Déconnecter"
#: ../vnc.html:280
msgid "Connect"
msgstr "Connecter"
#: ../vnc.html:290
msgid "Username:"
msgstr "Nom d'utilisateur:"
#: ../vnc.html:294
msgid "Password:"
msgstr "Mot de passe:"
#: ../vnc.html:298
msgid "Send Credentials"
msgstr "Envoyer les identifiants"
#: ../vnc.html:308
msgid "Cancel"
msgstr "Annuler"
+117 -96
View File
@@ -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: 2019-01-16 11:06+0100\n"
"PO-Revision-Date: 2020-06-15 17:44+0900\n"
"POT-Creation-Date: 2020-07-03 16:11+0200\n"
"PO-Revision-Date: 2021-01-15 12:37+0900\n"
"Last-Translator: nnn1590 <nnn1590@nnn1590.org>\n"
"Language-Team: Japanese\n"
"Language: ja\n"
@@ -17,287 +17,308 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Poedit 2.3\n"
#: ../app/ui.js:387
#: ../app/ui.js:394
msgid "Connecting..."
msgstr "接続しています..."
#: ../app/ui.js:394
#: ../app/ui.js:401
msgid "Disconnecting..."
msgstr "切断しています..."
#: ../app/ui.js:400
#: ../app/ui.js:407
msgid "Reconnecting..."
msgstr "再接続しています..."
#: ../app/ui.js:405
#: ../app/ui.js:412
msgid "Internal error"
msgstr "内部エラー"
#: ../app/ui.js:995
#: ../app/ui.js:1008
msgid "Must set host"
msgstr "ホストを設定する必要があります"
#: ../app/ui.js:1077
#: ../app/ui.js:1090
msgid "Connected (encrypted) to "
msgstr "接続しました (暗号化済み): "
#: ../app/ui.js:1079
#: ../app/ui.js:1092
msgid "Connected (unencrypted) to "
msgstr "接続しました (暗号化されていません): "
#: ../app/ui.js:1102
#: ../app/ui.js:1115
msgid "Something went wrong, connection is closed"
msgstr "何かが問題で、接続が閉じられました"
msgstr "何らかの問題で、接続が閉じられました"
#: ../app/ui.js:1105
#: ../app/ui.js:1118
msgid "Failed to connect to server"
msgstr "サーバーへの接続に失敗しました"
#: ../app/ui.js:1115
#: ../app/ui.js:1128
msgid "Disconnected"
msgstr "切断しました"
#: ../app/ui.js:1128
#: ../app/ui.js:1143
msgid "New connection has been rejected with reason: "
msgstr "新規接続は次の理由で拒否されました: "
#: ../app/ui.js:1131
#: ../app/ui.js:1146
msgid "New connection has been rejected"
msgstr "新規接続は拒否されました"
#: ../app/ui.js:1151
msgid "Password is required"
msgstr "パスワードが必要です"
#: ../app/ui.js:1181
msgid "Credentials are required"
msgstr "資格情報が必要です"
#: ../vnc.html:84
#: ../vnc.html:74
msgid "noVNC encountered an error:"
msgstr "noVNC でエラーが発生しました:"
#: ../vnc.html:94
#: ../vnc.html:84
msgid "Hide/Show the control bar"
msgstr "コントロールバーを隠す/表示する"
#: ../vnc.html:101
#: ../vnc.html:91
msgid "Drag"
msgstr "ドラッグ"
#: ../vnc.html:91
msgid "Move/Drag Viewport"
msgstr "ビューポートを移動/ドラッグ"
#: ../vnc.html:101
msgid "viewport drag"
msgstr "ビューポートをドラッグ"
#: ../vnc.html:107 ../vnc.html:110 ../vnc.html:113 ../vnc.html:116
msgid "Active Mouse Button"
msgstr "アクティブなマウスボタン"
#: ../vnc.html:107
msgid "No mousebutton"
msgstr "マウスボタンなし"
#: ../vnc.html:110
msgid "Left mousebutton"
msgstr "左マウスボタン"
#: ../vnc.html:113
msgid "Middle mousebutton"
msgstr "中マウスボタン"
#: ../vnc.html:116
msgid "Right mousebutton"
msgstr "右マウスボタン"
#: ../vnc.html:119
#: ../vnc.html:97
msgid "Keyboard"
msgstr "キーボード"
#: ../vnc.html:119
#: ../vnc.html:97
msgid "Show Keyboard"
msgstr "キーボードを表示"
#: ../vnc.html:126
#: ../vnc.html:102
msgid "Extra keys"
msgstr "追加キー"
#: ../vnc.html:126
#: ../vnc.html:102
msgid "Show Extra Keys"
msgstr "追加キーを表示"
#: ../vnc.html:131
#: ../vnc.html:107
msgid "Ctrl"
msgstr "Ctrl"
#: ../vnc.html:131
#: ../vnc.html:107
msgid "Toggle Ctrl"
msgstr "Ctrl キーを切り替え"
#: ../vnc.html:134
#: ../vnc.html:110
msgid "Alt"
msgstr "Alt"
#: ../vnc.html:134
#: ../vnc.html:110
msgid "Toggle Alt"
msgstr "Alt キーを切り替え"
#: ../vnc.html:137
#: ../vnc.html:113
msgid "Toggle Windows"
msgstr "Windows キーを切り替え"
#: ../vnc.html:137
#: ../vnc.html:113
msgid "Windows"
msgstr "Windows"
#: ../vnc.html:140
#: ../vnc.html:116
msgid "Send Tab"
msgstr "Tab キーを送信"
#: ../vnc.html:140
#: ../vnc.html:116
msgid "Tab"
msgstr "Tab"
#: ../vnc.html:143
#: ../vnc.html:119
msgid "Esc"
msgstr "Esc"
#: ../vnc.html:143
#: ../vnc.html:119
msgid "Send Escape"
msgstr "Escape キーを送信"
#: ../vnc.html:146
#: ../vnc.html:122
msgid "Ctrl+Alt+Del"
msgstr "Ctrl+Alt+Del"
#: ../vnc.html:146
#: ../vnc.html:122
msgid "Send Ctrl-Alt-Del"
msgstr "Ctrl-Alt-Del を送信"
#: ../vnc.html:154
#: ../vnc.html:129
msgid "Shutdown/Reboot"
msgstr "シャットダウン/再起動"
#: ../vnc.html:154
#: ../vnc.html:129
msgid "Shutdown/Reboot..."
msgstr "シャットダウン/再起動..."
#: ../vnc.html:160
#: ../vnc.html:135
msgid "Power"
msgstr "電源"
#: ../vnc.html:162
#: ../vnc.html:137
msgid "Shutdown"
msgstr "シャットダウン"
#: ../vnc.html:163
#: ../vnc.html:138
msgid "Reboot"
msgstr "再起動"
#: ../vnc.html:164
#: ../vnc.html:139
msgid "Reset"
msgstr "リセット"
#: ../vnc.html:169 ../vnc.html:175
#: ../vnc.html:144 ../vnc.html:150
msgid "Clipboard"
msgstr "クリップボード"
#: ../vnc.html:179
#: ../vnc.html:154
msgid "Clear"
msgstr "クリア"
#: ../vnc.html:185
#: ../vnc.html:160
msgid "Fullscreen"
msgstr "全画面表示"
#: ../vnc.html:190 ../vnc.html:197
#: ../vnc.html:165 ../vnc.html:172
msgid "Settings"
msgstr "設定"
#: ../vnc.html:200
#: ../vnc.html:175
msgid "Shared Mode"
msgstr "共有モード"
#: ../vnc.html:203
#: ../vnc.html:178
msgid "View Only"
msgstr "表示のみ"
#: ../vnc.html:207
#: ../vnc.html:182
msgid "Clip to Window"
msgstr "ウィンドウにクリップ"
#: ../vnc.html:210
#: ../vnc.html:185
msgid "Scaling Mode:"
msgstr "スケーリングモード:"
#: ../vnc.html:212
#: ../vnc.html:187
msgid "None"
msgstr "なし"
#: ../vnc.html:213
#: ../vnc.html:188
msgid "Local Scaling"
msgstr "ローカルスケーリング"
#: ../vnc.html:214
#: ../vnc.html:189
msgid "Remote Resizing"
msgstr "リモートでリサイズ"
#: ../vnc.html:219
#: ../vnc.html:194
msgid "Advanced"
msgstr "高度"
#: ../vnc.html:222
#: ../vnc.html:197
msgid "Quality:"
msgstr "品質:"
#: ../vnc.html:201
msgid "Compression level:"
msgstr "圧縮レベル:"
#: ../vnc.html:206
msgid "Repeater ID:"
msgstr "リピーター ID:"
#: ../vnc.html:226
#: ../vnc.html:210
msgid "WebSocket"
msgstr "WebSocket"
#: ../vnc.html:229
#: ../vnc.html:213
msgid "Encrypt"
msgstr "暗号化"
#: ../vnc.html:232
#: ../vnc.html:216
msgid "Host:"
msgstr "ホスト:"
#: ../vnc.html:236
#: ../vnc.html:220
msgid "Port:"
msgstr "ポート:"
#: ../vnc.html:240
#: ../vnc.html:224
msgid "Path:"
msgstr "パス:"
#: ../vnc.html:247
#: ../vnc.html:231
msgid "Automatic Reconnect"
msgstr "自動再接続"
#: ../vnc.html:250
#: ../vnc.html:234
msgid "Reconnect Delay (ms):"
msgstr "再接続する遅延 (ミリ秒):"
#: ../vnc.html:255
#: ../vnc.html:239
msgid "Show Dot when No Cursor"
msgstr "カーソルがないときにドットを表示"
#: ../vnc.html:260
#: ../vnc.html:244
msgid "Logging:"
msgstr "ロギング:"
#: ../vnc.html:272
#: ../vnc.html:253
msgid "Version:"
msgstr "バージョン:"
#: ../vnc.html:261
msgid "Disconnect"
msgstr "切断"
#: ../vnc.html:291
#: ../vnc.html:280
msgid "Connect"
msgstr "接続"
#: ../vnc.html:301
#: ../vnc.html:290
msgid "Username:"
msgstr "ユーザー名:"
#: ../vnc.html:294
msgid "Password:"
msgstr "パスワード:"
#: ../vnc.html:305
msgid "Send Password"
msgstr "パスワードを送信"
#: ../vnc.html:298
msgid "Send Credentials"
msgstr "資格情報を送信"
#: ../vnc.html:315
#: ../vnc.html:308
msgid "Cancel"
msgstr "キャンセル"
#~ msgid "Password is required"
#~ msgstr "パスワードが必要です"
#~ msgid "viewport drag"
#~ msgstr "ビューポートをドラッグ"
#~ msgid "Active Mouse Button"
#~ msgstr "アクティブなマウスボタン"
#~ msgid "No mousebutton"
#~ msgstr "マウスボタンなし"
#~ msgid "Left mousebutton"
#~ msgstr "左マウスボタン"
#~ msgid "Middle mousebutton"
#~ msgstr "中マウスボタン"
#~ msgid "Right mousebutton"
#~ msgstr "右マウスボタン"
#~ msgid "Send Password"
#~ msgstr "パスワードを送信"
+72 -72
View File
@@ -6,9 +6,9 @@
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: noVNC 1.2.0\n"
"Project-Id-Version: noVNC 1.3.0\n"
"Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
"POT-Creation-Date: 2020-07-03 16:11+0200\n"
"POT-Creation-Date: 2021-08-27 16:03+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -17,282 +17,282 @@ msgstr ""
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"
#: ../app/ui.js:394
#: ../app/ui.js:400
msgid "Connecting..."
msgstr ""
#: ../app/ui.js:401
#: ../app/ui.js:407
msgid "Disconnecting..."
msgstr ""
#: ../app/ui.js:407
#: ../app/ui.js:413
msgid "Reconnecting..."
msgstr ""
#: ../app/ui.js:412
#: ../app/ui.js:418
msgid "Internal error"
msgstr ""
#: ../app/ui.js:1008
#: ../app/ui.js:1009
msgid "Must set host"
msgstr ""
#: ../app/ui.js:1090
#: ../app/ui.js:1091
msgid "Connected (encrypted) to "
msgstr ""
#: ../app/ui.js:1092
#: ../app/ui.js:1093
msgid "Connected (unencrypted) to "
msgstr ""
#: ../app/ui.js:1115
#: ../app/ui.js:1116
msgid "Something went wrong, connection is closed"
msgstr ""
#: ../app/ui.js:1118
#: ../app/ui.js:1119
msgid "Failed to connect to server"
msgstr ""
#: ../app/ui.js:1128
#: ../app/ui.js:1129
msgid "Disconnected"
msgstr ""
#: ../app/ui.js:1143
#: ../app/ui.js:1144
msgid "New connection has been rejected with reason: "
msgstr ""
#: ../app/ui.js:1146
#: ../app/ui.js:1147
msgid "New connection has been rejected"
msgstr ""
#: ../app/ui.js:1181
#: ../app/ui.js:1182
msgid "Credentials are required"
msgstr ""
#: ../vnc.html:74
#: ../vnc.html:61
msgid "noVNC encountered an error:"
msgstr ""
#: ../vnc.html:84
#: ../vnc.html:71
msgid "Hide/Show the control bar"
msgstr ""
#: ../vnc.html:91
#: ../vnc.html:78
msgid "Drag"
msgstr ""
#: ../vnc.html:91
#: ../vnc.html:78
msgid "Move/Drag Viewport"
msgstr ""
#: ../vnc.html:97
#: ../vnc.html:84
msgid "Keyboard"
msgstr ""
#: ../vnc.html:97
#: ../vnc.html:84
msgid "Show Keyboard"
msgstr ""
#: ../vnc.html:102
#: ../vnc.html:89
msgid "Extra keys"
msgstr ""
#: ../vnc.html:102
#: ../vnc.html:89
msgid "Show Extra Keys"
msgstr ""
#: ../vnc.html:107
#: ../vnc.html:94
msgid "Ctrl"
msgstr ""
#: ../vnc.html:107
#: ../vnc.html:94
msgid "Toggle Ctrl"
msgstr ""
#: ../vnc.html:110
#: ../vnc.html:97
msgid "Alt"
msgstr ""
#: ../vnc.html:110
#: ../vnc.html:97
msgid "Toggle Alt"
msgstr ""
#: ../vnc.html:113
#: ../vnc.html:100
msgid "Toggle Windows"
msgstr ""
#: ../vnc.html:113
#: ../vnc.html:100
msgid "Windows"
msgstr ""
#: ../vnc.html:116
#: ../vnc.html:103
msgid "Send Tab"
msgstr ""
#: ../vnc.html:116
#: ../vnc.html:103
msgid "Tab"
msgstr ""
#: ../vnc.html:119
#: ../vnc.html:106
msgid "Esc"
msgstr ""
#: ../vnc.html:119
#: ../vnc.html:106
msgid "Send Escape"
msgstr ""
#: ../vnc.html:122
#: ../vnc.html:109
msgid "Ctrl+Alt+Del"
msgstr ""
#: ../vnc.html:122
#: ../vnc.html:109
msgid "Send Ctrl-Alt-Del"
msgstr ""
#: ../vnc.html:129
#: ../vnc.html:116
msgid "Shutdown/Reboot"
msgstr ""
#: ../vnc.html:129
#: ../vnc.html:116
msgid "Shutdown/Reboot..."
msgstr ""
#: ../vnc.html:135
#: ../vnc.html:122
msgid "Power"
msgstr ""
#: ../vnc.html:137
#: ../vnc.html:124
msgid "Shutdown"
msgstr ""
#: ../vnc.html:138
#: ../vnc.html:125
msgid "Reboot"
msgstr ""
#: ../vnc.html:139
#: ../vnc.html:126
msgid "Reset"
msgstr ""
#: ../vnc.html:144 ../vnc.html:150
#: ../vnc.html:131 ../vnc.html:137
msgid "Clipboard"
msgstr ""
#: ../vnc.html:154
#: ../vnc.html:141
msgid "Clear"
msgstr ""
#: ../vnc.html:160
#: ../vnc.html:147
msgid "Fullscreen"
msgstr ""
#: ../vnc.html:165 ../vnc.html:172
#: ../vnc.html:152 ../vnc.html:159
msgid "Settings"
msgstr ""
#: ../vnc.html:175
#: ../vnc.html:162
msgid "Shared Mode"
msgstr ""
#: ../vnc.html:178
#: ../vnc.html:165
msgid "View Only"
msgstr ""
#: ../vnc.html:182
#: ../vnc.html:169
msgid "Clip to Window"
msgstr ""
#: ../vnc.html:185
#: ../vnc.html:172
msgid "Scaling Mode:"
msgstr ""
#: ../vnc.html:187
#: ../vnc.html:174
msgid "None"
msgstr ""
#: ../vnc.html:188
#: ../vnc.html:175
msgid "Local Scaling"
msgstr ""
#: ../vnc.html:189
#: ../vnc.html:176
msgid "Remote Resizing"
msgstr ""
#: ../vnc.html:194
#: ../vnc.html:181
msgid "Advanced"
msgstr ""
#: ../vnc.html:197
#: ../vnc.html:184
msgid "Quality:"
msgstr ""
#: ../vnc.html:201
#: ../vnc.html:188
msgid "Compression level:"
msgstr ""
#: ../vnc.html:206
#: ../vnc.html:193
msgid "Repeater ID:"
msgstr ""
#: ../vnc.html:210
#: ../vnc.html:197
msgid "WebSocket"
msgstr ""
#: ../vnc.html:213
#: ../vnc.html:200
msgid "Encrypt"
msgstr ""
#: ../vnc.html:216
#: ../vnc.html:203
msgid "Host:"
msgstr ""
#: ../vnc.html:220
#: ../vnc.html:207
msgid "Port:"
msgstr ""
#: ../vnc.html:224
#: ../vnc.html:211
msgid "Path:"
msgstr ""
#: ../vnc.html:231
#: ../vnc.html:218
msgid "Automatic Reconnect"
msgstr ""
#: ../vnc.html:234
#: ../vnc.html:221
msgid "Reconnect Delay (ms):"
msgstr ""
#: ../vnc.html:239
#: ../vnc.html:226
msgid "Show Dot when No Cursor"
msgstr ""
#: ../vnc.html:244
#: ../vnc.html:231
msgid "Logging:"
msgstr ""
#: ../vnc.html:253
#: ../vnc.html:240
msgid "Version:"
msgstr ""
#: ../vnc.html:261
#: ../vnc.html:248
msgid "Disconnect"
msgstr ""
#: ../vnc.html:280
#: ../vnc.html:267
msgid "Connect"
msgstr ""
#: ../vnc.html:290
#: ../vnc.html:277
msgid "Username:"
msgstr ""
#: ../vnc.html:294
#: ../vnc.html:281
msgid "Password:"
msgstr ""
#: ../vnc.html:298
#: ../vnc.html:285
msgid "Send Credentials"
msgstr ""
#: ../vnc.html:308
#: ../vnc.html:295
msgid "Cancel"
msgstr ""
+299
View File
@@ -0,0 +1,299 @@
# Portuguese translations for noVNC package.
# Copyright (C) 2021 The noVNC Authors
# This file is distributed under the same license as the noVNC package.
# <liddack@outlook.com>, 2021.
#
msgid ""
msgstr ""
"Project-Id-Version: noVNC 1.2.0\n"
"Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
"POT-Creation-Date: 2021-03-15 21:55-0300\n"
"PO-Revision-Date: 2021-03-15 22:09-0300\n"
"Last-Translator: <liddack@outlook.com>\n"
"Language-Team: Brazilian Portuguese\n"
"Language: pt_BR\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"X-Generator: Poedit 2.4.1\n"
#: ../app/ui.js:400
msgid "Connecting..."
msgstr "Conectando..."
#: ../app/ui.js:407
msgid "Disconnecting..."
msgstr "Desconectando..."
#: ../app/ui.js:413
msgid "Reconnecting..."
msgstr "Reconectando..."
#: ../app/ui.js:418
msgid "Internal error"
msgstr "Erro interno"
#: ../app/ui.js:1009
msgid "Must set host"
msgstr "É necessário definir o host"
#: ../app/ui.js:1091
msgid "Connected (encrypted) to "
msgstr "Conectado (com criptografia) a "
#: ../app/ui.js:1093
msgid "Connected (unencrypted) to "
msgstr "Conectado (sem criptografia) a "
#: ../app/ui.js:1116
msgid "Something went wrong, connection is closed"
msgstr "Algo deu errado. A conexão foi encerrada."
#: ../app/ui.js:1119
msgid "Failed to connect to server"
msgstr "Falha ao conectar-se ao servidor"
#: ../app/ui.js:1129
msgid "Disconnected"
msgstr "Desconectado"
#: ../app/ui.js:1144
msgid "New connection has been rejected with reason: "
msgstr "A nova conexão foi rejeitada pelo motivo: "
#: ../app/ui.js:1147
msgid "New connection has been rejected"
msgstr "A nova conexão foi rejeitada"
#: ../app/ui.js:1182
msgid "Credentials are required"
msgstr "Credenciais são obrigatórias"
#: ../vnc.html:61
msgid "noVNC encountered an error:"
msgstr "O noVNC encontrou um erro:"
#: ../vnc.html:71
msgid "Hide/Show the control bar"
msgstr "Esconder/mostrar a barra de controles"
#: ../vnc.html:78
msgid "Drag"
msgstr "Arrastar"
#: ../vnc.html:78
msgid "Move/Drag Viewport"
msgstr "Mover/arrastar a janela"
#: ../vnc.html:84
msgid "Keyboard"
msgstr "Teclado"
#: ../vnc.html:84
msgid "Show Keyboard"
msgstr "Mostrar teclado"
#: ../vnc.html:89
msgid "Extra keys"
msgstr "Teclas adicionais"
#: ../vnc.html:89
msgid "Show Extra Keys"
msgstr "Mostar teclas adicionais"
#: ../vnc.html:94
msgid "Ctrl"
msgstr "Ctrl"
#: ../vnc.html:94
msgid "Toggle Ctrl"
msgstr "Pressionar/soltar Ctrl"
#: ../vnc.html:97
msgid "Alt"
msgstr "Alt"
#: ../vnc.html:97
msgid "Toggle Alt"
msgstr "Pressionar/soltar Alt"
#: ../vnc.html:100
msgid "Toggle Windows"
msgstr "Pressionar/soltar Windows"
#: ../vnc.html:100
msgid "Windows"
msgstr "Windows"
#: ../vnc.html:103
msgid "Send Tab"
msgstr "Enviar Tab"
#: ../vnc.html:103
msgid "Tab"
msgstr "Tab"
#: ../vnc.html:106
msgid "Esc"
msgstr "Esc"
#: ../vnc.html:106
msgid "Send Escape"
msgstr "Enviar Esc"
#: ../vnc.html:109
msgid "Ctrl+Alt+Del"
msgstr "Ctrl+Alt+Del"
#: ../vnc.html:109
msgid "Send Ctrl-Alt-Del"
msgstr "Enviar Ctrl-Alt-Del"
#: ../vnc.html:116
msgid "Shutdown/Reboot"
msgstr "Desligar/reiniciar"
#: ../vnc.html:116
msgid "Shutdown/Reboot..."
msgstr "Desligar/reiniciar..."
#: ../vnc.html:122
msgid "Power"
msgstr "Ligar"
#: ../vnc.html:124
msgid "Shutdown"
msgstr "Desligar"
#: ../vnc.html:125
msgid "Reboot"
msgstr "Reiniciar"
#: ../vnc.html:126
msgid "Reset"
msgstr "Reiniciar (forçado)"
#: ../vnc.html:131 ../vnc.html:137
msgid "Clipboard"
msgstr "Área de transferência"
#: ../vnc.html:141
msgid "Clear"
msgstr "Limpar"
#: ../vnc.html:147
msgid "Fullscreen"
msgstr "Tela cheia"
#: ../vnc.html:152 ../vnc.html:159
msgid "Settings"
msgstr "Configurações"
#: ../vnc.html:162
msgid "Shared Mode"
msgstr "Modo compartilhado"
#: ../vnc.html:165
msgid "View Only"
msgstr "Apenas visualizar"
#: ../vnc.html:169
msgid "Clip to Window"
msgstr "Recortar à janela"
#: ../vnc.html:172
msgid "Scaling Mode:"
msgstr "Modo de dimensionamento:"
#: ../vnc.html:174
msgid "None"
msgstr "Nenhum"
#: ../vnc.html:175
msgid "Local Scaling"
msgstr "Local"
#: ../vnc.html:176
msgid "Remote Resizing"
msgstr "Remoto"
#: ../vnc.html:181
msgid "Advanced"
msgstr "Avançado"
#: ../vnc.html:184
msgid "Quality:"
msgstr "Qualidade:"
#: ../vnc.html:188
msgid "Compression level:"
msgstr "Nível de compressão:"
#: ../vnc.html:193
msgid "Repeater ID:"
msgstr "ID do repetidor:"
#: ../vnc.html:197
msgid "WebSocket"
msgstr "WebSocket"
#: ../vnc.html:200
msgid "Encrypt"
msgstr "Criptografar"
#: ../vnc.html:203
msgid "Host:"
msgstr "Host:"
#: ../vnc.html:207
msgid "Port:"
msgstr "Porta:"
#: ../vnc.html:211
msgid "Path:"
msgstr "Caminho:"
#: ../vnc.html:218
msgid "Automatic Reconnect"
msgstr "Reconexão automática"
#: ../vnc.html:221
msgid "Reconnect Delay (ms):"
msgstr "Atraso da reconexão (ms)"
#: ../vnc.html:226
msgid "Show Dot when No Cursor"
msgstr "Mostrar ponto quando não há cursor"
#: ../vnc.html:231
msgid "Logging:"
msgstr "Registros:"
#: ../vnc.html:240
msgid "Version:"
msgstr "Versão:"
#: ../vnc.html:248
msgid "Disconnect"
msgstr "Desconectar"
#: ../vnc.html:267
msgid "Connect"
msgstr "Conectar"
#: ../vnc.html:277
msgid "Username:"
msgstr "Nome de usuário:"
#: ../vnc.html:281
msgid "Password:"
msgstr "Senha:"
#: ../vnc.html:285
msgid "Send Credentials"
msgstr "Enviar credenciais"
#: ../vnc.html:295
msgid "Cancel"
msgstr "Cancelar"
+74 -74
View File
@@ -6,10 +6,10 @@
#
msgid ""
msgstr ""
"Project-Id-Version: noVNC 1.2.0\n"
"Project-Id-Version: noVNC 1.3.0\n"
"Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
"POT-Creation-Date: 2020-07-03 16:11+0200\n"
"PO-Revision-Date: 2020-07-08 23:18+0200\n"
"POT-Creation-Date: 2021-08-27 16:03+0200\n"
"PO-Revision-Date: 2021-08-27 16:18+0200\n"
"Last-Translator: Samuel Mannehed <samuel@cendio.se>\n"
"Language-Team: none\n"
"Language: sv\n"
@@ -19,282 +19,282 @@ msgstr ""
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 2.0.3\n"
#: ../app/ui.js:394
#: ../app/ui.js:400
msgid "Connecting..."
msgstr "Ansluter..."
#: ../app/ui.js:401
#: ../app/ui.js:407
msgid "Disconnecting..."
msgstr "Kopplar ner..."
#: ../app/ui.js:407
#: ../app/ui.js:413
msgid "Reconnecting..."
msgstr "Återansluter..."
#: ../app/ui.js:412
#: ../app/ui.js:418
msgid "Internal error"
msgstr "Internt fel"
#: ../app/ui.js:1008
#: ../app/ui.js:1009
msgid "Must set host"
msgstr "Du måste specifiera en värd"
#: ../app/ui.js:1090
#: ../app/ui.js:1091
msgid "Connected (encrypted) to "
msgstr "Ansluten (krypterat) till "
#: ../app/ui.js:1092
#: ../app/ui.js:1093
msgid "Connected (unencrypted) to "
msgstr "Ansluten (okrypterat) till "
#: ../app/ui.js:1115
#: ../app/ui.js:1116
msgid "Something went wrong, connection is closed"
msgstr "Något gick fel, anslutningen avslutades"
#: ../app/ui.js:1118
#: ../app/ui.js:1119
msgid "Failed to connect to server"
msgstr "Misslyckades att ansluta till servern"
#: ../app/ui.js:1128
#: ../app/ui.js:1129
msgid "Disconnected"
msgstr "Frånkopplad"
#: ../app/ui.js:1143
#: ../app/ui.js:1144
msgid "New connection has been rejected with reason: "
msgstr "Ny anslutning har blivit nekad med följande skäl: "
#: ../app/ui.js:1146
#: ../app/ui.js:1147
msgid "New connection has been rejected"
msgstr "Ny anslutning har blivit nekad"
#: ../app/ui.js:1181
#: ../app/ui.js:1182
msgid "Credentials are required"
msgstr "Användaruppgifter krävs"
#: ../vnc.html:74
#: ../vnc.html:61
msgid "noVNC encountered an error:"
msgstr "noVNC stötte på ett problem:"
#: ../vnc.html:84
#: ../vnc.html:71
msgid "Hide/Show the control bar"
msgstr "Göm/Visa kontrollbaren"
#: ../vnc.html:91
#: ../vnc.html:78
msgid "Drag"
msgstr "Dra"
#: ../vnc.html:91
#: ../vnc.html:78
msgid "Move/Drag Viewport"
msgstr "Flytta/Dra Vyn"
#: ../vnc.html:97
#: ../vnc.html:84
msgid "Keyboard"
msgstr "Tangentbord"
#: ../vnc.html:97
#: ../vnc.html:84
msgid "Show Keyboard"
msgstr "Visa Tangentbord"
#: ../vnc.html:102
#: ../vnc.html:89
msgid "Extra keys"
msgstr "Extraknappar"
#: ../vnc.html:102
#: ../vnc.html:89
msgid "Show Extra Keys"
msgstr "Visa Extraknappar"
#: ../vnc.html:107
#: ../vnc.html:94
msgid "Ctrl"
msgstr "Ctrl"
#: ../vnc.html:107
#: ../vnc.html:94
msgid "Toggle Ctrl"
msgstr "Växla Ctrl"
#: ../vnc.html:110
#: ../vnc.html:97
msgid "Alt"
msgstr "Alt"
#: ../vnc.html:110
#: ../vnc.html:97
msgid "Toggle Alt"
msgstr "Växla Alt"
#: ../vnc.html:113
#: ../vnc.html:100
msgid "Toggle Windows"
msgstr "Växla Windows"
#: ../vnc.html:113
#: ../vnc.html:100
msgid "Windows"
msgstr "Windows"
#: ../vnc.html:116
#: ../vnc.html:103
msgid "Send Tab"
msgstr "Skicka Tab"
#: ../vnc.html:116
#: ../vnc.html:103
msgid "Tab"
msgstr "Tab"
#: ../vnc.html:119
#: ../vnc.html:106
msgid "Esc"
msgstr "Esc"
#: ../vnc.html:119
#: ../vnc.html:106
msgid "Send Escape"
msgstr "Skicka Escape"
#: ../vnc.html:122
#: ../vnc.html:109
msgid "Ctrl+Alt+Del"
msgstr "Ctrl+Alt+Del"
#: ../vnc.html:122
#: ../vnc.html:109
msgid "Send Ctrl-Alt-Del"
msgstr "Skicka Ctrl-Alt-Del"
#: ../vnc.html:129
#: ../vnc.html:116
msgid "Shutdown/Reboot"
msgstr "Stäng av/Boota om"
#: ../vnc.html:129
#: ../vnc.html:116
msgid "Shutdown/Reboot..."
msgstr "Stäng av/Boota om..."
#: ../vnc.html:135
#: ../vnc.html:122
msgid "Power"
msgstr "Ström"
#: ../vnc.html:137
#: ../vnc.html:124
msgid "Shutdown"
msgstr "Stäng av"
#: ../vnc.html:138
#: ../vnc.html:125
msgid "Reboot"
msgstr "Boota om"
#: ../vnc.html:139
#: ../vnc.html:126
msgid "Reset"
msgstr "Återställ"
#: ../vnc.html:144 ../vnc.html:150
#: ../vnc.html:131 ../vnc.html:137
msgid "Clipboard"
msgstr "Urklipp"
#: ../vnc.html:154
#: ../vnc.html:141
msgid "Clear"
msgstr "Rensa"
#: ../vnc.html:160
#: ../vnc.html:147
msgid "Fullscreen"
msgstr "Fullskärm"
#: ../vnc.html:165 ../vnc.html:172
#: ../vnc.html:152 ../vnc.html:159
msgid "Settings"
msgstr "Inställningar"
#: ../vnc.html:175
#: ../vnc.html:162
msgid "Shared Mode"
msgstr "Delat Läge"
#: ../vnc.html:178
#: ../vnc.html:165
msgid "View Only"
msgstr "Endast Visning"
#: ../vnc.html:182
#: ../vnc.html:169
msgid "Clip to Window"
msgstr "Begränsa till Fönster"
#: ../vnc.html:185
#: ../vnc.html:172
msgid "Scaling Mode:"
msgstr "Skalningsläge:"
#: ../vnc.html:187
#: ../vnc.html:174
msgid "None"
msgstr "Ingen"
#: ../vnc.html:188
#: ../vnc.html:175
msgid "Local Scaling"
msgstr "Lokal Skalning"
#: ../vnc.html:189
#: ../vnc.html:176
msgid "Remote Resizing"
msgstr "Ändra Storlek"
#: ../vnc.html:194
#: ../vnc.html:181
msgid "Advanced"
msgstr "Avancerat"
#: ../vnc.html:197
#: ../vnc.html:184
msgid "Quality:"
msgstr "Kvalitet:"
#: ../vnc.html:201
#: ../vnc.html:188
msgid "Compression level:"
msgstr "Kompressionsnivå:"
#: ../vnc.html:206
#: ../vnc.html:193
msgid "Repeater ID:"
msgstr "Repeater-ID:"
#: ../vnc.html:210
#: ../vnc.html:197
msgid "WebSocket"
msgstr "WebSocket"
#: ../vnc.html:213
#: ../vnc.html:200
msgid "Encrypt"
msgstr "Kryptera"
#: ../vnc.html:216
#: ../vnc.html:203
msgid "Host:"
msgstr "Värd:"
#: ../vnc.html:220
#: ../vnc.html:207
msgid "Port:"
msgstr "Port:"
#: ../vnc.html:224
#: ../vnc.html:211
msgid "Path:"
msgstr "Sökväg:"
#: ../vnc.html:231
#: ../vnc.html:218
msgid "Automatic Reconnect"
msgstr "Automatisk Återanslutning"
#: ../vnc.html:234
#: ../vnc.html:221
msgid "Reconnect Delay (ms):"
msgstr "Fördröjning (ms):"
#: ../vnc.html:239
#: ../vnc.html:226
msgid "Show Dot when No Cursor"
msgstr "Visa prick när ingen muspekare finns"
#: ../vnc.html:244
#: ../vnc.html:231
msgid "Logging:"
msgstr "Loggning:"
#: ../vnc.html:253
#: ../vnc.html:240
msgid "Version:"
msgstr "Version:"
#: ../vnc.html:261
#: ../vnc.html:248
msgid "Disconnect"
msgstr "Koppla från"
#: ../vnc.html:280
#: ../vnc.html:267
msgid "Connect"
msgstr "Anslut"
#: ../vnc.html:290
#: ../vnc.html:277
msgid "Username:"
msgstr "Användarnamn:"
#: ../vnc.html:294
#: ../vnc.html:281
msgid "Password:"
msgstr "Lösenord:"
#: ../vnc.html:298
#: ../vnc.html:285
msgid "Send Credentials"
msgstr "Skicka Användaruppgifter"
#: ../vnc.html:308
#: ../vnc.html:295
msgid "Cancel"
msgstr "Avbryt"
msgstr "Avbryt"
+1 -1
View File
@@ -24,6 +24,6 @@ snapctl get services | jq -c '.[]' | while read service; do # for each service t
echo "novnc: not starting service ${service} with listen_port ${listen_port} and vnc_host_port ${vnc_host_port}"
else
# start (and fork with '&') the service using the specified listen port and VNC host:port
$SNAP/launch.sh --listen $listen_port --vnc $vnc_host_port &
$SNAP/novnc_proxy --listen $listen_port --vnc $vnc_host_port &
fi
done
+23 -4
View File
@@ -13,13 +13,32 @@ confinement: strict
parts:
novnc:
source: build/
source: .
plugin: dump
organize:
utils/novnc_proxy: /
stage:
- vnc.html
- app
- core/**/*.js
- vendor/**/*.js
- novnc_proxy
stage-packages:
- bash
svc-script:
source: snap/local
plugin: dump
stage:
- svc_wrapper.sh
stage-packages:
- websockify
- bash
- jq
- python-numpy
websockify:
source: https://github.com/novnc/websockify/archive/v0.9.0.tar.gz
plugin: python
stage-packages:
- python3-numpy
hooks:
@@ -28,7 +47,7 @@ hooks:
apps:
novnc:
command: ./launch.sh
command: ./novnc_proxy
plugs: [network, network-bind]
novncsvc:
command: ./svc_wrapper.sh
+7 -6
View File
@@ -1,16 +1,17 @@
// noVNC specific assertions
chai.use(function (_chai, utils) {
_chai.Assertion.addMethod('displayed', function (targetData) {
function _equal(a, b) {
return a === b;
}
_chai.Assertion.addMethod('displayed', function (targetData, cmp=_equal) {
const obj = this._obj;
const ctx = obj._target.getContext('2d');
const dataCl = ctx.getImageData(0, 0, obj._target.width, obj._target.height).data;
// NB(directxman12): PhantomJS 1.x doesn't implement Uint8ClampedArray, so work around that
const data = new Uint8Array(dataCl);
const len = dataCl.length;
const data = ctx.getImageData(0, 0, obj._target.width, obj._target.height).data;
const len = data.length;
new chai.Assertion(len).to.be.equal(targetData.length, "unexpected display size");
let same = true;
for (let i = 0; i < len; i++) {
if (data[i] != targetData[i]) {
if (!cmp(data[i], targetData[i])) {
same = false;
break;
}
+7 -15
View File
@@ -1,23 +1,15 @@
import Base64 from '../core/base64.js';
// PhantomJS can't create Event objects directly, so we need to use this
function makeEvent(name, props) {
const evt = document.createEvent('Event');
evt.initEvent(name, true, true);
if (props) {
for (let prop in props) {
evt[prop] = props[prop];
}
}
return evt;
}
export default class FakeWebSocket {
constructor(uri, protocols) {
this.url = uri;
this.binaryType = "arraybuffer";
this.extensions = "";
this.onerror = null;
this.onmessage = null;
this.onopen = null;
if (!protocols || typeof protocols === 'string') {
this.protocol = protocols;
} else {
@@ -35,7 +27,7 @@ export default class FakeWebSocket {
close(code, reason) {
this.readyState = FakeWebSocket.CLOSED;
if (this.onclose) {
this.onclose(makeEvent("close", { 'code': code, 'reason': reason, 'wasClean': true }));
this.onclose(new CloseEvent("close", { 'code': code, 'reason': reason, 'wasClean': true }));
}
}
@@ -58,7 +50,7 @@ export default class FakeWebSocket {
_open() {
this.readyState = FakeWebSocket.OPEN;
if (this.onopen) {
this.onopen(makeEvent('open'));
this.onopen(new Event('open'));
}
}
@@ -67,7 +59,7 @@ export default class FakeWebSocket {
// neatly packaged
for (let i = 0;i < data.length;i++) {
let buf = data.subarray(i, i+1);
this.onmessage(makeEvent("message", { 'data': buf }));
this.onmessage(new MessageEvent("message", { 'data': buf }));
}
}
}
-48
View File
@@ -1,48 +0,0 @@
const TEST_REGEXP = /test\..*\.js/;
const allTestFiles = [];
const extraFiles = ['/base/tests/assertions.js'];
Object.keys(window.__karma__.files).forEach(function (file) {
if (TEST_REGEXP.test(file)) {
// TODO: normalize?
allTestFiles.push(file);
}
});
// Stub out mocha's start function so we can run it once we're done loading
mocha.origRun = mocha.run;
mocha.run = function () {};
let script;
// Script to import all our tests
script = document.createElement("script");
script.type = "module";
script.text = "";
let allModules = allTestFiles.concat(extraFiles);
allModules.forEach(function (file) {
script.text += "import \"" + file + "\";\n";
});
script.text += "\nmocha.origRun();\n";
document.body.appendChild(script);
// Fallback code for browsers that don't support modules (IE)
script = document.createElement("script");
script.type = "module";
script.text = "window._noVNC_has_module_support = true;\n";
document.body.appendChild(script);
function fallback() {
if (!window._noVNC_has_module_support) {
/* eslint-disable no-console */
if (console) {
console.log("No module support detected. Loading fallback...");
}
/* eslint-enable no-console */
let loader = document.createElement("script");
loader.src = "base/vendor/browser-es-module-loader/dist/browser-es-module-loader.js";
document.body.appendChild(loader);
}
}
setTimeout(fallback, 500);
+11 -6
View File
@@ -1,4 +1,4 @@
/* global vncFrameData, vncFrameEncoding */
/* global VNC_frame_data, VNC_frame_encoding */
import * as WebUtil from '../app/webutil.js';
import RecordingPlayer from './playback.js';
@@ -41,7 +41,8 @@ function enableUI() {
document.getElementById('mode1').checked = true;
}
message("Loaded " + vncFrameData.length + " frames");
/* eslint-disable-next-line camelcase */
message("Loaded " + VNC_frame_data.length + " frames");
const startButton = document.getElementById('startButton');
startButton.disabled = false;
@@ -49,12 +50,16 @@ function enableUI() {
message("Converting...");
frames = vncFrameData;
/* eslint-disable-next-line camelcase */
frames = VNC_frame_data;
let encoding;
// Only present in older recordings
if (window.vncFrameEncoding) {
encoding = vncFrameEncoding;
/* eslint-disable camelcase */
if (window.VNC_frame_encoding) {
// Only present in older recordings
encoding = VNC_frame_encoding;
/* eslint-enable camelcase */
} else {
let frame = frames[0];
let start = frame.indexOf('{', 1) + 1;
+23 -17
View File
@@ -42,6 +42,24 @@ if (window.setImmediate === undefined) {
});
}
class FakeWebSocket {
constructor() {
this.binaryType = "arraybuffer";
this.protocol = "";
this.readyState = "open";
this.onerror = () => {};
this.onmessage = () => {};
this.onopen = () => {};
}
send() {
}
close() {
}
}
export default class RecordingPlayer {
constructor(frames, disconnected) {
this._frames = frames;
@@ -63,13 +81,13 @@ export default class RecordingPlayer {
run(realtime, trafficManagement) {
// initialize a new RFB
this._rfb = new RFB(document.getElementById('VNC_screen'), 'wss://test');
this._ws = new FakeWebSocket();
this._rfb = new RFB(document.getElementById('VNC_screen'), this._ws);
this._rfb.viewOnly = true;
this._rfb.addEventListener("disconnect",
this._handleDisconnect.bind(this));
this._rfb.addEventListener("credentialsrequired",
this._handleCredentials.bind(this));
this._enablePlaybackMode();
// reset the frame index and timer
this._frameIndex = 0;
@@ -79,19 +97,7 @@ export default class RecordingPlayer {
this._trafficManagement = (trafficManagement === undefined) ? !realtime : trafficManagement;
this._running = true;
}
// _enablePlaybackMode mocks out things not required for running playback
_enablePlaybackMode() {
const self = this;
this._rfb._sock.send = () => {};
this._rfb._sock.close = () => {};
this._rfb._sock.flush = () => {};
this._rfb._sock.open = function () {
this.init();
this._eventHandlers.open();
self._queueNextPacket();
};
this._queueNextPacket();
}
_queueNextPacket() {
@@ -136,7 +142,7 @@ export default class RecordingPlayer {
const frame = this._frames[this._frameIndex];
this._rfb._sock._recv_message({'data': frame.data});
this._ws.onmessage({'data': frame.data});
this._frameIndex++;
this._queueNextPacket();
@@ -153,7 +159,7 @@ export default class RecordingPlayer {
this._rfb._display.flush();
} else {
this._running = false;
this._rfb._sock._eventHandlers.close({code: 1000, reason: ""});
this._ws.onclose({code: 1000, reason: ""});
delete this._rfb;
this.onfinish((new Date()).getTime() - this._startTime);
}
+83
View File
@@ -0,0 +1,83 @@
const expect = chai.expect;
import Websock from '../core/websock.js';
import Display from '../core/display.js';
import CopyRectDecoder from '../core/decoders/copyrect.js';
import FakeWebSocket from './fake.websocket.js';
function testDecodeRect(decoder, x, y, width, height, data, display, depth) {
let sock;
sock = new Websock;
sock.open("ws://example.com");
sock.on('message', () => {
decoder.decodeRect(x, y, width, height, sock, display, depth);
});
// Empty messages are filtered at multiple layers, so we need to
// do a direct call
if (data.length === 0) {
decoder.decodeRect(x, y, width, height, sock, display, depth);
} else {
sock._websocket._receiveData(new Uint8Array(data));
}
display.flip();
}
describe('CopyRect Decoder', function () {
let decoder;
let display;
before(FakeWebSocket.replace);
after(FakeWebSocket.restore);
beforeEach(function () {
decoder = new CopyRectDecoder();
display = new Display(document.createElement('canvas'));
display.resize(4, 4);
});
it('should handle the CopyRect encoding', function () {
// seed some initial data to copy
display.fillRect(0, 0, 4, 4, [ 0x11, 0x22, 0x33 ]);
display.fillRect(0, 0, 2, 2, [ 0x00, 0x00, 0xff ]);
display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);
testDecodeRect(decoder, 0, 2, 2, 2,
[0x00, 0x02, 0x00, 0x00],
display, 24);
testDecodeRect(decoder, 2, 2, 2, 2,
[0x00, 0x00, 0x00, 0x00],
display, 24);
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]);
expect(display).to.have.displayed(targetData);
});
it('should handle empty rects', function () {
display.fillRect(0, 0, 4, 4, [ 0x00, 0x00, 0xff ]);
display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);
display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]);
testDecodeRect(decoder, 1, 2, 0, 0, [0x00, 0x00, 0x00, 0x00], display, 24);
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]);
expect(display).to.have.displayed(targetData);
});
});
+18 -101
View File
@@ -4,28 +4,27 @@ import Base64 from '../core/base64.js';
import Display from '../core/display.js';
describe('Display/Canvas Helper', function () {
const checkedData = new Uint8Array([
const checkedData = new Uint8ClampedArray([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]);
const basicData = new Uint8Array([0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0xff, 0xff, 0xff, 255]);
const basicData = new Uint8ClampedArray([0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0xff, 0xff, 0xff, 255]);
function makeImageCanvas(inputData) {
function makeImageCanvas(inputData, width, height) {
const canvas = document.createElement('canvas');
canvas.width = 4;
canvas.height = 4;
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
const data = ctx.createImageData(4, 4);
for (let i = 0; i < checkedData.length; i++) { data.data[i] = inputData[i]; }
const data = new ImageData(inputData, width, height);
ctx.putImageData(data, 0, 0);
return canvas;
}
function makeImagePng(inputData) {
const canvas = makeImageCanvas(inputData);
function makeImagePng(inputData, width, height) {
const canvas = makeImageCanvas(inputData, width, height);
const url = canvas.toDataURL();
const data = url.split(",")[1];
return Base64.decode(data);
@@ -44,7 +43,7 @@ describe('Display/Canvas Helper', function () {
it('should take viewport location into consideration when drawing images', function () {
display.resize(4, 4);
display.viewportChangeSize(2, 2);
display.drawImage(makeImageCanvas(basicData), 1, 1);
display.drawImage(makeImageCanvas(basicData, 4, 1), 1, 1);
display.flip();
const expected = new Uint8Array(16);
@@ -128,7 +127,7 @@ describe('Display/Canvas Helper', function () {
});
it('should keep the framebuffer data', function () {
display.fillRect(0, 0, 4, 4, [0, 0, 0xff]);
display.fillRect(0, 0, 4, 4, [0xff, 0, 0]);
display.resize(2, 2);
display.flip();
const expected = [];
@@ -271,7 +270,7 @@ describe('Display/Canvas Helper', function () {
});
it('should not draw directly on the target canvas', function () {
display.fillRect(0, 0, 4, 4, [0, 0, 0xff]);
display.fillRect(0, 0, 4, 4, [0xff, 0, 0]);
display.flip();
display.fillRect(0, 0, 4, 4, [0, 0xff, 0]);
const expected = [];
@@ -285,22 +284,22 @@ describe('Display/Canvas Helper', function () {
it('should support filling a rectangle with particular color via #fillRect', function () {
display.fillRect(0, 0, 4, 4, [0, 0xff, 0]);
display.fillRect(0, 0, 2, 2, [0xff, 0, 0]);
display.fillRect(2, 2, 2, 2, [0xff, 0, 0]);
display.fillRect(0, 0, 2, 2, [0, 0, 0xff]);
display.fillRect(2, 2, 2, 2, [0, 0, 0xff]);
display.flip();
expect(display).to.have.displayed(checkedData);
});
it('should support copying an portion of the canvas via #copyImage', function () {
display.fillRect(0, 0, 4, 4, [0, 0xff, 0]);
display.fillRect(0, 0, 2, 2, [0xff, 0, 0x00]);
display.fillRect(0, 0, 2, 2, [0, 0, 0xff]);
display.copyImage(0, 0, 2, 2, 2, 2);
display.flip();
expect(display).to.have.displayed(checkedData);
});
it('should support drawing images via #imageRect', function (done) {
display.imageRect(0, 0, 4, 4, "image/png", makeImagePng(checkedData));
display.imageRect(0, 0, 4, 4, "image/png", makeImagePng(checkedData, 4, 4));
display.flip();
display.onflush = () => {
expect(display).to.have.displayed(checkedData);
@@ -309,68 +308,14 @@ describe('Display/Canvas Helper', function () {
display.flush();
});
it('should support drawing tile data with a background color and sub tiles', function () {
display.startTile(0, 0, 4, 4, [0, 0xff, 0]);
display.subTile(0, 0, 2, 2, [0xff, 0, 0]);
display.subTile(2, 2, 2, 2, [0xff, 0, 0]);
display.finishTile();
display.flip();
expect(display).to.have.displayed(checkedData);
});
// We have a special cache for 16x16 tiles that we need to test
it('should support drawing a 16x16 tile', function () {
const largeCheckedData = new Uint8Array(16*16*4);
display.resize(16, 16);
for (let y = 0;y < 16;y++) {
for (let x = 0;x < 16;x++) {
let pixel;
if ((x < 4) && (y < 4)) {
// NB: of course IE11 doesn't support #slice on ArrayBufferViews...
pixel = Array.prototype.slice.call(checkedData, (y*4+x)*4, (y*4+x+1)*4);
} else {
pixel = [0, 0xff, 0, 255];
}
largeCheckedData.set(pixel, (y*16+x)*4);
}
}
display.startTile(0, 0, 16, 16, [0, 0xff, 0]);
display.subTile(0, 0, 2, 2, [0xff, 0, 0]);
display.subTile(2, 2, 2, 2, [0xff, 0, 0]);
display.finishTile();
display.flip();
expect(display).to.have.displayed(largeCheckedData);
});
it('should support drawing BGRX blit images with true color via #blitImage', function () {
const data = [];
for (let i = 0; i < 16; i++) {
data[i * 4] = checkedData[i * 4 + 2];
data[i * 4 + 1] = checkedData[i * 4 + 1];
data[i * 4 + 2] = checkedData[i * 4];
data[i * 4 + 3] = checkedData[i * 4 + 3];
}
display.blitImage(0, 0, 4, 4, data, 0);
display.flip();
expect(display).to.have.displayed(checkedData);
});
it('should support drawing RGB blit images with true color via #blitRgbImage', function () {
const data = [];
for (let i = 0; i < 16; i++) {
data[i * 3] = checkedData[i * 4];
data[i * 3 + 1] = checkedData[i * 4 + 1];
data[i * 3 + 2] = checkedData[i * 4 + 2];
}
display.blitRgbImage(0, 0, 4, 4, data, 0);
it('should support blit images with true color via #blitImage', function () {
display.blitImage(0, 0, 4, 4, checkedData, 0);
display.flip();
expect(display).to.have.displayed(checkedData);
});
it('should support drawing an image object via #drawImage', function () {
const img = makeImageCanvas(checkedData);
const img = makeImageCanvas(checkedData, 4, 4);
display.drawImage(img, 0, 0);
display.flip();
expect(display).to.have.displayed(checkedData);
@@ -415,27 +360,6 @@ describe('Display/Canvas Helper', function () {
expect(img.addEventListener).to.have.been.calledOnce;
});
it('should wait if an image is incorrectly loaded', function () {
const img = { complete: true, width: 0, height: 0, addEventListener: sinon.spy() };
display._renderQ = [{ type: 'img', x: 3, y: 4, width: 4, height: 4, img: img },
{ type: 'fill', x: 1, y: 2, width: 3, height: 4, color: 5 }];
display.drawImage = sinon.spy();
display.fillRect = sinon.spy();
display._scanRenderQ();
expect(display.drawImage).to.not.have.been.called;
expect(display.fillRect).to.not.have.been.called;
expect(img.addEventListener).to.have.been.calledOnce;
display._renderQ[0].img.complete = true;
display._renderQ[0].img.width = 4;
display._renderQ[0].img.height = 4;
display._scanRenderQ();
expect(display.drawImage).to.have.been.calledOnce;
expect(display.fillRect).to.have.been.calledOnce;
expect(img.addEventListener).to.have.been.calledOnce;
});
it('should call callback when queue is flushed', function () {
display.onflush = sinon.spy();
display.fillRect(0, 0, 4, 4, [0, 0xff, 0]);
@@ -451,13 +375,6 @@ describe('Display/Canvas Helper', function () {
expect(display.blitImage).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9], 0);
});
it('should draw a blit RGB image on type "blitRgb"', function () {
display.blitRgbImage = sinon.spy();
display._renderQPush({ type: 'blitRgb', x: 3, y: 4, width: 5, height: 6, data: [7, 8, 9] });
expect(display.blitRgbImage).to.have.been.calledOnce;
expect(display.blitRgbImage).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9], 0);
});
it('should copy a region on type "copy"', function () {
display.copyImage = sinon.spy();
display._renderQPush({ type: 'copy', x: 3, y: 4, width: 5, height: 6, oldX: 7, oldY: 8 });
-7
View File
@@ -3,7 +3,6 @@ const expect = chai.expect;
import EventTargetMixin from '../core/util/eventtarget.js';
import GestureHandler from '../core/input/gesturehandler.js';
import * as browser from '../core/util/browser.js';
class DummyTarget extends EventTargetMixin {
}
@@ -23,12 +22,6 @@ describe('Gesture handler', function () {
});
beforeEach(function () {
// Touch events and gestures are not supported on IE
if (browser.isIE()) {
this.skip();
return;
}
target = new DummyTarget();
gestures = sinon.spy();
target.addEventListener('gesturestart', gestures);
+33 -66
View File
@@ -2,7 +2,6 @@
import keysyms from '../core/input/keysymdef.js';
import * as KeyboardUtil from "../core/input/util.js";
import * as browser from '../core/util/browser.js';
describe('Helpers', function () {
"use strict";
@@ -70,11 +69,6 @@ describe('Helpers', function () {
// environments, so we need to redefine it whilst running these
// tests.
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
if (origNavigator === undefined) {
// Object.getOwnPropertyDescriptor() doesn't work
// properly in any version of IE
this.skip();
}
Object.defineProperty(window, "navigator", {value: {}});
if (window.navigator.platform !== undefined) {
@@ -102,14 +96,10 @@ describe('Helpers', function () {
describe('getKey', function () {
it('should prefer key', function () {
if (browser.isIE() || browser.isEdge()) this.skip();
expect(KeyboardUtil.getKey({key: 'a', charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43})).to.be.equal('a');
});
it('should map legacy values', function () {
expect(KeyboardUtil.getKey({key: 'Spacebar'})).to.be.equal(' ');
expect(KeyboardUtil.getKey({key: 'Left'})).to.be.equal('ArrowLeft');
expect(KeyboardUtil.getKey({key: 'OS'})).to.be.equal('Meta');
expect(KeyboardUtil.getKey({key: 'Win'})).to.be.equal('Meta');
expect(KeyboardUtil.getKey({key: 'UIKeyInputLeftArrow'})).to.be.equal('ArrowLeft');
});
it('should handle broken Delete', function () {
@@ -130,60 +120,6 @@ describe('Helpers', function () {
it('should return Unidentified when it cannot map the key', function () {
expect(KeyboardUtil.getKey({keycode: 0x42})).to.be.equal('Unidentified');
});
describe('Broken key AltGraph on IE/Edge', function () {
let origNavigator;
beforeEach(function () {
// window.navigator is a protected read-only property in many
// environments, so we need to redefine it whilst running these
// tests.
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
if (origNavigator === undefined) {
// Object.getOwnPropertyDescriptor() doesn't work
// properly in any version of IE
this.skip();
}
Object.defineProperty(window, "navigator", {value: {}});
if (window.navigator.platform !== undefined) {
// Object.defineProperty() doesn't work properly in old
// versions of Chrome
this.skip();
}
});
afterEach(function () {
if (origNavigator !== undefined) {
Object.defineProperty(window, "navigator", origNavigator);
}
});
it('should ignore printable character key on IE', function () {
window.navigator.userAgent = "Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko";
expect(KeyboardUtil.getKey({key: 'a'})).to.be.equal('Unidentified');
});
it('should ignore printable character key on Edge', function () {
window.navigator.userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36 Edge/14.14393";
expect(KeyboardUtil.getKey({key: 'a'})).to.be.equal('Unidentified');
});
it('should allow non-printable character key on IE', function () {
window.navigator.userAgent = "Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko";
expect(KeyboardUtil.getKey({key: 'Shift'})).to.be.equal('Shift');
});
it('should allow non-printable character key on Edge', function () {
window.navigator.userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36 Edge/14.14393";
expect(KeyboardUtil.getKey({key: 'Shift'})).to.be.equal('Shift');
});
it('should allow printable character key with charCode on IE', function () {
window.navigator.userAgent = "Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko";
expect(KeyboardUtil.getKey({key: 'a', charCode: 0x61})).to.be.equal('a');
expect(KeyboardUtil.getKey({key: 'Unidentified', charCode: 0x61})).to.be.equal('a');
});
it('should allow printable character key with charCode on Edge', function () {
window.navigator.userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36 Edge/14.14393";
expect(KeyboardUtil.getKey({key: 'a', charCode: 0x61})).to.be.equal('a');
expect(KeyboardUtil.getKey({key: 'Unidentified', charCode: 0x61})).to.be.equal('a');
});
});
});
describe('getKeysym', function () {
@@ -236,7 +172,6 @@ describe('Helpers', function () {
describe('Numpad', function () {
it('should handle Numpad numbers', function () {
if (browser.isIE() || browser.isEdge()) this.skip();
expect(KeyboardUtil.getKeysym({code: 'Digit5', key: '5', location: 0})).to.be.equal(0x0035);
expect(KeyboardUtil.getKeysym({code: 'Numpad5', key: '5', location: 3})).to.be.equal(0xFFB5);
});
@@ -247,10 +182,42 @@ describe('Helpers', function () {
expect(KeyboardUtil.getKeysym({code: 'NumpadDecimal', key: 'Delete', location: 3})).to.be.equal(0xFF9F);
});
it('should handle Numpad Decimal key', function () {
if (browser.isIE() || browser.isEdge()) this.skip();
expect(KeyboardUtil.getKeysym({code: 'NumpadDecimal', key: '.', location: 3})).to.be.equal(0xFFAE);
expect(KeyboardUtil.getKeysym({code: 'NumpadDecimal', key: ',', location: 3})).to.be.equal(0xFFAC);
});
});
describe('Japanese IM keys on Windows', function () {
let origNavigator;
beforeEach(function () {
// window.navigator is a protected read-only property in many
// environments, so we need to redefine it whilst running these
// tests.
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
Object.defineProperty(window, "navigator", {value: {}});
if (window.navigator.platform !== undefined) {
// Object.defineProperty() doesn't work properly in old
// versions of Chrome
this.skip();
}
window.navigator.platform = "Windows";
});
afterEach(function () {
if (origNavigator !== undefined) {
Object.defineProperty(window, "navigator", origNavigator);
}
});
const keys = { 'Zenkaku': 0xff2a, 'Hankaku': 0xff2a,
'Romaji': 0xff24, 'KanaMode': 0xff24 };
for (let [key, keysym] of Object.entries(keys)) {
it(`should fake combined key for ${key} on Windows`, function () {
expect(KeyboardUtil.getKeysym({code: 'FakeIM', key: key})).to.be.equal(keysym);
});
}
});
});
});
+232
View File
@@ -0,0 +1,232 @@
const expect = chai.expect;
import Websock from '../core/websock.js';
import Display from '../core/display.js';
import HextileDecoder from '../core/decoders/hextile.js';
import FakeWebSocket from './fake.websocket.js';
function testDecodeRect(decoder, x, y, width, height, data, display, depth) {
let sock;
sock = new Websock;
sock.open("ws://example.com");
sock.on('message', () => {
decoder.decodeRect(x, y, width, height, sock, display, depth);
});
// Empty messages are filtered at multiple layers, so we need to
// do a direct call
if (data.length === 0) {
decoder.decodeRect(x, y, width, height, sock, display, depth);
} else {
sock._websocket._receiveData(new Uint8Array(data));
}
display.flip();
}
function push32(arr, num) {
arr.push((num >> 24) & 0xFF,
(num >> 16) & 0xFF,
(num >> 8) & 0xFF,
num & 0xFF);
}
describe('Hextile Decoder', function () {
let decoder;
let display;
before(FakeWebSocket.replace);
after(FakeWebSocket.restore);
beforeEach(function () {
decoder = new HextileDecoder();
display = new Display(document.createElement('canvas'));
display.resize(4, 4);
});
it('should handle a tile with fg, bg specified, normal subrects', function () {
let data = [];
data.push(0x02 | 0x04 | 0x08); // bg spec, fg spec, anysubrects
push32(data, 0x00ff0000); // becomes 00ff0000 --> #00FF00 bg color
data.push(0x00); // becomes 0000ff00 --> #0000FF fg color
data.push(0x00);
data.push(0xff);
data.push(0x00);
data.push(2); // 2 subrects
data.push(0); // x: 0, y: 0
data.push(1 | (1 << 4)); // width: 2, height: 2
data.push(2 | (2 << 4)); // x: 2, y: 2
data.push(1 | (1 << 4)); // width: 2, height: 2
testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]);
expect(display).to.have.displayed(targetData);
});
it('should handle a raw tile', function () {
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]);
let data = [];
data.push(0x01); // raw
for (let i = 0; i < targetData.length; i += 4) {
data.push(targetData[i]);
data.push(targetData[i + 1]);
data.push(targetData[i + 2]);
// Last byte zero to test correct alpha handling
data.push(0);
}
testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
expect(display).to.have.displayed(targetData);
});
it('should handle a tile with only bg specified (solid bg)', function () {
let data = [];
data.push(0x02);
push32(data, 0x00ff0000); // becomes 00ff0000 --> #00FF00 bg color
testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
let expected = [];
for (let i = 0; i < 16; i++) {
push32(expected, 0x00ff00ff);
}
expect(display).to.have.displayed(new Uint8Array(expected));
});
it('should handle a tile with only bg specified and an empty frame afterwards', function () {
// set the width so we can have two tiles
display.resize(8, 4);
let data = [];
// send a bg frame
data.push(0x02);
push32(data, 0x00ff0000); // becomes 00ff0000 --> #00FF00 bg color
// send an empty frame
data.push(0x00);
testDecodeRect(decoder, 0, 0, 32, 4, data, display, 24);
let expected = [];
for (let i = 0; i < 16; i++) {
push32(expected, 0x00ff00ff); // rect 1: solid
}
for (let i = 0; i < 16; i++) {
push32(expected, 0x00ff00ff); // rect 2: same bkground color
}
expect(display).to.have.displayed(new Uint8Array(expected));
});
it('should handle a tile with bg and coloured subrects', function () {
let data = [];
data.push(0x02 | 0x08 | 0x10); // bg spec, anysubrects, colouredsubrects
push32(data, 0x00ff0000); // becomes 00ff0000 --> #00FF00 bg color
data.push(2); // 2 subrects
data.push(0x00); // becomes 0000ff00 --> #0000FF fg color
data.push(0x00);
data.push(0xff);
data.push(0x00);
data.push(0); // x: 0, y: 0
data.push(1 | (1 << 4)); // width: 2, height: 2
data.push(0x00); // becomes 0000ff00 --> #0000FF fg color
data.push(0x00);
data.push(0xff);
data.push(0x00);
data.push(2 | (2 << 4)); // x: 2, y: 2
data.push(1 | (1 << 4)); // width: 2, height: 2
testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]);
expect(display).to.have.displayed(targetData);
});
it('should carry over fg and bg colors from the previous tile if not specified', function () {
display.resize(4, 17);
let data = [];
data.push(0x02 | 0x04 | 0x08); // bg spec, fg spec, anysubrects
push32(data, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
data.push(0x00); // becomes 0000ffff --> #0000FF fg color
data.push(0x00);
data.push(0xff);
data.push(0xff);
data.push(8); // 8 subrects
for (let i = 0; i < 4; i++) {
data.push((0 << 4) | (i * 4)); // x: 0, y: i*4
data.push(1 | (1 << 4)); // width: 2, height: 2
data.push((2 << 4) | (i * 4 + 2)); // x: 2, y: i * 4 + 2
data.push(1 | (1 << 4)); // width: 2, height: 2
}
data.push(0x08); // anysubrects
data.push(1); // 1 subrect
data.push(0); // x: 0, y: 0
data.push(1 | (1 << 4)); // width: 2, height: 2
testDecodeRect(decoder, 0, 0, 4, 17, data, display, 24);
let targetData = [
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
];
let expected = [];
for (let i = 0; i < 4; i++) {
expected = expected.concat(targetData);
}
expected = expected.concat(targetData.slice(0, 16));
expect(display).to.have.displayed(new Uint8Array(expected));
});
it('should fail on an invalid subencoding', function () {
let data = [45]; // an invalid subencoding
expect(() => testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24)).to.throw();
});
it('should handle empty rects', function () {
display.fillRect(0, 0, 4, 4, [ 0x00, 0x00, 0xff ]);
display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);
display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]);
testDecodeRect(decoder, 1, 2, 0, 0, [], display, 24);
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]);
expect(display).to.have.displayed(targetData);
});
});
+45 -142
View File
@@ -1,7 +1,6 @@
const expect = chai.expect;
import Keyboard from '../core/input/keyboard.js';
import * as browser from '../core/util/browser.js';
describe('Key Event Handling', function () {
"use strict";
@@ -20,7 +19,6 @@ describe('Key Event Handling', function () {
describe('Decode Keyboard Events', function () {
it('should decode keydown events', function (done) {
if (browser.isIE() || browser.isEdge()) this.skip();
const kbd = new Keyboard(document);
kbd.onkeyevent = (keysym, code, down) => {
expect(keysym).to.be.equal(0x61);
@@ -31,7 +29,6 @@ describe('Key Event Handling', function () {
kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));
});
it('should decode keyup events', function (done) {
if (browser.isIE() || browser.isEdge()) this.skip();
let calls = 0;
const kbd = new Keyboard(document);
kbd.onkeyevent = (keysym, code, down) => {
@@ -45,118 +42,10 @@ describe('Key Event Handling', function () {
kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));
kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA', key: 'a'}));
});
describe('Legacy keypress Events', function () {
it('should wait for keypress when needed', function () {
const kbd = new Keyboard(document);
kbd.onkeyevent = sinon.spy();
kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41}));
expect(kbd.onkeyevent).to.not.have.been.called;
});
it('should decode keypress events', function (done) {
const kbd = new Keyboard(document);
kbd.onkeyevent = (keysym, code, down) => {
expect(keysym).to.be.equal(0x61);
expect(code).to.be.equal('KeyA');
expect(down).to.be.equal(true);
done();
};
kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41}));
kbd._handleKeyPress(keyevent('keypress', {code: 'KeyA', charCode: 0x61}));
});
it('should ignore keypress with different code', function () {
const kbd = new Keyboard(document);
kbd.onkeyevent = sinon.spy();
kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41}));
kbd._handleKeyPress(keyevent('keypress', {code: 'KeyB', charCode: 0x61}));
expect(kbd.onkeyevent).to.not.have.been.called;
});
it('should handle keypress with missing code', function (done) {
const kbd = new Keyboard(document);
kbd.onkeyevent = (keysym, code, down) => {
expect(keysym).to.be.equal(0x61);
expect(code).to.be.equal('KeyA');
expect(down).to.be.equal(true);
done();
};
kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41}));
kbd._handleKeyPress(keyevent('keypress', {charCode: 0x61}));
});
it('should guess key if no keypress and numeric key', function (done) {
const kbd = new Keyboard(document);
kbd.onkeyevent = (keysym, code, down) => {
expect(keysym).to.be.equal(0x32);
expect(code).to.be.equal('Digit2');
expect(down).to.be.equal(true);
done();
};
kbd._handleKeyDown(keyevent('keydown', {code: 'Digit2', keyCode: 0x32}));
});
it('should guess key if no keypress and alpha key', function (done) {
const kbd = new Keyboard(document);
kbd.onkeyevent = (keysym, code, down) => {
expect(keysym).to.be.equal(0x61);
expect(code).to.be.equal('KeyA');
expect(down).to.be.equal(true);
done();
};
kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41, shiftKey: false}));
});
it('should guess key if no keypress and alpha key (with shift)', function (done) {
const kbd = new Keyboard(document);
kbd.onkeyevent = (keysym, code, down) => {
expect(keysym).to.be.equal(0x41);
expect(code).to.be.equal('KeyA');
expect(down).to.be.equal(true);
done();
};
kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41, shiftKey: true}));
});
it('should not guess key if no keypress and unknown key', function (done) {
const kbd = new Keyboard(document);
kbd.onkeyevent = (keysym, code, down) => {
expect(keysym).to.be.equal(0);
expect(code).to.be.equal('KeyA');
expect(down).to.be.equal(true);
done();
};
kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x09}));
});
});
describe('suppress the right events at the right time', function () {
beforeEach(function () {
if (browser.isIE() || browser.isEdge()) this.skip();
});
it('should suppress anything with a valid key', function () {
const kbd = new Keyboard(document, {});
const evt1 = keyevent('keydown', {code: 'KeyA', key: 'a'});
kbd._handleKeyDown(evt1);
expect(evt1.preventDefault).to.have.been.called;
const evt2 = keyevent('keyup', {code: 'KeyA', key: 'a'});
kbd._handleKeyUp(evt2);
expect(evt2.preventDefault).to.have.been.called;
});
it('should not suppress keys without key', function () {
const kbd = new Keyboard(document, {});
const evt = keyevent('keydown', {code: 'KeyA', keyCode: 0x41});
kbd._handleKeyDown(evt);
expect(evt.preventDefault).to.not.have.been.called;
});
it('should suppress the following keypress event', function () {
const kbd = new Keyboard(document, {});
const evt1 = keyevent('keydown', {code: 'KeyA', keyCode: 0x41});
kbd._handleKeyDown(evt1);
const evt2 = keyevent('keypress', {code: 'KeyA', charCode: 0x41});
kbd._handleKeyPress(evt2);
expect(evt2.preventDefault).to.have.been.called;
});
});
});
describe('Fake keyup', function () {
it('should fake keyup events for virtual keyboards', function (done) {
if (browser.isIE() || browser.isEdge()) this.skip();
let count = 0;
const kbd = new Keyboard(document);
kbd.onkeyevent = (keysym, code, down) => {
@@ -178,9 +67,6 @@ describe('Key Event Handling', function () {
});
describe('Track Key State', function () {
beforeEach(function () {
if (browser.isIE() || browser.isEdge()) this.skip();
});
it('should send release using the same keysym as the press', function (done) {
const kbd = new Keyboard(document);
kbd.onkeyevent = (keysym, code, down) => {
@@ -256,11 +142,6 @@ describe('Key Event Handling', function () {
// environments, so we need to redefine it whilst running these
// tests.
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
if (origNavigator === undefined) {
// Object.getOwnPropertyDescriptor() doesn't work
// properly in any version of IE
this.skip();
}
Object.defineProperty(window, "navigator", {value: {}});
if (window.navigator.platform !== undefined) {
@@ -323,11 +204,6 @@ describe('Key Event Handling', function () {
// environments, so we need to redefine it whilst running these
// tests.
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
if (origNavigator === undefined) {
// Object.getOwnPropertyDescriptor() doesn't work
// properly in any version of IE
this.skip();
}
Object.defineProperty(window, "navigator", {value: {}});
if (window.navigator.platform !== undefined) {
@@ -343,7 +219,7 @@ describe('Key Event Handling', function () {
}
});
it('should toggle caps lock on key press on iOS', function (done) {
it('should toggle caps lock on key press on iOS', function () {
window.navigator.platform = "iPad";
const kbd = new Keyboard(document);
kbd.onkeyevent = sinon.spy();
@@ -352,10 +228,9 @@ describe('Key Event Handling', function () {
expect(kbd.onkeyevent).to.have.been.calledTwice;
expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0xFFE5, "CapsLock", true);
expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0xFFE5, "CapsLock", false);
done();
});
it('should toggle caps lock on key press on mac', function (done) {
it('should toggle caps lock on key press on mac', function () {
window.navigator.platform = "Mac";
const kbd = new Keyboard(document);
kbd.onkeyevent = sinon.spy();
@@ -364,10 +239,9 @@ describe('Key Event Handling', function () {
expect(kbd.onkeyevent).to.have.been.calledTwice;
expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0xFFE5, "CapsLock", true);
expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0xFFE5, "CapsLock", false);
done();
});
it('should toggle caps lock on key release on iOS', function (done) {
it('should toggle caps lock on key release on iOS', function () {
window.navigator.platform = "iPad";
const kbd = new Keyboard(document);
kbd.onkeyevent = sinon.spy();
@@ -376,10 +250,9 @@ describe('Key Event Handling', function () {
expect(kbd.onkeyevent).to.have.been.calledTwice;
expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0xFFE5, "CapsLock", true);
expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0xFFE5, "CapsLock", false);
done();
});
it('should toggle caps lock on key release on mac', function (done) {
it('should toggle caps lock on key release on mac', function () {
window.navigator.platform = "Mac";
const kbd = new Keyboard(document);
kbd.onkeyevent = sinon.spy();
@@ -388,10 +261,50 @@ describe('Key Event Handling', function () {
expect(kbd.onkeyevent).to.have.been.calledTwice;
expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0xFFE5, "CapsLock", true);
expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0xFFE5, "CapsLock", false);
done();
});
});
describe('Japanese IM keys on Windows', function () {
let origNavigator;
beforeEach(function () {
// window.navigator is a protected read-only property in many
// environments, so we need to redefine it whilst running these
// tests.
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
Object.defineProperty(window, "navigator", {value: {}});
if (window.navigator.platform !== undefined) {
// Object.defineProperty() doesn't work properly in old
// versions of Chrome
this.skip();
}
window.navigator.platform = "Windows";
});
afterEach(function () {
if (origNavigator !== undefined) {
Object.defineProperty(window, "navigator", origNavigator);
}
});
const keys = { 'Zenkaku': 0xff2a, 'Hankaku': 0xff2a,
'Alphanumeric': 0xff30, 'Katakana': 0xff26,
'Hiragana': 0xff25, 'Romaji': 0xff24,
'KanaMode': 0xff24 };
for (let [key, keysym] of Object.entries(keys)) {
it(`should fake key release for ${key} on Windows`, function () {
let kbd = new Keyboard(document);
kbd.onkeyevent = sinon.spy();
kbd._handleKeyDown(keyevent('keydown', {code: 'FakeIM', key: key}));
expect(kbd.onkeyevent).to.have.been.calledTwice;
expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(keysym, "FakeIM", true);
expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(keysym, "FakeIM", false);
});
}
});
describe('Escape AltGraph on Windows', function () {
let origNavigator;
beforeEach(function () {
@@ -399,11 +312,6 @@ describe('Key Event Handling', function () {
// environments, so we need to redefine it whilst running these
// tests.
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
if (origNavigator === undefined) {
// Object.getOwnPropertyDescriptor() doesn't work
// properly in any version of IE
this.skip();
}
Object.defineProperty(window, "navigator", {value: {}});
if (window.navigator.platform !== undefined) {
@@ -549,11 +457,6 @@ describe('Key Event Handling', function () {
// environments, so we need to redefine it whilst running these
// tests.
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
if (origNavigator === undefined) {
// Object.getOwnPropertyDescriptor() doesn't work
// properly in any version of IE
this.skip();
}
Object.defineProperty(window, "navigator", {value: {}});
if (window.navigator.platform !== undefined) {
-5
View File
@@ -11,11 +11,6 @@ describe('Localization', function () {
// environments, so we need to redefine it whilst running these
// tests.
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
if (origNavigator === undefined) {
// Object.getOwnPropertyDescriptor() doesn't work
// properly in any version of IE
this.skip();
}
Object.defineProperty(window, "navigator", {value: {}});
if (window.navigator.languages !== undefined) {
+129
View File
@@ -0,0 +1,129 @@
const expect = chai.expect;
import Websock from '../core/websock.js';
import Display from '../core/display.js';
import RawDecoder from '../core/decoders/raw.js';
import FakeWebSocket from './fake.websocket.js';
function testDecodeRect(decoder, x, y, width, height, data, display, depth) {
let sock;
sock = new Websock;
sock.open("ws://example.com");
sock.on('message', () => {
decoder.decodeRect(x, y, width, height, sock, display, depth);
});
// Empty messages are filtered at multiple layers, so we need to
// do a direct call
if (data.length === 0) {
decoder.decodeRect(x, y, width, height, sock, display, depth);
} else {
sock._websocket._receiveData(new Uint8Array(data));
}
display.flip();
}
describe('Raw Decoder', function () {
let decoder;
let display;
before(FakeWebSocket.replace);
after(FakeWebSocket.restore);
beforeEach(function () {
decoder = new RawDecoder();
display = new Display(document.createElement('canvas'));
display.resize(4, 4);
});
it('should handle the Raw encoding', function () {
testDecodeRect(decoder, 0, 0, 2, 2,
[0xff, 0x00, 0x00, 0, 0x00, 0xff, 0x00, 0,
0x00, 0xff, 0x00, 0, 0xff, 0x00, 0x00, 0],
display, 24);
testDecodeRect(decoder, 2, 0, 2, 2,
[0x00, 0x00, 0xff, 0, 0x00, 0x00, 0xff, 0,
0x00, 0x00, 0xff, 0, 0x00, 0x00, 0xff, 0],
display, 24);
testDecodeRect(decoder, 0, 2, 4, 1,
[0xee, 0x00, 0xff, 0, 0x00, 0xee, 0xff, 0,
0xaa, 0xee, 0xff, 0, 0xab, 0xee, 0xff, 0],
display, 24);
testDecodeRect(decoder, 0, 3, 4, 1,
[0xee, 0x00, 0xff, 0, 0x00, 0xee, 0xff, 0,
0xaa, 0xee, 0xff, 0, 0xab, 0xee, 0xff, 0],
display, 24);
let targetData = new Uint8Array([
0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0x00, 0xff, 0x00, 255, 0xff, 0x00, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0xee, 0x00, 0xff, 255, 0x00, 0xee, 0xff, 255, 0xaa, 0xee, 0xff, 255, 0xab, 0xee, 0xff, 255,
0xee, 0x00, 0xff, 255, 0x00, 0xee, 0xff, 255, 0xaa, 0xee, 0xff, 255, 0xab, 0xee, 0xff, 255
]);
expect(display).to.have.displayed(targetData);
});
it('should handle the Raw encoding in low colour mode', function () {
testDecodeRect(decoder, 0, 0, 2, 2,
[0x30, 0x30, 0x30, 0x30],
display, 8);
testDecodeRect(decoder, 2, 0, 2, 2,
[0x0c, 0x0c, 0x0c, 0x0c],
display, 8);
testDecodeRect(decoder, 0, 2, 4, 1,
[0x0c, 0x0c, 0x30, 0x30],
display, 8);
testDecodeRect(decoder, 0, 3, 4, 1,
[0x0c, 0x0c, 0x30, 0x30],
display, 8);
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]);
expect(display).to.have.displayed(targetData);
});
it('should handle empty rects', function () {
display.fillRect(0, 0, 4, 4, [ 0x00, 0x00, 0xff ]);
display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);
display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]);
testDecodeRect(decoder, 1, 2, 0, 0, [], display, 24);
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]);
expect(display).to.have.displayed(targetData);
});
it('should handle empty rects in low colour mode', function () {
display.fillRect(0, 0, 4, 4, [ 0x00, 0x00, 0xff ]);
display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);
display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]);
testDecodeRect(decoder, 1, 2, 0, 0, [], display, 8);
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]);
expect(display).to.have.displayed(targetData);
});
});
+236 -521
View File
@@ -8,26 +8,9 @@ import { encodings } from '../core/encodings.js';
import { toUnsigned32bit } from '../core/util/int.js';
import { encodeUTF8 } from '../core/util/strings.js';
import KeyTable from '../core/input/keysym.js';
import * as browser from '../core/util/browser.js';
import FakeWebSocket from './fake.websocket.js';
/* UIEvent constructor polyfill for IE */
(() => {
if (typeof window.UIEvent === "function") return;
function UIEvent( event, params ) {
params = params || { bubbles: false, cancelable: false, view: window, detail: undefined };
const evt = document.createEvent( 'UIEvent' );
evt.initUIEvent( event, params.bubbles, params.cancelable, params.view, params.detail );
return evt;
}
UIEvent.prototype = window.UIEvent.prototype;
window.UIEvent = UIEvent;
})();
function push8(arr, num) {
"use strict";
arr.push(num & 0xFF);
@@ -88,6 +71,18 @@ function deflateWithSize(data) {
describe('Remote Frame Buffer Protocol Client', function () {
let clock;
let raf;
let fakeResizeObserver = null;
const realObserver = window.ResizeObserver;
class FakeResizeObserver {
constructor(handler) {
this.fire = handler;
fakeResizeObserver = this;
}
disconnect() {}
observe(target, options) {}
unobserve(target) {}
}
before(FakeWebSocket.replace);
after(FakeWebSocket.restore);
@@ -97,6 +92,9 @@ describe('Remote Frame Buffer Protocol Client', function () {
// sinon doesn't support this yet
raf = window.requestAnimationFrame;
window.requestAnimationFrame = setTimeout;
// We must do this in a 'before' since it needs to be set before
// the RFB constructor, which runs in beforeEach further down
window.ResizeObserver = FakeResizeObserver;
// Use a single set of buffers instead of reallocating to
// speed up tests
const sock = new Websock();
@@ -117,6 +115,7 @@ describe('Remote Frame Buffer Protocol Client', function () {
delete Websock.prototype.toString;
this.clock.restore();
window.requestAnimationFrame = raf;
window.ResizeObserver = realObserver;
});
let container;
@@ -157,38 +156,112 @@ describe('Remote Frame Buffer Protocol Client', function () {
}
describe('Connecting/Disconnecting', function () {
describe('#RFB', function () {
it('should set the current state to "connecting"', function () {
const client = new RFB(document.createElement('div'), 'wss://host:8675');
client._rfbConnectionState = '';
this.clock.tick();
expect(client._rfbConnectionState).to.equal('connecting');
describe('#RFB (constructor)', function () {
let open, attach;
beforeEach(function () {
open = sinon.spy(Websock.prototype, 'open');
attach = sinon.spy(Websock.prototype, 'attach');
});
afterEach(function () {
open.restore();
attach.restore();
});
it('should actually connect to the websocket', function () {
const client = new RFB(document.createElement('div'), 'ws://HOST:8675/PATH');
sinon.spy(client._sock, 'open');
this.clock.tick();
expect(client._sock.open).to.have.been.calledOnce;
expect(client._sock.open).to.have.been.calledWith('ws://HOST:8675/PATH');
new RFB(document.createElement('div'), 'ws://HOST:8675/PATH');
expect(open).to.have.been.calledOnceWithExactly('ws://HOST:8675/PATH', []);
});
it('should pass on connection problems', function () {
open.restore();
open = sinon.stub(Websock.prototype, 'open');
open.throws(new Error('Failure'));
expect(() => new RFB(document.createElement('div'), 'ws://HOST:8675/PATH')).to.throw('Failure');
});
it('should handle WebSocket/RTCDataChannel objects', function () {
let sock = new FakeWebSocket('ws://HOST:8675/PATH', []);
new RFB(document.createElement('div'), sock);
expect(open).to.not.have.been.called;
expect(attach).to.have.been.calledOnceWithExactly(sock);
});
it('should handle already open WebSocket/RTCDataChannel objects', function () {
let sock = new FakeWebSocket('ws://HOST:8675/PATH', []);
sock._open();
const client = new RFB(document.createElement('div'), sock);
let callback = sinon.spy();
client.addEventListener('disconnect', callback);
expect(open).to.not.have.been.called;
expect(attach).to.have.been.calledOnceWithExactly(sock);
// Check if it is ready for some data
sock._receiveData(new Uint8Array(['R', 'F', 'B', '0', '0', '3', '0', '0', '8']));
expect(callback).to.not.have.been.called;
});
it('should refuse closed WebSocket/RTCDataChannel objects', function () {
let sock = new FakeWebSocket('ws://HOST:8675/PATH', []);
sock.readyState = WebSocket.CLOSED;
expect(() => new RFB(document.createElement('div'), sock)).to.throw();
});
it('should pass on attach problems', function () {
attach.restore();
attach = sinon.stub(Websock.prototype, 'attach');
attach.throws(new Error('Failure'));
let sock = new FakeWebSocket('ws://HOST:8675/PATH', []);
expect(() => new RFB(document.createElement('div'), sock)).to.throw('Failure');
});
});
describe('#disconnect', function () {
let client;
let close;
beforeEach(function () {
client = makeRFB();
close = sinon.stub(Websock.prototype, "close");
});
afterEach(function () {
close.restore();
});
it('should go to state "disconnecting" before "disconnected"', function () {
sinon.spy(client, '_updateConnectionState');
it('should start closing WebSocket', function () {
let callback = sinon.spy();
client.addEventListener('disconnect', callback);
client.disconnect();
expect(client._updateConnectionState).to.have.been.calledTwice;
expect(client._updateConnectionState.getCall(0).args[0])
.to.equal('disconnecting');
expect(client._updateConnectionState.getCall(1).args[0])
.to.equal('disconnected');
expect(client._rfbConnectionState).to.equal('disconnected');
expect(close).to.have.been.calledOnceWithExactly();
expect(callback).to.not.have.been.called;
});
it('should send disconnect event', function () {
let callback = sinon.spy();
client.addEventListener('disconnect', callback);
client.disconnect();
close.thisValues[0]._eventHandlers.close(new CloseEvent("close", { 'code': 1000, 'reason': "", 'wasClean': true }));
expect(callback).to.have.been.calledOnce;
expect(callback.args[0][0].detail.clean).to.be.true;
});
it('should force disconnect if disconnecting takes too long', function () {
let callback = sinon.spy();
client.addEventListener('disconnect', callback);
client.disconnect();
this.clock.tick(3 * 1000);
expect(callback).to.have.been.calledOnce;
expect(callback.args[0][0].detail.clean).to.be.true;
});
it('should not fail if disconnect completes before timeout', function () {
let callback = sinon.spy();
client.addEventListener('disconnect', callback);
client.disconnect();
client._updateConnectionState('disconnecting');
this.clock.tick(3 * 1000 / 2);
close.thisValues[0]._eventHandlers.close(new CloseEvent("close", { 'code': 1000, 'reason': "", 'wasClean': true }));
this.clock.tick(3 * 1000 / 2 + 1);
expect(callback).to.have.been.calledOnce;
expect(callback.args[0][0].detail.clean).to.be.true;
});
it('should unregister error event handler', function () {
@@ -413,6 +486,7 @@ describe('Remote Frame Buffer Protocol Client', function () {
describe('Clipping', function () {
let client;
beforeEach(function () {
client = makeRFB();
container.style.width = '70px';
@@ -438,8 +512,7 @@ describe('Remote Frame Buffer Protocol Client', function () {
container.style.width = '40px';
container.style.height = '50px';
const event = new UIEvent('resize');
window.dispatchEvent(event);
fakeResizeObserver.fire();
clock.tick();
expect(client._display.viewportChangeSize).to.have.been.calledOnce;
@@ -635,8 +708,7 @@ describe('Remote Frame Buffer Protocol Client', function () {
container.style.width = '40px';
container.style.height = '50px';
const event = new UIEvent('resize');
window.dispatchEvent(event);
fakeResizeObserver.fire();
clock.tick();
expect(client._display.autoscale).to.have.been.calledOnce;
@@ -725,8 +797,7 @@ describe('Remote Frame Buffer Protocol Client', function () {
it('should request a resize when the container resizes', function () {
container.style.width = '40px';
container.style.height = '50px';
const event = new UIEvent('resize');
window.dispatchEvent(event);
fakeResizeObserver.fire();
clock.tick(1000);
expect(RFB.messages.setDesktopSize).to.have.been.calledOnce;
@@ -736,16 +807,14 @@ describe('Remote Frame Buffer Protocol Client', function () {
it('should not resize until the container size is stable', function () {
container.style.width = '20px';
container.style.height = '30px';
const event1 = new UIEvent('resize');
window.dispatchEvent(event1);
fakeResizeObserver.fire();
clock.tick(400);
expect(RFB.messages.setDesktopSize).to.not.have.been.called;
container.style.width = '40px';
container.style.height = '50px';
const event2 = new UIEvent('resize');
window.dispatchEvent(event2);
fakeResizeObserver.fire();
clock.tick(400);
expect(RFB.messages.setDesktopSize).to.not.have.been.called;
@@ -807,62 +876,6 @@ describe('Remote Frame Buffer Protocol Client', function () {
});
describe('Misc Internals', function () {
describe('#_updateConnectionState', function () {
let client;
beforeEach(function () {
client = makeRFB();
});
it('should clear the disconnect timer if the state is not "disconnecting"', function () {
const spy = sinon.spy();
client._disconnTimer = setTimeout(spy, 50);
client._rfbConnectionState = 'connecting';
client._updateConnectionState('connected');
this.clock.tick(51);
expect(spy).to.not.have.been.called;
expect(client._disconnTimer).to.be.null;
});
it('should set the rfbConnectionState', function () {
client._rfbConnectionState = 'connecting';
client._updateConnectionState('connected');
expect(client._rfbConnectionState).to.equal('connected');
});
it('should not change the state when we are disconnected', function () {
client.disconnect();
expect(client._rfbConnectionState).to.equal('disconnected');
client._updateConnectionState('connecting');
expect(client._rfbConnectionState).to.not.equal('connecting');
});
it('should ignore state changes to the same state', function () {
const connectSpy = sinon.spy();
client.addEventListener("connect", connectSpy);
expect(client._rfbConnectionState).to.equal('connected');
client._updateConnectionState('connected');
expect(connectSpy).to.not.have.been.called;
client.disconnect();
const disconnectSpy = sinon.spy();
client.addEventListener("disconnect", disconnectSpy);
expect(client._rfbConnectionState).to.equal('disconnected');
client._updateConnectionState('disconnected');
expect(disconnectSpy).to.not.have.been.called;
});
it('should ignore illegal state changes', function () {
const spy = sinon.spy();
client.addEventListener("disconnect", spy);
client._updateConnectionState('disconnected');
expect(client._rfbConnectionState).to.not.equal('disconnected');
expect(spy).to.not.have.been.called;
});
});
describe('#_fail', function () {
let client;
beforeEach(function () {
@@ -903,106 +916,6 @@ describe('Remote Frame Buffer Protocol Client', function () {
});
});
describe('Connection States', function () {
describe('connecting', function () {
it('should open the websocket connection', function () {
const client = new RFB(document.createElement('div'),
'ws://HOST:8675/PATH');
sinon.spy(client._sock, 'open');
this.clock.tick();
expect(client._sock.open).to.have.been.calledOnce;
});
});
describe('connected', function () {
let client;
beforeEach(function () {
client = makeRFB();
});
it('should result in a connect event if state becomes connected', function () {
const spy = sinon.spy();
client.addEventListener("connect", spy);
client._rfbConnectionState = 'connecting';
client._updateConnectionState('connected');
expect(spy).to.have.been.calledOnce;
});
it('should not result in a connect event if the state is not "connected"', function () {
const spy = sinon.spy();
client.addEventListener("connect", spy);
client._sock._websocket.open = () => {}; // explicitly don't call onopen
client._updateConnectionState('connecting');
expect(spy).to.not.have.been.called;
});
});
describe('disconnecting', function () {
let client;
beforeEach(function () {
client = makeRFB();
});
it('should force disconnect if we do not call Websock.onclose within the disconnection timeout', function () {
sinon.spy(client, '_updateConnectionState');
client._sock._websocket.close = () => {}; // explicitly don't call onclose
client._updateConnectionState('disconnecting');
this.clock.tick(3 * 1000);
expect(client._updateConnectionState).to.have.been.calledTwice;
expect(client._rfbDisconnectReason).to.not.equal("");
expect(client._rfbConnectionState).to.equal("disconnected");
});
it('should not fail if Websock.onclose gets called within the disconnection timeout', function () {
client._updateConnectionState('disconnecting');
this.clock.tick(3 * 1000 / 2);
client._sock._websocket.close();
this.clock.tick(3 * 1000 / 2 + 1);
expect(client._rfbConnectionState).to.equal('disconnected');
});
it('should close the WebSocket connection', function () {
sinon.spy(client._sock, 'close');
client._updateConnectionState('disconnecting');
expect(client._sock.close).to.have.been.calledOnce;
});
it('should not result in a disconnect event', function () {
const spy = sinon.spy();
client.addEventListener("disconnect", spy);
client._sock._websocket.close = () => {}; // explicitly don't call onclose
client._updateConnectionState('disconnecting');
expect(spy).to.not.have.been.called;
});
});
describe('disconnected', function () {
let client;
beforeEach(function () {
client = new RFB(document.createElement('div'), 'ws://HOST:8675/PATH');
});
it('should result in a disconnect event if state becomes "disconnected"', function () {
const spy = sinon.spy();
client.addEventListener("disconnect", spy);
client._rfbConnectionState = 'disconnecting';
client._updateConnectionState('disconnected');
expect(spy).to.have.been.calledOnce;
expect(spy.args[0][0].detail.clean).to.be.true;
});
it('should result in a disconnect event without msg when no reason given', function () {
const spy = sinon.spy();
client.addEventListener("disconnect", spy);
client._rfbConnectionState = 'disconnecting';
client._rfbDisconnectReason = "";
client._updateConnectionState('disconnected');
expect(spy).to.have.been.calledOnce;
expect(spy.args[0].length).to.equal(1);
});
});
});
describe('Protocol Initialization States', function () {
let client;
beforeEach(function () {
@@ -1395,6 +1308,102 @@ describe('Remote Frame Buffer Protocol Client', function () {
expect(client._fail).to.have.been.calledOnce;
});
});
describe('VeNCrypt Authentication (type 19) Handler', function () {
beforeEach(function () {
client._rfbInitState = 'Security';
client._rfbVersion = 3.8;
sendSecurity(19, client);
expect(client._sock).to.have.sent(new Uint8Array([19]));
});
it('should fail with non-0.2 versions', function () {
sinon.spy(client, "_fail");
client._sock._websocket._receiveData(new Uint8Array([0, 1]));
expect(client._fail).to.have.been.calledOnce;
});
it('should fail if the Plain authentication is not present', function () {
// VeNCrypt version
client._sock._websocket._receiveData(new Uint8Array([0, 2]));
expect(client._sock).to.have.sent(new Uint8Array([0, 2]));
// Server ACK.
client._sock._websocket._receiveData(new Uint8Array([0]));
// Subtype list, only list subtype 1.
sinon.spy(client, "_fail");
client._sock._websocket._receiveData(new Uint8Array([1, 0, 0, 0, 1]));
expect(client._fail).to.have.been.calledOnce;
});
it('should support Plain authentication', function () {
client._rfbCredentials = { username: 'username', password: 'password' };
// VeNCrypt version
client._sock._websocket._receiveData(new Uint8Array([0, 2]));
expect(client._sock).to.have.sent(new Uint8Array([0, 2]));
// Server ACK.
client._sock._websocket._receiveData(new Uint8Array([0]));
// Subtype list.
client._sock._websocket._receiveData(new Uint8Array([1, 0, 0, 1, 0]));
const expectedResponse = [];
push32(expectedResponse, 256); // Chosen subtype.
push32(expectedResponse, client._rfbCredentials.username.length);
push32(expectedResponse, client._rfbCredentials.password.length);
pushString(expectedResponse, client._rfbCredentials.username);
pushString(expectedResponse, client._rfbCredentials.password);
expect(client._sock).to.have.sent(new Uint8Array(expectedResponse));
client._initMsg = sinon.spy();
client._sock._websocket._receiveData(new Uint8Array([0, 0, 0, 0]));
expect(client._initMsg).to.have.been.called;
});
it('should support Plain authentication with an empty password', function () {
client._rfbCredentials = { username: 'username', password: '' };
// VeNCrypt version
client._sock._websocket._receiveData(new Uint8Array([0, 2]));
expect(client._sock).to.have.sent(new Uint8Array([0, 2]));
// Server ACK.
client._sock._websocket._receiveData(new Uint8Array([0]));
// Subtype list.
client._sock._websocket._receiveData(new Uint8Array([1, 0, 0, 1, 0]));
const expectedResponse = [];
push32(expectedResponse, 256); // Chosen subtype.
push32(expectedResponse, client._rfbCredentials.username.length);
push32(expectedResponse, client._rfbCredentials.password.length);
pushString(expectedResponse, client._rfbCredentials.username);
pushString(expectedResponse, client._rfbCredentials.password);
expect(client._sock).to.have.sent(new Uint8Array(expectedResponse));
client._initMsg = sinon.spy();
client._sock._websocket._receiveData(new Uint8Array([0, 0, 0, 0]));
expect(client._initMsg).to.have.been.called;
});
it('should support Plain authentication with a very long username and password', function () {
client._rfbCredentials = { username: 'a'.repeat(300), password: 'a'.repeat(300) };
// VeNCrypt version
client._sock._websocket._receiveData(new Uint8Array([0, 2]));
expect(client._sock).to.have.sent(new Uint8Array([0, 2]));
// Server ACK.
client._sock._websocket._receiveData(new Uint8Array([0]));
// Subtype list.
client._sock._websocket._receiveData(new Uint8Array([1, 0, 0, 1, 0]));
const expectedResponse = [];
push32(expectedResponse, 256); // Chosen subtype.
push32(expectedResponse, client._rfbCredentials.username.length);
push32(expectedResponse, client._rfbCredentials.password.length);
pushString(expectedResponse, client._rfbCredentials.username);
pushString(expectedResponse, client._rfbCredentials.password);
expect(client._sock).to.have.sent(new Uint8Array(expectedResponse));
client._initMsg = sinon.spy();
client._sock._websocket._receiveData(new Uint8Array([0, 0, 0, 0]));
expect(client._initMsg).to.have.been.called;
});
});
});
describe('SecurityResult', function () {
@@ -1627,9 +1636,11 @@ describe('Remote Frame Buffer Protocol Client', function () {
});
});
it('should transition to the "connected" state', function () {
it('should send the "connect" event', function () {
let spy = sinon.spy();
client.addEventListener('connect', spy);
sendServerInit({}, client);
expect(client._rfbConnectionState).to.equal('connected');
expect(spy).to.have.been.calledOnce;
});
});
});
@@ -1645,28 +1656,6 @@ describe('Remote Frame Buffer Protocol Client', function () {
});
describe('Framebuffer Update Handling', function () {
const targetDataArr = [
0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0x00, 0xff, 0x00, 255, 0xff, 0x00, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0xee, 0x00, 0xff, 255, 0x00, 0xee, 0xff, 255, 0xaa, 0xee, 0xff, 255, 0xab, 0xee, 0xff, 255,
0xee, 0x00, 0xff, 255, 0x00, 0xee, 0xff, 255, 0xaa, 0xee, 0xff, 255, 0xab, 0xee, 0xff, 255
];
let targetData;
const targetDataCheckArr = [
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
];
let targetDataCheck;
before(function () {
// NB(directxman12): PhantomJS 1.x doesn't implement Uint8ClampedArray
targetData = new Uint8Array(targetDataArr);
targetDataCheck = new Uint8Array(targetDataCheckArr);
});
function sendFbuMsg(rectInfo, rectData, client, rectCnt) {
let data = [];
@@ -1735,22 +1724,6 @@ describe('Remote Frame Buffer Protocol Client', function () {
expect(client._fail).to.have.been.calledOnce;
});
it('should be able to pause and resume receiving rects if not enought data', function () {
// seed some initial data to copy
client._fbWidth = 4;
client._fbHeight = 4;
client._display.resize(4, 4);
client._display.blitRgbxImage(0, 0, 4, 2, new Uint8Array(targetDataCheckArr.slice(0, 32)), 0);
const info = [{ x: 0, y: 2, width: 2, height: 2, encoding: 0x01},
{ x: 2, y: 2, width: 2, height: 2, encoding: 0x01}];
// data says [{ old_x: 2, old_y: 0 }, { old_x: 0, old_y: 0 }]
const rects = [[0, 2, 0, 0], [0, 0, 0, 0]];
sendFbuMsg([info[0]], [rects[0]], client, 2);
sendFbuMsg([info[1]], [rects[1]], client, -1);
expect(client._display).to.have.displayed(targetDataCheck);
});
describe('Message Encoding Handlers', function () {
beforeEach(function () {
// a really small frame
@@ -1760,216 +1733,6 @@ describe('Remote Frame Buffer Protocol Client', function () {
client._display.resize(4, 4);
});
it('should handle the RAW encoding', function () {
const info = [{ x: 0, y: 0, width: 2, height: 2, encoding: 0x00 },
{ x: 2, y: 0, width: 2, height: 2, encoding: 0x00 },
{ x: 0, y: 2, width: 4, height: 1, encoding: 0x00 },
{ x: 0, y: 3, width: 4, height: 1, encoding: 0x00 }];
// data is in bgrx
const rects = [
[0x00, 0x00, 0xff, 0, 0x00, 0xff, 0x00, 0, 0x00, 0xff, 0x00, 0, 0x00, 0x00, 0xff, 0],
[0xff, 0x00, 0x00, 0, 0xff, 0x00, 0x00, 0, 0xff, 0x00, 0x00, 0, 0xff, 0x00, 0x00, 0],
[0xff, 0x00, 0xee, 0, 0xff, 0xee, 0x00, 0, 0xff, 0xee, 0xaa, 0, 0xff, 0xee, 0xab, 0],
[0xff, 0x00, 0xee, 0, 0xff, 0xee, 0x00, 0, 0xff, 0xee, 0xaa, 0, 0xff, 0xee, 0xab, 0]];
sendFbuMsg(info, rects, client);
expect(client._display).to.have.displayed(targetData);
});
it('should handle the RAW encoding in low colour mode', function () {
const info = [{ x: 0, y: 0, width: 2, height: 2, encoding: 0x00 },
{ x: 2, y: 0, width: 2, height: 2, encoding: 0x00 },
{ x: 0, y: 2, width: 4, height: 1, encoding: 0x00 },
{ x: 0, y: 3, width: 4, height: 1, encoding: 0x00 }];
const rects = [
[0x03, 0x03, 0x03, 0x03],
[0x0c, 0x0c, 0x0c, 0x0c],
[0x0c, 0x0c, 0x03, 0x03],
[0x0c, 0x0c, 0x03, 0x03]];
client._fbDepth = 8;
sendFbuMsg(info, rects, client);
expect(client._display).to.have.displayed(targetDataCheck);
});
it('should handle the COPYRECT encoding', function () {
// seed some initial data to copy
client._display.blitRgbxImage(0, 0, 4, 2, new Uint8Array(targetDataCheckArr.slice(0, 32)), 0);
const info = [{ x: 0, y: 2, width: 2, height: 2, encoding: 0x01},
{ x: 2, y: 2, width: 2, height: 2, encoding: 0x01}];
// data says [{ old_x: 0, old_y: 0 }, { old_x: 0, old_y: 0 }]
const rects = [[0, 2, 0, 0], [0, 0, 0, 0]];
sendFbuMsg(info, rects, client);
expect(client._display).to.have.displayed(targetDataCheck);
});
// TODO(directxman12): for encodings with subrects, test resuming on partial send?
// TODO(directxman12): test rre_chunk_sz (related to above about subrects)?
it('should handle the RRE encoding', function () {
const info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x02 }];
const rect = [];
push32(rect, 2); // 2 subrects
push32(rect, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
rect.push(0xff); // becomes ff0000ff --> #0000FF color
rect.push(0x00);
rect.push(0x00);
rect.push(0xff);
push16(rect, 0); // x: 0
push16(rect, 0); // y: 0
push16(rect, 2); // width: 2
push16(rect, 2); // height: 2
rect.push(0xff); // becomes ff0000ff --> #0000FF color
rect.push(0x00);
rect.push(0x00);
rect.push(0xff);
push16(rect, 2); // x: 2
push16(rect, 2); // y: 2
push16(rect, 2); // width: 2
push16(rect, 2); // height: 2
sendFbuMsg(info, [rect], client);
expect(client._display).to.have.displayed(targetDataCheck);
});
describe('the HEXTILE encoding handler', function () {
it('should handle a tile with fg, bg specified, normal subrects', function () {
const info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x05 }];
const rect = [];
rect.push(0x02 | 0x04 | 0x08); // bg spec, fg spec, anysubrects
push32(rect, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
rect.push(0xff); // becomes ff0000ff --> #0000FF fg color
rect.push(0x00);
rect.push(0x00);
rect.push(0xff);
rect.push(2); // 2 subrects
rect.push(0); // x: 0, y: 0
rect.push(1 | (1 << 4)); // width: 2, height: 2
rect.push(2 | (2 << 4)); // x: 2, y: 2
rect.push(1 | (1 << 4)); // width: 2, height: 2
sendFbuMsg(info, [rect], client);
expect(client._display).to.have.displayed(targetDataCheck);
});
it('should handle a raw tile', function () {
const info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x05 }];
const rect = [];
rect.push(0x01); // raw
for (let i = 0; i < targetData.length; i += 4) {
rect.push(targetData[i + 2]);
rect.push(targetData[i + 1]);
rect.push(targetData[i]);
rect.push(targetData[i + 3]);
}
sendFbuMsg(info, [rect], client);
expect(client._display).to.have.displayed(targetData);
});
it('should handle a tile with only bg specified (solid bg)', function () {
const info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x05 }];
const rect = [];
rect.push(0x02);
push32(rect, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
sendFbuMsg(info, [rect], client);
const expected = [];
for (let i = 0; i < 16; i++) { push32(expected, 0xff00ff); }
expect(client._display).to.have.displayed(new Uint8Array(expected));
});
it('should handle a tile with only bg specified and an empty frame afterwards', function () {
// set the width so we can have two tiles
client._fbWidth = 8;
client._display.resize(8, 4);
const info = [{ x: 0, y: 0, width: 32, height: 4, encoding: 0x05 }];
const rect = [];
// send a bg frame
rect.push(0x02);
push32(rect, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
// send an empty frame
rect.push(0x00);
sendFbuMsg(info, [rect], client);
const expected = [];
for (let i = 0; i < 16; i++) { push32(expected, 0xff00ff); } // rect 1: solid
for (let i = 0; i < 16; i++) { push32(expected, 0xff00ff); } // rect 2: same bkground color
expect(client._display).to.have.displayed(new Uint8Array(expected));
});
it('should handle a tile with bg and coloured subrects', function () {
const info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x05 }];
const rect = [];
rect.push(0x02 | 0x08 | 0x10); // bg spec, anysubrects, colouredsubrects
push32(rect, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
rect.push(2); // 2 subrects
rect.push(0xff); // becomes ff0000ff --> #0000FF fg color
rect.push(0x00);
rect.push(0x00);
rect.push(0xff);
rect.push(0); // x: 0, y: 0
rect.push(1 | (1 << 4)); // width: 2, height: 2
rect.push(0xff); // becomes ff0000ff --> #0000FF fg color
rect.push(0x00);
rect.push(0x00);
rect.push(0xff);
rect.push(2 | (2 << 4)); // x: 2, y: 2
rect.push(1 | (1 << 4)); // width: 2, height: 2
sendFbuMsg(info, [rect], client);
expect(client._display).to.have.displayed(targetDataCheck);
});
it('should carry over fg and bg colors from the previous tile if not specified', function () {
client._fbWidth = 4;
client._fbHeight = 17;
client._display.resize(4, 17);
const info = [{ x: 0, y: 0, width: 4, height: 17, encoding: 0x05}];
const rect = [];
rect.push(0x02 | 0x04 | 0x08); // bg spec, fg spec, anysubrects
push32(rect, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
rect.push(0xff); // becomes ff0000ff --> #0000FF fg color
rect.push(0x00);
rect.push(0x00);
rect.push(0xff);
rect.push(8); // 8 subrects
for (let i = 0; i < 4; i++) {
rect.push((0 << 4) | (i * 4)); // x: 0, y: i*4
rect.push(1 | (1 << 4)); // width: 2, height: 2
rect.push((2 << 4) | (i * 4 + 2)); // x: 2, y: i * 4 + 2
rect.push(1 | (1 << 4)); // width: 2, height: 2
}
rect.push(0x08); // anysubrects
rect.push(1); // 1 subrect
rect.push(0); // x: 0, y: 0
rect.push(1 | (1 << 4)); // width: 2, height: 2
sendFbuMsg(info, [rect], client);
let expected = [];
for (let i = 0; i < 4; i++) { expected = expected.concat(targetDataCheckArr); }
expected = expected.concat(targetDataCheckArr.slice(0, 16));
expect(client._display).to.have.displayed(new Uint8Array(expected));
});
it('should fail on an invalid subencoding', function () {
sinon.spy(client, "_fail");
const info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x05 }];
const rects = [[45]]; // an invalid subencoding
sendFbuMsg(info, rects, client);
expect(client._fail).to.have.been.calledOnce;
});
});
it.skip('should handle the TIGHT encoding', function () {
// TODO(directxman12): test this
});
it.skip('should handle the TIGHT_PNG encoding', function () {
// TODO(directxman12): test this
});
it('should handle the DesktopSize pseduo-encoding', function () {
sinon.spy(client._display, 'resize');
sendFbuMsg([{ x: 0, y: 0, width: 20, height: 50, encoding: -223 }], [[]], client);
@@ -2525,12 +2288,7 @@ describe('Remote Frame Buffer Protocol Client', function () {
});
it('should be able to handle large Provide messages', function () {
// repeat() is not supported in IE so a loop is needed instead
let expectedData = "hello";
for (let i = 1; i <= 100000; i++) {
expectedData += "hello";
}
let expectedData = "hello".repeat(100000);
let data = [3, 0, 0, 0];
const flags = [0x10, 0x00, 0x00, 0x01];
@@ -2776,23 +2534,11 @@ describe('Remote Frame Buffer Protocol Client', function () {
let pos = elementToClient(x, y);
let ev;
try {
ev = new MouseEvent('mousemove',
{ 'screenX': pos.x + window.screenX,
'screenY': pos.y + window.screenY,
'clientX': pos.x,
'clientY': pos.y });
} catch (e) {
ev = document.createEvent('MouseEvent');
ev.initMouseEvent('mousemove',
true, true, window, 0,
pos.x + window.screenX,
pos.y + window.screenY,
pos.x, pos.y,
false, false, false, false,
0, null);
}
ev = new MouseEvent('mousemove',
{ 'screenX': pos.x + window.screenX,
'screenY': pos.y + window.screenY,
'clientX': pos.x,
'clientY': pos.y });
client._canvas.dispatchEvent(ev);
}
@@ -2800,25 +2546,13 @@ describe('Remote Frame Buffer Protocol Client', function () {
let pos = elementToClient(x, y);
let ev;
try {
ev = new MouseEvent(down ? 'mousedown' : 'mouseup',
{ 'screenX': pos.x + window.screenX,
'screenY': pos.y + window.screenY,
'clientX': pos.x,
'clientY': pos.y,
'button': button,
'buttons': 1 << button });
} catch (e) {
ev = document.createEvent('MouseEvent');
ev.initMouseEvent(down ? 'mousedown' : 'mouseup',
true, true, window, 0,
pos.x + window.screenX,
pos.y + window.screenY,
pos.x, pos.y,
false, false, false, false,
button, null);
}
ev = new MouseEvent(down ? 'mousedown' : 'mouseup',
{ 'screenX': pos.x + window.screenX,
'screenY': pos.y + window.screenY,
'clientX': pos.x,
'clientY': pos.y,
'button': button,
'buttons': 1 << button });
client._canvas.dispatchEvent(ev);
}
@@ -3053,25 +2787,14 @@ describe('Remote Frame Buffer Protocol Client', function () {
let pos = elementToClient(x, y);
let ev;
try {
ev = new WheelEvent('wheel',
{ 'screenX': pos.x + window.screenX,
'screenY': pos.y + window.screenY,
'clientX': pos.x,
'clientY': pos.y,
'deltaX': dx,
'deltaY': dy,
'deltaMode': mode });
} catch (e) {
ev = document.createEvent('WheelEvent');
ev.initWheelEvent('wheel', true, true, window, 0,
pos.x + window.screenX,
pos.y + window.screenY,
pos.x, pos.y,
0, null, "",
dx, dy, 0, mode);
}
ev = new WheelEvent('wheel',
{ 'screenX': pos.x + window.screenX,
'screenY': pos.y + window.screenY,
'clientX': pos.x,
'clientY': pos.y,
'deltaX': dx,
'deltaY': dy,
'deltaMode': mode });
client._canvas.dispatchEvent(ev);
}
@@ -3186,14 +2909,6 @@ describe('Remote Frame Buffer Protocol Client', function () {
});
describe('Gesture event handlers', function () {
beforeEach(function () {
// Touch events and gestures are not supported on IE
if (browser.isIE()) {
this.skip();
return;
}
});
function gestureStart(gestureType, x, y,
magnitudeX = 0, magnitudeY = 0) {
let pos = elementToClient(x, y);
+107
View File
@@ -0,0 +1,107 @@
const expect = chai.expect;
import Websock from '../core/websock.js';
import Display from '../core/display.js';
import RREDecoder from '../core/decoders/rre.js';
import FakeWebSocket from './fake.websocket.js';
function testDecodeRect(decoder, x, y, width, height, data, display, depth) {
let sock;
sock = new Websock;
sock.open("ws://example.com");
sock.on('message', () => {
decoder.decodeRect(x, y, width, height, sock, display, depth);
});
// Empty messages are filtered at multiple layers, so we need to
// do a direct call
if (data.length === 0) {
decoder.decodeRect(x, y, width, height, sock, display, depth);
} else {
sock._websocket._receiveData(new Uint8Array(data));
}
display.flip();
}
function push16(arr, num) {
arr.push((num >> 8) & 0xFF,
num & 0xFF);
}
function push32(arr, num) {
arr.push((num >> 24) & 0xFF,
(num >> 16) & 0xFF,
(num >> 8) & 0xFF,
num & 0xFF);
}
describe('RRE Decoder', function () {
let decoder;
let display;
before(FakeWebSocket.replace);
after(FakeWebSocket.restore);
beforeEach(function () {
decoder = new RREDecoder();
display = new Display(document.createElement('canvas'));
display.resize(4, 4);
});
// TODO(directxman12): test rre_chunk_sz?
it('should handle the RRE encoding', function () {
let data = [];
push32(data, 2); // 2 subrects
push32(data, 0x00ff0000); // becomes 00ff0000 --> #00FF00 bg color
data.push(0x00); // becomes 0000ff00 --> #0000FF fg color
data.push(0x00);
data.push(0xff);
data.push(0x00);
push16(data, 0); // x: 0
push16(data, 0); // y: 0
push16(data, 2); // width: 2
push16(data, 2); // height: 2
data.push(0x00); // becomes 0000ff00 --> #0000FF fg color
data.push(0x00);
data.push(0xff);
data.push(0x00);
push16(data, 2); // x: 2
push16(data, 2); // y: 2
push16(data, 2); // width: 2
push16(data, 2); // height: 2
testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]);
expect(display).to.have.displayed(targetData);
});
it('should handle empty rects', function () {
display.fillRect(0, 0, 4, 4, [ 0x00, 0x00, 0xff ]);
display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);
display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]);
testDecodeRect(decoder, 1, 2, 0, 0, [ 0x00, 0xff, 0xff, 0xff, 0xff ], display, 24);
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]);
expect(display).to.have.displayed(targetData);
});
});
+394
View File
@@ -0,0 +1,394 @@
const expect = chai.expect;
import Websock from '../core/websock.js';
import Display from '../core/display.js';
import TightDecoder from '../core/decoders/tight.js';
import FakeWebSocket from './fake.websocket.js';
function testDecodeRect(decoder, x, y, width, height, data, display, depth) {
let sock;
sock = new Websock;
sock.open("ws://example.com");
sock.on('message', () => {
decoder.decodeRect(x, y, width, height, sock, display, depth);
});
// Empty messages are filtered at multiple layers, so we need to
// do a direct call
if (data.length === 0) {
decoder.decodeRect(x, y, width, height, sock, display, depth);
} else {
sock._websocket._receiveData(new Uint8Array(data));
}
display.flip();
}
describe('Tight Decoder', function () {
let decoder;
let display;
before(FakeWebSocket.replace);
after(FakeWebSocket.restore);
beforeEach(function () {
decoder = new TightDecoder();
display = new Display(document.createElement('canvas'));
display.resize(4, 4);
});
it('should handle fill rects', function () {
testDecodeRect(decoder, 0, 0, 4, 4,
[0x80, 0xff, 0x88, 0x44],
display, 24);
let targetData = new Uint8Array([
0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255,
0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255,
0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255,
0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255,
]);
expect(display).to.have.displayed(targetData);
});
it('should handle uncompressed copy rects', function () {
let blueData = [ 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0xff ];
let greenData = [ 0x00, 0x00, 0xff, 0x00, 0x00, 0xff, 0x00 ];
testDecodeRect(decoder, 0, 0, 2, 1, blueData, display, 24);
testDecodeRect(decoder, 0, 1, 2, 1, blueData, display, 24);
testDecodeRect(decoder, 2, 0, 2, 1, greenData, display, 24);
testDecodeRect(decoder, 2, 1, 2, 1, greenData, display, 24);
testDecodeRect(decoder, 0, 2, 2, 1, greenData, display, 24);
testDecodeRect(decoder, 0, 3, 2, 1, greenData, display, 24);
testDecodeRect(decoder, 2, 2, 2, 1, blueData, display, 24);
testDecodeRect(decoder, 2, 3, 2, 1, blueData, display, 24);
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]);
expect(display).to.have.displayed(targetData);
});
it('should handle compressed copy rects', function () {
let data = [
// Control byte
0x00,
// Pixels (compressed)
0x15,
0x78, 0x9c, 0x63, 0x60, 0xf8, 0xcf, 0x00, 0x44,
0x60, 0x82, 0x01, 0x99, 0x8d, 0x29, 0x02, 0xa6,
0x00, 0x7e, 0xbf, 0x0f, 0xf1 ];
testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]);
expect(display).to.have.displayed(targetData);
});
it('should handle uncompressed mono rects', function () {
let data = [
// Control bytes
0x40, 0x01,
// Palette
0x01, 0x00, 0x00, 0xff, 0x00, 0xff, 0x00,
// Pixels
0x30, 0x30, 0xc0, 0xc0 ];
testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]);
expect(display).to.have.displayed(targetData);
});
it('should handle compressed mono rects', function () {
display.resize(4, 12);
let data = [
// Control bytes
0x40, 0x01,
// Palette
0x01, 0x00, 0x00, 0xff, 0x00, 0xff, 0x00,
// Pixels (compressed)
0x0e,
0x78, 0x9c, 0x33, 0x30, 0x38, 0x70, 0xc0, 0x00,
0x8a, 0x01, 0x21, 0x3c, 0x05, 0xa1 ];
testDecodeRect(decoder, 0, 0, 4, 12, data, display, 24);
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]);
expect(display).to.have.displayed(targetData);
});
it('should handle uncompressed palette rects', function () {
let data1 = [
// Control bytes
0x40, 0x01,
// Palette
0x02, 0x00, 0x00, 0xff, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00,
// Pixels
0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x01 ];
let data2 = [
// Control bytes
0x40, 0x01,
// Palette
0x02, 0x00, 0x00, 0xff, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00,
// Pixels
0x01, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00 ];
testDecodeRect(decoder, 0, 0, 4, 2, data1, display, 24);
testDecodeRect(decoder, 0, 2, 4, 2, data2, display, 24);
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]);
expect(display).to.have.displayed(targetData);
});
it('should handle compressed palette rects', function () {
let data = [
// Control bytes
0x40, 0x01,
// Palette
0x02, 0x00, 0x00, 0xff, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00,
// Pixels (compressed)
0x12,
0x78, 0x9c, 0x63, 0x60, 0x60, 0x64, 0x64, 0x00,
0x62, 0x08, 0xc9, 0xc0, 0x00, 0x00, 0x00, 0x54,
0x00, 0x09 ];
testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]);
expect(display).to.have.displayed(targetData);
});
it.skip('should handle uncompressed gradient rects', function () {
// Not implemented yet
});
it.skip('should handle compressed gradient rects', function () {
// Not implemented yet
});
it('should handle empty copy rects', function () {
display.fillRect(0, 0, 4, 4, [ 0x00, 0x00, 0xff ]);
display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);
display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]);
testDecodeRect(decoder, 1, 2, 0, 0, [ 0x00 ], display, 24);
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]);
expect(display).to.have.displayed(targetData);
});
it('should handle empty palette rects', function () {
display.fillRect(0, 0, 4, 4, [ 0x00, 0x00, 0xff ]);
display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);
display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]);
testDecodeRect(decoder, 1, 2, 0, 0,
[ 0x40, 0x01, 0x01,
0xff, 0xff, 0xff,
0xff, 0xff, 0xff ], display, 24);
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]);
expect(display).to.have.displayed(targetData);
});
it('should handle empty fill rects', function () {
display.fillRect(0, 0, 4, 4, [ 0x00, 0x00, 0xff ]);
display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);
display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]);
testDecodeRect(decoder, 1, 2, 0, 0,
[ 0x80, 0xff, 0xff, 0xff ], display, 24);
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]);
expect(display).to.have.displayed(targetData);
});
it('should handle JPEG rects', function (done) {
let data = [
// Control bytes
0x90, 0xd6, 0x05,
// JPEG data
0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46,
0x49, 0x46, 0x00, 0x01, 0x01, 0x01, 0x00, 0x48,
0x00, 0x48, 0x00, 0x00, 0xff, 0xfe, 0x00, 0x13,
0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x20,
0x77, 0x69, 0x74, 0x68, 0x20, 0x47, 0x49, 0x4d,
0x50, 0xff, 0xdb, 0x00, 0x43, 0x00, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0xff, 0xdb,
0x00, 0x43, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0xff, 0xc2, 0x00, 0x11, 0x08,
0x00, 0x04, 0x00, 0x04, 0x03, 0x01, 0x11, 0x00,
0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xff, 0xc4,
0x00, 0x14, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x07, 0xff, 0xc4, 0x00, 0x14,
0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x08, 0xff, 0xda, 0x00, 0x0c, 0x03, 0x01,
0x00, 0x02, 0x10, 0x03, 0x10, 0x00, 0x00, 0x01,
0x1e, 0x0a, 0xa7, 0x7f, 0xff, 0xc4, 0x00, 0x14,
0x10, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x05, 0xff, 0xda, 0x00, 0x08, 0x01, 0x01,
0x00, 0x01, 0x05, 0x02, 0x5d, 0x74, 0x41, 0x47,
0xff, 0xc4, 0x00, 0x1f, 0x11, 0x00, 0x01, 0x04,
0x02, 0x02, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x04, 0x05,
0x07, 0x08, 0x14, 0x16, 0x03, 0x15, 0x17, 0x25,
0x26, 0xff, 0xda, 0x00, 0x08, 0x01, 0x03, 0x01,
0x01, 0x3f, 0x01, 0xad, 0x35, 0xa6, 0x13, 0xb8,
0x10, 0x98, 0x5d, 0x8a, 0xb1, 0x41, 0x7e, 0x43,
0x99, 0x24, 0x3d, 0x8f, 0x70, 0x30, 0xd8, 0xcb,
0x44, 0xbb, 0x7d, 0x48, 0xb5, 0xf8, 0x18, 0x7f,
0xe7, 0xc1, 0x9f, 0x86, 0x45, 0x9b, 0xfa, 0xf1,
0x61, 0x96, 0x46, 0xbf, 0x56, 0xc8, 0x8b, 0x2b,
0x0b, 0x35, 0x6e, 0x4b, 0x8a, 0x95, 0x6a, 0xf9,
0xff, 0x00, 0xff, 0xc4, 0x00, 0x1f, 0x11, 0x00,
0x01, 0x04, 0x02, 0x02, 0x03, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,
0x02, 0x04, 0x05, 0x12, 0x13, 0x14, 0x01, 0x06,
0x11, 0x22, 0x23, 0xff, 0xda, 0x00, 0x08, 0x01,
0x02, 0x01, 0x01, 0x3f, 0x01, 0x85, 0x85, 0x8c,
0xec, 0x31, 0x8d, 0xa6, 0x26, 0x1b, 0x6e, 0x48,
0xbc, 0xcd, 0xb0, 0xe3, 0x33, 0x86, 0xf9, 0x35,
0xdc, 0x15, 0xa8, 0xbe, 0x4d, 0x4a, 0x10, 0x22,
0x80, 0x00, 0x91, 0xe8, 0x24, 0xda, 0xb6, 0x57,
0x95, 0xf2, 0xa5, 0x73, 0xff, 0xc4, 0x00, 0x1e,
0x10, 0x00, 0x01, 0x04, 0x03, 0x00, 0x03, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x03, 0x01, 0x02, 0x04, 0x12, 0x05, 0x11,
0x13, 0x14, 0x22, 0x23, 0xff, 0xda, 0x00, 0x08,
0x01, 0x01, 0x00, 0x06, 0x3f, 0x02, 0x91, 0x89,
0xc4, 0xc8, 0xf1, 0x60, 0x45, 0xe5, 0xc0, 0x1c,
0x80, 0x7a, 0x77, 0x00, 0xe4, 0x97, 0xeb, 0x24,
0x66, 0x33, 0xac, 0x63, 0x11, 0xfe, 0xe4, 0x76,
0xad, 0x56, 0xe9, 0xa8, 0x88, 0x9f, 0xff, 0xc4,
0x00, 0x14, 0x10, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xff, 0xda, 0x00, 0x08,
0x01, 0x01, 0x00, 0x01, 0x3f, 0x21, 0x68, 0x3f,
0x92, 0x17, 0x81, 0x1f, 0x7f, 0xff, 0xda, 0x00,
0x0c, 0x03, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00,
0x00, 0x00, 0x10, 0x5f, 0xff, 0xc4, 0x00, 0x14,
0x11, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xff, 0xda, 0x00, 0x08, 0x01, 0x03,
0x01, 0x01, 0x3f, 0x10, 0x03, 0xeb, 0x11, 0xe4,
0xa7, 0xe3, 0xff, 0x00, 0xff, 0xc4, 0x00, 0x14,
0x11, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xff, 0xda, 0x00, 0x08, 0x01, 0x02,
0x01, 0x01, 0x3f, 0x10, 0x6b, 0xd3, 0x02, 0xdc,
0x9a, 0xf4, 0xff, 0x00, 0xff, 0xc4, 0x00, 0x14,
0x10, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xff, 0xda, 0x00, 0x08, 0x01, 0x01,
0x00, 0x01, 0x3f, 0x10, 0x62, 0x7b, 0x3a, 0xd0,
0x3f, 0xeb, 0xff, 0x00, 0xff, 0xd9,
];
testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
let targetData = new Uint8Array([
0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255
]);
// Browsers have rounding errors, so we need an approximate
// comparing function
function almost(a, b) {
let diff = Math.abs(a - b);
return diff < 5;
}
display.onflush = () => {
expect(display).to.have.displayed(targetData, almost);
done();
};
display.flush();
});
});
+144
View File
@@ -0,0 +1,144 @@
const expect = chai.expect;
import Websock from '../core/websock.js';
import Display from '../core/display.js';
import TightPngDecoder from '../core/decoders/tightpng.js';
import FakeWebSocket from './fake.websocket.js';
function testDecodeRect(decoder, x, y, width, height, data, display, depth) {
let sock;
sock = new Websock;
sock.open("ws://example.com");
sock.on('message', () => {
decoder.decodeRect(x, y, width, height, sock, display, depth);
});
// Empty messages are filtered at multiple layers, so we need to
// do a direct call
if (data.length === 0) {
decoder.decodeRect(x, y, width, height, sock, display, depth);
} else {
sock._websocket._receiveData(new Uint8Array(data));
}
display.flip();
}
describe('TightPng Decoder', function () {
let decoder;
let display;
before(FakeWebSocket.replace);
after(FakeWebSocket.restore);
beforeEach(function () {
decoder = new TightPngDecoder();
display = new Display(document.createElement('canvas'));
display.resize(4, 4);
});
it('should handle the TightPng encoding', function (done) {
let data = [
// Control bytes
0xa0, 0xb4, 0x04,
// PNG data
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a,
0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52,
0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04,
0x08, 0x02, 0x00, 0x00, 0x00, 0x26, 0x93, 0x09,
0x29, 0x00, 0x00, 0x01, 0x84, 0x69, 0x43, 0x43,
0x50, 0x49, 0x43, 0x43, 0x20, 0x70, 0x72, 0x6f,
0x66, 0x69, 0x6c, 0x65, 0x00, 0x00, 0x28, 0x91,
0x7d, 0x91, 0x3d, 0x48, 0xc3, 0x40, 0x18, 0x86,
0xdf, 0xa6, 0x6a, 0x45, 0x2a, 0x0e, 0x76, 0x10,
0x71, 0x08, 0x52, 0x9d, 0x2c, 0x88, 0x8a, 0x38,
0x6a, 0x15, 0x8a, 0x50, 0x21, 0xd4, 0x0a, 0xad,
0x3a, 0x98, 0x5c, 0xfa, 0x07, 0x4d, 0x1a, 0x92,
0x14, 0x17, 0x47, 0xc1, 0xb5, 0xe0, 0xe0, 0xcf,
0x62, 0xd5, 0xc1, 0xc5, 0x59, 0x57, 0x07, 0x57,
0x41, 0x10, 0xfc, 0x01, 0x71, 0x72, 0x74, 0x52,
0x74, 0x91, 0x12, 0xbf, 0x4b, 0x0a, 0x2d, 0x62,
0xbc, 0xe3, 0xb8, 0x87, 0xf7, 0xbe, 0xf7, 0xe5,
0xee, 0x3b, 0x40, 0xa8, 0x97, 0x99, 0x66, 0x75,
0x8c, 0x03, 0x9a, 0x6e, 0x9b, 0xa9, 0x44, 0x5c,
0xcc, 0x64, 0x57, 0xc5, 0xd0, 0x2b, 0xba, 0x68,
0x86, 0x31, 0x8c, 0x2e, 0x99, 0x59, 0xc6, 0x9c,
0x24, 0x25, 0xe1, 0x3b, 0xbe, 0xee, 0x11, 0xe0,
0xfb, 0x5d, 0x8c, 0x67, 0xf9, 0xd7, 0xfd, 0x39,
0x7a, 0xd5, 0x9c, 0xc5, 0x80, 0x80, 0x48, 0x3c,
0xcb, 0x0c, 0xd3, 0x26, 0xde, 0x20, 0x9e, 0xde,
0xb4, 0x0d, 0xce, 0xfb, 0xc4, 0x11, 0x56, 0x94,
0x55, 0xe2, 0x73, 0xe2, 0x31, 0x93, 0x2e, 0x48,
0xfc, 0xc8, 0x75, 0xc5, 0xe3, 0x37, 0xce, 0x05,
0x97, 0x05, 0x9e, 0x19, 0x31, 0xd3, 0xa9, 0x79,
0xe2, 0x08, 0xb1, 0x58, 0x68, 0x63, 0xa5, 0x8d,
0x59, 0xd1, 0xd4, 0x88, 0xa7, 0x88, 0xa3, 0xaa,
0xa6, 0x53, 0xbe, 0x90, 0xf1, 0x58, 0xe5, 0xbc,
0xc5, 0x59, 0x2b, 0x57, 0x59, 0xf3, 0x9e, 0xfc,
0x85, 0xe1, 0x9c, 0xbe, 0xb2, 0xcc, 0x75, 0x5a,
0x43, 0x48, 0x60, 0x11, 0x4b, 0x90, 0x20, 0x42,
0x41, 0x15, 0x25, 0x94, 0x61, 0x23, 0x46, 0xbb,
0x4e, 0x8a, 0x85, 0x14, 0x9d, 0xc7, 0x7d, 0xfc,
0x83, 0xae, 0x5f, 0x22, 0x97, 0x42, 0xae, 0x12,
0x18, 0x39, 0x16, 0x50, 0x81, 0x06, 0xd9, 0xf5,
0x83, 0xff, 0xc1, 0xef, 0xde, 0x5a, 0xf9, 0xc9,
0x09, 0x2f, 0x29, 0x1c, 0x07, 0x3a, 0x5f, 0x1c,
0xe7, 0x63, 0x04, 0x08, 0xed, 0x02, 0x8d, 0x9a,
0xe3, 0x7c, 0x1f, 0x3b, 0x4e, 0xe3, 0x04, 0x08,
0x3e, 0x03, 0x57, 0x7a, 0xcb, 0x5f, 0xa9, 0x03,
0x33, 0x9f, 0xa4, 0xd7, 0x5a, 0x5a, 0xf4, 0x08,
0xe8, 0xdb, 0x06, 0x2e, 0xae, 0x5b, 0x9a, 0xb2,
0x07, 0x5c, 0xee, 0x00, 0x03, 0x4f, 0x86, 0x6c,
0xca, 0xae, 0x14, 0xa4, 0x25, 0xe4, 0xf3, 0xc0,
0xfb, 0x19, 0x7d, 0x53, 0x16, 0xe8, 0xbf, 0x05,
0x7a, 0xd6, 0xbc, 0xbe, 0x35, 0xcf, 0x71, 0xfa,
0x00, 0xa4, 0xa9, 0x57, 0xc9, 0x1b, 0xe0, 0xe0,
0x10, 0x18, 0x2d, 0x50, 0xf6, 0xba, 0xcf, 0xbb,
0xbb, 0xdb, 0xfb, 0xf6, 0x6f, 0x4d, 0xb3, 0x7f,
0x3f, 0x0a, 0x27, 0x72, 0x7d, 0x49, 0x29, 0x8b,
0xbb, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59,
0x73, 0x00, 0x00, 0x2e, 0x23, 0x00, 0x00, 0x2e,
0x23, 0x01, 0x78, 0xa5, 0x3f, 0x76, 0x00, 0x00,
0x00, 0x07, 0x74, 0x49, 0x4d, 0x45, 0x07, 0xe4,
0x06, 0x06, 0x0c, 0x23, 0x1d, 0x3f, 0x9f, 0xbb,
0x94, 0x00, 0x00, 0x00, 0x19, 0x74, 0x45, 0x58,
0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74,
0x00, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64,
0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x47, 0x49,
0x4d, 0x50, 0x57, 0x81, 0x0e, 0x17, 0x00, 0x00,
0x00, 0x1e, 0x49, 0x44, 0x41, 0x54, 0x08, 0xd7,
0x65, 0xc9, 0xb1, 0x0d, 0x00, 0x00, 0x08, 0x03,
0x20, 0xea, 0xff, 0x3f, 0xd7, 0xd5, 0x44, 0x56,
0x52, 0x90, 0xc2, 0x38, 0xa2, 0xd0, 0xbc, 0x59,
0x8a, 0x9f, 0x04, 0x05, 0x6b, 0x38, 0x7b, 0xb2,
0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44,
0xae, 0x42, 0x60, 0x82,
];
testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
let targetData = new Uint8Array([
0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255
]);
// Firefox currently has some very odd rounding bug:
// https://bugzilla.mozilla.org/show_bug.cgi?id=1667747
function almost(a, b) {
let diff = Math.abs(a - b);
return diff < 30;
}
display.onflush = () => {
expect(display).to.have.displayed(targetData, almost);
done();
};
display.flush();
});
});
+98 -9
View File
@@ -254,13 +254,7 @@ describe('Websock', function () {
beforeEach(function () {
sock = new Websock();
// eslint-disable-next-line no-global-assign
WebSocket = sinon.spy();
WebSocket.OPEN = oldWS.OPEN;
WebSocket.CONNECTING = oldWS.CONNECTING;
WebSocket.CLOSING = oldWS.CLOSING;
WebSocket.CLOSED = oldWS.CLOSED;
WebSocket.prototype.binaryType = 'arraybuffer';
WebSocket = sinon.spy(FakeWebSocket);
});
describe('opening', function () {
@@ -276,9 +270,17 @@ describe('Websock', function () {
// it('should initialize the event handlers')?
});
describe('attaching', function () {
it('should attach to an existing websocket', function () {
let ws = new FakeWebSocket('ws://localhost:8675');
sock.attach(ws);
expect(WebSocket).to.not.have.been.called;
});
});
describe('closing', function () {
beforeEach(function () {
sock.open('ws://');
sock.open('ws://localhost');
sock._websocket.close = sinon.spy();
});
@@ -324,7 +326,7 @@ describe('Websock', function () {
sock.on('open', sinon.spy());
sock.on('close', sinon.spy());
sock.on('error', sinon.spy());
sock.open('ws://');
sock.open('ws://localhost');
});
it('should call _recvMessage on a message', function () {
@@ -348,6 +350,93 @@ describe('Websock', function () {
});
});
describe('ready state', function () {
it('should be "unused" after construction', function () {
let sock = new Websock();
expect(sock.readyState).to.equal('unused');
});
it('should be "connecting" if WebSocket is connecting', function () {
let sock = new Websock();
let ws = new FakeWebSocket();
ws.readyState = WebSocket.CONNECTING;
sock.attach(ws);
expect(sock.readyState).to.equal('connecting');
});
it('should be "open" if WebSocket is open', function () {
let sock = new Websock();
let ws = new FakeWebSocket();
ws.readyState = WebSocket.OPEN;
sock.attach(ws);
expect(sock.readyState).to.equal('open');
});
it('should be "closing" if WebSocket is closing', function () {
let sock = new Websock();
let ws = new FakeWebSocket();
ws.readyState = WebSocket.CLOSING;
sock.attach(ws);
expect(sock.readyState).to.equal('closing');
});
it('should be "closed" if WebSocket is closed', function () {
let sock = new Websock();
let ws = new FakeWebSocket();
ws.readyState = WebSocket.CLOSED;
sock.attach(ws);
expect(sock.readyState).to.equal('closed');
});
it('should be "unknown" if WebSocket state is unknown', function () {
let sock = new Websock();
let ws = new FakeWebSocket();
ws.readyState = 666;
sock.attach(ws);
expect(sock.readyState).to.equal('unknown');
});
it('should be "connecting" if RTCDataChannel is connecting', function () {
let sock = new Websock();
let ws = new FakeWebSocket();
ws.readyState = 'connecting';
sock.attach(ws);
expect(sock.readyState).to.equal('connecting');
});
it('should be "open" if RTCDataChannel is open', function () {
let sock = new Websock();
let ws = new FakeWebSocket();
ws.readyState = 'open';
sock.attach(ws);
expect(sock.readyState).to.equal('open');
});
it('should be "closing" if RTCDataChannel is closing', function () {
let sock = new Websock();
let ws = new FakeWebSocket();
ws.readyState = 'closing';
sock.attach(ws);
expect(sock.readyState).to.equal('closing');
});
it('should be "closed" if RTCDataChannel is closed', function () {
let sock = new Websock();
let ws = new FakeWebSocket();
ws.readyState = 'closed';
sock.attach(ws);
expect(sock.readyState).to.equal('closed');
});
it('should be "unknown" if RTCDataChannel state is unknown', function () {
let sock = new Websock();
let ws = new FakeWebSocket();
ws.readyState = 'foobar';
sock.attach(ws);
expect(sock.readyState).to.equal('unknown');
});
});
after(function () {
// eslint-disable-next-line no-global-assign
WebSocket = oldWS;
+42 -5
View File
@@ -7,6 +7,48 @@ import * as WebUtil from '../app/webutil.js';
describe('WebUtil', function () {
"use strict";
describe('config variables', function () {
it('should parse query string variables', function () {
// history.pushState() will not cause the browser to attempt loading
// the URL, this is exactly what we want here for the tests.
history.pushState({}, '', "test?myvar=myval");
expect(WebUtil.getConfigVar("myvar")).to.be.equal("myval");
});
it('should return default value when no query match', function () {
history.pushState({}, '', "test?myvar=myval");
expect(WebUtil.getConfigVar("other", "def")).to.be.equal("def");
});
it('should handle no query match and no default value', function () {
history.pushState({}, '', "test?myvar=myval");
expect(WebUtil.getConfigVar("other")).to.be.equal(null);
});
it('should parse fragment variables', function () {
history.pushState({}, '', "test#myvar=myval");
expect(WebUtil.getConfigVar("myvar")).to.be.equal("myval");
});
it('should return default value when no fragment match', function () {
history.pushState({}, '', "test#myvar=myval");
expect(WebUtil.getConfigVar("other", "def")).to.be.equal("def");
});
it('should handle no fragment match and no default value', function () {
history.pushState({}, '', "test#myvar=myval");
expect(WebUtil.getConfigVar("other")).to.be.equal(null);
});
it('should handle both query and fragment', function () {
history.pushState({}, '', "test?myquery=1#myhash=2");
expect(WebUtil.getConfigVar("myquery")).to.be.equal("1");
expect(WebUtil.getConfigVar("myhash")).to.be.equal("2");
});
it('should prioritize fragment if both provide same var', function () {
history.pushState({}, '', "test?myvar=1#myvar=2");
expect(WebUtil.getConfigVar("myvar")).to.be.equal("2");
});
});
describe('cookies', function () {
// TODO
});
describe('settings', function () {
describe('localStorage', function () {
@@ -22,11 +64,6 @@ describe('WebUtil', function () {
let origLocalStorage;
beforeEach(function () {
origLocalStorage = Object.getOwnPropertyDescriptor(window, "localStorage");
if (origLocalStorage === undefined) {
// Object.getOwnPropertyDescriptor() doesn't work
// properly in any version of IE
this.skip();
}
Object.defineProperty(window, "localStorage", {value: {}});
if (window.localStorage.setItem !== undefined) {
-17
View File
@@ -2,21 +2,6 @@
<html lang="en">
<head>
<title>VNC Playback</title>
<!-- promise polyfills promises for IE11 -->
<script src="../vendor/promise.js"></script>
<!-- ES2015/ES6 modules polyfill -->
<script type="module">
window._noVNC_has_module_support = true;
</script>
<script>
window.addEventListener("load", function() {
if (window._noVNC_has_module_support) return;
var loader = document.createElement("script");
loader.src = "../vendor/browser-es-module-loader/dist/browser-es-module-loader.js";
document.head.appendChild(loader);
});
</script>
<!-- actual script modules -->
<script type="module" src="./playback-ui.js"></script>
</head>
<body>
@@ -37,7 +22,5 @@
<div id="VNC_screen">
<div id="VNC_status">Loading</div>
</div>
<script type="module" src="./playback-ui.js"></script>
</body>
</html>
+1 -1
View File
@@ -1,6 +1,6 @@
## WebSockets Proxy/Bridge
Websockify has been forked out into its own project. `launch.sh` wil
Websockify has been forked out into its own project. `novnc_proxy` will
automatically download it here if it is not already present and not
installed as system-wide.
+16 -1
View File
@@ -27,6 +27,13 @@ usage() {
echo " "
echo " --record FILE Record traffic to FILE.session.js"
echo " "
echo " --syslog SERVER Can be local socket such as /dev/log, or a UDP host:port pair."
echo " "
echo " --heartbeat SEC send a ping to the client every SEC seconds"
echo " --timeout SEC after SEC seconds exit when not connected"
echo " --idle-timeout SEC server exits after SEC seconds if there are no"
echo " active connections"
echo " "
exit 2
}
@@ -41,6 +48,10 @@ WEB=""
proxy_pid=""
SSLONLY=""
RECORD_ARG=""
SYSLOG_ARG=""
HEARTBEAT_ARG=""
IDLETIMEOUT_ARG=""
TIMEOUT_ARG=""
die() {
echo "$*"
@@ -70,6 +81,10 @@ while [ "$*" ]; do
--web) WEB="${OPTARG}"; shift ;;
--ssl-only) SSLONLY="--ssl-only" ;;
--record) RECORD_ARG="--record ${OPTARG}"; shift ;;
--syslog) SYSLOG_ARG="--syslog ${OPTARG}"; shift ;;
--heartbeat) HEARTBEAT_ARG="--heartbeat ${OPTARG}"; shift ;;
--idle-timeout) IDLETIMEOUT_ARG="--idle-timeout ${OPTARG}"; shift ;;
--timeout) TIMEOUT_ARG="--timeout ${OPTARG}"; shift ;;
-h|--help) usage ;;
-*) usage "Unknown chrooter option: ${param}" ;;
*) break ;;
@@ -162,7 +177,7 @@ fi
echo "Starting webserver and WebSockets proxy on port ${PORT}"
#${HERE}/websockify --web ${WEB} ${CERT:+--cert ${CERT}} ${PORT} ${VNC_DEST} &
${WEBSOCKIFY} ${SSLONLY} --web ${WEB} ${CERT:+--cert ${CERT}} ${KEY:+--key ${KEY}} ${PORT} ${VNC_DEST} ${RECORD_ARG} &
${WEBSOCKIFY} ${SYSLOG_ARG} ${SSLONLY} --web ${WEB} ${CERT:+--cert ${CERT}} ${KEY:+--key ${KEY}} ${PORT} ${VNC_DEST} ${HEARTBEAT_ARG} ${IDLETIMEOUT_ARG} ${RECORD_ARG} ${TIMEOUT_ARG} &
proxy_pid="$!"
sleep 1
if ! ps -p ${proxy_pid} >/dev/null; then
+19 -203
View File
@@ -6,13 +6,8 @@ const fs = require('fs');
const fse = require('fs-extra');
const babel = require('@babel/core');
const SUPPORTED_FORMATS = new Set(['amd', 'commonjs', 'systemjs', 'umd']);
program
.option('--as [format]', `output files using various import formats instead of ES6 import and export. Supports ${Array.from(SUPPORTED_FORMATS)}.`)
.option('-m, --with-source-maps [type]', 'output source maps when not generating a bundled app (type may be empty for external source maps, inline for inline source maps, or both) ')
.option('--with-app', 'process app files as well as core files')
.option('--only-legacy', 'only output legacy files (no ES6 modules) for the app')
.option('--clean', 'clear the lib folder before building')
.parse(process.argv);
@@ -20,30 +15,10 @@ program
const paths = {
main: path.resolve(__dirname, '..'),
core: path.resolve(__dirname, '..', 'core'),
app: path.resolve(__dirname, '..', 'app'),
vendor: path.resolve(__dirname, '..', 'vendor'),
outDirBase: path.resolve(__dirname, '..', 'build'),
libDirBase: path.resolve(__dirname, '..', 'lib'),
};
const noCopyFiles = new Set([
// skip these -- they don't belong in the processed application
path.join(paths.vendor, 'sinon.js'),
path.join(paths.vendor, 'browser-es-module-loader'),
path.join(paths.app, 'images', 'icons', 'Makefile'),
]);
const onlyLegacyScripts = new Set([
path.join(paths.vendor, 'promise.js'),
]);
const noTransformFiles = new Set([
// don't transform this -- we want it imported as-is to properly catch loading errors
path.join(paths.app, 'error-handler.js'),
]);
noCopyFiles.forEach(file => noTransformFiles.add(file));
// util.promisify requires Node.js 8.x, so we have our own
function promisify(original) {
return function promiseWrap() {
@@ -57,16 +32,12 @@ function promisify(original) {
};
}
const readFile = promisify(fs.readFile);
const writeFile = promisify(fs.writeFile);
const readdir = promisify(fs.readdir);
const lstat = promisify(fs.lstat);
const copy = promisify(fse.copy);
const unlink = promisify(fse.unlink);
const ensureDir = promisify(fse.ensureDir);
const rmdir = promisify(fse.rmdir);
const babelTransformFile = promisify(babel.transformFile);
@@ -87,157 +58,57 @@ function walkDir(basePath, cb, filter) {
});
}
function transformHtml(legacyScripts, onlyLegacy) {
// write out the modified vnc.html file that works with the bundle
const srcHtmlPath = path.resolve(__dirname, '..', 'vnc.html');
const outHtmlPath = path.resolve(paths.outDirBase, 'vnc.html');
return readFile(srcHtmlPath)
.then((contentsRaw) => {
let contents = contentsRaw.toString();
const startMarker = '<!-- begin scripts -->\n';
const endMarker = '<!-- end scripts -->';
const startInd = contents.indexOf(startMarker) + startMarker.length;
const endInd = contents.indexOf(endMarker, startInd);
let newScript = '';
if (onlyLegacy) {
// Only legacy version, so include things directly
for (let i = 0;i < legacyScripts.length;i++) {
newScript += ` <script src="${legacyScripts[i]}"></script>\n`;
}
} else {
// Otherwise include both modules and legacy fallbacks
newScript += ' <script type="module" crossorigin="anonymous" src="app/ui.js"></script>\n';
for (let i = 0;i < legacyScripts.length;i++) {
newScript += ` <script nomodule src="${legacyScripts[i]}"></script>\n`;
}
}
contents = contents.slice(0, startInd) + `${newScript}\n` + contents.slice(endInd);
return contents;
})
.then((contents) => {
console.log(`Writing ${outHtmlPath}`);
return writeFile(outHtmlPath, contents);
});
}
function makeLibFiles(importFormat, sourceMaps, withAppDir, onlyLegacy) {
if (!importFormat) {
throw new Error("you must specify an import format to generate compiled noVNC libraries");
} else if (!SUPPORTED_FORMATS.has(importFormat)) {
throw new Error(`unsupported output format "${importFormat}" for import/export -- only ${Array.from(SUPPORTED_FORMATS)} are supported`);
}
function makeLibFiles(sourceMaps) {
// NB: we need to make a copy of babelOpts, since babel sets some defaults on it
const babelOpts = () => ({
plugins: [],
presets: [
[ '@babel/preset-env',
{ targets: 'ie >= 11',
modules: importFormat } ]
{ modules: 'commonjs' } ]
],
ast: false,
sourceMaps: sourceMaps,
});
// No point in duplicate files without the app, so force only converted files
if (!withAppDir) {
onlyLegacy = true;
}
let inPath;
let outPathBase;
if (withAppDir) {
outPathBase = paths.outDirBase;
inPath = paths.main;
} else {
outPathBase = paths.libDirBase;
}
const legacyPathBase = onlyLegacy ? outPathBase : path.join(outPathBase, 'legacy');
fse.ensureDirSync(outPathBase);
const helpers = require('./use_require_helpers');
const helper = helpers[importFormat];
fse.ensureDirSync(paths.libDirBase);
const outFiles = [];
const legacyFiles = [];
const handleDir = (jsOnly, vendorRewrite, inPathBase, filename) => Promise.resolve()
const handleDir = (vendorRewrite, inPathBase, filename) => Promise.resolve()
.then(() => {
const outPath = path.join(outPathBase, path.relative(inPathBase, filename));
const legacyPath = path.join(legacyPathBase, path.relative(inPathBase, filename));
const outPath = path.join(paths.libDirBase, path.relative(inPathBase, filename));
if (path.extname(filename) !== '.js') {
if (!jsOnly) {
console.log(`Writing ${outPath}`);
return copy(filename, outPath);
}
return; // skip non-javascript files
}
if (noTransformFiles.has(filename)) {
return ensureDir(path.dirname(outPath))
.then(() => {
console.log(`Writing ${outPath}`);
return copy(filename, outPath);
});
}
if (onlyLegacyScripts.has(filename)) {
legacyFiles.push(legacyPath);
return ensureDir(path.dirname(legacyPath))
.then(() => {
console.log(`Writing ${legacyPath}`);
return copy(filename, legacyPath);
});
}
return Promise.resolve()
.then(() => {
if (onlyLegacy) {
return;
}
return ensureDir(path.dirname(outPath))
.then(() => {
console.log(`Writing ${outPath}`);
return copy(filename, outPath);
});
})
.then(() => ensureDir(path.dirname(legacyPath)))
.then(() => ensureDir(path.dirname(outPath)))
.then(() => {
const opts = babelOpts();
if (helper && helpers.optionsOverride) {
helper.optionsOverride(opts);
}
// Adjust for the fact that we move the core files relative
// to the vendor directory
if (vendorRewrite) {
opts.plugins.push(["import-redirect",
{"root": legacyPathBase,
{"root": paths.libDirBase,
"redirect": { "vendor/(.+)": "./vendor/$1"}}]);
}
return babelTransformFile(filename, opts)
.then((res) => {
console.log(`Writing ${legacyPath}`);
console.log(`Writing ${outPath}`);
const {map} = res;
let {code} = res;
if (sourceMaps === true) {
// append URL for external source map
code += `\n//# sourceMappingURL=${path.basename(legacyPath)}.map\n`;
code += `\n//# sourceMappingURL=${path.basename(outPath)}.map\n`;
}
outFiles.push(`${legacyPath}`);
return writeFile(legacyPath, code)
outFiles.push(`${outPath}`);
return writeFile(outPath, code)
.then(() => {
if (sourceMaps === true || sourceMaps === 'both') {
console.log(` and ${legacyPath}.map`);
outFiles.push(`${legacyPath}.map`);
return writeFile(`${legacyPath}.map`, JSON.stringify(map));
console.log(` and ${outPath}.map`);
outFiles.push(`${outPath}.map`);
return writeFile(`${outPath}.map`, JSON.stringify(map));
}
});
});
@@ -246,64 +117,12 @@ function makeLibFiles(importFormat, sourceMaps, withAppDir, onlyLegacy) {
Promise.resolve()
.then(() => {
const handler = handleDir.bind(null, true, false, inPath || paths.main);
const filter = (filename, stats) => !noCopyFiles.has(filename);
return walkDir(paths.vendor, handler, filter);
const handler = handleDir.bind(null, false, paths.main);
return walkDir(paths.vendor, handler);
})
.then(() => {
const handler = handleDir.bind(null, true, !inPath, inPath || paths.core);
const filter = (filename, stats) => !noCopyFiles.has(filename);
return walkDir(paths.core, handler, filter);
})
.then(() => {
if (!withAppDir) return;
const handler = handleDir.bind(null, false, false, inPath);
const filter = (filename, stats) => !noCopyFiles.has(filename);
return walkDir(paths.app, handler, filter);
})
.then(() => {
if (!withAppDir) return;
if (!helper || !helper.appWriter) {
throw new Error(`Unable to generate app for the ${importFormat} format!`);
}
const outAppPath = path.join(legacyPathBase, 'app.js');
console.log(`Writing ${outAppPath}`);
return helper.appWriter(outPathBase, legacyPathBase, outAppPath)
.then((extraScripts) => {
let legacyScripts = [];
legacyFiles.forEach((file) => {
let relFilePath = path.relative(outPathBase, file);
legacyScripts.push(relFilePath);
});
legacyScripts = legacyScripts.concat(extraScripts);
let relAppPath = path.relative(outPathBase, outAppPath);
legacyScripts.push(relAppPath);
transformHtml(legacyScripts, onlyLegacy);
})
.then(() => {
if (!helper.removeModules) return;
console.log(`Cleaning up temporary files...`);
return Promise.all(outFiles.map((filepath) => {
unlink(filepath)
.then(() => {
// Try to clean up any empty directories if
// this was the last file in there
const rmdirR = dir =>
rmdir(dir)
.then(() => rmdirR(path.dirname(dir)))
.catch(() => {
// Assume the error was ENOTEMPTY and ignore it
});
return rmdirR(path.dirname(filepath));
});
}));
});
const handler = handleDir.bind(null, true, paths.core);
return walkDir(paths.core, handler);
})
.catch((err) => {
console.error(`Failure converting modules: ${err}`);
@@ -314,9 +133,6 @@ function makeLibFiles(importFormat, sourceMaps, withAppDir, onlyLegacy) {
if (program.clean) {
console.log(`Removing ${paths.libDirBase}`);
fse.removeSync(paths.libDirBase);
console.log(`Removing ${paths.outDirBase}`);
fse.removeSync(paths.outDirBase);
}
makeLibFiles(program.as, program.withSourceMaps, program.withApp, program.onlyLegacy);
makeLibFiles(program.withSourceMaps);
-60
View File
@@ -1,60 +0,0 @@
// writes helpers require for vnc.html (they should output app.js)
const fs = require('fs');
const path = require('path');
// util.promisify requires Node.js 8.x, so we have our own
function promisify(original) {
return function promiseWrap() {
const args = Array.prototype.slice.call(arguments);
return new Promise((resolve, reject) => {
original.apply(this, args.concat((err, value) => {
if (err) return reject(err);
resolve(value);
}));
});
};
}
const writeFile = promisify(fs.writeFile);
module.exports = {
'amd': {
appWriter: (baseOutPath, scriptBasePath, outPath) => {
// setup for requirejs
const uiPath = path.relative(baseOutPath,
path.join(scriptBasePath, 'app', 'ui'));
return writeFile(outPath, `requirejs(["${uiPath}"], function (ui) {});`)
.then(() => {
console.log(`Please place RequireJS in ${path.join(scriptBasePath, 'require.js')}`);
const requirePath = path.relative(baseOutPath,
path.join(scriptBasePath, 'require.js'));
return [ requirePath ];
});
},
},
'commonjs': {
appWriter: (baseOutPath, scriptBasePath, outPath) => {
const browserify = require('browserify');
const b = browserify(path.join(scriptBasePath, 'app/ui.js'), {});
return promisify(b.bundle).call(b)
.then(buf => writeFile(outPath, buf))
.then(() => []);
},
removeModules: true,
},
'systemjs': {
appWriter: (baseOutPath, scriptBasePath, outPath) => {
const uiPath = path.relative(baseOutPath,
path.join(scriptBasePath, 'app', 'ui.js'));
return writeFile(outPath, `SystemJS.import("${uiPath}");`)
.then(() => {
console.log(`Please place SystemJS in ${path.join(scriptBasePath, 'system-production.js')}`);
const systemjsPath = path.relative(baseOutPath,
path.join(scriptBasePath, 'system-production.js'));
return [ systemjsPath ];
});
},
},
'umd': {
},
};
-15
View File
@@ -1,15 +0,0 @@
Custom Browser ES Module Loader
===============================
This is a module loader using babel and the ES Module Loader polyfill.
It's based heavily on
https://github.com/ModuleLoader/browser-es-module-loader, but uses
WebWorkers to compile the modules in the background.
To generate, run `npx rollup -c` in this directory, and then run
`./genworker.js`.
LICENSE
-------
MIT
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
-13
View File
@@ -1,13 +0,0 @@
#!/usr/bin/env node
var fs = require("fs");
var browserify = require("browserify");
browserify("src/babel-worker.js")
.transform("babelify", {
presets: [ [ "@babel/preset-env", { targets: "ie >= 11" } ] ],
global: true,
ignore: [ "../../node_modules/core-js" ]
})
.bundle()
.pipe(fs.createWriteStream("dist/babel-worker.js"));
-15
View File
@@ -1,15 +0,0 @@
import nodeResolve from 'rollup-plugin-node-resolve';
export default {
input: 'src/browser-es-module-loader.js',
output: {
file: 'dist/browser-es-module-loader.js',
format: 'umd',
name: 'BrowserESModuleLoader',
sourcemap: true,
},
plugins: [
nodeResolve(),
],
};
-23
View File
@@ -1,23 +0,0 @@
// Polyfills needed for Babel to function
require("core-js");
var babelTransform = require('@babel/core').transform;
var babelTransformDynamicImport = require('@babel/plugin-syntax-dynamic-import');
var babelTransformModulesSystemJS = require('@babel/plugin-transform-modules-systemjs');
var babelPresetEnv = require('@babel/preset-env');
self.onmessage = function (evt) {
// transform source with Babel
var output = babelTransform(evt.data.source, {
compact: false,
filename: evt.data.key + '!transpiled',
sourceFileName: evt.data.key,
moduleIds: false,
sourceMaps: 'inline',
babelrc: false,
plugins: [babelTransformDynamicImport, babelTransformModulesSystemJS],
presets: [ [ babelPresetEnv, { targets: 'ie >= 11' } ] ],
});
self.postMessage({key: evt.data.key, code: output.code, source: evt.data.source});
};
@@ -1,279 +0,0 @@
import RegisterLoader from 'es-module-loader/core/register-loader.js';
import { baseURI, global, isBrowser } from 'es-module-loader/core/common.js';
import { resolveIfNotPlain } from 'es-module-loader/core/resolve.js';
var loader;
// <script type="module"> support
var anonSources = {};
if (typeof document != 'undefined' && document.getElementsByTagName) {
var handleError = function(err) {
// dispatch an error event so that we can display in errors in browsers
// that don't yet support unhandledrejection
if (window.onunhandledrejection === undefined) {
try {
var evt = new Event('error');
} catch (_eventError) {
var evt = document.createEvent('Event');
evt.initEvent('error', true, true);
}
evt.message = err.message;
if (err.fileName) {
evt.filename = err.fileName;
evt.lineno = err.lineNumber;
evt.colno = err.columnNumber;
} else if (err.sourceURL) {
evt.filename = err.sourceURL;
evt.lineno = err.line;
evt.colno = err.column;
}
evt.error = err;
window.dispatchEvent(evt);
}
// throw so it still shows up in the console
throw err;
}
var ready = function() {
document.removeEventListener('DOMContentLoaded', ready, false );
var anonCnt = 0;
var scripts = document.getElementsByTagName('script');
for (var i = 0; i < scripts.length; i++) {
var script = scripts[i];
if (script.type == 'module' && !script.loaded) {
script.loaded = true;
if (script.src) {
loader.import(script.src).catch(handleError);
}
// anonymous modules supported via a custom naming scheme and registry
else {
var uri = './<anon' + ++anonCnt + '>.js';
if (script.id !== ""){
uri = "./" + script.id;
}
var anonName = resolveIfNotPlain(uri, baseURI);
anonSources[anonName] = script.innerHTML;
loader.import(anonName).catch(handleError);
}
}
}
}
// simple DOM ready
if (document.readyState !== 'loading')
setTimeout(ready);
else
document.addEventListener('DOMContentLoaded', ready, false);
}
function BrowserESModuleLoader(baseKey) {
if (baseKey)
this.baseKey = resolveIfNotPlain(baseKey, baseURI) || resolveIfNotPlain('./' + baseKey, baseURI);
RegisterLoader.call(this);
var loader = this;
// ensure System.register is available
global.System = global.System || {};
if (typeof global.System.register == 'function')
var prevRegister = global.System.register;
global.System.register = function() {
loader.register.apply(loader, arguments);
if (prevRegister)
prevRegister.apply(this, arguments);
};
}
BrowserESModuleLoader.prototype = Object.create(RegisterLoader.prototype);
// normalize is never given a relative name like "./x", that part is already handled
BrowserESModuleLoader.prototype[RegisterLoader.resolve] = function(key, parent) {
var resolved = RegisterLoader.prototype[RegisterLoader.resolve].call(this, key, parent || this.baseKey) || key;
if (!resolved)
throw new RangeError('ES module loader does not resolve plain module names, resolving "' + key + '" to ' + parent);
return resolved;
};
function xhrFetch(url, resolve, reject) {
var xhr = new XMLHttpRequest();
var load = function(source) {
resolve(xhr.responseText);
}
var error = function() {
reject(new Error('XHR error' + (xhr.status ? ' (' + xhr.status + (xhr.statusText ? ' ' + xhr.statusText : '') + ')' : '') + ' loading ' + url));
}
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
// in Chrome on file:/// URLs, status is 0
if (xhr.status == 0) {
if (xhr.responseText) {
load();
}
else {
// when responseText is empty, wait for load or error event
// to inform if it is a 404 or empty file
xhr.addEventListener('error', error);
xhr.addEventListener('load', load);
}
}
else if (xhr.status === 200) {
load();
}
else {
error();
}
}
};
xhr.open("GET", url, true);
xhr.send(null);
}
var WorkerPool = function (script, size) {
var current = document.currentScript;
// IE doesn't support currentScript
if (!current) {
// Find an entry with out basename
var scripts = document.getElementsByTagName('script');
for (var i = 0; i < scripts.length; i++) {
if (scripts[i].src.indexOf("browser-es-module-loader.js") !== -1) {
current = scripts[i];
break;
}
}
if (!current)
throw Error("Could not find own <script> element");
}
script = current.src.substr(0, current.src.lastIndexOf("/")) + "/" + script;
this._workers = new Array(size);
this._ind = 0;
this._size = size;
this._jobs = 0;
this.onmessage = undefined;
this._stopTimeout = undefined;
for (var i = 0; i < size; i++) {
var wrkr = new Worker(script);
wrkr._count = 0;
wrkr._ind = i;
wrkr.onmessage = this._onmessage.bind(this, wrkr);
wrkr.onerror = this._onerror.bind(this);
this._workers[i] = wrkr;
}
this._checkJobs();
};
WorkerPool.prototype = {
postMessage: function (msg) {
if (this._stopTimeout !== undefined) {
clearTimeout(this._stopTimeout);
this._stopTimeout = undefined;
}
var wrkr = this._workers[this._ind % this._size];
wrkr._count++;
this._jobs++;
wrkr.postMessage(msg);
this._ind++;
},
_onmessage: function (wrkr, evt) {
wrkr._count--;
this._jobs--;
this.onmessage(evt, wrkr);
this._checkJobs();
},
_onerror: function(err) {
try {
var evt = new Event('error');
} catch (_eventError) {
var evt = document.createEvent('Event');
evt.initEvent('error', true, true);
}
evt.message = err.message;
evt.filename = err.filename;
evt.lineno = err.lineno;
evt.colno = err.colno;
evt.error = err.error;
window.dispatchEvent(evt);
},
_checkJobs: function () {
if (this._jobs === 0 && this._stopTimeout === undefined) {
// wait for 2s of inactivity before stopping (that should be enough for local loading)
this._stopTimeout = setTimeout(this._stop.bind(this), 2000);
}
},
_stop: function () {
this._workers.forEach(function(wrkr) {
wrkr.terminate();
});
}
};
var promiseMap = new Map();
var babelWorker = new WorkerPool('babel-worker.js', 3);
babelWorker.onmessage = function (evt) {
var promFuncs = promiseMap.get(evt.data.key);
promFuncs.resolve(evt.data);
promiseMap.delete(evt.data.key);
};
// instantiate just needs to run System.register
// so we fetch the source, convert into the Babel System module format, then evaluate it
BrowserESModuleLoader.prototype[RegisterLoader.instantiate] = function(key, processAnonRegister) {
var loader = this;
// load as ES with Babel converting into System.register
return new Promise(function(resolve, reject) {
// anonymous module
if (anonSources[key]) {
resolve(anonSources[key])
anonSources[key] = undefined;
}
// otherwise we fetch
else {
xhrFetch(key, resolve, reject);
}
})
.then(function(source) {
// check our cache first
var cacheEntry = localStorage.getItem(key);
if (cacheEntry) {
cacheEntry = JSON.parse(cacheEntry);
// TODO: store a hash instead
if (cacheEntry.source === source) {
return Promise.resolve({key: key, code: cacheEntry.code, source: cacheEntry.source});
}
}
return new Promise(function (resolve, reject) {
promiseMap.set(key, {resolve: resolve, reject: reject});
babelWorker.postMessage({key: key, source: source});
});
}).then(function (data) {
// evaluate without require, exports and module variables
// we leave module in for now to allow module.require access
try {
var cacheEntry = JSON.stringify({source: data.source, code: data.code});
localStorage.setItem(key, cacheEntry);
} catch (e) {
if (window.console) {
window.console.warn('Unable to cache transpiled version of ' + key + ': ' + e);
}
}
(0, eval)(data.code + '\n//# sourceURL=' + data.key + '!transpiled');
processAnonRegister();
});
};
// create a default loader instance in the browser
if (isBrowser)
loader = new BrowserESModuleLoader();
export default BrowserESModuleLoader;
-255
View File
@@ -1,255 +0,0 @@
/* Copyright (c) 2014 Taylor Hakes
* Copyright (c) 2014 Forbes Lindesay
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
(function (root) {
// Store setTimeout reference so promise-polyfill will be unaffected by
// other code modifying setTimeout (like sinon.useFakeTimers())
var setTimeoutFunc = setTimeout;
function noop() {}
// Polyfill for Function.prototype.bind
function bind(fn, thisArg) {
return function () {
fn.apply(thisArg, arguments);
};
}
function Promise(fn) {
if (typeof this !== 'object') throw new TypeError('Promises must be constructed via new');
if (typeof fn !== 'function') throw new TypeError('not a function');
this._state = 0;
this._handled = false;
this._value = undefined;
this._deferreds = [];
doResolve(fn, this);
}
function handle(self, deferred) {
while (self._state === 3) {
self = self._value;
}
if (self._state === 0) {
self._deferreds.push(deferred);
return;
}
self._handled = true;
Promise._immediateFn(function () {
var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected;
if (cb === null) {
(self._state === 1 ? resolve : reject)(deferred.promise, self._value);
return;
}
var ret;
try {
ret = cb(self._value);
} catch (e) {
reject(deferred.promise, e);
return;
}
resolve(deferred.promise, ret);
});
}
function resolve(self, newValue) {
try {
// Promise Resolution Procedure: https://github.com/promises-aplus/promises-spec#the-promise-resolution-procedure
if (newValue === self) throw new TypeError('A promise cannot be resolved with itself.');
if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) {
var then = newValue.then;
if (newValue instanceof Promise) {
self._state = 3;
self._value = newValue;
finale(self);
return;
} else if (typeof then === 'function') {
doResolve(bind(then, newValue), self);
return;
}
}
self._state = 1;
self._value = newValue;
finale(self);
} catch (e) {
reject(self, e);
}
}
function reject(self, newValue) {
self._state = 2;
self._value = newValue;
finale(self);
}
function finale(self) {
if (self._state === 2 && self._deferreds.length === 0) {
Promise._immediateFn(function() {
if (!self._handled) {
Promise._unhandledRejectionFn(self._value);
}
});
}
for (var i = 0, len = self._deferreds.length; i < len; i++) {
handle(self, self._deferreds[i]);
}
self._deferreds = null;
}
function Handler(onFulfilled, onRejected, promise) {
this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null;
this.onRejected = typeof onRejected === 'function' ? onRejected : null;
this.promise = promise;
}
/**
* Take a potentially misbehaving resolver function and make sure
* onFulfilled and onRejected are only called once.
*
* Makes no guarantees about asynchrony.
*/
function doResolve(fn, self) {
var done = false;
try {
fn(function (value) {
if (done) return;
done = true;
resolve(self, value);
}, function (reason) {
if (done) return;
done = true;
reject(self, reason);
});
} catch (ex) {
if (done) return;
done = true;
reject(self, ex);
}
}
Promise.prototype['catch'] = function (onRejected) {
return this.then(null, onRejected);
};
Promise.prototype.then = function (onFulfilled, onRejected) {
var prom = new (this.constructor)(noop);
handle(this, new Handler(onFulfilled, onRejected, prom));
return prom;
};
Promise.all = function (arr) {
var args = Array.prototype.slice.call(arr);
return new Promise(function (resolve, reject) {
if (args.length === 0) return resolve([]);
var remaining = args.length;
function res(i, val) {
try {
if (val && (typeof val === 'object' || typeof val === 'function')) {
var then = val.then;
if (typeof then === 'function') {
then.call(val, function (val) {
res(i, val);
}, reject);
return;
}
}
args[i] = val;
if (--remaining === 0) {
resolve(args);
}
} catch (ex) {
reject(ex);
}
}
for (var i = 0; i < args.length; i++) {
res(i, args[i]);
}
});
};
Promise.resolve = function (value) {
if (value && typeof value === 'object' && value.constructor === Promise) {
return value;
}
return new Promise(function (resolve) {
resolve(value);
});
};
Promise.reject = function (value) {
return new Promise(function (resolve, reject) {
reject(value);
});
};
Promise.race = function (values) {
return new Promise(function (resolve, reject) {
for (var i = 0, len = values.length; i < len; i++) {
values[i].then(resolve, reject);
}
});
};
// Use polyfill for setImmediate for performance gains
Promise._immediateFn = (typeof setImmediate === 'function' && function (fn) { setImmediate(fn); }) ||
function (fn) {
setTimeoutFunc(fn, 0);
};
Promise._unhandledRejectionFn = function _unhandledRejectionFn(err) {
if (typeof console !== 'undefined' && console) {
console.warn('Possible Unhandled Promise Rejection:', err); // eslint-disable-line no-console
}
};
/**
* Set the immediate function to execute callbacks
* @param fn {function} Function to execute
* @deprecated
*/
Promise._setImmediateFn = function _setImmediateFn(fn) {
Promise._immediateFn = fn;
};
/**
* Change the function to execute on unhandled rejection
* @param {function} fn Function to execute on unhandled rejection
* @deprecated
*/
Promise._setUnhandledRejectionFn = function _setUnhandledRejectionFn(fn) {
Promise._unhandledRejectionFn = fn;
};
if (typeof module !== 'undefined' && module.exports) {
module.exports = Promise;
} else if (!root.Promise) {
root.Promise = Promise;
}
})(this);
-13
View File
@@ -16,10 +16,6 @@
<title>noVNC</title>
<meta charset="utf-8">
<!-- Always force latest IE rendering engine (even in intranet) & Chrome Frame
Remove this if you use the .htaccess -->
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<!-- Icons (see app/images/icons/Makefile for what the sizes are for) -->
<link rel="icon" sizes="16x16" type="image/png" href="app/images/icons/novnc-16x16.png">
@@ -54,17 +50,8 @@
<!-- Stylesheets -->
<link rel="stylesheet" href="app/styles/base.css">
<!-- this is included as a normal file in order to catch script-loading errors as well -->
<script src="app/error-handler.js"></script>
<!-- begin scripts -->
<!-- promise polyfills promises for IE11 -->
<script src="vendor/promise.js"></script>
<!-- ES2015/ES6 modules polyfill -->
<script nomodule src="vendor/browser-es-module-loader/dist/browser-es-module-loader.js"></script>
<!-- actual script modules -->
<script type="module" crossorigin="anonymous" src="app/ui.js"></script>
<!-- end scripts -->
</head>
<body>
+9 -13
View File
@@ -18,10 +18,6 @@
<meta charset="utf-8">
<!-- Always force latest IE rendering engine (even in intranet) &
Chrome Frame. Remove this if you use the .htaccess -->
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<style>
body {
@@ -61,13 +57,6 @@
</style>
<!-- Promise polyfill for IE11 -->
<script src="vendor/promise.js"></script>
<!-- ES2015/ES6 modules polyfill -->
<script nomodule src="vendor/browser-es-module-loader/dist/browser-es-module-loader.js"></script>
<!-- actual script modules -->
<script type="module" crossorigin="anonymous">
// RFB holds the API to connect and communicate with a VNC server
import RFB from './core/rfb.js';
@@ -120,13 +109,20 @@
// query string. If the variable isn't defined in the URL
// it returns the default value instead.
function readQueryVariable(name, defaultValue) {
// A URL with a query parameter can look like this:
// A URL with a query parameter can look like this (But will most probably get logged on the http server):
// https://www.example.com?myqueryparam=myvalue
//
// For privacy (Using a hastag #, the parameters will not be sent to the server)
// the url can be requested in the following way:
// https://www.example.com#myqueryparam=myvalue&password=secreatvalue
//
// Even Mixing public and non public parameters will work:
// https://www.example.com?nonsecretparam=example.com#password=secreatvalue
//
// Note that we use location.href instead of location.search
// because Firefox < 53 has a bug w.r.t location.search
const re = new RegExp('.*[?&]' + name + '=([^&#]*)'),
match = document.location.href.match(re);
match = ''.concat(document.location.href, window.location.hash).match(re);
if (match) {
// We have to decode the URL since want the cleartext value