412 Commits

Author SHA1 Message Date
Pierre Ossman
e17de291b9 noVNC 1.2.0 beta 2020-07-07 10:48:24 +02:00
Pierre Ossman
81898d7cea Remove note about clipboard ISO 8859-1 restriction
We now support full Unicode, provided the server also supports the
proper extension.
2020-07-07 10:38:06 +02:00
Pierre Ossman
dcdc17bf24 Update translation template file 2020-07-03 16:11:53 +02:00
Pierre Ossman
72ca470750 Merge branch 'deploy' of https://github.com/CendioOssman/noVNC 2020-07-03 16:06:08 +02:00
Pierre Ossman
ef5db94a89 Publish pre-releases to beta channels
Extra important for NPM which doesn't allow replacing a release once it
is published.
2020-07-03 16:00:04 +02:00
Samuel Mannehed
90ead240c7 Mention gestures in README 2020-06-28 23:01:23 +02:00
Pierre Ossman
244c02c5ea Deploy new snap on release 2020-06-26 14:48:32 +02:00
Pierre Ossman
fe2ad57077 Move snap specific script to snap directory 2020-06-26 14:46:52 +02:00
Pierre Ossman
9b3cea950a Remove redundant VERSION file
We want to avoid having this in multiple places.
2020-06-26 13:25:25 +02:00
Pierre Ossman
794b06b2bd Don't detach handler if it doesn't exist
If the beforeEach() step has been skipped then there won't be anything
here to detach.
2020-06-25 14:45:58 +02:00
Pierre Ossman
484a9551d1 Handle quick Cursor detach after mouse up
This timer might fire after the Cursor object has detached from a DOM
element, causing crashes. This will likely not happen in real scenarios,
but the tests are quick enough to trigger this.
2020-06-25 14:37:21 +02:00
Pierre Ossman
6c6776a7a0 Update build badges for GitHub actions 2020-06-24 13:02:03 +02:00
Pierre Ossman
ffb9dfdc0a Merge branch 'actions' of https://github.com/CendioOssman/noVNC 2020-06-24 12:50:31 +02:00
Pierre Ossman
2835616b75 Use GitHub actions instead of Travis/Sauce Labs
The ability to use Sauce Labs for pull requests has now stopped working,
as Travis warned about several years ago. Instead run our tests directly
on GitHub on their various virtual machines.
2020-06-23 13:56:33 +02:00
Pierre Ossman
643442feac Remove default Karma options
No need to mention things where we already use the default value.
2020-06-23 13:54:46 +02:00
Pierre Ossman
57ba67f306 Alway focus on touchstart
The new gesture detection code will always prevent the default behaviour
of touchstart, so this check no longer works properly. We might want to
add something similar to GestureHandler in the future, but let's wait
and see what use cases are requested.
2020-06-16 15:32:38 +02:00
Pierre Ossman
48f15efa69 Compensate for visual viewport when moving cursor 2020-06-16 14:24:00 +02:00
Samuel Mannehed
bb09e766ba Merge pull request #1414 from CendioOssman/gesture
Add gesture handling
2020-06-12 16:11:30 +02:00
Samuel Mannehed
32ed7c6724 Fake cursor position when using touch
With the new gestures we will simulate the cursor being in a different
location than any of the touch points. This is a bit too complex for the
Cursor class, so let's just explicitly tell it where we want the cursor
rendered.
2020-06-12 14:36:10 +02:00
Pierre Ossman
50cde2faab Move mouse event handling to RFB class
Move the last remaining bits to the RFB class to keep things simple, as
the Mouse class no longer provides any real value.
2020-06-12 14:36:10 +02:00
Pierre Ossman
88589a44f7 Increase wheel step threshold
The previous value made the detection too sensitive and it was very
difficult to scroll precisely. A value of 50 pixels should give similar
behaviour to systems that don't do fine grained scrolling.
2020-06-12 09:18:46 +02:00
Pierre Ossman
f84bc57bda Move wheel event handling to RFB class
The Mouse class does very little now so it mostly just obfuscate things.
Move everything directly in to the RFB class instead.
2020-06-12 09:18:46 +02:00
Pierre Ossman
4a87038080 Remove very legacy mouse event handling
This is only needed on such ancient versions of Internet Explorer that
it wouldn't satisfy our other requirements anyway.
2020-06-12 09:18:46 +02:00
Pierre Ossman
77c32d164d Remove delayed wheel timer
This isn't really expected behaviour from a user, i.e. that an extremely
small wheel movement still gives a large scroll event in the remote application.
2020-06-12 09:18:46 +02:00
Samuel Mannehed
07a69954b1 Add lint rule for function declaration indentation 2020-06-12 09:18:46 +02:00
Pierre Ossman
8be924c9d9 Add touch gestures for mouse emulation
Add several single and multitouch gestures to simulate various mouse
actions that would otherwise be impossible to perform.

This replaces the old system where you could select which mouse button
a single touch would generate.
2020-06-12 09:18:46 +02:00
Pierre Ossman
440ec8a0b6 Start fake test clock at real clock time
Some code relies on the clock having a somewhat sane value, so let's not
start at 0.
2020-06-11 16:50:08 +02:00
Pierre Ossman
97b86abc94 Avoid fractional pixel sizes from Display 2020-06-11 16:50:08 +02:00
Pierre Ossman
0a6aec3578 Avoid printing the Websock buffer in tests
It takes forever and just messes up the output.
2020-06-11 16:50:08 +02:00
Pierre Ossman
f694c32fd5 Merge branch 'camelcase' of https://github.com/samhed/noVNC 2020-06-05 09:41:25 +02:00
Pierre Ossman
0e37a3f83a Merge branch 'limitmouse' of https://github.com/novnc/noVNC 2020-06-03 13:55:33 +02:00
Samuel Mannehed
cfb824ed03 Add camelCase rule to eslint 2020-05-31 23:37:29 +02:00
Samuel Mannehed
756af5b44c Standardize on camelCase in App 2020-05-31 23:37:29 +02:00
Samuel Mannehed
f2fbaacc82 Standardize on camelCase in Base64 2020-05-31 23:37:29 +02:00
Samuel Mannehed
164bf50fda Standardize on camelCase in Decoders 2020-05-31 23:37:29 +02:00
Samuel Mannehed
a7fe079f81 Standardize on camelCase in Logging 2020-05-31 23:37:29 +02:00
Samuel Mannehed
ea858bfa27 Standardize on camelCase in Websock 2020-05-31 23:21:35 +02:00
Samuel Mannehed
5d570207f7 Standardize on camelCase in Display 2020-05-31 23:21:35 +02:00
Samuel Mannehed
95632e413d Standardize on camelCase in tests 2020-05-31 23:21:35 +02:00
Samuel Mannehed
8b0034ee84 Standardize on camelCase in utils 2020-05-31 23:21:35 +02:00
Samuel Mannehed
80187d158c Standardize on camelCase in RFB 2020-05-31 23:21:35 +02:00
Samuel Mannehed
dff4fefa3c Remove unused properties and variables
The code that used these were removed in the following commits:

* 9ff86fb718 (RFB._mouse_arr)
* bb6965f2e6 (old_requestAnimationFrame)
* 490d471c53 (Display._c_forceCanvas)
2020-05-31 22:49:41 +02:00
Samuel Mannehed
150596be83 Properly limit mouse moves to once every 17 ms
Previous attempt in c958269 had a number of issues, this is a full
rewrite, complete with improved unit tests.

Fixes github issue #1402
2020-05-31 00:53:15 +02:00
Samuel Mannehed
11a22dbf0c Stop send mouse clicks while dragging in view only 2020-05-31 00:53:15 +02:00
Samuel Mannehed
006743857b Standardize on camelCase for functions in RFB 2020-05-31 00:53:15 +02:00
Samuel Mannehed
e7dec5270e Standardize on camelCase for variables in RFB 2020-05-31 00:53:15 +02:00
Samuel Mannehed
b5ff33a556 Remove unused mouse_arr variable 2020-05-08 22:30:20 +02:00
Pierre Ossman
42e3b03fa8 Consistently close dialogs on connect/disconnect
This was done a bit arbitrarily before which could easily miss things,
end up in the wrong state and not trigger animations correctly.

This reverts commit c12e5b2b54 and fixes
things in a different way.
2020-05-05 12:43:04 +02:00
Samuel Mannehed
776cda5dc4 Merge pull request #1398 from novnc/compressionlevel
Add ability to set Tight compression level
2020-05-01 20:49:49 +02:00
Samuel Mannehed
479d8cefd1 Add ability to set compression level
Fixes github issue #1382.
2020-05-01 20:47:36 +02:00
Samuel Mannehed
a672168d4d Add unit tests for mouse move limit 2020-05-01 20:37:48 +02:00
Samuel Mannehed
0f81407c64 Shorten rows to max 80 chars in mouse.js 2020-05-01 20:37:48 +02:00
Samuel Mannehed
f477469fb5 Fix wording in comment 2020-05-01 20:37:48 +02:00
Samuel Mannehed
c9582690ac Merge pull request #1352 from uklatt/master
Limit mouse move events to one every 17 mS.
2020-05-01 20:36:18 +02:00
Uwe Klatt
44eb1fe59b Limit mouse move events to one every 17 ms 2020-05-01 20:28:33 +02:00
Samuel Mannehed
e7fa686f32 Fix indentation for focus check 2020-05-01 15:34:14 +02:00
Samuel Mannehed
8df281cce6 Don't fade the control bar if it has focus
Fixes github issue #1369
2020-05-01 14:38:09 +02:00
Samuel Mannehed
c12e5b2b54 Hide the clipboard when not connected
Fixes github issue #1367.
2020-05-01 13:35:34 +02:00
Samuel Mannehed
302895cdf3 Merge pull request #1396 from iblech/patch-1
Document default setting of `focusOnClick`
2020-04-29 18:58:04 +02:00
Ingo Blechschmidt
27a6978e30 Document default setting of focusOnClick 2020-04-29 09:36:32 +02:00
Filip Stedronsky
a1015d8db5 rfb: VeNCrypt Plain SecurityType support
This allows using TigerVNC server with PAM authentication (e.g. agains
LDAP or other extensible authentication mechanisms)

Tested with TigerVNC server (Xvnc -SecurityTypes Plain -PlainUsers '*')

Should not break anything else, this method is tried last when all
other fail.

Tested in Firefox 74 and Chromium 80
2020-04-08 08:58:32 +02:00
Alex Tanskanen
a040c402ed Fix focus problem after closing the toolbar
Closing the toolbar would make the focus remain on the toolbar and
not in the session. The only way to switch focus was to click in the
session. This commit will automatically switch back focus to the session
after closing the toolbar.
2020-03-12 13:17:51 +01:00
Pierre Ossman
c4633ab333 Set a default value for the quality input 2020-02-28 14:56:57 +01:00
Pierre Ossman
5243cbf611 Add UI for quality setting 2020-02-28 14:54:09 +01:00
Pierre Ossman
71429d45d0 Merge branch 'quality-level' of https://github.com/eDrillingSolutions/noVNC 2020-02-28 13:54:36 +01:00
Andrey Trebler
efd1f8a4f2 adds qualityLevel property to RFB class for updating JPEG quality level encoding on the fly 2020-02-28 13:14:19 +01:00
Niko Lehto
9253e178fc Hide clipboard side bar button when view only mode
The clipboard side bar button serves no purpose if user uses 'View Only'
mode, this commit hides this button in those instances.
2020-02-24 08:57:28 +01:00
Alex Tanskanen
ceb8ef4ec1 Fix crash with too large clipboard data
If too much text is copied in the session, String.fromCharCode.apply()
would crash in Safari on macOS and Chrome on Linux. This commit fixes
this issue by avoiding apply() altogether. Also added test to cover this
issue.
2020-02-21 09:39:31 +01:00
Pierre Ossman
e4e6a9b9b4 Style all input types for consistent UI
At least all that the browsers will let us.
2020-02-18 15:24:51 +01:00
Pierre Ossman
384232fb56 Merge branch 'clipboard_unicode' of https://github.com/CendioNiko/noVNC 2020-02-18 09:46:10 +01:00
Niko Lehto
f73fdc3ed3 Add extended clipboard Pseudo-Encoding
Add extended clipboard pseudo-encoding to allow the use of unicode
characters in the clipboard.
2020-02-18 09:32:36 +01:00
Niko Lehto
9a31083a8a Export constants in inflate.js for easier usage 2020-02-17 11:29:41 +01:00
Niko Lehto
13be552d60 Fix bug where inflate would read too much data 2020-02-17 11:29:41 +01:00
Niko Lehto
2cee106eee Split api of inflate
Added ability to read data chunk wise.
2020-02-17 11:29:41 +01:00
Niko Lehto
3cf11004b4 Handle errors from zlib/pako 2020-02-17 11:29:41 +01:00
Niko Lehto
f6669ff7b2 Move error handling to Inflate class
Every call wants this check so this should be done inside the class.
2020-02-17 11:29:41 +01:00
Niko Lehto
fe5aa6408a Add missing copyright header for Inflator.js 2020-02-17 11:29:41 +01:00
Niko Lehto
183cab0eca Remove unused inflate argument
The value true was an invalid flush argument so it was in practice
unused.
2020-02-17 11:29:40 +01:00
Niko Lehto
9575ded8da Add util for unsigned and signed int. conversion
Will be used in later commit in extended clipboard handling.
2020-02-17 11:29:40 +01:00
Niko Lehto
f52e979082 Add deflator helper class for deflating data
Wraps pako's deflate for easier usage.
2020-02-17 11:29:29 +01:00
Niko Lehto
3b562e8a0f Make clipBoardPasteFrom() test more specific
Don't rely on clientCutText() to test clipboardPasteFrom().
2020-02-17 09:34:44 +01:00
Samuel Mannehed
4ab5070548 Merge pull request #1361 from alvintownsend/master
Correcting path to package.json for running at a path other than root.
2020-02-12 11:25:04 +01:00
Alvin Townsend
546edcd4a0 Correcting path to package.json for running at a path other than root. 2020-01-31 11:34:53 +01:00
Samuel Mannehed
71bb3fdfa5 Fix color channels for VMware alpha cursors
The red and blue channels were incorrectly swapped.
2020-01-30 11:48:17 +01:00
Pierre Ossman
eb05b45b70 Make afterEach() hooks work when skipping tests
Mocha will now run afterEach() hooks when tests are skipped, so we need
to make them more robust against things being partially set up.
2020-01-23 14:27:37 +01:00
Juanjo Diaz
8394462356 Remove generated HTML by Cursor when it detaches 2020-01-23 11:58:16 +02:00
Pierre Ossman
2d53a785d5 Merge branch 'abstraction_for_detection' of https://github.com/samhed/noVNC 2020-01-14 09:45:28 +01:00
Samuel Mannehed
64fdd336a0 Simplify encodeUTF8/decodeUTF8 unittests 2020-01-03 10:41:34 +01:00
Samuel Mannehed
80c72e92d2 Add unit tests for encodeUTF8 and decodeUTF8 2020-01-02 17:29:41 +01:00
Samuel Mannehed
cbf090fe70 Remove unused python scripts 2020-01-02 13:56:07 +01:00
Samuel Mannehed
274652d119 Fix chinese translation for "Disconnect"
Thanks for @wavezhang, @litongjava, and @bhzhu203 for helping out.
2020-01-02 13:24:42 +01:00
Samuel Mannehed
208e34bc34 Update chinese translation strings
Thanks to @QQ2017 and @wavezhang for helping out with correcting these.
2020-01-02 13:18:24 +01:00
Samuel Mannehed
11ae8f0ef4 Add comment for browser and platform detection 2020-01-02 11:30:34 +01:00
Samuel Mannehed
c32d4f3cd0 Add short description at the top of browser.js 2020-01-02 11:30:34 +01:00
Samuel Mannehed
e52a278ed7 Properly detect scrollbar gutter
As a rule, instead of hard-coding a behavior on specific platforms we
should do dynamic detection.

This commit moves away from always hiding scrollbars on Android and iOS
and instead detects the rendered width of scrollbars in the browser.
2020-01-02 11:30:18 +01:00
Samuel Mannehed
8f230f45cc Remove Google Chrome Frame plugin compatability
Chrome Frame has been retired we so we can remove it from this
compatability tag.
2019-12-31 02:15:07 +01:00
Samuel Mannehed
78bbf6bad2 Restore X-UA-Compatible meta tag to vnc_lite
It's still required since without this IE's default setting for "Display intranet web pages in compatibility mode" will cause errors.
2019-12-31 02:07:31 +01:00
Samuel Mannehed
3a64043f28 Restore X-UA-Compatible meta tag to vnc.html
It's still required since without this IE's default setting for "Display intranet web pages in compatibility mode" will cause errors.
2019-12-31 02:06:02 +01:00
Pierre Ossman
49db41ea4b Allow cursor to be updated while connecting
We haven't got a server provided cursor at this point, but we might
have done something local, e.g. enabled the dot cursor.
2019-12-30 09:30:00 +01:00
Pierre Ossman
d507d1415e Make sure "undefined" can be a default parameter value
Lower layers can consider null to be a valid value, when we'd rather
they treat the value as not set.
2019-12-25 12:10:21 +01:00
Pierre Ossman
c4eb4ddcfe Handle slow loading of images
Internet Explorer seems to flag images as loaded prematurely, which
can result in rendering bugs. We can detect this by looking at the
dimensions though.
2019-12-23 15:52:54 +01:00
Pierre Ossman
4babdf33bd Validate decoded image dimensions
They are expected to be a certain size, so verify this so no server
tries to do something broken.
2019-12-23 15:42:02 +01:00
Pierre Ossman
b8d1a8bb57 Avoid using Array.includes()
Internet Explorer doesn't have this method. Use the safer indexOf()
instead.
2019-12-23 15:42:02 +01:00
Samuel Mannehed
2cf82a5c8e Build in the behavior to ignore decodeUTF8 errors
Makes the code clearer and more explicit in intent.
2019-12-23 10:27:40 +01:00
Pierre Ossman
06a8f7d91a Use undefined as the default value for password
An empty password is techincally legal, and now supported by the
RFB core, so we cannot use that as a placeholder for "no password".
2019-12-23 09:35:51 +01:00
Samuel Mannehed
dbbb676da9 Peter is no longer part of the noVNC team 2019-12-21 00:26:57 +01:00
Samuel Mannehed
84a8c1b0cc Merge pull request #1327 from vanym/decodeUTF8_try_catch
Fixes exception when desktop name contains non-utf8 character
2019-12-07 20:12:27 +01:00
VanyM
ff1b10ca66 Add try catch in every place that uses decodeUTF8 2019-12-06 09:44:11 +03:00
Chris "Koying" Browet
1c9826140a Add support for Unix Tight auth 2019-12-05 15:46:31 +01:00
Chris Koying Browet
5b453ed4a8 Expand password dialog to work for usernames too
Some VNC authentication schemes use usernames, our UI should support
these.
2019-12-05 15:46:24 +01:00
Samuel Mannehed
b39caa7469 Merge pull request #1318 from chrisjdev/emptyPassword
Allow connecting with empty string credentials
2019-12-05 12:15:43 +01:00
Chris J
01d4514dee Allow connecting with empty string credentials
Checking for undefined instead of falsy. That way an empty strings are allowed.
2019-12-05 12:13:11 +01:00
Samuel Mannehed
80b078c469 Add encodeUTF8 function to core/util/strings.js 2019-12-04 10:37:11 +01:00
Pierre Ossman
9f557f5280 Make Cursor.detach() safe to call when not attached
Avoids having checks in higher layers.
2019-11-29 10:08:15 +01:00
Samuel Mannehed
94c89284fc Fix chinese translation errors
Thanks to @QQ2017 for providing the translation strings
2019-11-28 12:51:32 +01:00
Samuel Mannehed
b17f6c6929 Simplify logic for status hierarchy
Removes unnecessary variable
2019-11-25 17:03:55 +01:00
Samuel Mannehed
afa1f8a2ab Ensure warning status timeouts are honored
When showing a new status popup we want to set a timer for how long to
show it. In cases where we show many statuses in a fast succession we
need to remove any running timeouts when showing a new one.

There are exceptions when new statuses won't be shown, and thats if a
more severe status is already showing, i.e and error or a warning.

Warnings can still have timeouts. There was a bug that occured when we
tried to show a normal status while a warning was showing. The bug
caused the warning status timeout to be removed even if the normal
status was never shown. We should only remove running timeouts if we're
actually going to show a new status.
2019-11-25 16:38:03 +01:00
Pierre Ossman
686c8d259a Merge branch 'babel' of https://github.com/CendioOssman/noVNC 2019-11-12 14:12:18 +01:00
Pierre Ossman
d01ecc18d5 Don't use arrow functions in legacy loader
The browsers that need the legacy code do not support such fancy
modern things.
2019-11-12 14:05:55 +01:00
Pierre Ossman
66ab0d98d7 Load support scripts first
E.g. SystemJS requires the Promise polyfill, so make sure all our
support files are loaded first.
2019-11-12 14:05:55 +01:00
Pierre Ossman
0dd439a874 Upgrade to latest babel
There has been a lot of renaming and restructuring in babel, so we need
to modify our code to handle the latest version. We also need to adjust
the way we build our babel worker as babel itself no longer runs in older
browsers such as Internet Explorer.
2019-11-12 14:05:55 +01:00
Samuel Mannehed
ae127d8a38 Merge pull request #1319 from wavezhang/patch-2
Update zh_CN.po
2019-11-11 16:46:36 +01:00
Pierre Ossman
b88a92afe8 Always include Promise polyfill for legacy browsers
It is now used by our general code and not just by the conversion
routines, so we need to make sure it is always included for the
old browsers.
2019-11-11 13:36:30 +01:00
Pierre Ossman
6b20803401 Clean up handling of untransformed files
This control flow is difficult enough as it is to follow. Move the
handling of the untransformed files to a separate block to make it
slightly easier to understand.
2019-11-11 13:33:47 +01:00
Pierre Ossman
8cfa673d94 Remove redundant "no copy" check
We also check this list as a filter to walkDir(), so no need for the
extra check here.
2019-11-11 13:32:19 +01:00
Pierre Ossman
c6e37040de Use proper backticks for generating legacy script tags 2019-11-11 10:01:43 +01:00
wavezhang
9653598af7 Update zh_CN.po
fix spell errors, improve translations
2019-11-08 14:08:23 +08:00
Alex Tanskanen
a6304f91d0 Fix missing caps lock events on iOS
Caps Lock on iOS only trigged key release or key press events.
When it's clicked it would only send keydown, and next time
it would only send keyup and so on. It should send both a key press
and a key release.

Also added the unit tests for macOS since those were missing.

Co-Authored-By: Alex Tanskanen <aleta@cendio.se>
2019-11-07 16:44:26 +01:00
Samuel Mannehed
c15502525e Add README to app/locale warning not to modify 2019-11-07 13:07:45 +01:00
Alex Tanskanen
175b843b66 Add "macOS shuffle" to iOS as well
Since iOS functions like macOS with regards to Alt behaving like AltGr,
we need the same workaround on iOS as well.
2019-11-04 14:22:46 +01:00
Pierre Ossman
ccb511a527 Handle missing Shift events on Windows
This is a bug in the OS that leaks through to the browsers. We need
to fake a Shift release here to avoid Shift getting stuck in the remote
session.
2019-11-04 10:17:45 +01:00
Pierre Ossman
3388c92c7f Send NumLock on macOS, even though the key is Clear
There is no obvious choice what works best here, but this is what
TigerVNC has been doing for years without complaints. Let's follow
them until we get reports that this doesn't work well.
2019-11-01 13:13:35 +01:00
Pierre Ossman
1096555414 Fix typo for MailSend key 2019-11-01 10:49:19 +01:00
Pierre Ossman
ebee9cddbf Update to latest UI Events key specification 2019-11-01 10:25:23 +01:00
Pierre Ossman
5736ea0bd5 Fix AltGr for a few more keys in IE and Edge
Some keys apparently send 'Unidentified' rather than an unshifted value.
Make sure those are also handled. Examples are \ and | on a Swedish
keyboard.
2019-11-01 09:59:02 +01:00
Samuel Mannehed
94a01b0ae0 Keep the virtual keyboard after using extra keys
If using the extra keys always gives focus to the screen then an
on-screen keyboard would be closed. When using on-screen keyboards we
instead want to give focus to our virtual keyboard input element.
2019-10-31 16:24:06 +01:00
Pierre Ossman
8c51e9a8a2 Revert iOS keyup workaround
It seems Apple has fixed their buggy keyup events, so remove the
workaround and allow keys to be kept pressed again.

This is a revert of 9e99ce126c.
2019-10-31 15:36:40 +01:00
Pierre Ossman
9d956e9198 Handle broken numpad delete key in Chrome 2019-10-31 14:51:36 +01:00
Pierre Ossman
dd4341fe67 Explain why Clear maps to KP_Begin 2019-10-31 14:12:58 +01:00
Pierre Ossman
758399050d Try to handle Meta key properly
The standards have unfortunatly caused some confusion between the Windows
key and the original Meta key. Try to handle the common case sanely at least.
2019-10-31 14:12:58 +01:00
Samuel Mannehed
1dd1bf0306 Merge pull request #1312 from samhed/master
Fix so that you can use the keyboard after using the extra keys
2019-10-28 10:45:08 +01:00
Samuel Mannehed
a5aa8e1282 Move focus to the screen when using extra keys
A regression from 2afda54 and friends was that you couldn't use the
extra keys and then directly use the keyboard, you would have to click
in the session first.

This commit restores the correct behavior and also adds a visual queue
to the fact that the screen got the focus by fading the controlbar.
2019-10-28 10:37:10 +01:00
Samuel Mannehed
c568ad4c74 Add missing scancode for sendTab 2019-10-28 10:02:23 +01:00
Samuel Mannehed
0c4b3e802f Rename document.capturedElem to captureElement
To better fit most naming.
2019-10-23 15:59:43 +02:00
Pierre Ossman
65066326c5 Improve Windows key image
The previous one didn't have sharp lines, or follow pixel boundaries
properly.
2019-10-23 15:51:41 +02:00
Samuel Mannehed
ffdd0dfeef Merge pull request #1309 from samhed/disappearing_cursor
Fix disappearing cursor
2019-10-21 14:16:48 +02:00
Samuel Mannehed
c3a7524c9e Hide the emulated cursor when target is null
Makes it easier to understand what happens when a real element isn't
passed as a target to updateVisibility(). Also makes the code more
robust to future changes.

Co-authored-by: Alex Tanskanen <aleta@cendio.se>
Co-authored-by: Niko Lehto <nikle@cendio.se>
2019-10-21 13:51:42 +02:00
Samuel Mannehed
7a96fc3785 Fix disappearing cursor after click
In the cursor emulation when deciding if the cursor should be hidden -
Instead of checking what's under the cursor, we check the element that
has capture.

This introduced another bug in the cursor emulation. The cursor did not
always disappear properly when using our cursor emulation together with
our setCapture polyfill. More specifically, we saw a problem when a
capture ended on an element without cursor emulation.

We solved this by introducing another visibility check on a timer in
the cursor emulation. However this led to yet another problem where
this timer conflicted with the timer in the setCapture polyfill.

We removed the timeout in the setCapture polyfill and created a
variable to make sure that all the events remaining in the queue can be
completed.

Co-authored-by: Alex Tanskanen <aleta@cendio.se>
Co-authored-by: Niko Lehto <nikle@cendio.se>
2019-10-21 13:51:42 +02:00
Samuel Mannehed
938690375b Check next elem at mouseleave in cursor emulation
It's not obvious that we want to hide the cursor when we get a leave,
it depends on the element that we're leaving to. This makes the code
more robust.

Co-authored-by: Alex Tanskanen <aleta@cendio.se>
Co-authored-by: Niko Lehto <nikle@cendio.se>
2019-10-21 13:10:13 +02:00
Samuel Mannehed
fcd99d04fb Rename variables in setCapture proxy
The names of many variables were too similar. To make the code easier
to follow we renamed:

* _captureElem to _capturedElem
* _captureElemChanged() to _capturedElemChanged()
* captureElem to proxyElem
* elem to target

Co-authored-by: Alex Tanskanen <aleta@cendio.se>
Co-authored-by: Niko Lehto <nikle@cendio.se>
2019-10-21 13:10:09 +02:00
Pierre Ossman
2b4c655405 Fix alt text for drag button
We had left an old placeholder text on this button.
2019-10-14 10:17:44 +02:00
Pierre Ossman
f2d42dc357 Never show drag icon if clipping is disabled
Toggling the enabled state is a remnant from an earlier version
of the code where we could determine if the the session is actually
clipped, and not just that the setting is enabled.

Right now we only change things based on the setting, so let's
completely hide the button when clipping is disabled.
2019-10-14 10:15:19 +02:00
Samuel Mannehed
412d93060d Update copyright to 2019 for modified files 2019-09-30 15:35:33 +02:00
Samuel Mannehed
e8614e20ef Code comments for how the receieve queue works 2019-09-25 21:04:46 +02:00
Samuel Mannehed
c90d53565a Clarify why we ENABLE_COPYWITHIN is false 2019-09-25 21:03:37 +02:00
Samuel Mannehed
3aeaea50af Merge pull request #1298 from CendioNiko/edgeCursorURI
Fix url cursor detection on Edge
2019-09-25 14:46:58 +02:00
Samuel Mannehed
3055307d3d Merge pull request #1299 from CendioNiko/vmwarecursor
Add support for VMware cursor encoding
2019-09-25 14:45:29 +02:00
Samuel Mannehed
8dc47f3c06 Merge pull request #1289 from CendioNiko/master
Desktop name improvements
2019-09-25 14:43:55 +02:00
Samuel Mannehed
c51a77c2eb Merge pull request #1281 from jalfd/optimize-receive-buffer
Optimize receive buffer
2019-09-25 13:26:02 +02:00
Samuel Mannehed
d39e0d1244 Clarify comments for broken alt in FF on Windows 2019-09-24 16:00:05 +02:00
Samuel Mannehed
ebb58c34da Merge pull request #1280 from jalfd/alt-key-recursion
Avoid recursion in Alt check on Firefox
2019-09-24 15:56:13 +02:00
Samuel Mannehed
35b78e95d2 Merge pull request #1279 from CendioOssman/nomodule
Use "nomodule" instead of manual check
2019-09-24 15:40:05 +02:00
Pierre Ossman
0b51419ca4 Use "nomodule" instead of manual check
Very few browsers are left in the wild that supports modules but not
"nomodule", so let's simplify our handling a bit.

Safari 10 supports modules but not 'nomodule', this means that this
particular version of Safari will be broken. Due to this we have to
bump up the required Safari version to 11.
2019-09-24 15:34:59 +02:00
Niko Lehto
296ba51f49 Add support for VMware cursor encoding
Supports both classic cursor type and alpha cursor type. In classic
mode the server can send 'inverted' pixels for the cursor, our code
does not support this but handles these pixels as opaque black.

Co-authored-by: Samuel Mannehed <samuel@cendio.se>
2019-09-24 10:26:30 +02:00
Niko Lehto
a1afb2a215 Fix url cursor detection on Edge
_supportCursorURIs was set to true even when Edge didn't support
URIs because the fallback value "default" was used.
2019-09-23 13:50:17 +02:00
Niko Lehto
c90245da25 Restore page name after disconnect 2019-09-04 15:05:14 +02:00
Niko Lehto
8d6f686b59 Test unicode desktop names 2019-09-04 14:47:40 +02:00
Niko Lehto
ce66b46986 Add support for DesktopName extension
This extension allows session name to be changed during runtime.
2019-09-04 14:23:37 +02:00
Pierre Ossman
9886d5951d Set viewport size for autoscale tests
We were incorrectly relying on the viewport being indirectly set
for us. Make sure we are explicit in what we want for these tests.
2019-08-23 15:48:30 +02:00
Pierre Ossman
30ff15a35a Merge branch 'upgrade' of https://github.com/CendioOssman/noVNC 2019-08-23 15:08:56 +02:00
Pierre Ossman
e5255fc246 Remove pointless Display.clear()
It served no meaningful purpose and it had bugs. So let's remove it
rather than try to fix it.
2019-08-23 15:05:58 +02:00
Pierre Ossman
3855a7bee4 Remove unused Display.logo attribute 2019-08-23 15:04:23 +02:00
Pierre Ossman
6aed0b4dd2 Deprecate showDotCursor option for RFB constructor
It is not relevant for the connection stage so it should not have
been a constructor argument to begin with. Ship with a warning for
a release before we remove it.
2019-08-23 14:00:20 +02:00
Pierre Ossman
1f2bb52850 Make sure showDotCursor can be modified before connecting
The cursor object is only attached to our canvas whilst connecting,
so we need to make sure we don't try to update anything when were
not connected or we'll get a crash.
2019-08-23 13:57:30 +02:00
Pierre Ossman
4222d72bfe Regenerate module loader after tool upgrades 2019-08-19 13:32:41 +02:00
Pierre Ossman
e24b501c47 Use latest versions of development dependencies
Let's make sure we get the latest features and fixes for all the
tools we are using.
2019-08-19 13:32:41 +02:00
Pierre Ossman
c47a3a3e09 Use latest NodeJS version in Travis
We want the latest and not be stuck on some old version. Otherwise
our scripts might not execute correctly.
2019-08-19 13:32:41 +02:00
Pierre Ossman
90d463f969 Make sure translation tools are lint checked
They do not have a .js suffix so eslint isn't finding them
automatically.
2019-08-19 13:32:29 +02:00
Jesper Alf Dam
08567b08ac When compacting the receive buffer don't copy unused buffer space
When compacting the receive buffer, we should only copy the bytes
between _rQi and _rQlen (the index of the first unread byte, and the
next write position).

Previously, we copied everything for _rQi up untill the end of the
buffer.
2019-08-16 19:34:09 +02:00
Jesper Alf Dam
7d755d10dc Don't compact the receive buffer unless we've actually run out of space
Previously, we would compact the buffer (moving unread data to the
start of the buffer) as follows:

- after processing a message, if there are zero unread bytes, just reset
  the indices for first and last unread byte to zero
- else, if at least 1/8th of the buffer is used, copy remaining data to the beginning of the buffer

The second option is never actually necessary, as before inserting new data
into the array, we already check if there's enough free space, and
compact the buffer first if necessary. So we've been doing a lot of
copies that weren't actually needed. Let's not do that any more.
2019-08-16 19:34:09 +02:00
Jesper Alf Dam
e9f489a629 Avoid recursion in Alt check on Firefox
The Firefox workaround which checks for missing Alt key events may
synthesise new KeyboardEvents. On these events, checkAlt should not be
recursively triggered. Otherwise, we get "too much recursion" errors
whenever the Alt key is pressed.
2019-08-16 16:20:41 +02:00
Pierre Ossman
776024a008 Fix trivial lint issues in translation tools
Indentation, missing semicolon, etc.
2019-08-15 15:58:09 +02:00
Pierre Ossman
604edf07d2 Upgrade to latest rollup 2019-08-15 15:58:09 +02:00
Pierre Ossman
ff7882c44c Remove unused import from module loader 2019-08-15 15:58:09 +02:00
Pierre Ossman
c9765e5066 Upgrade to latest sinon and chai 2019-08-15 15:58:09 +02:00
Pierre Ossman
b875486db8 Avoid deprecated called.once from sinon-chai
It's been removed in newer versions and will break eventually.
2019-08-15 15:58:09 +02:00
Tim Edwards
e1d50c8c10 Added a Snap package for noVNC (#1231)
Creating an Ubuntu Snap package to make noVNC easier to deploy.

Checks for the websockify binary in both the PATH (using which) and in the location where the Snap package places the binary. This is necessary for noVNC to be usable in a Snap. It doesn't affect the original functionality of git cloning websockify if it's not found in PATH or the Snap location.
2019-07-25 22:22:48 +02:00
Samuel Mannehed
897b465b87 Add missing parentheses for arrow func arg
Our lint tests expect this when an arrow functino has a body with
curly braces.
2019-07-23 16:12:22 +02:00
Samuel Mannehed
e14aa4d0fe Add documentation for the option showDotCursor
This is not only a property, it's also a parameter to the constructor.
2019-07-23 16:07:17 +02:00
Samuel Mannehed
c912230309 Remove the default value of wsProtocols
Using the 'binary' protocol by default is very non-standard.
2019-07-23 16:03:49 +02:00
Samuel Mannehed
21387f9c24 Merge pull request #1262 from shiramax/sub_protocols
Add support in websocket sub-protocols
2019-07-23 15:24:10 +02:00
Shira Maximov
25b3d49d32 Add support in websocket sub-protocols 2019-07-23 13:44:18 +03:00
Samuel Mannehed
8f2bcfbe79 Merge pull request #1265 from juanjoDiaz/add_version_number_to_UI_2
Add version number to UI
2019-07-23 11:51:09 +02:00
Juanjo Diaz
15c7b7a619 Add version number to UI 2019-07-23 10:25:59 +03:00
Samuel Mannehed
21ac6ca0f2 Update link to websock.js API 2019-07-15 14:05:24 +02:00
Samuel Mannehed
df4b7515a3 Merge pull request #1259 from lyarwood/launch.sh
launch.sh: Check for a local websockify directory
2019-07-14 23:31:10 +02:00
Lee Yarwood
188c9a591b launch.sh: Check for a local websockify directory
Previously launch.sh would check both for the existence of a local
websockify file and /websockify/run file.

This initial check should really be for a local websockify directory
as in packaged environments a file could very well be the actual
executable leading to launch.sh incorrectly attempting to use a local
version of websockify.
2019-07-09 12:27:26 +01:00
Pierre Ossman
23af6e142a Merge branch 'add-japanese-translation' of https://github.com/nnn1590/noVNC 2019-06-13 13:59:35 +02:00
Pierre Ossman
97924ebd5d Add support for separate key file in launch script 2019-06-13 13:51:56 +02:00
nnn1590
7ded517823 Add Japanese translation 2019-06-12 04:30:56 +09:00
Samuel Mannehed
32e081950c Revert "Fullscreen from iframe (#1236)" (#1247)
This reverts commit 19cdc15aa3.
2019-05-25 02:51:38 +02:00
Pierre Ossman
755d6eae99 Remove server pixel format warnings
These are harmless and really only for debugging. So remove them
as they tend to trick people in to thinking something is wrong.
We already print the entire server pixel format earlier anyway in
case we need the details.
2019-05-24 13:06:26 +02:00
Ján Jockusch
19cdc15aa3 Fullscreen from iframe (#1236)
* First attempt to make the fullscreen button work inside an iframe.

* Cleaner distinction between document element and document.

* Scoping corrections. Auto-detect correct iframe.

* Added comments to the relevant sections.

* IE issue fixed.

* Same source issue solved. fullscreenToggle now checks if it is permitted to inspect other iframes.
2019-05-13 15:06:32 +02:00
Pierre Ossman
2b2b6073dd Don't do cleanup on deploy
Travis wipes out node_modules otherwise, which we need for the prepare
stages of the deployment to npm.
2019-04-15 16:11:01 +02:00
Samuel Mannehed
9fe2fd04d4 noVNC 1.1.0 2019-04-09 16:29:53 +02:00
Samuel Mannehed
3ba5cefef2 Update generated JS files for translations 2019-04-09 16:22:16 +02:00
Samuel Mannehed
e94e83c6c8 Update Dutch translations
Co-authored-by: Arend Lapere <arend.lapere@gmail.com>
2019-04-09 16:21:35 +02:00
Samuel Mannehed
d6804167ef Update Swedish translations 2019-04-09 16:21:19 +02:00
Samuel Mannehed
a136b4b078 Allow autoscale() with zero height or width
Commit 6e7e6f9 stopped the function from running if width or height was
zero, this commit reverts that change. This commit also makes the
resulting canvas 0x0 if autoscale is called with zero. By adding this
special case we can avoid division by zero in the calculations.
2019-04-02 16:51:18 +02:00
Pierre Ossman
2aa3b5bc79 Get rid of self-closing tags
This is a remnant of XHTML and not used in standard HTML. Get rid
of them so that no-one mistakes our files for being XHTML compatible.
2019-04-02 14:22:34 +02:00
Pierre Ossman
dcc41bde61 Fix up errors and warnings in vnc_playback.html
Remove the styling, as it isn't really needed, and fix some minor
things that the w3c validator complains about.
2019-03-26 15:21:31 +01:00
Pierre Ossman
a98a223e13 Validate HTML and CSS in Travis 2019-03-26 15:19:11 +01:00
Samuel Mannehed
f5d76dd5bb Add translations as a feature 2019-03-21 13:57:16 +01:00
Samuel Mannehed
effd53838c Merge pull request #1218 from samhed/htmlformat
Some formatting fixes for HTML files
2019-03-14 14:44:10 +01:00
Samuel Mannehed
94e6f8c2fa Remove trailing whitespace 2019-03-14 14:17:04 +01:00
Samuel Mannehed
2500f65d01 Consistently end self closing tags with />
Even though this isn't strictly required by the standard its nice to be
consistent.
2019-03-14 14:16:40 +01:00
Samuel Mannehed
26a9c1c14d Remove invalid HTML attributes from textarea 2019-03-14 14:13:27 +01:00
Samuel Mannehed
fe8d784bce img elements must have alt attributes 2019-03-14 14:13:27 +01:00
Samuel Mannehed
5a76000848 Fix invalid input type 'input' 2019-03-14 14:13:27 +01:00
Samuel Mannehed
892c3330cf Input type image is not allowed to have values 2019-03-14 14:13:27 +01:00
Samuel Mannehed
45c644a68d Remove unnecessary type attributes 2019-03-14 14:13:27 +01:00
Samuel Mannehed
daff988e17 Remove X-UA-Compatible meta tag
It's only required if we wanted support IE8, IE9 or older. We require at
least IE11 at the moment.
2019-03-14 14:13:27 +01:00
Samuel Mannehed
80c52ba7cb Add default language 2019-03-14 14:13:27 +01:00
Samuel Mannehed
6e7e6f9c9e Add check for bad values for Display.autoscale() 2019-03-08 16:30:43 +01:00
Samuel Mannehed
9a823732a0 Merge pull request #1204 from juanjoDiaz/small_improvements
Small improvements
2019-03-04 09:32:11 +01:00
Juanjo Diaz
1c9b904d1a Remove callbacks from UI in favour of promises 2019-02-27 10:18:59 +02:00
Juanjo Diaz
41ddb35458 Replace unnecessary function supportsCursorURIs by a constant variable 2019-02-27 10:14:50 +02:00
Juanjo Diaz
44f4c5545f Move support check from display to browser 2019-02-27 10:13:50 +02:00
Dmitriy Shweew
d917ccdaf7 Add Russian translation (#1211)
By Dmitriy Shweew (shweew)
2019-02-27 01:24:22 +01:00
Juanjo Diaz
0505214cd9 Convert DES into a class 2019-02-26 23:53:43 +02:00
Juanjo Diaz
9d2c9d1a75 Use default argument for base64 2019-02-26 23:52:47 +02:00
Juanjo Diaz
667f3cc20e Remove unnecessary context from eventtarget 2019-02-26 23:51:45 +02:00
Juanjo Diaz
fe5974a740 Remove unnecessary constructor parameter from Cursor 2019-02-26 23:51:33 +02:00
Juanjo Diaz
9255e0fb47 Remove intermediate variable from mouse 2019-02-16 23:31:58 +02:00
Pierre Ossman
b00a608af7 Remove error handling in clientCutText()
It is not necessary as Websock.flush() is guaranteed to succeed and
give us some space. It also remove the call to _fail(), which was
invalid at this place as clientCutText() is not a method on RFB.
2019-02-15 13:14:36 +01:00
Pierre Ossman
47c66517ae Throw correct Error object
We've already defined the name Error as a logging function, so we
need to be more explicit when we want to refer to the exception
class.
2019-02-15 13:01:28 +01:00
Pierre Ossman
9e03a98182 Merge branch 'slowdata' of https://github.com/CendioOssman/noVNC 2019-02-15 10:40:49 +01:00
Pierre Ossman
70e6795829 Send data one byte at a time in tests
This makes sure we don't have code assuming that everything is
neatly packaged in a single WebSocket message.
2019-02-15 10:26:27 +01:00
Pierre Ossman
c02b18f06f Clean up RFB._rfb_auth_schema assignment 2019-02-15 10:25:50 +01:00
Pierre Ossman
3bb15d4aa0 Fix security failure reason handling of slow data
Things would break if the security result and security reason did
not arrive in the same WebSocket message.
2019-02-15 10:24:41 +01:00
Pierre Ossman
c13df5ae67 Fix version handshake to handle slow data 2019-02-15 10:23:32 +01:00
Pierre Ossman
b8ff5d1bde Use arrow function to avoid bind 2019-02-15 10:22:27 +01:00
Pierre Ossman
17eea9574d Consume data properly in Hextile decoder
We accidentally removed the code updating the data index in 8a189a6,
resulting in the decoder newer consuming any data. So the data would
be parsed as the next rect, causing weird errors.
2019-02-14 16:57:26 +01:00
Samuel Mannehed
36bfcb0714 Merge pull request #1190 from saucelabs/screen_background
Make the screen background customizable
2019-01-21 13:39:31 +01:00
Mykola Mokhnach
d7791ebbcd Make the screen background cutomizable 2019-01-18 19:36:23 +01:00
Pierre Ossman
7dc0a67808 Fix naming of Korean translation file 2019-01-16 11:10:04 +01:00
Pierre Ossman
20de5749d2 Update translation template file 2019-01-16 11:07:56 +01:00
Pierre Ossman
f50ccd80d1 Fix copyright tag in translations 2019-01-16 11:07:31 +01:00
Baw_Appie
823daa8002 Add Korean translation 2019-01-16 10:43:49 +01:00
Samuel Mannehed
099c419996 Merge pull request #1182 from novnc/scrollbarsfortouch
Enable scrollbars for all touch devices aside from Android and iOS
2019-01-14 22:29:47 +01:00
Pierre Ossman
b4819c2558 Merge branch 'npm' of https://github.com/CendioOssman/noVNC 2019-01-11 13:42:31 +01:00
Pierre Ossman
69a9fd6029 Revert 'prepare' to 'prepublish'
Travis uses a so ancient version of npm that it doesn't support
'prepare', so we have to continue using 'prepublish' for now.
2019-01-11 13:39:02 +01:00
Pierre Ossman
1ced5688b5 Change to Ossman's NPM authentication
Solly's doesn't seem to work anymore, so switch to mine.
2019-01-11 13:39:02 +01:00
Pierre Ossman
364849c67b Avoid extra environment for Travis deploy stage
These are not needed, so keep things more clear by skipping them.
2019-01-11 13:39:02 +01:00
Pierre Ossman
6532b4d1b8 List main entry point for our NPM config 2019-01-11 13:39:02 +01:00
Pierre Ossman
ea4065f33a Explicitly list what we want in our NPM package
Switch over to explicitly listing what we want to include, rather
than listing what we don't want to include. There is too much risk
of getting random junk from your working copy otherwise. This should
also hopefully complain if something is missing.
2019-01-11 13:39:02 +01:00
Samuel Mannehed
ef64917a90 Only disable scrollbars on Android and iOS
Previously scrollbars were disabled on all touch devices. This meant
that they were disabled on Windows when touch was detected. Windows does
in fact have useful scrollbars even in touch mode. Fixes Issue #1172
2019-01-09 14:59:22 +01:00
Samuel Mannehed
47b3eac82b Move UI.isSafari into core/util/browser.js
This is where the rest of these kinds of functions are.
2019-01-09 14:58:03 +01:00
Samuel Mannehed
97e23ebbb2 Reorder browser and platform info functions
Platform info functions grouped together and browser info functions
grouped together.
2019-01-09 14:56:59 +01:00
Samuel Mannehed
77e261dba3 Merge pull request #1179 from ssebs/super-key-feature
Super key feature
2019-01-09 10:41:32 +01:00
Sebastian Safari
3e835a5d37 Add Windows Key Feature 2019-01-08 06:57:12 -08:00
Pierre Ossman
7a1f2e4cf5 Merge branch 'user_getters_and_setters_in_websock' of https://github.com/juanjoDiaz/noVNC 2019-01-08 12:26:17 +01:00
Pierre Ossman
e35570227c Use CustomEvent for playback events
Stop abusing Event as that doesn't work everwhere.
2019-01-08 12:25:42 +01:00
Pierre Ossman
568f6567e1 Avoid using String.prototype.startsWith()
IE doesn't support it.
2019-01-08 12:25:01 +01:00
Pierre Ossman
527a1fd0ae Pre-convert recordings for playback
Convert the recordings ahead of time instead of during the playback.
That way we aren't messing up the profiling with time spent converting
data, rather than processing it.
2019-01-08 12:24:39 +01:00
Juanjo Diaz
879e33ab64 Extract rQshift to common function 2018-12-08 17:32:00 +02:00
Juanjo Diaz
8a189a6291 Add getters/setter to websock 2018-12-08 17:31:20 +02:00
Samuel Mannehed
18439b0680 Merge pull request #1165 from juanjoDiaz/throw_errors_instead_of_strings
Throw Error instead of String
2018-11-26 19:09:44 +01:00
Samuel Mannehed
2bab9a0460 Merge pull request #1166 from juanjoDiaz/simplify_EventTargetMixin
Simplify EventTargetMixin
2018-11-26 18:42:10 +01:00
Samuel Mannehed
ae1f7a8f5c Merge pull request #1168 from juanjoDiaz/use_new_error
Use `new` when constructing errors
2018-11-26 18:36:10 +01:00
Juanjo Diaz
11ef53544f Simplify EventTargetMixin 2018-11-25 13:53:23 +02:00
Juanjo Diaz
d3ed883a8f Use new when constructing errors 2018-11-24 21:44:11 +02:00
Juanjo Diaz
84a5a2d827 Throw Error instead of String 2018-11-24 19:47:57 +02:00
Samuel Mannehed
cffb42ee8f Dont reset touch mouse button state for view-only
Fixes bug introduced in 61f93180c8.
2018-11-01 11:22:19 +01:00
Samuel Mannehed
7449170cc8 Fix chinese translation of "Connect"
Thanks to Lyon Hu (@chnhyg) for finding the issue.
2018-11-01 11:07:58 +01:00
Petr
56c82ecd35 Czech translation 2018-10-19 13:01:39 +02:00
Solly Ross
84586c0f17 Change copyright header (#1138)
* Change copyright header

This updates the copyright header to say "The noVNC Authors".  People
who previously had copyright listings are now under the AUTHORS file.
2018-10-09 12:15:35 +02:00
Pierre Ossman
d105040581 Add tests for Cursor encoding 2018-09-18 11:36:32 +02:00
Pierre Ossman
679535ec29 Fix cursor encoding handling from earlier merge
Old code snuck in when merging the split of decoders to separate
classes. Restore the proper handling of cursors.
2018-09-18 11:08:02 +02:00
Pierre Ossman
9881899e7b Merge branch 'style' of https://github.com/CendioOssman/noVNC 2018-09-17 13:54:04 +02:00
Pierre Ossman
934c606d91 Merge branch 'decoders' of https://github.com/CendioOssman/noVNC 2018-09-17 13:47:12 +02:00
Samuel Mannehed
772c686776 Merge pull request #1119 from patrakov/master
Show dot when there otherwise would be no visible cursor
2018-09-16 11:20:34 +02:00
Alexander E. Patrakov
4c38179d15 Show dot when there is no visible cursor
Disabled by default.
2018-09-14 20:31:59 +05:00
Alexander E. Patrakov
d1314d4b3a Moved the "pixels + mask -> RGBA" logic to rfb.js
As requested by Pierre Ossman - he needs this for supporting other
cursor extensions.
2018-09-07 23:01:46 +08:00
Pierre Ossman
0997b319a3 Enforce switch colon spacing 2018-09-06 17:36:48 +02:00
Pierre Ossman
2c5491e131 Enforce space after function name 2018-09-06 17:34:15 +02:00
Pierre Ossman
3f1cda2e37 Enforce space before code block 2018-09-06 17:29:26 +02:00
Pierre Ossman
0ae5c54ab3 Enforce explicit semi-colons 2018-09-06 17:25:02 +02:00
Pierre Ossman
426a8c927b Enforce curly braces for control statements 2018-09-06 17:22:40 +02:00
Pierre Ossman
4a16dc51a8 Enforce no trailing whitespace 2018-09-06 17:12:45 +02:00
Pierre Ossman
35068204f4 Enforce keyword spacing 2018-09-06 17:08:19 +02:00
Pierre Ossman
942a312779 Enforce object key spacing 2018-09-06 17:07:11 +02:00
Pierre Ossman
22d10c756a Enforce function declaration style 2018-09-06 17:01:43 +02:00
Pierre Ossman
e777765320 Enforce function names 2018-09-06 16:53:40 +02:00
Pierre Ossman
d80d9d3731 Enforce function call spacing 2018-09-06 16:46:38 +02:00
Pierre Ossman
f77f41ee95 Enforce comma style 2018-09-06 16:44:53 +02:00
Pierre Ossman
6786fd8719 Enforce comma spacing 2018-09-06 16:43:31 +02:00
Pierre Ossman
7b536961b2 Enforce indentation 2018-09-06 16:37:38 +02:00
Pierre Ossman
1404984668 Don't lint xtscancodes.js
It's generated by another project so we cannot control the style
of it.
2018-09-06 16:37:11 +02:00
Pierre Ossman
a98881151f Enforce brace style 2018-09-06 15:39:26 +02:00
Samuel Mannehed
e15950a8ef Merge pull request #1129 from novnc/vnclitecleanup
Cleanup of vnc_lite.html
2018-08-28 10:46:11 +02:00
Samuel Mannehed
e20f0ee9b6 Limit line length to 80 in vnc_lite 2018-08-28 10:42:39 +02:00
Samuel Mannehed
1c945f812b Add a screen element for vnc_lite.html
Makes it clearer where the remote screen will be created.
2018-08-28 10:42:39 +02:00
Samuel Mannehed
8613f6f4ae Simplify element names in vnc_lite 2018-08-28 10:42:39 +02:00
Samuel Mannehed
011e4bff34 Create our own button for CtrlAltDel in vnc_lite
In order to have better control of the layout and to make the code
easier to follow. Using input type button or buttons will imply a lot of
built in styling that differs from browser to browser.
2018-08-28 10:42:39 +02:00
Samuel Mannehed
5271e30049 Use a simple prompt for passwords in vnc_lite
It's not password-masked, but it allows for a lot simpler code.
2018-08-28 10:42:39 +02:00
Samuel Mannehed
26d51e490e Order vnc_lite functions 2018-08-28 10:42:39 +02:00
Samuel Mannehed
c756665e81 Rename functions in vnc_lite
Give them obvious names to make the code easier to understand.
2018-08-28 10:42:39 +02:00
Samuel Mannehed
25551b6b40 Use let and const instead of var in vnc_lite
The rest of noVNC has been converted already. This allows us to remove
the extra scope that was created for the VNC connection.
2018-08-28 10:42:37 +02:00
Samuel Mannehed
6517c498b9 Remove support for the fragment and WebUtil dep
The only remaining use we had of WebUtil was getConfigVar(). Let's get
rid of that dependency and use our own, query-string-only and richly
commented version of that function. It's easier for people to get an
overview of vnc_lite if it's all in one file.

This commit removes support for the fragment, parameters can only be
passed using the query string from now on.
2018-08-28 10:41:56 +02:00
Samuel Mannehed
51f9f0098d Cleanup non-essential options from vnc_lite
This is supposed to be a simple example, it shouldn't have this many
options. This commit removes the following options:
 * logging - the default level 'warn' is good enough
 * title - a weird thing to set from the query string anyway
 * token - not used by most setups
 * encrypt - looking at the URL is good enough
 * repeaterID - not used by most setups
 * shared - uncommon setting
 * resize - not supported by most servers

Note that the removal of 'encrypt' allows us to remove logic for
establishing a default port. The default port for wss is 443 and for ws
it's 80 anyway.
2018-08-28 10:41:52 +02:00
Samuel Mannehed
8c2866df36 Add code comments to vnc_lite 2018-08-28 10:41:19 +02:00
Pierre Ossman
923cd22083 Move decoders to separate classes
Makes things a lot clearer by letting each encoding handle its own
details and state.
2018-08-22 15:12:34 +02:00
Pierre Ossman
11309f3243 Handle pseudo encodings directly
These have very special behaviour compared to normal data encodings,
so separate out them and handle them separately.
2018-08-22 15:12:10 +02:00
Pierre Ossman
e17cae8f32 Remove statistics tracking
The profiles in the browsers are much better these days and give us
much better data than we can provide ourselves.
2018-08-22 15:12:05 +02:00
Pierre Ossman
71960cda85 Give proper int argument to encodingName() 2018-08-22 14:46:37 +02:00
Samuel Mannehed
27dff4a0a2 Simplify connected() function 2018-08-21 12:23:17 +02:00
Samuel Mannehed
de79ae92e5 Remove unneccessary code and bling from vnc_lite
Unused code, variables and unnecessary styles. The host/port check
would only have an effect if someone explicitly set them as empty in
the query string. The different colors of the status bar are not
necessary, nor is the styling of the background.
2018-08-21 12:22:57 +02:00
Samuel Mannehed
e0d4e5a1c0 Move css rules for vnc_lite to the html
Easier to get an overview if it's all in one file.
2018-08-21 11:34:28 +02:00
Samuel Mannehed
e7c1074b65 Get rid of icons for vnc_lite
Icons aren't required and we want to get rid of the 'app/' dependency.
2018-08-20 09:22:05 +02:00
Pierre Ossman
ce6287574f Merge branch 'hidpi_scale' of https://github.com/CendioOssman/noVNC 2018-08-16 17:50:09 +02:00
Pierre Ossman
7407c1f4e2 Replace bad sinon stub in mouse tests
It screwed up important calls inside the code being tested. Avoid
the stub by creating a temporary element with the desired properties.
2018-08-16 17:36:54 +02:00
Samuel Mannehed
cc2fe2c26e Remove iOS specific code from vnc_lite
vnc_lite.html doesn't have touch support anyway
2018-08-16 15:53:32 +02:00
Samuel Mannehed
0f207c808c Remove machine control buttons from vnc_lite
The vnc_lite example is intended to be minimal and these buttons are
only useful in special cases.
2018-08-16 15:53:32 +02:00
Samuel Mannehed
c995c0863e Revert "Handle if desktopName isn't set.."
This reverts commit 22000b93d5. The
'desktopname' and the 'connect' events are dispatched by us in RFB and
are thus serial.
2018-08-16 15:52:21 +02:00
Samuel Mannehed
2c0b146630 Merge pull request #1117 from novnc/bug/fix-test-playback
Fix test playback
2018-08-16 15:38:43 +02:00
Pierre Ossman
16f0861501 Support password auth recordings for playback
When password auth is enabled on the server, the RFB object sends a
'credentialsrequired' event to the UI. This commit adds support for
this event to our recoding playback.
2018-08-16 15:36:43 +02:00
Samuel Mannehed
22000b93d5 Handle if desktopName isn't set when connected
We can't guarantee that the desktopName event has been fired before the
connect event.
2018-08-16 13:33:35 +02:00
Samuel Mannehed
a793df3d6d Merge pull request #1118 from novnc/disabledragwhilescale
Turn off view drag when scaling
2018-08-16 10:31:09 +02:00
Samuel Mannehed
4ddcc7537f Merge enableDisableViewClip and updateViewClip
Makes the code easier to follow and makes sure that viewDrag is
properly disabled when scaling. Fixes #1110.
2018-08-16 10:29:01 +02:00
Samuel Mannehed
6d1c036e0c Use macOS 10.13 for Safari tests
Safari 11 had a bug (#1125) which should be fixed in Safari 11.1 which comes with macOS 10.13.
2018-08-15 08:10:38 +02:00
Samuel Mannehed
3b7c47417e Move dragThreshold definition to util/
In order to avoid multiple declarations that has to be updated in the
case of future updates.
2018-08-10 11:24:09 +02:00
Samuel Mannehed
b3ac94a978 Remove firebug comments
Firebug is discontinued and it's features are included in browser
development tools now a days.
2018-08-08 14:05:06 +02:00
Samuel Mannehed
eebef339be Detail path to icon Makefile 2018-08-08 14:04:45 +02:00
Solly Ross
ee3493c060 Add a record flag to launch.sh
Add the `--record` flag to launch.js, for easy recording when testing.
2018-07-30 11:07:33 -04:00
Samuel Mannehed
2bbd15ccaf Remove setViewDrag function
Unnecessary function only used in the toggle function above.
2018-07-30 10:46:41 +02:00
Samuel Mannehed
0b903af296 Remove unnecessary code
The enableDisableViewClip call in the fullscreen code didn't have any
effect and should have been removed when the special case for clipping
in IE and Safari fullscreen was removed in b18ef81.

The setViewDrag call claimed to disable view drag on UI state change.
The UI states are:

 init, connecting, connected, reconnecting, disconnecting, disconnected

The only state where the called function didn't immediately return was
"connected" and that's the only state where enabling view drag is
possible. Thus it could never have been enabled when changing to the
"connected" state.
2018-07-30 10:29:35 +02:00
Solly Ross
cccf3b008a Fix perf/playback tool
Somewhere along the way, the refactors broke playback.js.  This fixes
the actual functionality, and makes its JS loading match that in
vnc.html.
2018-07-29 19:14:56 -04:00
Pierre Ossman
ab1ace383e Handle fractional screen sizes
With high DPI systems we can end up with a container with a size that
is not an integer number of CSS pixels. Make sure we can handle those
cases by allowing a fractional size for the output canvas. Framebuffer
size and viewport coordinates are still restricted to integer dimensions
though.

Based on initial patch by Alexander E. Patrakov.
2018-07-26 14:15:59 +02:00
Pierre Ossman
862967e089 Merge branch 'master' of https://github.com/patrakov/noVNC 2018-07-25 20:40:51 +02:00
Alexander E. Patrakov
599588fe5f Documented browser cache issue 2018-07-22 23:03:05 +08:00
Pierre Ossman
f9b6d7665d Use newer macOS test machine for Travis 2018-07-16 13:46:48 +02:00
Pierre Ossman
7bcdbbc65b Stop transpiling karma tests
This runs our code in the same manner as it would be used if loaded
directly in the browser. Includes the same kind of fallback for older
browsers.
2018-07-16 13:32:35 +02:00
Pierre Ossman
800abf1277 Fix proper triggering of module fallback
We might be in the "interactive" readyState, which means that
DOMContentLoaded has already fired and we'll hang.
2018-07-13 15:57:24 +02:00
Pierre Ossman
9eaea86234 Don't stub out ES module imports
It is not allowed and only happens to work because babel doesn't
strictly follow the specification. It doesn't seem necessary for the
tests to run, so just remove it.
2018-07-13 15:57:24 +02:00
Pierre Ossman
d131633471 Better currentScript fallback
The previous heuristic didn't work under all circumstances, so try
something more robust.
2018-07-13 15:57:24 +02:00
Pierre Ossman
ae2e1ff7bd Move sinon to karma framework
This frees us from manual imports, and makes things less magical
as those aren't ES modules even if the code suggest that the are.
2018-07-13 15:57:24 +02:00
Juanjo Diaz
885363a373 Use the classic function foo() { ... } for top level functions or functions that depend on the scope 2018-07-12 19:06:57 +02:00
Juanjo Diaz
651c23ece3 Use fat arrow functions const foo = () => { ... }; for callbacks
and any other function that is passed around and it's not a top level function
2018-07-12 19:06:57 +02:00
Juanjo Diaz
0e4808bf6f Use ES6 classes
Always use the shorthand notation if the function is a method of an object or class `{ foo() { ... } }` or `class bar { foo() { ... } }`
unless it's a callback in which case you a fat arrow function should be used `{ cb: () => { ... } }`
2018-07-12 19:06:57 +02:00
Pierre Ossman
67fefcf184 Merge branch 'cursor' of https://github.com/CendioOssman/noVNC 2018-07-11 13:39:37 +02:00
Pierre Ossman
baa4f23ee5 Provide fallback cursor method
Some browsers don't support custom cursors, and there are cases
where the browsers refuse to show the cursor. Handle both of these
cases by letting the browser render the cursor via a floating
canvas.

This allows us to support a local cursor at all times.
2018-07-06 16:37:27 +02:00
Pierre Ossman
1073b60155 Sort vkeys table 2018-07-04 15:53:41 +02:00
Pierre Ossman
8acadd9e97 Merge branch 'fix/ie11-numpad5-compatibility' of https://github.com/vlastoun/noVNC 2018-07-04 15:53:30 +02:00
Henry Vindin
9700e3592b Fixes #1075
Rather than trying to pick a utility, we should be able to just use bash to check if a port is available or not.

We can probably assume bash is available due to the shebang declaring it.
2018-07-01 15:37:34 +10:00
Samuel Mannehed
f90c2a6d4b Avoid TypedArray.slice() because of IE11 2018-06-15 12:00:43 +02:00
Samuel Mannehed
d9814c06bf Use string assignment operator instead of concat()
The assignment operator is a lot faster.
2018-06-15 11:59:28 +02:00
Samuel Mannehed
4318c8cafd Use the correct slicing for rQshiftStr
This didn't result in any error however since slice() handles such
mistakes gracefully.
2018-06-15 11:56:56 +02:00
Samuel Mannehed
178b92d380 Add rQshiftStr unit test for large strings 2018-06-15 11:53:51 +02:00
Samuel Mannehed
db9daa98a5 Avoid big strings on the stack
Previous code resulted in RangeErrors by potentially creating big
strings.

Fixes issue #1065
2018-06-14 16:59:52 +02:00
Samuel Mannehed
362bd5e3a2 Call rQshiftBytes to avoid code duplication 2018-06-14 16:51:29 +02:00
Samuel Mannehed
e87b645b56 Remove typedArrayToString
We don't use PhantomJS anymore
2018-06-14 16:43:48 +02:00
Pierre Ossman
aaa2ecbd95 Merge branch 'issue_templates' of https://github.com/novnc/noVNC 2018-06-07 16:07:54 +02:00
Samuel Mannehed
11715f2092 Properly force clipping on touch
It shouldn't depend on what's saved in webstorage nor save the forced
value to webstorage.

Includes a revert of 0342e4f489
2018-06-07 15:58:31 +02:00
Pierre Ossman
8f47bd296c Work around Siemens touch panel authentication bug
Siemens' touch panels support Tight authentication as well as NOTUNNEL,
but they fail to advertise the latter. Work around this issue by detecting
a Siemens device (through their custom tunnel types) and assume NOTUNNEL
support even if not advertised.
2018-06-07 15:03:34 +02:00
Pierre Ossman
e6bad200e4 Add debug logging for Tight authentication
Makes it easier to diagnose user issues when we can see what the
server and noVNC are trying to negotiate.
2018-06-07 14:57:17 +02:00
Pierre Ossman
13364d70dd Merge branch 'travis-lint' of https://github.com/CendioOssman/noVNC 2018-06-07 14:53:02 +02:00
Samuel Mannehed
0342e4f489 Clipping should be enabled on touch
This was always the intention and the main use case of 'clipping'. It
seems like it got lost somewhere along the way.
2018-06-04 21:41:45 +02:00
Pierre Ossman
127b63b79f Stop combining test platforms
Sauce is very unstable, so spread things out so we can more easily
throttle things to more sane levels.
2018-06-01 15:26:52 +02:00
Pierre Ossman
81207ffebd Run eslint in travis
Makes sure we get early feedback for lint violations.
2018-06-01 14:37:00 +02:00
Samuel Mannehed
fe70a1d51f Merge pull request #1013 from juanjoDiaz/es6_refactor_2
Add eslint and update noVNC to ES6
2018-05-25 10:22:36 +02:00
Juanjo Diaz
2b5f94fa6a Prefer const/let over var 2018-05-24 00:27:09 +03:00
Juanjo Diaz
cdb860ad84 Add transpilation for IE11 and skip linux tests 2018-05-24 00:26:34 +03:00
Juanjo Diaz
8727f598c2 Add eslint and fix reported issues 2018-05-24 00:25:44 +03:00
Pierre Ossman (Work account)
5dad77b9d5 Add issue templates
We often have to ask for the same information for every new issue. Try to avoid this by using github's templates for issues.
2018-05-21 09:37:37 +02:00
Vlastimil Sadilek
5858f472e3 Fix: IE11 Numpad5 compatibility when numlock off
This fix Numpad5 in Internet Explorer 11 if numlock state of host differs with numlock state of
VNC console.
2018-05-16 13:52:56 +02:00
Samuel Mannehed
cfe1e44ed7 Merge pull request #1074 from samhed/largeclipboard
Handle sending large clipboards
2018-05-07 13:25:46 +02:00
Samuel Mannehed
2bb8b28d78 Handle sending large clipboards
Pasting clipboard texts that were larger than 10240 bytes didnt work and
caused a crash in noVNC. This commit fixes the crash and adds handling
for sending large clipboard texts. Fixes issue #1065.
2018-05-07 13:02:51 +02:00
Vlastimil Sadilek
f3e2fc58ec Fix: undefined err, undefined Exception 2018-05-04 14:23:02 +02:00
Samuel Mannehed
43bbaa8d6e Merge pull request #1066 from colin-zhou/master
Update the internationalization module
2018-04-29 20:18:52 +02:00
Pierre Ossman
9dc580db27 Update browser test list
We want to also test Microsoft Edge, and Internet Explorer on Windows 10
is really a reskinned Edge so we also need to test on Windows 7.
2018-04-27 16:19:40 +02:00
Zhou Chaolin
024aca48e5 Update the noVNC translation part
fix the specification in zh_CN.po
2018-04-24 02:29:51 +08:00
Samuel Mannehed
24231f1ae3 Merge pull request #1048 from ghostplant/master
Add translation in zh_CN
2018-04-09 09:26:52 +02:00
CUI Wei
dcee7c5e91 Add translation in zh_CN
Signed-off-by: CUI Wei <ghostplant@qq.com>
2018-04-07 06:16:00 -04:00
Pierre Ossman
3328675b44 Clarify which Chinese translation we have
Chinese has several writing systems so we need to be clear which one
our translation covers. The one we currently have is for Traditional
Chinese so make sure it uses the matching language tag.
2018-04-03 14:51:12 +02:00
Pierre Ossman
7d60e97cc9 Only show error stack if it is not empty
Parsing errors will not have a stack, and we don't want an empty
box in those cases.
2018-03-21 15:33:14 +01:00
Pierre Ossman
b475eed5fa Separate out cursor handling
Make cursor handling more generic in preparation for generic handling
of corner cases.
2018-03-15 17:22:21 +01:00
Samuel Mannehed
3f9ca4f5dc Move VERSION to top-level 2018-03-15 14:33:09 +01:00
Samuel Mannehed
25cbf00e13 Remove docs/release.txt
Instructions has been moved to the wiki:

https://github.com/novnc/noVNC/wiki/Development:-Making-a-release
2018-03-15 14:28:08 +01:00
Samuel Mannehed
a07d4abe1f Fix docs/VERSION 2018-03-15 09:28:18 +01:00
Pierre Ossman
35dd3c2299 Merge branches 'ffalt' and 'altgr' of https://github.com/CendioOssman/noVNC 2018-03-13 16:03:01 +01:00
Pierre Ossman
3a7c0c67c1 Work around broken Alt keyup in Firefox
Firefox no longer sends keyup events properly for the Alt keys. Try
to sniff out the state of the Alt key by monitoring other events that
include its state.
2018-03-13 16:01:38 +01:00
Pierre Ossman
e9118e3bda Get localStorage tests running on more browsers 2018-03-09 12:15:21 +01:00
Pierre Ossman
b22c9ef954 Better detection of AltGr on Windows
Try to properly detect the fake CtrlL+AltR sequence Windows sends
when pressing AltGr. This allows us to send more accurate key
events over to the server.
2018-03-09 12:14:23 +01:00
Pierre Ossman
d6ae445773 Handle _keyDownList in _sendKeyEvent()
This makes sure it never gets out of sync with what we've actually
sent.
2018-03-09 12:13:21 +01:00
Samuel Mannehed
06309160ee Only disable animation when element is displayed
The transitionend event will not fire when display=none. This can
prevent the initial animation for hiding the controlbar in some cases.
2018-03-08 16:52:53 +01:00
Samuel Mannehed
d7a575a2c8 Merge pull request #989 from PeterDaveHelloKitchen/update-travis-ci
Update Travis CI configuration
2018-03-06 16:22:27 +01:00
Pierre Ossman
e62b4ccb5e Merge branch 'userequire' of https://github.com/CendioOssman/noVNC 2018-02-28 13:34:28 +01:00
Pierre Ossman
4a65d50d0c Only use converted modules as legacy fallback for app
Several of the major browsers now natively support modules, so we
only need the converted modules to handle older browsers. Make sure
it's only used when necessary.
2018-02-28 13:28:07 +01:00
Pierre Ossman
8aad8f269c Merge branch 'settings' of https://github.com/andrwwbstr/noVNC 2018-02-28 12:57:48 +01:00
Pierre Ossman
e1802cac7f Separate Tight PNG in stats output 2018-02-27 10:52:02 +01:00
Pierre Ossman
5bdcf5d31c Enforce Tight PNG restrictions
Tight PNG rects cannot use the basic compression variants, and PNG
cannot be used in a standard Tight rect.

This is a partial revert of 3e8b26a based on better understanding
of the encoding.
2018-02-27 10:50:13 +01:00
Leslie Qi Wang
2c813a33fe add encoding support for TightPNG 2018-02-23 10:38:17 -08:00
Pierre Ossman
e91a095ad6 noVNC 1.0.0 2018-02-22 14:10:10 +01:00
Andrew Webster
8ad8f15cf6 Move writeSetting from updateSetting to initSetting
initSetting was the only place that supplied a 'value' to
updateSetting.  So move it to clean up updateSetting.
2018-02-13 10:22:36 -05:00
Andrew Webster
e0750f9b2c Use localstorage only to initialize settings map
This only reads from localstorage in order to initialize the settings
map.  After initializaton, reads will return the value from the map.

When writing a value, the settings map and the local storage
are updated, unless the setting is a default value or derived from
the query string.

This has a few advantages:
 1. Saved settings will not be overridden by settings specified in
the query string.  This means a setting could be temporarily changed
using the query string, but once removed from the query string, the
setting would return back to what the user selected.
 2. Default values will not be saved.  If a user has always used
the default value for a setting, then they can move to a new version
with different defaults without clearing localstorage.
 3. Changes made to localstorage in a session running in a different
window will not affect the settings in the current window (until
the page is refreshed).

Regarding eraseSetting:

It is possible that another tab could change the value, leading
to an unexpected value change in the tab that deletes.  However,
this function is currently unused, so this will be evaluted if
and when it used.
2018-02-13 10:22:21 -05:00
Pierre Ossman
37b4d13db8 Add Spanish and Turkish JSON files 2018-02-07 09:33:13 +01:00
Pierre Ossman
7c332ad930 Don't crash on translation errors
A non-translated interface is better than no interface at all.
2018-02-07 09:23:45 +01:00
Peter Dave Hello
1a76fb843a Use node.js 6 instead 6.1 to have the latest v6.x 2018-01-09 11:49:28 +08:00
Pierre Ossman
d584c5f624 Don't include icons Makefile when packaging 2018-01-05 16:17:29 +01:00
Pierre Ossman
be7b4e88f0 Remove intermediate files when bundling 2018-01-05 16:17:29 +01:00
Pierre Ossman
2163326888 Convert use_require.js to use promises
We had some race conditions between the callbacks that could cause
failures. Order everything properly using promises.
2018-01-05 16:17:26 +01:00
136 changed files with 124304 additions and 53656 deletions

1
.eslintignore Normal file
View File

@@ -0,0 +1 @@
**/xtscancodes.js

50
.eslintrc Normal file
View File

@@ -0,0 +1,50 @@
{
"env": {
"browser": true,
"es6": true
},
"parserOptions": {
"sourceType": "module"
},
"extends": "eslint:recommended",
"rules": {
// Unsafe or confusing stuff that we forbid
"no-unused-vars": ["error", { "vars": "all", "args": "none", "ignoreRestSiblings": true }],
"no-constant-condition": ["error", { "checkLoops": false }],
"no-var": "error",
"no-useless-constructor": "error",
"object-shorthand": ["error", "methods", { "avoidQuotes": true }],
"prefer-arrow-callback": "error",
"arrow-body-style": ["error", "as-needed", { "requireReturnForObjectLiteral": false } ],
"arrow-parens": ["error", "as-needed", { "requireForBlockBody": true }],
"arrow-spacing": ["error"],
"no-confusing-arrow": ["error", { "allowParens": true }],
// Enforced coding style
"brace-style": ["error", "1tbs", { "allowSingleLine": true }],
"indent": ["error", 4, { "SwitchCase": 1,
"FunctionDeclaration": { "parameters": "first" },
"CallExpression": { "arguments": "first" },
"ArrayExpression": "first",
"ObjectExpression": "first",
"ignoreComments": true }],
"comma-spacing": ["error"],
"comma-style": ["error"],
"curly": ["error", "multi-line"],
"func-call-spacing": ["error"],
"func-names": ["error"],
"func-style": ["error", "declaration", { "allowArrowFunctions": true }],
"key-spacing": ["error"],
"keyword-spacing": ["error"],
"no-trailing-spaces": ["error"],
"semi": ["error"],
"space-before-blocks": ["error"],
"space-before-function-paren": ["error", { "anonymous": "always",
"named": "never",
"asyncArrow": "always" }],
"switch-colon-spacing": ["error"],
"camelcase": ["error", { allow: ["^XK_", "^XF86XK_"] }],
}
}

34
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,34 @@
---
name: Bug report
about: Create a report to help us improve
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Client (please complete the following information):**
- OS: [e.g. iOS]
- Browser: [e.g. chrome, safari]
- Browser version: [e.g. 22]
**Server (please complete the following information):**
- noVNC version: [e.g. 1.0.0 or git commit id]
- VNC server: [e.g. QEMU, TigerVNC]
- WebSocket proxy: [e.g. websockify]
**Additional context**
Add any other context about the problem here.

View File

@@ -0,0 +1,17 @@
---
name: Feature request
about: Suggest an idea for this project
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

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

@@ -0,0 +1,46 @@
name: Publish
on:
release:
types: [published]
jobs:
npm:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
- run: npm install
- run: npm publish --access public
env:
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
if: ${{ !github.event.release.prerelease }}
- run: npm publish --access public --tag beta
env:
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
if: ${{ github.event.release.prerelease }}
snap:
runs-on: ubuntu-latest
container: snapcore/snapcraft
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
- run: npm install
- run: ./utils/use_require.js --clean --as commonjs --with-app
- run: |
cp utils/launch.sh build/launch.sh
cp snap/local/svc_wrapper.sh build/svc_wrapper.sh
- run: |
VERSION=$(grep '"version"' package.json | cut -d '"' -f 4)
echo $VERSION
sed -i "s/@VERSION@/$VERSION/g" snap/snapcraft.yaml
- run: snapcraft
- run: |
mkdir .snapcraft
echo ${SNAPCRAFT_LOGIN} | base64 --decode --ignore-garbage > .snapcraft/snapcraft.cfg
env:
SNAPCRAFT_LOGIN: ${{secrets.SNAPCRAFT_LOGIN}}
- run: snapcraft push --release=stable *.snap
if: ${{ !github.event.release.prerelease }}
- run: snapcraft push --release=beta *.snap
if: ${{ github.event.release.prerelease }}

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

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

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

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

View File

@@ -1,37 +0,0 @@
# infra JS
/build/
/node_modules/
/tests/
/utils/
/recordings/
/vendor/sinon.js
# noVNC application files
/app
/vendor/browser-es-module-loader
/vendor/promise.js
/vnc.html
/vnc_lite.html
# raw translation files
/po
# config files
/.travis.yml
/karma.conf.js
# various other files
/.gitmodules
.*
*~
*.swp
*.swo
# documentation (except licenses)
/docs/notes
/docs/links
/docs/release.txt
/docs/rfb_notes
/docs/*.pdf
/docs/flash_policy.txt
/CONTRIBUTING.md

View File

@@ -1,38 +0,0 @@
language: node_js
sudo: false
cache:
directories:
- node_modules
node_js:
- '6.1'
env:
matrix:
- TEST_BROWSER_NAME=chrome TEST_BROWSER_OS='Windows 10,Linux,OS X 10.11'
- TEST_BROWSER_NAME=firefox TEST_BROWSER_OS='Windows 10,Linux,OS X 10.11'
- TEST_BROWSER_NAME='internet explorer' TEST_BROWSER_OS='Windows 10'
- TEST_BROWSER_NAME=safari TEST_BROWSER_OS='OS X 10.11'
before_script: npm install -g karma-cli
addons:
sauce_connect:
username: "directxman12"
jwt:
secure: "d3ekMYslpn6R4f0ajtRMt9SUFmNGDiItHpqaXC5T4KI0KMEsxgvEOfJot5PiFFJWg1DSpJZH6oaW2UxGZ3duJLZrXIEd/JePY8a6NtT35BNgiDPgcp+eu2Bu3rhrSNg7/HEsD1ma+JeUTnv18Ai5oMFfCCQJx2J6osIxyl/ZVxA="
stages:
- test
- name: deploy
if: tag is PRESENT
jobs:
include:
- stage: deploy
script: skip
before_script: skip
deploy:
provider: npm
email: directxman12+npm@gmail.com
api_key:
secure: cIidkFmvkdmdwWsqBpxyPUCzBqgK8LhPiNxTrIfhwbUunMsJep9MiiBJtv8poVYG2Y4yfiZmqGn4nfetUdc/LDctd73j+/EM4Z/NUDexVAhJ+9/qCogvpJsSQ96VQo7yBceW4E1fBM3WCU0kcGToYIVSSrwvvRDtJfeYJf2Qqw0=
on:
tags: true
repo: novnc/noVNC

13
AUTHORS Normal file
View File

@@ -0,0 +1,13 @@
maintainers:
- Joel Martin (@kanaka)
- Solly Ross (@directxman12)
- Samuel Mannehed for Cendio AB (@samhed)
- Pierre Ossman for Cendio AB (@CendioOssman)
maintainersEmeritus:
- @astrand
contributors:
# There are a bunch of people that should be here.
# If you want to be on this list, feel free send a PR
# to add yourself.
- jalf <git@jalf.dk>
- NTT corp.

View File

@@ -1,4 +1,5 @@
noVNC is Copyright (C) 2011 Joel Martin <github@martintribe.org>
noVNC is Copyright (C) 2019 The noVNC Authors
(./AUTHORS)
The noVNC core library files are licensed under the MPL 2.0 (Mozilla
Public License 2.0). The noVNC core library is composed of the

View File

@@ -1,6 +1,7 @@
## noVNC: HTML VNC Client Library and Application
[![Build Status](https://travis-ci.org/novnc/noVNC.svg?branch=master)](https://travis-ci.org/novnc/noVNC)
[![Test Status](https://github.com/novnc/noVNC/workflows/Test/badge.svg)](https://github.com/novnc/noVNC/actions?query=workflow%3ATest)
[![Lint Status](https://github.com/novnc/noVNC/workflows/Lint/badge.svg)](https://github.com/novnc/noVNC/actions?query=workflow%3ALint)
### Description
@@ -24,6 +25,7 @@ for a more complete list with additional info and links.
- [Browser Requirements](#browser-requirements)
- [Server Requirements](#server-requirements)
- [Quick Start](#quick-start)
- [Installation from Snap Package](#installation-from-snap-package)
- [Integration and Deployment](#integration-and-deployment)
- [Authors/Contributors](#authorscontributors)
@@ -67,6 +69,8 @@ Please tweet [@noVNC](http://www.twitter.com/noVNC) if you do.
* Supports scaling, clipping and resizing the desktop
* Local cursor rendering
* Clipboard copy/paste
* Translations
* Touch gestures for emulating common mouse actions
* Licensed mainly under the [MPL 2.0](http://www.mozilla.org/MPL/2.0/), see
[the license document](LICENSE.txt) for details
@@ -87,7 +91,7 @@ noVNC uses many modern web technologies so a formal requirement list is
not available. However these are the minimum versions we are currently
aware of:
* Chrome 49, Firefox 44, Safari 10, Opera 36, IE 11, Edge 12
* Chrome 49, Firefox 44, Safari 11, Opera 36, IE 11, Edge 12
### Server Requirements
@@ -114,6 +118,66 @@ proxy.
script. Hit the Connect button, enter a password if the VNC server has one
configured, and enjoy!
### Installation from Snap Package
Running the command below will install the latest release of noVNC from Snap:
`sudo snap install novnc`
#### Running noVNC
You can run the Snap-package installed novnc directly with, for example:
`novnc --listen 6081 --vnc localhost:5901 # /snap/bin/novnc if /snap/bin is not in your PATH`
#### Running as a Service (Daemon)
The Snap package also has the capability to run a 'novnc' service which can be
configured to listen on multiple ports connecting to multiple VNC servers
(effectively a service runing multiple instances of novnc).
Instructions (with example values):
List current services (out-of-box this will be blank):
```
sudo snap get novnc services
Key Value
services.n6080 {...}
services.n6081 {...}
```
Create a new service that listens on port 6082 and connects to the VNC server
running on port 5902 on localhost:
`sudo snap set novnc services.n6082.listen=6082 services.n6082.vnc=localhost:5902`
(Any services you define with 'snap set' will be automatically started)
Note that the name of the service, 'n6082' in this example, can be anything
as long as it doesn't start with a number or contain spaces/special characters.
View the configuration of the service just created:
```
sudo snap get novnc services.n6082
Key Value
services.n6082.listen 6082
services.n6082.vnc localhost:5902
```
Disable a service (note that because of a limitation in Snap it's currently not
possible to unset config variables, setting them to blank values is the way
to disable a service):
`sudo snap set novnc services.n6082.listen='' services.n6082.vnc=''`
(Any services you set to blank with 'snap set' like this will be automatically stopped)
Verify that the service is disabled (blank values):
```
sudo snap get novnc services.n6082
Key Value
services.n6082.listen
services.n6082.vnc
```
### Integration and Deployment
@@ -126,10 +190,12 @@ or deploying the noVNC application in production environments:
### Authors/Contributors
See [AUTHORS](AUTHORS) for a (full-ish) list of authors. If you're not on
that list and you think you should be, feel free to send a PR to fix that.
* Core team:
* [Joel Martin](https://github.com/kanaka)
* [Samuel Mannehed](https://github.com/samhed) (Cendio)
* [Peter Åstrand](https://github.com/astrand) (Cendio)
* [Solly Ross](https://github.com/DirectXMan12) (Red Hat / OpenStack)
* [Pierre Ossman](https://github.com/CendioOssman) (Cendio)

View File

@@ -1,22 +1,32 @@
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2019 The noVNC Authors
* Licensed under MPL 2.0 (see LICENSE.txt)
*
* See README.md for usage and integration instructions.
*/
// NB: this should *not* be included as a module until we have
// native support in the browsers, so that our error handler
// can catch script-loading errors.
// No ES6 can be used in this file since it's used for the translation
/* eslint-disable prefer-arrow-callback */
(function(){
(function _scope() {
"use strict";
// Fallback for all uncought errors
function handleError (event, err) {
function handleError(event, err) {
try {
var msg = document.getElementById('noVNC_fallback_errormsg');
const msg = document.getElementById('noVNC_fallback_errormsg');
// Only show the initial error
if (msg.hasChildNodes()) {
return false;
}
var div = document.createElement("div");
let div = document.createElement("div");
div.classList.add('noVNC_message');
div.appendChild(document.createTextNode(event.message));
msg.appendChild(div);
@@ -24,7 +34,7 @@
if (event.filename) {
div = document.createElement("div");
div.className = 'noVNC_location';
var text = event.filename;
let text = event.filename;
if (event.lineno !== undefined) {
text += ":" + event.lineno;
if (event.colno !== undefined) {
@@ -35,7 +45,7 @@
msg.appendChild(div);
}
if (err && (err.stack !== undefined)) {
if (err && err.stack) {
div = document.createElement("div");
div.className = 'noVNC_stack';
div.appendChild(document.createTextNode(err.stack));
@@ -51,6 +61,6 @@
// from being printed to the browser console.
return false;
}
window.addEventListener('error', function (evt) { handleError(evt, evt.error); });
window.addEventListener('unhandledrejection', function (evt) { handleError(evt.reason, evt.reason); });
window.addEventListener('error', function onerror(evt) { handleError(evt, evt.error); });
window.addEventListener('unhandledrejection', function onreject(evt) { handleError(evt.reason, evt.reason); });
})();

View File

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

Before

Width:  |  Height:  |  Size: 6.8 KiB

View File

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

Before

Width:  |  Height:  |  Size: 6.8 KiB

View File

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

Before

Width:  |  Height:  |  Size: 6.8 KiB

View File

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

Before

Width:  |  Height:  |  Size: 6.8 KiB

65
app/images/windows.svg Normal file
View File

@@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
id="svg2"
inkscape:export-ydpi="90"
inkscape:export-xdpi="90"
sodipodi:docname="windows.svg"
inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
inkscape:version="0.92.4 (unknown)"
x="0px"
y="0px"
viewBox="-293 384 25 25"
xml:space="preserve"
width="25"
height="25"><metadata
id="metadata21"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs19" /><sodipodi:namedview
pagecolor="#959595"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1136"
id="namedview17"
showgrid="true"
inkscape:pagecheckerboard="false"
inkscape:zoom="32"
inkscape:cx="3.926913"
inkscape:cy="13.255959"
inkscape:window-x="1920"
inkscape:window-y="27"
inkscape:window-maximized="1"
inkscape:current-layer="svg2"><inkscape:grid
type="xygrid"
id="grid818" /></sodipodi:namedview>
<style
type="text/css"
id="style2">
.st0{fill:#FFFFFF;}
</style>
<path
style="fill:#ffffff;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;fill-opacity:1"
d="M 21 4 L 11 5.1757812 L 11 12 L 21 12 L 21 4 z M 10 5.2949219 L 4 6 L 4 12 L 10 12 L 10 5.2949219 z "
transform="translate(-293,384)"
id="path853" /><path
id="path858"
d="m -272,405 -10,-1.17578 V 397 h 10 z M -283,403.70508 -289,403 v -6 h 6 z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
inkscape:connector-curvature="0" /></svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

1
app/locale/README Normal file
View File

@@ -0,0 +1 @@
DO NOT MODIFY THE FILES IN THIS FOLDER, THEY ARE AUTOMATICALLY GENERATED FROM THE PO-FILES.

71
app/locale/cs.json Normal file
View File

@@ -0,0 +1,71 @@
{
"Connecting...": "Připojení...",
"Disconnecting...": "Odpojení...",
"Reconnecting...": "Obnova připojení...",
"Internal error": "Vnitřní chyba",
"Must set host": "Hostitel musí být nastavení",
"Connected (encrypted) to ": "Připojení (šifrované) k ",
"Connected (unencrypted) to ": "Připojení (nešifrované) k ",
"Something went wrong, connection is closed": "Něco se pokazilo, odpojeno",
"Failed to connect to server": "Chyba připojení k serveru",
"Disconnected": "Odpojeno",
"New connection has been rejected with reason: ": "Nové připojení bylo odmítnuto s odůvodněním: ",
"New connection has been rejected": "Nové připojení bylo odmítnuto",
"Password is required": "Je vyžadováno heslo",
"noVNC encountered an error:": "noVNC narazilo na chybu:",
"Hide/Show the control bar": "Skrýt/zobrazit ovládací panel",
"Move/Drag Viewport": "Přesunout/přetáhnout výřez",
"viewport drag": "přesun výřezu",
"Active Mouse Button": "Aktivní tlačítka myši",
"No mousebutton": "Žádné",
"Left mousebutton": "Levé tlačítko myši",
"Middle mousebutton": "Prostřední tlačítko myši",
"Right mousebutton": "Pravé tlačítko myši",
"Keyboard": "Klávesnice",
"Show Keyboard": "Zobrazit klávesnici",
"Extra keys": "Extra klávesy",
"Show Extra Keys": "Zobrazit extra klávesy",
"Ctrl": "Ctrl",
"Toggle Ctrl": "Přepnout Ctrl",
"Alt": "Alt",
"Toggle Alt": "Přepnout Alt",
"Send Tab": "Odeslat tabulátor",
"Tab": "Tab",
"Esc": "Esc",
"Send Escape": "Odeslat Esc",
"Ctrl+Alt+Del": "Ctrl+Alt+Del",
"Send Ctrl-Alt-Del": "Poslat Ctrl-Alt-Del",
"Shutdown/Reboot": "Vypnutí/Restart",
"Shutdown/Reboot...": "Vypnutí/Restart...",
"Power": "Napájení",
"Shutdown": "Vypnout",
"Reboot": "Restart",
"Reset": "Reset",
"Clipboard": "Schránka",
"Clear": "Vymazat",
"Fullscreen": "Celá obrazovka",
"Settings": "Nastavení",
"Shared Mode": "Sdílený režim",
"View Only": "Pouze prohlížení",
"Clip to Window": "Přizpůsobit oknu",
"Scaling Mode:": "Přizpůsobení velikosti",
"None": "Žádné",
"Local Scaling": "Místní",
"Remote Resizing": "Vzdálené",
"Advanced": "Pokročilé",
"Repeater ID:": "ID opakovače",
"WebSocket": "WebSocket",
"Encrypt": "Šifrování:",
"Host:": "Hostitel:",
"Port:": "Port:",
"Path:": "Cesta",
"Automatic Reconnect": "Automatická obnova připojení",
"Reconnect Delay (ms):": "Zpoždění připojení (ms)",
"Show Dot when No Cursor": "Tečka místo chybějícího kurzoru myši",
"Logging:": "Logování:",
"Disconnect": "Odpojit",
"Connect": "Připojit",
"Password:": "Heslo",
"Send Password": "Odeslat heslo",
"Cancel": "Zrušit"
}

68
app/locale/es.json Normal file
View File

@@ -0,0 +1,68 @@
{
"Connecting...": "Conectando...",
"Connected (encrypted) to ": "Conectado (con encriptación) a",
"Connected (unencrypted) to ": "Conectado (sin encriptación) a",
"Disconnecting...": "Desconectando...",
"Disconnected": "Desconectado",
"Must set host": "Debes configurar el host",
"Reconnecting...": "Reconectando...",
"Password is required": "Contraseña es obligatoria",
"Disconnect timeout": "Tiempo de desconexión agotado",
"noVNC encountered an error:": "noVNC ha encontrado un error:",
"Hide/Show the control bar": "Ocultar/Mostrar la barra de control",
"Move/Drag Viewport": "Mover/Arrastrar la ventana",
"viewport drag": "Arrastrar la ventana",
"Active Mouse Button": "Botón activo del ratón",
"No mousebutton": "Ningún botón del ratón",
"Left mousebutton": "Botón izquierdo del ratón",
"Middle mousebutton": "Botón central del ratón",
"Right mousebutton": "Botón derecho del ratón",
"Keyboard": "Teclado",
"Show Keyboard": "Mostrar teclado",
"Extra keys": "Teclas adicionales",
"Show Extra Keys": "Mostrar Teclas Adicionales",
"Ctrl": "Ctrl",
"Toggle Ctrl": "Pulsar/Soltar Ctrl",
"Alt": "Alt",
"Toggle Alt": "Pulsar/Soltar Alt",
"Send Tab": "Enviar Tabulación",
"Tab": "Tabulación",
"Esc": "Esc",
"Send Escape": "Enviar Escape",
"Ctrl+Alt+Del": "Ctrl+Alt+Del",
"Send Ctrl-Alt-Del": "Enviar Ctrl+Alt+Del",
"Shutdown/Reboot": "Apagar/Reiniciar",
"Shutdown/Reboot...": "Apagar/Reiniciar...",
"Power": "Encender",
"Shutdown": "Apagar",
"Reboot": "Reiniciar",
"Reset": "Restablecer",
"Clipboard": "Portapapeles",
"Clear": "Vaciar",
"Fullscreen": "Pantalla Completa",
"Settings": "Configuraciones",
"Shared Mode": "Modo Compartido",
"View Only": "Solo visualización",
"Clip to Window": "Recortar al tamaño de la ventana",
"Scaling Mode:": "Modo de escalado:",
"None": "Ninguno",
"Local Scaling": "Escalado Local",
"Local Downscaling": "Reducción de escala local",
"Remote Resizing": "Cambio de tamaño remoto",
"Advanced": "Avanzado",
"Local Cursor": "Cursor Local",
"Repeater ID:": "ID del Repetidor",
"WebSocket": "WebSocket",
"Encrypt": "",
"Host:": "Host",
"Port:": "Puesto",
"Path:": "Ruta",
"Automatic Reconnect": "Reconexión automática",
"Reconnect Delay (ms):": "Retraso en la reconexión (ms)",
"Logging:": "Logging",
"Disconnect": "Desconectar",
"Connect": "Conectar",
"Password:": "Contraseña",
"Cancel": "Cancelar",
"Canvas not supported.": "Canvas no está soportado"
}

73
app/locale/ja.json Normal file
View File

@@ -0,0 +1,73 @@
{
"Connecting...": "接続しています...",
"Disconnecting...": "切断しています...",
"Reconnecting...": "再接続しています...",
"Internal error": "内部エラー",
"Must set host": "ホストを設定する必要があります",
"Connected (encrypted) to ": "接続しました (暗号化済み): ",
"Connected (unencrypted) to ": "接続しました (暗号化されていません): ",
"Something went wrong, connection is closed": "何かが問題で、接続が閉じられました",
"Failed to connect to server": "サーバーへの接続に失敗しました",
"Disconnected": "切断しました",
"New connection has been rejected with reason: ": "新規接続は次の理由で拒否されました: ",
"New connection has been rejected": "新規接続は拒否されました",
"Password is required": "パスワードが必要です",
"noVNC encountered an error:": "noVNC でエラーが発生しました:",
"Hide/Show the control bar": "コントロールバーを隠す/表示する",
"Move/Drag Viewport": "ビューポートを移動/ドラッグ",
"viewport drag": "ビューポートをドラッグ",
"Active Mouse Button": "アクティブなマウスボタン",
"No mousebutton": "マウスボタンなし",
"Left mousebutton": "左マウスボタン",
"Middle mousebutton": "中マウスボタン",
"Right mousebutton": "右マウスボタン",
"Keyboard": "キーボード",
"Show Keyboard": "キーボードを表示",
"Extra keys": "追加キー",
"Show Extra Keys": "追加キーを表示",
"Ctrl": "Ctrl",
"Toggle Ctrl": "Ctrl キーを切り替え",
"Alt": "Alt",
"Toggle Alt": "Alt キーを切り替え",
"Toggle Windows": "Windows キーを切り替え",
"Windows": "Windows",
"Send Tab": "Tab キーを送信",
"Tab": "Tab",
"Esc": "Esc",
"Send Escape": "Escape キーを送信",
"Ctrl+Alt+Del": "Ctrl+Alt+Del",
"Send Ctrl-Alt-Del": "Ctrl-Alt-Del を送信",
"Shutdown/Reboot": "シャットダウン/再起動",
"Shutdown/Reboot...": "シャットダウン/再起動...",
"Power": "電源",
"Shutdown": "シャットダウン",
"Reboot": "再起動",
"Reset": "リセット",
"Clipboard": "クリップボード",
"Clear": "クリア",
"Fullscreen": "全画面表示",
"Settings": "設定",
"Shared Mode": "共有モード",
"View Only": "表示のみ",
"Clip to Window": "ウィンドウにクリップ",
"Scaling Mode:": "スケーリングモード:",
"None": "なし",
"Local Scaling": "ローカルスケーリング",
"Remote Resizing": "リモートでリサイズ",
"Advanced": "高度",
"Repeater ID:": "リピーター ID:",
"WebSocket": "WebSocket",
"Encrypt": "暗号化",
"Host:": "ホスト:",
"Port:": "ポート:",
"Path:": "パス:",
"Automatic Reconnect": "自動再接続",
"Reconnect Delay (ms):": "再接続する遅延 (ミリ秒):",
"Show Dot when No Cursor": "カーソルがないときにドットを表示",
"Logging:": "ロギング:",
"Disconnect": "切断",
"Connect": "接続",
"Password:": "パスワード:",
"Send Password": "パスワードを送信",
"Cancel": "キャンセル"
}

70
app/locale/ko.json Normal file
View File

@@ -0,0 +1,70 @@
{
"Connecting...": "연결중...",
"Disconnecting...": "연결 해제중...",
"Reconnecting...": "재연결중...",
"Internal error": "내부 오류",
"Must set host": "호스트는 설정되어야 합니다.",
"Connected (encrypted) to ": "다음과 (암호화되어) 연결되었습니다:",
"Connected (unencrypted) to ": "다음과 (암호화 없이) 연결되었습니다:",
"Something went wrong, connection is closed": "무언가 잘못되었습니다, 연결이 닫혔습니다.",
"Failed to connect to server": "서버에 연결하지 못했습니다.",
"Disconnected": "연결이 해제되었습니다.",
"New connection has been rejected with reason: ": "새 연결이 다음 이유로 거부되었습니다:",
"New connection has been rejected": "새 연결이 거부되었습니다.",
"Password is required": "비밀번호가 필요합니다.",
"noVNC encountered an error:": "noVNC에 오류가 발생했습니다:",
"Hide/Show the control bar": "컨트롤 바 숨기기/보이기",
"Move/Drag Viewport": "움직이기/드래그 뷰포트",
"viewport drag": "뷰포트 드래그",
"Active Mouse Button": "마우스 버튼 활성화",
"No mousebutton": "마우스 버튼 없음",
"Left mousebutton": "왼쪽 마우스 버튼",
"Middle mousebutton": "중간 마우스 버튼",
"Right mousebutton": "오른쪽 마우스 버튼",
"Keyboard": "키보드",
"Show Keyboard": "키보드 보이기",
"Extra keys": "기타 키들",
"Show Extra Keys": "기타 키들 보이기",
"Ctrl": "Ctrl",
"Toggle Ctrl": "Ctrl 켜기/끄기",
"Alt": "Alt",
"Toggle Alt": "Alt 켜기/끄기",
"Send Tab": "Tab 보내기",
"Tab": "Tab",
"Esc": "Esc",
"Send Escape": "Esc 보내기",
"Ctrl+Alt+Del": "Ctrl+Alt+Del",
"Send Ctrl-Alt-Del": "Ctrl+Alt+Del 보내기",
"Shutdown/Reboot": "셧다운/리붓",
"Shutdown/Reboot...": "셧다운/리붓...",
"Power": "전원",
"Shutdown": "셧다운",
"Reboot": "리붓",
"Reset": "리셋",
"Clipboard": "클립보드",
"Clear": "지우기",
"Fullscreen": "전체화면",
"Settings": "설정",
"Shared Mode": "공유 모드",
"View Only": "보기 전용",
"Clip to Window": "창에 클립",
"Scaling Mode:": "스케일링 모드:",
"None": "없음",
"Local Scaling": "로컬 스케일링",
"Remote Resizing": "원격 크기 조절",
"Advanced": "고급",
"Repeater ID:": "중계 ID",
"WebSocket": "웹소켓",
"Encrypt": "암호화",
"Host:": "호스트:",
"Port:": "포트:",
"Path:": "위치:",
"Automatic Reconnect": "자동 재연결",
"Reconnect Delay (ms):": "재연결 지연 시간 (ms)",
"Logging:": "로깅",
"Disconnect": "연결 해제",
"Connect": "연결",
"Password:": "비밀번호:",
"Send Password": "비밀번호 전송",
"Cancel": "취소"
}

View File

@@ -1,13 +1,17 @@
{
"Connecting...": "Verbinden...",
"Disconnecting...": "Verbinding verbreken...",
"Reconnecting...": "Opnieuw verbinding maken...",
"Internal error": "Interne fout",
"Must set host": "Host moeten worden ingesteld",
"Connected (encrypted) to ": "Verbonden (versleuteld) met ",
"Connected (unencrypted) to ": "Verbonden (onversleuteld) met ",
"Disconnecting...": "Verbinding verbreken...",
"Something went wrong, connection is closed": "Er iets fout gelopen, verbinding werd verbroken",
"Failed to connect to server": "Verbinding maken met server is mislukt",
"Disconnected": "Verbinding verbroken",
"Must set host": "Host moeten worden ingesteld",
"Reconnecting...": "Opnieuw verbinding maken...",
"New connection has been rejected with reason: ": "Nieuwe verbinding is geweigerd omwille van de volgende reden: ",
"New connection has been rejected": "Nieuwe verbinding is geweigerd",
"Password is required": "Wachtwoord is vereist",
"Disconnect timeout": "Timeout tijdens verbreken van verbinding",
"noVNC encountered an error:": "noVNC heeft een fout bemerkt:",
"Hide/Show the control bar": "Verberg/Toon de bedieningsbalk",
"Move/Drag Viewport": "Verplaats/Versleep Kijkvenster",
@@ -22,9 +26,11 @@
"Extra keys": "Extra toetsen",
"Show Extra Keys": "Toon Extra Toetsen",
"Ctrl": "Ctrl",
"Toggle Ctrl": "Ctrl aan/uitzetten",
"Toggle Ctrl": "Ctrl omschakelen",
"Alt": "Alt",
"Toggle Alt": "Alt aan/uitzetten",
"Toggle Alt": "Alt omschakelen",
"Toggle Windows": "Windows omschakelen",
"Windows": "Windows",
"Send Tab": "Tab Sturen",
"Tab": "Tab",
"Esc": "Esc",
@@ -47,10 +53,8 @@
"Scaling Mode:": "Schaalmodus:",
"None": "Geen",
"Local Scaling": "Lokaal Schalen",
"Local Downscaling": "Lokaal Neerschalen",
"Remote Resizing": "Op Afstand Formaat Wijzigen",
"Advanced": "Geavanceerd",
"Local Cursor": "Lokale Cursor",
"Repeater ID:": "Repeater ID:",
"WebSocket": "WebSocket",
"Encrypt": "Versleutelen",
@@ -59,10 +63,11 @@
"Path:": "Pad:",
"Automatic Reconnect": "Automatisch Opnieuw Verbinden",
"Reconnect Delay (ms):": "Vertraging voor Opnieuw Verbinden (ms):",
"Show Dot when No Cursor": "Geef stip weer indien geen cursor",
"Logging:": "Logmeldingen:",
"Disconnect": "Verbinding verbreken",
"Connect": "Verbinden",
"Password:": "Wachtwoord:",
"Cancel": "Annuleren",
"Canvas not supported.": "Canvas wordt niet ondersteund."
"Send Password": "Verzend Wachtwoord:",
"Cancel": "Annuleren"
}

73
app/locale/ru.json Normal file
View File

@@ -0,0 +1,73 @@
{
"Connecting...": "Подключение...",
"Disconnecting...": "Отключение...",
"Reconnecting...": "Переподключение...",
"Internal error": "Внутренняя ошибка",
"Must set host": "Задайте имя сервера или IP",
"Connected (encrypted) to ": "Подключено (с шифрованием) к ",
"Connected (unencrypted) to ": "Подключено (без шифрования) к ",
"Something went wrong, connection is closed": "Что-то пошло не так, подключение разорвано",
"Failed to connect to server": "Ошибка подключения к серверу",
"Disconnected": "Отключено",
"New connection has been rejected with reason: ": "Подключиться не удалось: ",
"New connection has been rejected": "Подключиться не удалось",
"Password is required": "Требуется пароль",
"noVNC encountered an error:": "Ошибка noVNC: ",
"Hide/Show the control bar": "Скрыть/Показать контрольную панель",
"Move/Drag Viewport": "Переместить окно",
"viewport drag": "Переместить окно",
"Active Mouse Button": "Активировать кнопки мыши",
"No mousebutton": "Отключить кнопки мыши",
"Left mousebutton": "Левая кнопка мыши",
"Middle mousebutton": "Средняя кнопка мыши",
"Right mousebutton": "Правая кнопка мыши",
"Keyboard": "Клавиатура",
"Show Keyboard": "Показать клавиатуру",
"Extra keys": "Доп. кнопки",
"Show Extra Keys": "Показать дополнительные кнопки",
"Ctrl": "Ctrl",
"Toggle Ctrl": "Передать нажатие Ctrl",
"Alt": "Alt",
"Toggle Alt": "Передать нажатие Alt",
"Toggle Windows": "Переключение вкладок",
"Windows": "Вкладка",
"Send Tab": "Передать нажатие Tab",
"Tab": "Tab",
"Esc": "Esc",
"Send Escape": "Передать нажатие Escape",
"Ctrl+Alt+Del": "Ctrl+Alt+Del",
"Send Ctrl-Alt-Del": "Передать нажатие Ctrl-Alt-Del",
"Shutdown/Reboot": "Выключить/Перезагрузить",
"Shutdown/Reboot...": "Выключить/Перезагрузить...",
"Power": "Питание",
"Shutdown": "Выключить",
"Reboot": "Перезагрузить",
"Reset": "Сброс",
"Clipboard": "Буфер обмена",
"Clear": "Очистить",
"Fullscreen": "Во весь экран",
"Settings": "Настройки",
"Shared Mode": "Общий режим",
"View Only": "Просмотр",
"Clip to Window": "В окно",
"Scaling Mode:": "Масштаб:",
"None": "Нет",
"Local Scaling": "Локльный масштаб",
"Remote Resizing": "Удаленный масштаб",
"Advanced": "Дополнительно",
"Repeater ID:": "Идентификатор ID:",
"WebSocket": "WebSocket",
"Encrypt": "Шифрование",
"Host:": "Сервер:",
"Port:": "Порт:",
"Path:": "Путь:",
"Automatic Reconnect": "Автоматическое переподключение",
"Reconnect Delay (ms):": "Задержка переподключения (мс):",
"Show Dot when No Cursor": "Показать точку вместо курсора",
"Logging:": "Лог:",
"Disconnect": "Отключение",
"Connect": "Подключение",
"Password:": "Пароль:",
"Send Password": "Пароль: ",
"Cancel": "Выход"
}

View File

@@ -1,13 +1,17 @@
{
"Connecting...": "Ansluter...",
"Disconnecting...": "Kopplar ner...",
"Reconnecting...": "Återansluter...",
"Internal error": "Internt fel",
"Must set host": "Du måste specifiera en värd",
"Connected (encrypted) to ": "Ansluten (krypterat) till ",
"Connected (unencrypted) to ": "Ansluten (okrypterat) till ",
"Disconnecting...": "Kopplar ner...",
"Something went wrong, connection is closed": "Något gick fel, anslutningen avslutades",
"Failed to connect to server": "Misslyckades att ansluta till servern",
"Disconnected": "Frånkopplad",
"Must set host": "Du måste specifiera en värd",
"Reconnecting...": "Återansluter...",
"New connection has been rejected with reason: ": "Ny anslutning har blivit nekad med följande skäl: ",
"New connection has been rejected": "Ny anslutning har blivit nekad",
"Password is required": "Lösenord krävs",
"Disconnect timeout": "Det tog för lång tid att koppla ner",
"noVNC encountered an error:": "noVNC stötte på ett problem:",
"Hide/Show the control bar": "Göm/Visa kontrollbaren",
"Move/Drag Viewport": "Flytta/Dra Vyn",
@@ -25,6 +29,8 @@
"Toggle Ctrl": "Växla Ctrl",
"Alt": "Alt",
"Toggle Alt": "Växla Alt",
"Toggle Windows": "Växla Windows",
"Windows": "Windows",
"Send Tab": "Skicka Tab",
"Tab": "Tab",
"Esc": "Esc",
@@ -47,10 +53,8 @@
"Scaling Mode:": "Skalningsläge:",
"None": "Ingen",
"Local Scaling": "Lokal Skalning",
"Local Downscaling": "Lokal Nedskalning",
"Remote Resizing": "Ändra Storlek",
"Advanced": "Avancerat",
"Local Cursor": "Lokal Muspekare",
"Repeater ID:": "Repeater-ID:",
"WebSocket": "WebSocket",
"Encrypt": "Kryptera",
@@ -59,10 +63,11 @@
"Path:": "Sökväg:",
"Automatic Reconnect": "Automatisk Återanslutning",
"Reconnect Delay (ms):": "Fördröjning (ms):",
"Show Dot when No Cursor": "Visa prick när ingen muspekare finns",
"Logging:": "Loggning:",
"Disconnect": "Koppla från",
"Connect": "Anslut",
"Password:": "Lösenord:",
"Cancel": "Avbryt",
"Canvas not supported.": "Canvas stöds ej"
"Send Password": "Skicka lösenord",
"Cancel": "Avbryt"
}

69
app/locale/tr.json Normal file
View File

@@ -0,0 +1,69 @@
{
"Connecting...": "Bağlanıyor...",
"Disconnecting...": "Bağlantı kesiliyor...",
"Reconnecting...": "Yeniden bağlantı kuruluyor...",
"Internal error": "İç hata",
"Must set host": "Sunucuyu kur",
"Connected (encrypted) to ": "Bağlı (şifrelenmiş)",
"Connected (unencrypted) to ": "Bağlandı (şifrelenmemiş)",
"Something went wrong, connection is closed": "Bir şeyler ters gitti, bağlantı kesildi",
"Disconnected": "Bağlantı kesildi",
"New connection has been rejected with reason: ": "Bağlantı aşağıdaki nedenlerden dolayı reddedildi: ",
"New connection has been rejected": "Bağlantı reddedildi",
"Password is required": "Şifre gerekli",
"noVNC encountered an error:": "Bir hata oluştu:",
"Hide/Show the control bar": "Denetim masasını Gizle/Göster",
"Move/Drag Viewport": "Görünümü Taşı/Sürükle",
"viewport drag": "Görüntü penceresini sürükle",
"Active Mouse Button": "Aktif Fare Düğmesi",
"No mousebutton": "Fare düğmesi yok",
"Left mousebutton": "Farenin sol düğmesi",
"Middle mousebutton": "Farenin orta düğmesi",
"Right mousebutton": "Farenin sağ düğmesi",
"Keyboard": "Klavye",
"Show Keyboard": "Klavye Düzenini Göster",
"Extra keys": "Ekstra tuşlar",
"Show Extra Keys": "Ekstra tuşları göster",
"Ctrl": "Ctrl",
"Toggle Ctrl": "Ctrl Değiştir ",
"Alt": "Alt",
"Toggle Alt": "Alt Değiştir",
"Send Tab": "Sekme Gönder",
"Tab": "Sekme",
"Esc": "Esc",
"Send Escape": "Boşluk Gönder",
"Ctrl+Alt+Del": "Ctrl + Alt + Del",
"Send Ctrl-Alt-Del": "Ctrl-Alt-Del Gönder",
"Shutdown/Reboot": "Kapat/Yeniden Başlat",
"Shutdown/Reboot...": "Kapat/Yeniden Başlat...",
"Power": "Güç",
"Shutdown": "Kapat",
"Reboot": "Yeniden Başlat",
"Reset": "Sıfırla",
"Clipboard": "Pano",
"Clear": "Temizle",
"Fullscreen": "Tam Ekran",
"Settings": "Ayarlar",
"Shared Mode": "Paylaşım Modu",
"View Only": "Sadece Görüntüle",
"Clip to Window": "Pencereye Tıkla",
"Scaling Mode:": "Ölçekleme Modu:",
"None": "Bilinmeyen",
"Local Scaling": "Yerel Ölçeklendirme",
"Remote Resizing": "Uzaktan Yeniden Boyutlandırma",
"Advanced": "Gelişmiş",
"Repeater ID:": "Tekralayıcı ID:",
"WebSocket": "WebSocket",
"Encrypt": "Şifrele",
"Host:": "Ana makine:",
"Port:": "Port:",
"Path:": "Yol:",
"Automatic Reconnect": "Otomatik Yeniden Bağlan",
"Reconnect Delay (ms):": "Yeniden Bağlanma Süreci (ms):",
"Logging:": "Giriş yapılıyor:",
"Disconnect": "Bağlantıyı Kes",
"Connect": "Bağlan",
"Password:": "Parola:",
"Cancel": "Vazgeç",
"Canvas not supported.": "Tuval desteklenmiyor."
}

69
app/locale/zh_CN.json Normal file
View File

@@ -0,0 +1,69 @@
{
"Connecting...": "链接中...",
"Disconnecting...": "正在中断连接...",
"Reconnecting...": "重新链接中...",
"Internal error": "内部错误",
"Must set host": "请提供主机名",
"Connected (encrypted) to ": "已加密链接到",
"Connected (unencrypted) to ": "未加密链接到",
"Something went wrong, connection is closed": "发生错误,链接已关闭",
"Failed to connect to server": "无法链接到服务器",
"Disconnected": "链接已中断",
"New connection has been rejected with reason: ": "链接被拒绝,原因:",
"New connection has been rejected": "链接被拒绝",
"Password is required": "请提供密码",
"noVNC encountered an error:": "noVNC 遇到一个错误:",
"Hide/Show the control bar": "显示/隐藏控制列",
"Move/Drag Viewport": "拖放显示范围",
"viewport drag": "显示范围拖放",
"Active Mouse Button": "启动鼠标按鍵",
"No mousebutton": "禁用鼠标按鍵",
"Left mousebutton": "鼠标左鍵",
"Middle mousebutton": "鼠标中鍵",
"Right mousebutton": "鼠标右鍵",
"Keyboard": "键盘",
"Show Keyboard": "显示键盘",
"Extra keys": "额外按键",
"Show Extra Keys": "显示额外按键",
"Ctrl": "Ctrl",
"Toggle Ctrl": "切换 Ctrl",
"Alt": "Alt",
"Toggle Alt": "切换 Alt",
"Send Tab": "发送 Tab 键",
"Tab": "Tab",
"Esc": "Esc",
"Send Escape": "发送 Escape 键",
"Ctrl+Alt+Del": "Ctrl-Alt-Del",
"Send Ctrl-Alt-Del": "发送 Ctrl-Alt-Del 键",
"Shutdown/Reboot": "关机/重新启动",
"Shutdown/Reboot...": "关机/重新启动...",
"Power": "电源",
"Shutdown": "关机",
"Reboot": "重新启动",
"Reset": "重置",
"Clipboard": "剪贴板",
"Clear": "清除",
"Fullscreen": "全屏幕",
"Settings": "设置",
"Shared Mode": "分享模式",
"View Only": "仅检视",
"Clip to Window": "限制/裁切窗口大小",
"Scaling Mode:": "缩放模式:",
"None": "无",
"Local Scaling": "本地缩放",
"Remote Resizing": "远程调整大小",
"Advanced": "高级",
"Repeater ID:": "中继站 ID",
"WebSocket": "WebSocket",
"Encrypt": "加密",
"Host:": "主机:",
"Port:": "端口:",
"Path:": "路径:",
"Automatic Reconnect": "自动重新链接",
"Reconnect Delay (ms):": "重新链接间隔 (ms)",
"Logging:": "日志级别:",
"Disconnect": "终端链接",
"Connect": "链接",
"Password:": "密码:",
"Cancel": "取消"
}

View File

@@ -1,6 +1,6 @@
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2012 Joel Martin
* Copyright (C) 2018 The noVNC Authors
* Licensed under MPL 2.0 (see LICENSE.txt)
*
* See README.md for usage and integration instructions.
@@ -10,36 +10,35 @@
* Localization Utilities
*/
export function Localizer() {
// Currently configured language
this.language = 'en';
export class Localizer {
constructor() {
// Currently configured language
this.language = 'en';
// Current dictionary of translations
this.dictionary = undefined;
}
// Current dictionary of translations
this.dictionary = undefined;
}
Localizer.prototype = {
// Configure suitable language based on user preferences
setup: function (supportedLanguages) {
var userLanguages;
setup(supportedLanguages) {
this.language = 'en'; // Default: US English
/*
* Navigator.languages only available in Chrome (32+) and FireFox (32+)
* Fall back to navigator.language for other browsers
*/
let userLanguages;
if (typeof window.navigator.languages == 'object') {
userLanguages = window.navigator.languages;
} else {
userLanguages = [navigator.language || navigator.userLanguage];
}
for (var i = 0;i < userLanguages.length;i++) {
var userLang = userLanguages[i];
userLang = userLang.toLowerCase();
userLang = userLang.replace("_", "-");
userLang = userLang.split("-");
for (let i = 0;i < userLanguages.length;i++) {
const userLang = userLanguages[i]
.toLowerCase()
.replace("_", "-")
.split("-");
// Built-in default?
if ((userLang[0] === 'en') &&
@@ -48,66 +47,69 @@ Localizer.prototype = {
}
// First pass: perfect match
for (var j = 0;j < supportedLanguages.length;j++) {
var supLang = supportedLanguages[j];
supLang = supLang.toLowerCase();
supLang = supLang.replace("_", "-");
supLang = supLang.split("-");
for (let j = 0; j < supportedLanguages.length; j++) {
const supLang = supportedLanguages[j]
.toLowerCase()
.replace("_", "-")
.split("-");
if (userLang[0] !== supLang[0])
if (userLang[0] !== supLang[0]) {
continue;
if (userLang[1] !== supLang[1])
}
if (userLang[1] !== supLang[1]) {
continue;
}
this.language = supportedLanguages[j];
return;
}
// Second pass: fallback
for (var j = 0;j < supportedLanguages.length;j++) {
supLang = supportedLanguages[j];
supLang = supLang.toLowerCase();
supLang = supLang.replace("_", "-");
supLang = supLang.split("-");
for (let j = 0;j < supportedLanguages.length;j++) {
const supLang = supportedLanguages[j]
.toLowerCase()
.replace("_", "-")
.split("-");
if (userLang[0] !== supLang[0])
if (userLang[0] !== supLang[0]) {
continue;
if (supLang[1] !== undefined)
}
if (supLang[1] !== undefined) {
continue;
}
this.language = supportedLanguages[j];
return;
}
}
},
}
// Retrieve localised text
get: function (id) {
get(id) {
if (typeof this.dictionary !== 'undefined' && this.dictionary[id]) {
return this.dictionary[id];
} else {
return id;
}
},
}
// Traverses the DOM and translates relevant fields
// See https://html.spec.whatwg.org/multipage/dom.html#attr-translate
translateDOM: function () {
var self = this;
translateDOM() {
const self = this;
function process(elem, enabled) {
function isAnyOf(searchElement, items) {
return items.indexOf(searchElement) !== -1;
}
function translateAttribute(elem, attr) {
var str = elem.getAttribute(attr);
str = self.get(str);
const str = self.get(elem.getAttribute(attr));
elem.setAttribute(attr, str);
}
function translateTextNode(node) {
var str = node.data.trim();
str = self.get(str);
const str = self.get(node.data.trim());
node.data = str;
}
@@ -134,7 +136,7 @@ Localizer.prototype = {
}
if (elem.hasAttribute("label") &&
isAnyOf(elem.tagName, ["MENUITEM", "MENU", "OPTGROUP",
"OPTION", "TRACK"])) {
"OPTION", "TRACK"])) {
translateAttribute(elem, "label");
}
// FIXME: Should update "lang"
@@ -152,8 +154,8 @@ Localizer.prototype = {
}
}
for (var i = 0;i < elem.childNodes.length;i++) {
var node = elem.childNodes[i];
for (let i = 0; i < elem.childNodes.length; i++) {
const node = elem.childNodes[i];
if (node.nodeType === node.ELEMENT_NODE) {
process(node, enabled);
} else if (node.nodeType === node.TEXT_NODE && enabled) {
@@ -163,8 +165,8 @@ Localizer.prototype = {
}
process(document.body, true);
},
};
}
}
export var l10n = new Localizer();
export const l10n = new Localizer();
export default l10n.get.bind(l10n);

View File

@@ -1,8 +1,6 @@
/*
* noVNC base CSS
* Copyright (C) 2012 Joel Martin
* Copyright (C) 2016 Samuel Mannehed for Cendio AB
* Copyright (C) 2016 Pierre Ossman for Cendio AB
* Copyright (C) 2019 The noVNC Authors
* noVNC is licensed under the MPL 2.0 (see LICENSE.txt)
* This file is licensed under the 2-Clause BSD license (see LICENSE.txt).
*/
@@ -85,8 +83,20 @@ html {
* ----------------------------------------
*/
input[type=input], input[type=password], input[type=number],
input:not([type]), textarea {
input:not([type]),
input[type=date],
input[type=datetime-local],
input[type=email],
input[type=month],
input[type=number],
input[type=password],
input[type=search],
input[type=tel],
input[type=text],
input[type=time],
input[type=url],
input[type=week],
textarea {
/* Disable default rendering */
-webkit-appearance: none;
-moz-appearance: none;
@@ -100,7 +110,11 @@ input:not([type]), textarea {
background: linear-gradient(to top, rgb(255, 255, 255) 80%, rgb(240, 240, 240));
}
input[type=button], input[type=submit], select {
input[type=button],
input[type=color],
input[type=reset],
input[type=submit],
select {
/* Disable default rendering */
-webkit-appearance: none;
-moz-appearance: none;
@@ -118,7 +132,10 @@ input[type=button], input[type=submit], select {
vertical-align: middle;
}
input[type=button], input[type=submit] {
input[type=button],
input[type=color],
input[type=reset],
input[type=submit] {
padding-left: 20px;
padding-right: 20px;
}
@@ -128,35 +145,72 @@ option {
background: white;
}
input[type=input]:focus, input[type=password]:focus,
input:not([type]):focus, input[type=button]:focus,
input:not([type]):focus,
input[type=button]:focus,
input[type=color]:focus,
input[type=date]:focus,
input[type=datetime-local]:focus,
input[type=email]:focus,
input[type=month]:focus,
input[type=number]:focus,
input[type=password]:focus,
input[type=reset]:focus,
input[type=search]:focus,
input[type=submit]:focus,
textarea:focus, select:focus {
input[type=tel]:focus,
input[type=text]:focus,
input[type=time]:focus,
input[type=url]:focus,
input[type=week]:focus,
select:focus,
textarea:focus {
box-shadow: 0px 0px 3px rgba(74, 144, 217, 0.5);
border-color: rgb(74, 144, 217);
outline: none;
}
input[type=button]::-moz-focus-inner,
input[type=color]::-moz-focus-inner,
input[type=reset]::-moz-focus-inner,
input[type=submit]::-moz-focus-inner {
border: none;
}
input[type=input]:disabled, input[type=password]:disabled,
input:not([type]):disabled, input[type=button]:disabled,
input[type=submit]:disabled, input[type=number]:disabled,
textarea:disabled, select:disabled {
input:not([type]):disabled,
input[type=button]:disabled,
input[type=color]:disabled,
input[type=date]:disabled,
input[type=datetime-local]:disabled,
input[type=email]:disabled,
input[type=month]:disabled,
input[type=number]:disabled,
input[type=password]:disabled,
input[type=reset]:disabled,
input[type=search]:disabled,
input[type=submit]:disabled,
input[type=tel]:disabled,
input[type=text]:disabled,
input[type=time]:disabled,
input[type=url]:disabled,
input[type=week]:disabled,
select:disabled,
textarea:disabled {
color: rgb(128, 128, 128);
background: rgb(240, 240, 240);
}
input[type=button]:active, input[type=submit]:active,
input[type=button]:active,
input[type=color]:active,
input[type=reset]:active,
input[type=submit]:active,
select:active {
border-bottom-width: 1px;
margin-top: 3px;
}
:root:not(.noVNC_touch) input[type=button]:hover:not(:disabled),
:root:not(.noVNC_touch) input[type=color]:hover:not(:disabled),
:root:not(.noVNC_touch) input[type=reset]:hover:not(:disabled),
:root:not(.noVNC_touch) input[type=submit]:hover:not(:disabled),
:root:not(.noVNC_touch) select:hover:not(:disabled) {
background: linear-gradient(to top, rgb(255, 255, 255), rgb(250, 250, 250));
@@ -581,7 +635,7 @@ select:active {
}
/* Extra manual keys */
:root:not(.noVNC_connected) #noVNC_extra_keys {
:root:not(.noVNC_connected) #noVNC_toggle_extra_keys_button {
display: none;
}
@@ -633,6 +687,16 @@ select:active {
width: 100px;
}
/* Version */
.noVNC_version_wrapper {
font-size: small;
}
.noVNC_version {
margin-left: 1rem;
}
/* Connection Controls */
:root:not(.noVNC_connected) #noVNC_disconnect_button {
display: none;
@@ -782,19 +846,23 @@ select:active {
* ----------------------------------------
*/
#noVNC_password_dlg {
#noVNC_credentials_dlg {
position: relative;
transform: translateY(-50px);
}
#noVNC_password_dlg.noVNC_open {
#noVNC_credentials_dlg.noVNC_open {
transform: translateY(0);
}
#noVNC_password_dlg ul {
#noVNC_credentials_dlg ul {
list-style: none;
margin: 0px;
padding: 0px;
}
.noVNC_hidden {
display: none;
}
/* ----------------------------------------
* Main Area

View File

@@ -1,63 +0,0 @@
/*
* noVNC auto CSS
* Copyright (C) 2012 Joel Martin
* Copyright (C) 2017 Samuel Mannehed for Cendio AB
* noVNC is licensed under the MPL 2.0 (see LICENSE.txt)
* This file is licensed under the 2-Clause BSD license (see LICENSE.txt).
*/
body {
margin:0;
background-color:#313131;
border-bottom-right-radius: 800px 600px;
height:100%;
display: flex;
flex-direction: column;
}
html {
background-color:#494949;
height:100%;
}
#noVNC_status_bar {
width: 100%;
display:flex;
justify-content: space-between;
}
#noVNC_status {
color: #fff;
font: bold 12px Helvetica;
margin: auto;
}
.noVNC_status_normal {
background: linear-gradient(#b2bdcd 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%);
}
.noVNC_status_error {
background: linear-gradient(#c83737 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%);
}
.noVNC_status_warn {
background: linear-gradient(#b4b41e 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%);
}
.noNVC_shown {
display: inline;
}
.noVNC_hidden {
display: none;
}
#noVNC_left_dummy_elem {
flex: 1;
}
#noVNC_buttons {
padding: 1px;
flex: 1;
display: flex;
justify-content: flex-end;
}

790
app/ui.js

File diff suppressed because it is too large Load Diff

View File

@@ -1,70 +1,73 @@
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2012 Joel Martin
* Copyright (C) 2013 NTT corp.
* Copyright (C) 2019 The noVNC Authors
* Licensed under MPL 2.0 (see LICENSE.txt)
*
* See README.md for usage and integration instructions.
*/
import { init_logging as main_init_logging } from '../core/util/logging.js';
import { initLogging as mainInitLogging } from '../core/util/logging.js';
// init log level reading the logging HTTP param
export function init_logging (level) {
export function initLogging(level) {
"use strict";
if (typeof level !== "undefined") {
main_init_logging(level);
mainInitLogging(level);
} else {
var param = document.location.href.match(/logging=([A-Za-z0-9\._\-]*)/);
main_init_logging(param || undefined);
const param = document.location.href.match(/logging=([A-Za-z0-9._-]*)/);
mainInitLogging(param || undefined);
}
};
}
// Read a query string variable
export function getQueryVar (name, defVal) {
export function getQueryVar(name, defVal) {
"use strict";
var re = new RegExp('.*[?&]' + name + '=([^&#]*)'),
const re = new RegExp('.*[?&]' + name + '=([^&#]*)'),
match = document.location.href.match(re);
if (typeof defVal === 'undefined') { defVal = null; }
if (match) {
return decodeURIComponent(match[1]);
} else {
return defVal;
}
};
return defVal;
}
// Read a hash fragment variable
export function getHashVar (name, defVal) {
export function getHashVar(name, defVal) {
"use strict";
var re = new RegExp('.*[&#]' + name + '=([^&]*)'),
const re = new RegExp('.*[&#]' + name + '=([^&]*)'),
match = document.location.hash.match(re);
if (typeof defVal === 'undefined') { defVal = null; }
if (match) {
return decodeURIComponent(match[1]);
} else {
return defVal;
}
};
return defVal;
}
// Read a variable from the fragment or the query string
// Fragment takes precedence
export function getConfigVar (name, defVal) {
export function getConfigVar(name, defVal) {
"use strict";
var val = getHashVar(name);
const val = getHashVar(name);
if (val === null) {
val = getQueryVar(name, defVal);
return getQueryVar(name, defVal);
}
return val;
};
}
/*
* Cookie handling. Dervied from: http://www.quirksmode.org/js/cookies.html
*/
// No days means only for this browser session
export function createCookie (name, value, days) {
export function createCookie(name, value, days) {
"use strict";
var date, expires;
let date, expires;
if (days) {
date = new Date();
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
@@ -73,116 +76,124 @@ export function createCookie (name, value, days) {
expires = "";
}
var secure;
let secure;
if (document.location.protocol === "https:") {
secure = "; secure";
} else {
secure = "";
}
document.cookie = name + "=" + value + expires + "; path=/" + secure;
};
}
export function readCookie (name, defaultValue) {
export function readCookie(name, defaultValue) {
"use strict";
var nameEQ = name + "=",
ca = document.cookie.split(';');
const nameEQ = name + "=";
const ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i += 1) {
var c = ca[i];
while (c.charAt(0) === ' ') { c = c.substring(1, c.length); }
if (c.indexOf(nameEQ) === 0) { return c.substring(nameEQ.length, c.length); }
for (let i = 0; i < ca.length; i += 1) {
let c = ca[i];
while (c.charAt(0) === ' ') {
c = c.substring(1, c.length);
}
if (c.indexOf(nameEQ) === 0) {
return c.substring(nameEQ.length, c.length);
}
}
return (typeof defaultValue !== 'undefined') ? defaultValue : null;
};
export function eraseCookie (name) {
return (typeof defaultValue !== 'undefined') ? defaultValue : null;
}
export function eraseCookie(name) {
"use strict";
createCookie(name, "", -1);
};
}
/*
* Setting handling.
*/
var settings = {};
let settings = {};
export function initSettings (callback /*, ...callbackArgs */) {
"use strict";
var callbackArgs = Array.prototype.slice.call(arguments, 1);
if (window.chrome && window.chrome.storage) {
window.chrome.storage.sync.get(function (cfg) {
settings = cfg;
if (callback) {
callback.apply(this, callbackArgs);
}
});
} else {
// No-op
if (callback) {
callback.apply(this, callbackArgs);
}
export function initSettings() {
if (!window.chrome || !window.chrome.storage) {
settings = {};
return Promise.resolve();
}
};
return new Promise(resolve => window.chrome.storage.sync.get(resolve))
.then((cfg) => { settings = cfg; });
}
// Update the settings cache, but do not write to permanent storage
export function setSetting(name, value) {
settings[name] = value;
}
// No days means only for this browser session
export function writeSetting (name, value) {
export function writeSetting(name, value) {
"use strict";
if (settings[name] === value) return;
settings[name] = value;
if (window.chrome && window.chrome.storage) {
if (settings[name] !== value) {
settings[name] = value;
window.chrome.storage.sync.set(settings);
}
window.chrome.storage.sync.set(settings);
} else {
localStorage.setItem(name, value);
}
};
}
export function readSetting (name, defaultValue) {
export function readSetting(name, defaultValue) {
"use strict";
var value;
if (window.chrome && window.chrome.storage) {
let value;
if ((name in settings) || (window.chrome && window.chrome.storage)) {
value = settings[name];
} else {
value = localStorage.getItem(name);
settings[name] = value;
}
if (typeof value === "undefined") {
value = null;
}
if (value === null && typeof defaultValue !== "undefined") {
return defaultValue;
} else {
return value;
}
};
export function eraseSetting (name) {
return value;
}
export function eraseSetting(name) {
"use strict";
// Deleting here means that next time the setting is read when using local
// storage, it will be pulled from local storage again.
// If the setting in local storage is changed (e.g. in another tab)
// between this delete and the next read, it could lead to an unexpected
// value change.
delete settings[name];
if (window.chrome && window.chrome.storage) {
window.chrome.storage.sync.remove(name);
delete settings[name];
} else {
localStorage.removeItem(name);
}
};
}
export function injectParamIfMissing (path, param, value) {
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;
var elem = document.createElement('a');
const elem = document.createElement('a');
elem.href = path;
var param_eq = encodeURIComponent(param) + "=";
var query;
const paramEq = encodeURIComponent(param) + "=";
let query;
if (elem.search) {
query = elem.search.slice(1).split('&');
} else {
query = [];
}
if (!query.some(function (v) { return v.startsWith(param_eq); })) {
query.push(param_eq + encodeURIComponent(value));
if (!query.some(v => v.startsWith(paramEq))) {
query.push(paramEq + encodeURIComponent(value));
elem.search = "?" + query.join("&");
}
@@ -190,41 +201,39 @@ export function injectParamIfMissing (path, param, value) {
// in the elem.pathname string. Handle that case gracefully.
if (elem.pathname.charAt(0) == "/") {
return elem.pathname.slice(1) + elem.search + elem.hash;
} else {
return elem.pathname + 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, resolve, reject) {
// NB: IE11 doesn't support JSON as a responseType
var req = new XMLHttpRequest();
req.open('GET', path);
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 = function () {
if (req.status === 200) {
try {
var resObj = JSON.parse(req.responseText);
} catch (err) {
reject(err);
return;
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));
}
resolve(resObj);
} else {
reject(new Error("XHR got non-200 status while trying to load '" + path + "': " + req.status));
}
};
};
req.onerror = function (evt) {
reject(new Error("XHR encountered an error while trying to load '" + path + "': " + evt.message));
};
req.onerror = evt => reject(new Error("XHR encountered an error while trying to load '" + path + "': " + evt.message));
req.ontimeout = function (evt) {
reject(new Error("XHR timed out while trying to load '" + path + "'"));
};
req.ontimeout = evt => reject(new Error("XHR timed out while trying to load '" + path + "'"));
req.send();
req.send();
});
}

View File

@@ -8,45 +8,43 @@ import * as Log from './util/logging.js';
export default {
/* Convert data (an array of integers) to a Base64 string. */
toBase64Table : 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='.split(''),
base64Pad : '=',
toBase64Table: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='.split(''),
base64Pad: '=',
encode: function (data) {
encode(data) {
"use strict";
var result = '';
var toBase64Table = this.toBase64Table;
var length = data.length;
var lengthpad = (length % 3);
let result = '';
const length = data.length;
const lengthpad = (length % 3);
// Convert every three bytes to 4 ascii characters.
for (var i = 0; i < (length - 2); i += 3) {
result += toBase64Table[data[i] >> 2];
result += toBase64Table[((data[i] & 0x03) << 4) + (data[i + 1] >> 4)];
result += toBase64Table[((data[i + 1] & 0x0f) << 2) + (data[i + 2] >> 6)];
result += toBase64Table[data[i + 2] & 0x3f];
for (let i = 0; i < (length - 2); i += 3) {
result += this.toBase64Table[data[i] >> 2];
result += this.toBase64Table[((data[i] & 0x03) << 4) + (data[i + 1] >> 4)];
result += this.toBase64Table[((data[i + 1] & 0x0f) << 2) + (data[i + 2] >> 6)];
result += this.toBase64Table[data[i + 2] & 0x3f];
}
// Convert the remaining 1 or 2 bytes, pad out to 4 characters.
var j = 0;
const j = length - lengthpad;
if (lengthpad === 2) {
j = length - lengthpad;
result += toBase64Table[data[j] >> 2];
result += toBase64Table[((data[j] & 0x03) << 4) + (data[j + 1] >> 4)];
result += toBase64Table[(data[j + 1] & 0x0f) << 2];
result += toBase64Table[64];
result += this.toBase64Table[data[j] >> 2];
result += this.toBase64Table[((data[j] & 0x03) << 4) + (data[j + 1] >> 4)];
result += this.toBase64Table[(data[j + 1] & 0x0f) << 2];
result += this.toBase64Table[64];
} else if (lengthpad === 1) {
j = length - lengthpad;
result += toBase64Table[data[j] >> 2];
result += toBase64Table[(data[j] & 0x03) << 4];
result += toBase64Table[64];
result += toBase64Table[64];
result += this.toBase64Table[data[j] >> 2];
result += this.toBase64Table[(data[j] & 0x03) << 4];
result += this.toBase64Table[64];
result += this.toBase64Table[64];
}
return result;
},
/* Convert Base64 data to a string */
toBinaryTable : [
/* eslint-disable comma-spacing */
toBinaryTable: [
-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, -1,-1,-1,63,
@@ -56,27 +54,23 @@ export default {
-1,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,
41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1
],
/* eslint-enable comma-spacing */
decode: function (data, offset) {
"use strict";
offset = typeof(offset) !== 'undefined' ? offset : 0;
var toBinaryTable = this.toBinaryTable;
var base64Pad = this.base64Pad;
var result, result_length;
var leftbits = 0; // number of bits decoded, but yet to be appended
var leftdata = 0; // bits decoded, but yet to be appended
var data_length = data.indexOf('=') - offset;
if (data_length < 0) { data_length = data.length - offset; }
decode(data, offset = 0) {
let dataLength = data.indexOf('=') - offset;
if (dataLength < 0) { dataLength = data.length - offset; }
/* Every four characters is 3 resulting numbers */
result_length = (data_length >> 2) * 3 + Math.floor((data_length % 4) / 1.5);
result = new Array(result_length);
const resultLength = (dataLength >> 2) * 3 + Math.floor((dataLength % 4) / 1.5);
const result = new Array(resultLength);
// Convert one by one.
for (var idx = 0, i = offset; i < data.length; i++) {
var c = toBinaryTable[data.charCodeAt(i) & 0x7f];
var padding = (data.charAt(i) === base64Pad);
let leftbits = 0; // number of bits decoded, but yet to be appended
let leftdata = 0; // bits decoded, but yet to be appended
for (let idx = 0, i = offset; i < data.length; i++) {
const c = this.toBinaryTable[data.charCodeAt(i) & 0x7f];
const padding = (data.charAt(i) === this.base64Pad);
// Skip illegal characters and whitespace
if (c === -1) {
Log.Error("Illegal character code " + data.charCodeAt(i) + " at position " + i);
@@ -100,7 +94,7 @@ export default {
// If there are any bits left, the base64 string was corrupted
if (leftbits) {
err = new Error('Corrupted base64 string');
const err = new Error('Corrupted base64 string');
err.name = 'Base64-Error';
throw err;
}

22
core/decoders/copyrect.js Normal file
View File

@@ -0,0 +1,22 @@
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2019 The noVNC Authors
* Licensed under MPL 2.0 (see LICENSE.txt)
*
* See README.md for usage and integration instructions.
*
*/
export default class CopyRectDecoder {
decodeRect(x, y, width, height, sock, display, depth) {
if (sock.rQwait("COPYRECT", 4)) {
return false;
}
let deltaX = sock.rQshift16();
let deltaY = sock.rQshift16();
display.copyImage(deltaX, deltaY, x, y, width, height);
return true;
}
}

137
core/decoders/hextile.js Normal file
View File

@@ -0,0 +1,137 @@
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2019 The noVNC Authors
* Licensed under MPL 2.0 (see LICENSE.txt)
*
* See README.md for usage and integration instructions.
*
*/
import * as Log from '../util/logging.js';
export default class HextileDecoder {
constructor() {
this._tiles = 0;
this._lastsubencoding = 0;
}
decodeRect(x, y, width, height, sock, display, depth) {
if (this._tiles === 0) {
this._tilesX = Math.ceil(width / 16);
this._tilesY = Math.ceil(height / 16);
this._totalTiles = this._tilesX * this._tilesY;
this._tiles = this._totalTiles;
}
while (this._tiles > 0) {
let bytes = 1;
if (sock.rQwait("HEXTILE", bytes)) {
return false;
}
let rQ = sock.rQ;
let rQi = sock.rQi;
let subencoding = rQ[rQi]; // Peek
if (subencoding > 30) { // Raw
throw new Error("Illegal hextile subencoding (subencoding: " +
subencoding + ")");
}
const currTile = this._totalTiles - this._tiles;
const tileX = currTile % this._tilesX;
const tileY = Math.floor(currTile / this._tilesX);
const tx = x + tileX * 16;
const ty = y + tileY * 16;
const tw = Math.min(16, (x + width) - tx);
const th = Math.min(16, (y + height) - ty);
// Figure out how much we are expecting
if (subencoding & 0x01) { // Raw
bytes += tw * th * 4;
} else {
if (subencoding & 0x02) { // Background
bytes += 4;
}
if (subencoding & 0x04) { // Foreground
bytes += 4;
}
if (subencoding & 0x08) { // AnySubrects
bytes++; // Since we aren't shifting it off
if (sock.rQwait("HEXTILE", bytes)) {
return false;
}
let subrects = rQ[rQi + bytes - 1]; // Peek
if (subencoding & 0x10) { // SubrectsColoured
bytes += subrects * (4 + 2);
} else {
bytes += subrects * 2;
}
}
}
if (sock.rQwait("HEXTILE", bytes)) {
return false;
}
// We know the encoding and have a whole tile
rQi++;
if (subencoding === 0) {
if (this._lastsubencoding & 0x01) {
// Weird: ignore blanks are RAW
Log.Debug(" Ignoring blank after RAW");
} else {
display.fillRect(tx, ty, tw, th, this._background);
}
} else if (subencoding & 0x01) { // Raw
display.blitImage(tx, ty, tw, th, rQ, rQi);
rQi += bytes - 1;
} else {
if (subencoding & 0x02) { // Background
this._background = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]];
rQi += 4;
}
if (subencoding & 0x04) { // Foreground
this._foreground = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]];
rQi += 4;
}
display.startTile(tx, ty, tw, th, this._background);
if (subencoding & 0x08) { // AnySubrects
let subrects = rQ[rQi];
rQi++;
for (let s = 0; s < subrects; s++) {
let color;
if (subencoding & 0x10) { // SubrectsColoured
color = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]];
rQi += 4;
} else {
color = this._foreground;
}
const xy = rQ[rQi];
rQi++;
const sx = (xy >> 4);
const sy = (xy & 0x0f);
const wh = rQ[rQi];
rQi++;
const sw = (wh >> 4) + 1;
const sh = (wh & 0x0f) + 1;
display.subTile(sx, sy, sw, sh, color);
}
}
display.finishTile();
}
sock.rQi = rQi;
this._lastsubencoding = subencoding;
this._tiles--;
}
return true;
}
}

56
core/decoders/raw.js Normal file
View File

@@ -0,0 +1,56 @@
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2019 The noVNC Authors
* Licensed under MPL 2.0 (see LICENSE.txt)
*
* See README.md for usage and integration instructions.
*
*/
export default class RawDecoder {
constructor() {
this._lines = 0;
}
decodeRect(x, y, width, height, sock, display, depth) {
if (this._lines === 0) {
this._lines = height;
}
const pixelSize = depth == 8 ? 1 : 4;
const bytesPerLine = width * pixelSize;
if (sock.rQwait("RAW", bytesPerLine)) {
return false;
}
const curY = y + (height - this._lines);
const currHeight = Math.min(this._lines,
Math.floor(sock.rQlen / bytesPerLine));
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;
}
data = newdata;
index = 0;
}
display.blitImage(x, curY, width, currHeight, data, index);
sock.rQskipBytes(currHeight * bytesPerLine);
this._lines -= currHeight;
if (this._lines > 0) {
return false;
}
return true;
}
}

44
core/decoders/rre.js Normal file
View File

@@ -0,0 +1,44 @@
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2019 The noVNC Authors
* Licensed under MPL 2.0 (see LICENSE.txt)
*
* See README.md for usage and integration instructions.
*
*/
export default class RREDecoder {
constructor() {
this._subrects = 0;
}
decodeRect(x, y, width, height, sock, display, depth) {
if (this._subrects === 0) {
if (sock.rQwait("RRE", 4 + 4)) {
return false;
}
this._subrects = sock.rQshift32();
let color = sock.rQshiftBytes(4); // Background
display.fillRect(x, y, width, height, color);
}
while (this._subrects > 0) {
if (sock.rQwait("RRE", 4 + 8)) {
return false;
}
let color = sock.rQshiftBytes(4);
let sx = sock.rQshift16();
let sy = sock.rQshift16();
let swidth = sock.rQshift16();
let sheight = sock.rQshift16();
display.fillRect(x + sx, y + sy, swidth, sheight, color);
this._subrects--;
}
return true;
}
}

315
core/decoders/tight.js Normal file
View File

@@ -0,0 +1,315 @@
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2019 The noVNC Authors
* (c) 2012 Michael Tinglof, Joe Balaz, Les Piech (Mercuri.ca)
* Licensed under MPL 2.0 (see LICENSE.txt)
*
* See README.md for usage and integration instructions.
*
*/
import * as Log from '../util/logging.js';
import Inflator from "../inflator.js";
export default class TightDecoder {
constructor() {
this._ctl = null;
this._filter = null;
this._numColors = 0;
this._palette = new Uint8Array(1024); // 256 * 4 (max palette size * max bytes-per-pixel)
this._len = 0;
this._zlibs = [];
for (let i = 0; i < 4; i++) {
this._zlibs[i] = new Inflator();
}
}
decodeRect(x, y, width, height, sock, display, depth) {
if (this._ctl === null) {
if (sock.rQwait("TIGHT compression-control", 1)) {
return false;
}
this._ctl = sock.rQshift8();
// Reset streams if the server requests it
for (let i = 0; i < 4; i++) {
if ((this._ctl >> i) & 1) {
this._zlibs[i].reset();
Log.Info("Reset zlib stream " + i);
}
}
// Figure out filter
this._ctl = this._ctl >> 4;
}
let ret;
if (this._ctl === 0x08) {
ret = this._fillRect(x, y, width, height,
sock, display, depth);
} else if (this._ctl === 0x09) {
ret = this._jpegRect(x, y, width, height,
sock, display, depth);
} else if (this._ctl === 0x0A) {
ret = this._pngRect(x, y, width, height,
sock, display, depth);
} else if ((this._ctl & 0x80) == 0) {
ret = this._basicRect(this._ctl, x, y, width, height,
sock, display, depth);
} else {
throw new Error("Illegal tight compression received (ctl: " +
this._ctl + ")");
}
if (ret) {
this._ctl = null;
}
return ret;
}
_fillRect(x, y, width, height, sock, display, depth) {
if (sock.rQwait("TIGHT", 3)) {
return false;
}
const rQi = sock.rQi;
const rQ = sock.rQ;
display.fillRect(x, y, width, height,
[rQ[rQi + 2], rQ[rQi + 1], rQ[rQi]], false);
sock.rQskipBytes(3);
return true;
}
_jpegRect(x, y, width, height, sock, display, depth) {
let data = this._readData(sock);
if (data === null) {
return false;
}
display.imageRect(x, y, width, height, "image/jpeg", data);
return true;
}
_pngRect(x, y, width, height, sock, display, depth) {
throw new Error("PNG received in standard Tight rect");
}
_basicRect(ctl, x, y, width, height, sock, display, depth) {
if (this._filter === null) {
if (ctl & 0x4) {
if (sock.rQwait("TIGHT", 1)) {
return false;
}
this._filter = sock.rQshift8();
} else {
// Implicit CopyFilter
this._filter = 0;
}
}
let streamId = ctl & 0x3;
let ret;
switch (this._filter) {
case 0: // CopyFilter
ret = this._copyFilter(streamId, x, y, width, height,
sock, display, depth);
break;
case 1: // PaletteFilter
ret = this._paletteFilter(streamId, x, y, width, height,
sock, display, depth);
break;
case 2: // GradientFilter
ret = this._gradientFilter(streamId, x, y, width, height,
sock, display, depth);
break;
default:
throw new Error("Illegal tight filter received (ctl: " +
this._filter + ")");
}
if (ret) {
this._filter = null;
}
return ret;
}
_copyFilter(streamId, x, y, width, height, sock, display, depth) {
const uncompressedSize = width * height * 3;
let data;
if (uncompressedSize < 12) {
if (sock.rQwait("TIGHT", uncompressedSize)) {
return false;
}
data = sock.rQshiftBytes(uncompressedSize);
} else {
data = this._readData(sock);
if (data === null) {
return false;
}
this._zlibs[streamId].setInput(data);
data = this._zlibs[streamId].inflate(uncompressedSize);
this._zlibs[streamId].setInput(null);
}
display.blitRgbImage(x, y, width, height, data, 0, false);
return true;
}
_paletteFilter(streamId, x, y, width, height, sock, display, depth) {
if (this._numColors === 0) {
if (sock.rQwait("TIGHT palette", 1)) {
return false;
}
const numColors = sock.rQpeek8() + 1;
const paletteSize = numColors * 3;
if (sock.rQwait("TIGHT palette", 1 + paletteSize)) {
return false;
}
this._numColors = numColors;
sock.rQskipBytes(1);
sock.rQshiftTo(this._palette, paletteSize);
}
const bpp = (this._numColors <= 2) ? 1 : 8;
const rowSize = Math.floor((width * bpp + 7) / 8);
const uncompressedSize = rowSize * height;
let data;
if (uncompressedSize < 12) {
if (sock.rQwait("TIGHT", uncompressedSize)) {
return false;
}
data = sock.rQshiftBytes(uncompressedSize);
} else {
data = this._readData(sock);
if (data === null) {
return false;
}
this._zlibs[streamId].setInput(data);
data = this._zlibs[streamId].inflate(uncompressedSize);
this._zlibs[streamId].setInput(null);
}
// Convert indexed (palette based) image data to RGB
if (this._numColors == 2) {
this._monoRect(x, y, width, height, data, this._palette, display);
} else {
this._paletteRect(x, y, width, height, data, this._palette, display);
}
this._numColors = 0;
return true;
}
_monoRect(x, y, width, height, data, palette, display) {
// Convert indexed (palette based) image data to RGB
// TODO: reduce number of calculations inside loop
const dest = this._getScratchBuffer(width * height * 4);
const w = Math.floor((width + 7) / 8);
const w1 = Math.floor(width / 8);
for (let y = 0; y < height; y++) {
let dp, sp, x;
for (x = 0; x < w1; x++) {
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 + 1] = palette[sp + 1];
dest[dp + 2] = palette[sp + 2];
dest[dp + 3] = 255;
}
}
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 + 1] = palette[sp + 1];
dest[dp + 2] = palette[sp + 2];
dest[dp + 3] = 255;
}
}
display.blitRgbxImage(x, y, width, height, dest, 0, false);
}
_paletteRect(x, y, width, height, data, palette, display) {
// Convert indexed (palette based) image data to RGB
const dest = this._getScratchBuffer(width * height * 4);
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 + 1] = palette[sp + 1];
dest[i + 2] = palette[sp + 2];
dest[i + 3] = 255;
}
display.blitRgbxImage(x, y, width, height, dest, 0, false);
}
_gradientFilter(streamId, x, y, width, height, sock, display, depth) {
throw new Error("Gradient filter not implemented");
}
_readData(sock) {
if (this._len === 0) {
if (sock.rQwait("TIGHT", 3)) {
return null;
}
let byte;
byte = sock.rQshift8();
this._len = byte & 0x7f;
if (byte & 0x80) {
byte = sock.rQshift8();
this._len |= (byte & 0x7f) << 7;
if (byte & 0x80) {
byte = sock.rQshift8();
this._len |= byte << 14;
}
}
}
if (sock.rQwait("TIGHT", this._len)) {
return null;
}
let data = sock.rQshiftBytes(this._len);
this._len = 0;
return data;
}
_getScratchBuffer(size) {
if (!this._scratchBuffer || (this._scratchBuffer.length < size)) {
this._scratchBuffer = new Uint8Array(size);
}
return this._scratchBuffer;
}
}

27
core/decoders/tightpng.js Normal file
View File

@@ -0,0 +1,27 @@
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2019 The noVNC Authors
* Licensed under MPL 2.0 (see LICENSE.txt)
*
* See README.md for usage and integration instructions.
*
*/
import TightDecoder from './tight.js';
export default class TightPNGDecoder extends TightDecoder {
_pngRect(x, y, width, height, sock, display, depth) {
let data = this._readData(sock);
if (data === null) {
return false;
}
display.imageRect(x, y, width, height, "image/png", data);
return true;
}
_basicRect(ctl, x, y, width, height, sock, display, depth) {
throw new Error("BasicCompression received in TightPNG rect");
}
}

85
core/deflator.js Normal file
View File

@@ -0,0 +1,85 @@
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2020 The noVNC Authors
* Licensed under MPL 2.0 (see LICENSE.txt)
*
* See README.md for usage and integration instructions.
*/
import { deflateInit, deflate } from "../vendor/pako/lib/zlib/deflate.js";
import { Z_FULL_FLUSH } from "../vendor/pako/lib/zlib/deflate.js";
import ZStream from "../vendor/pako/lib/zlib/zstream.js";
export default class Deflator {
constructor() {
this.strm = new ZStream();
this.chunkSize = 1024 * 10 * 10;
this.outputBuffer = new Uint8Array(this.chunkSize);
this.windowBits = 5;
deflateInit(this.strm, this.windowBits);
}
deflate(inData) {
/* eslint-disable camelcase */
this.strm.input = inData;
this.strm.avail_in = this.strm.input.length;
this.strm.next_in = 0;
this.strm.output = this.outputBuffer;
this.strm.avail_out = this.chunkSize;
this.strm.next_out = 0;
/* eslint-enable camelcase */
let lastRet = deflate(this.strm, Z_FULL_FLUSH);
let outData = new Uint8Array(this.strm.output.buffer, 0, this.strm.next_out);
if (lastRet < 0) {
throw new Error("zlib deflate failed");
}
if (this.strm.avail_in > 0) {
// Read chunks until done
let chunks = [outData];
let totalLen = outData.length;
do {
/* eslint-disable camelcase */
this.strm.output = new Uint8Array(this.chunkSize);
this.strm.next_out = 0;
this.strm.avail_out = this.chunkSize;
/* eslint-enable camelcase */
lastRet = deflate(this.strm, Z_FULL_FLUSH);
if (lastRet < 0) {
throw new Error("zlib deflate failed");
}
let chunk = new Uint8Array(this.strm.output.buffer, 0, this.strm.next_out);
totalLen += chunk.length;
chunks.push(chunk);
} while (this.strm.avail_in > 0);
// Combine chunks into a single data
let newData = new Uint8Array(totalLen);
let offset = 0;
for (let i = 0; i < chunks.length; i++) {
newData.set(chunks[i], offset);
offset += chunks[i].length;
}
outData = newData;
}
/* eslint-disable camelcase */
this.strm.input = null;
this.strm.avail_in = 0;
this.strm.next_in = 0;
/* eslint-enable camelcase */
return outData;
}
}

View File

@@ -75,84 +75,83 @@
* fine Java utilities: http://www.acme.com/java/
*/
export default function DES(passwd) {
"use strict";
/* eslint-disable comma-spacing */
// Tables, permutations, S-boxes, etc.
var PC2 = [13,16,10,23, 0, 4, 2,27,14, 5,20, 9,22,18,11, 3,
25, 7,15, 6,26,19,12, 1,40,51,30,36,46,54,29,39,
50,44,32,47,43,48,38,55,33,52,45,41,49,35,28,31 ],
totrot = [ 1, 2, 4, 6, 8,10,12,14,15,17,19,21,23,25,27,28],
z = 0x0, a,b,c,d,e,f, SP1,SP2,SP3,SP4,SP5,SP6,SP7,SP8,
keys = [];
// Tables, permutations, S-boxes, etc.
const PC2 = [13,16,10,23, 0, 4, 2,27,14, 5,20, 9,22,18,11, 3,
25, 7,15, 6,26,19,12, 1,40,51,30,36,46,54,29,39,
50,44,32,47,43,48,38,55,33,52,45,41,49,35,28,31 ],
totrot = [ 1, 2, 4, 6, 8,10,12,14,15,17,19,21,23,25,27,28];
a=1<<16; b=1<<24; c=a|b; d=1<<2; e=1<<10; f=d|e;
SP1 = [c|e,z|z,a|z,c|f,c|d,a|f,z|d,a|z,z|e,c|e,c|f,z|e,b|f,c|d,b|z,z|d,
z|f,b|e,b|e,a|e,a|e,c|z,c|z,b|f,a|d,b|d,b|d,a|d,z|z,z|f,a|f,b|z,
a|z,c|f,z|d,c|z,c|e,b|z,b|z,z|e,c|d,a|z,a|e,b|d,z|e,z|d,b|f,a|f,
c|f,a|d,c|z,b|f,b|d,z|f,a|f,c|e,z|f,b|e,b|e,z|z,a|d,a|e,z|z,c|d];
a=1<<20; b=1<<31; c=a|b; d=1<<5; e=1<<15; f=d|e;
SP2 = [c|f,b|e,z|e,a|f,a|z,z|d,c|d,b|f,b|d,c|f,c|e,b|z,b|e,a|z,z|d,c|d,
a|e,a|d,b|f,z|z,b|z,z|e,a|f,c|z,a|d,b|d,z|z,a|e,z|f,c|e,c|z,z|f,
z|z,a|f,c|d,a|z,b|f,c|z,c|e,z|e,c|z,b|e,z|d,c|f,a|f,z|d,z|e,b|z,
z|f,c|e,a|z,b|d,a|d,b|f,b|d,a|d,a|e,z|z,b|e,z|f,b|z,c|d,c|f,a|e];
a=1<<17; b=1<<27; c=a|b; d=1<<3; e=1<<9; f=d|e;
SP3 = [z|f,c|e,z|z,c|d,b|e,z|z,a|f,b|e,a|d,b|d,b|d,a|z,c|f,a|d,c|z,z|f,
b|z,z|d,c|e,z|e,a|e,c|z,c|d,a|f,b|f,a|e,a|z,b|f,z|d,c|f,z|e,b|z,
c|e,b|z,a|d,z|f,a|z,c|e,b|e,z|z,z|e,a|d,c|f,b|e,b|d,z|e,z|z,c|d,
b|f,a|z,b|z,c|f,z|d,a|f,a|e,b|d,c|z,b|f,z|f,c|z,a|f,z|d,c|d,a|e];
a=1<<13; b=1<<23; c=a|b; d=1<<0; e=1<<7; f=d|e;
SP4 = [c|d,a|f,a|f,z|e,c|e,b|f,b|d,a|d,z|z,c|z,c|z,c|f,z|f,z|z,b|e,b|d,
z|d,a|z,b|z,c|d,z|e,b|z,a|d,a|e,b|f,z|d,a|e,b|e,a|z,c|e,c|f,z|f,
b|e,b|d,c|z,c|f,z|f,z|z,z|z,c|z,a|e,b|e,b|f,z|d,c|d,a|f,a|f,z|e,
c|f,z|f,z|d,a|z,b|d,a|d,c|e,b|f,a|d,a|e,b|z,c|d,z|e,b|z,a|z,c|e];
a=1<<25; b=1<<30; c=a|b; d=1<<8; e=1<<19; f=d|e;
SP5 = [z|d,a|f,a|e,c|d,z|e,z|d,b|z,a|e,b|f,z|e,a|d,b|f,c|d,c|e,z|f,b|z,
a|z,b|e,b|e,z|z,b|d,c|f,c|f,a|d,c|e,b|d,z|z,c|z,a|f,a|z,c|z,z|f,
z|e,c|d,z|d,a|z,b|z,a|e,c|d,b|f,a|d,b|z,c|e,a|f,b|f,z|d,a|z,c|e,
c|f,z|f,c|z,c|f,a|e,z|z,b|e,c|z,z|f,a|d,b|d,z|e,z|z,b|e,a|f,b|d];
a=1<<22; b=1<<29; c=a|b; d=1<<4; e=1<<14; f=d|e;
SP6 = [b|d,c|z,z|e,c|f,c|z,z|d,c|f,a|z,b|e,a|f,a|z,b|d,a|d,b|e,b|z,z|f,
z|z,a|d,b|f,z|e,a|e,b|f,z|d,c|d,c|d,z|z,a|f,c|e,z|f,a|e,c|e,b|z,
b|e,z|d,c|d,a|e,c|f,a|z,z|f,b|d,a|z,b|e,b|z,z|f,b|d,c|f,a|e,c|z,
a|f,c|e,z|z,c|d,z|d,z|e,c|z,a|f,z|e,a|d,b|f,z|z,c|e,b|z,a|d,b|f];
a=1<<21; b=1<<26; c=a|b; d=1<<1; e=1<<11; f=d|e;
SP7 = [a|z,c|d,b|f,z|z,z|e,b|f,a|f,c|e,c|f,a|z,z|z,b|d,z|d,b|z,c|d,z|f,
b|e,a|f,a|d,b|e,b|d,c|z,c|e,a|d,c|z,z|e,z|f,c|f,a|e,z|d,b|z,a|e,
b|z,a|e,a|z,b|f,b|f,c|d,c|d,z|d,a|d,b|z,b|e,a|z,c|e,z|f,a|f,c|e,
z|f,b|d,c|f,c|z,a|e,z|z,z|d,c|f,z|z,a|f,c|z,z|e,b|d,b|e,z|e,a|d];
a=1<<18; b=1<<28; c=a|b; d=1<<6; e=1<<12; f=d|e;
SP8 = [b|f,z|e,a|z,c|f,b|z,b|f,z|d,b|z,a|d,c|z,c|f,a|e,c|e,a|f,z|e,z|d,
c|z,b|d,b|e,z|f,a|e,a|d,c|d,c|e,z|f,z|z,z|z,c|d,b|d,b|e,a|f,a|z,
a|f,a|z,c|e,z|e,z|d,c|d,z|e,a|f,b|e,z|d,b|d,c|z,c|d,b|z,a|z,b|f,
z|z,c|f,a|d,b|d,c|z,b|e,b|f,z|z,c|f,a|e,a|e,z|f,z|f,a|d,b|z,c|e];
const z = 0x0;
let a,b,c,d,e,f;
a=1<<16; b=1<<24; c=a|b; d=1<<2; e=1<<10; f=d|e;
const SP1 = [c|e,z|z,a|z,c|f,c|d,a|f,z|d,a|z,z|e,c|e,c|f,z|e,b|f,c|d,b|z,z|d,
z|f,b|e,b|e,a|e,a|e,c|z,c|z,b|f,a|d,b|d,b|d,a|d,z|z,z|f,a|f,b|z,
a|z,c|f,z|d,c|z,c|e,b|z,b|z,z|e,c|d,a|z,a|e,b|d,z|e,z|d,b|f,a|f,
c|f,a|d,c|z,b|f,b|d,z|f,a|f,c|e,z|f,b|e,b|e,z|z,a|d,a|e,z|z,c|d];
a=1<<20; b=1<<31; c=a|b; d=1<<5; e=1<<15; f=d|e;
const SP2 = [c|f,b|e,z|e,a|f,a|z,z|d,c|d,b|f,b|d,c|f,c|e,b|z,b|e,a|z,z|d,c|d,
a|e,a|d,b|f,z|z,b|z,z|e,a|f,c|z,a|d,b|d,z|z,a|e,z|f,c|e,c|z,z|f,
z|z,a|f,c|d,a|z,b|f,c|z,c|e,z|e,c|z,b|e,z|d,c|f,a|f,z|d,z|e,b|z,
z|f,c|e,a|z,b|d,a|d,b|f,b|d,a|d,a|e,z|z,b|e,z|f,b|z,c|d,c|f,a|e];
a=1<<17; b=1<<27; c=a|b; d=1<<3; e=1<<9; f=d|e;
const SP3 = [z|f,c|e,z|z,c|d,b|e,z|z,a|f,b|e,a|d,b|d,b|d,a|z,c|f,a|d,c|z,z|f,
b|z,z|d,c|e,z|e,a|e,c|z,c|d,a|f,b|f,a|e,a|z,b|f,z|d,c|f,z|e,b|z,
c|e,b|z,a|d,z|f,a|z,c|e,b|e,z|z,z|e,a|d,c|f,b|e,b|d,z|e,z|z,c|d,
b|f,a|z,b|z,c|f,z|d,a|f,a|e,b|d,c|z,b|f,z|f,c|z,a|f,z|d,c|d,a|e];
a=1<<13; b=1<<23; c=a|b; d=1<<0; e=1<<7; f=d|e;
const SP4 = [c|d,a|f,a|f,z|e,c|e,b|f,b|d,a|d,z|z,c|z,c|z,c|f,z|f,z|z,b|e,b|d,
z|d,a|z,b|z,c|d,z|e,b|z,a|d,a|e,b|f,z|d,a|e,b|e,a|z,c|e,c|f,z|f,
b|e,b|d,c|z,c|f,z|f,z|z,z|z,c|z,a|e,b|e,b|f,z|d,c|d,a|f,a|f,z|e,
c|f,z|f,z|d,a|z,b|d,a|d,c|e,b|f,a|d,a|e,b|z,c|d,z|e,b|z,a|z,c|e];
a=1<<25; b=1<<30; c=a|b; d=1<<8; e=1<<19; f=d|e;
const SP5 = [z|d,a|f,a|e,c|d,z|e,z|d,b|z,a|e,b|f,z|e,a|d,b|f,c|d,c|e,z|f,b|z,
a|z,b|e,b|e,z|z,b|d,c|f,c|f,a|d,c|e,b|d,z|z,c|z,a|f,a|z,c|z,z|f,
z|e,c|d,z|d,a|z,b|z,a|e,c|d,b|f,a|d,b|z,c|e,a|f,b|f,z|d,a|z,c|e,
c|f,z|f,c|z,c|f,a|e,z|z,b|e,c|z,z|f,a|d,b|d,z|e,z|z,b|e,a|f,b|d];
a=1<<22; b=1<<29; c=a|b; d=1<<4; e=1<<14; f=d|e;
const SP6 = [b|d,c|z,z|e,c|f,c|z,z|d,c|f,a|z,b|e,a|f,a|z,b|d,a|d,b|e,b|z,z|f,
z|z,a|d,b|f,z|e,a|e,b|f,z|d,c|d,c|d,z|z,a|f,c|e,z|f,a|e,c|e,b|z,
b|e,z|d,c|d,a|e,c|f,a|z,z|f,b|d,a|z,b|e,b|z,z|f,b|d,c|f,a|e,c|z,
a|f,c|e,z|z,c|d,z|d,z|e,c|z,a|f,z|e,a|d,b|f,z|z,c|e,b|z,a|d,b|f];
a=1<<21; b=1<<26; c=a|b; d=1<<1; e=1<<11; f=d|e;
const SP7 = [a|z,c|d,b|f,z|z,z|e,b|f,a|f,c|e,c|f,a|z,z|z,b|d,z|d,b|z,c|d,z|f,
b|e,a|f,a|d,b|e,b|d,c|z,c|e,a|d,c|z,z|e,z|f,c|f,a|e,z|d,b|z,a|e,
b|z,a|e,a|z,b|f,b|f,c|d,c|d,z|d,a|d,b|z,b|e,a|z,c|e,z|f,a|f,c|e,
z|f,b|d,c|f,c|z,a|e,z|z,z|d,c|f,z|z,a|f,c|z,z|e,b|d,b|e,z|e,a|d];
a=1<<18; b=1<<28; c=a|b; d=1<<6; e=1<<12; f=d|e;
const SP8 = [b|f,z|e,a|z,c|f,b|z,b|f,z|d,b|z,a|d,c|z,c|f,a|e,c|e,a|f,z|e,z|d,
c|z,b|d,b|e,z|f,a|e,a|d,c|d,c|e,z|f,z|z,z|z,c|d,b|d,b|e,a|f,a|z,
a|f,a|z,c|e,z|e,z|d,c|d,z|e,a|f,b|e,z|d,b|d,c|z,c|d,b|z,a|z,b|f,
z|z,c|f,a|d,b|d,c|z,b|e,b|f,z|z,c|f,a|e,a|e,z|f,z|f,a|d,b|z,c|e];
// Set the key.
function setKeys(keyBlock) {
var i, j, l, m, n, o, pc1m = [], pcr = [], kn = [],
raw0, raw1, rawi, KnLi;
/* eslint-enable comma-spacing */
for (j = 0, l = 56; j < 56; ++j, l -= 8) {
export default class DES {
constructor(password) {
this.keys = [];
// Set the key.
const pc1m = [], pcr = [], kn = [];
for (let j = 0, l = 56; j < 56; ++j, l -= 8) {
l += l < -5 ? 65 : l < -3 ? 31 : l < -1 ? 63 : l === 27 ? 35 : 0; // PC1
m = l & 0x7;
pc1m[j] = ((keyBlock[l >>> 3] & (1<<m)) !== 0) ? 1: 0;
const m = l & 0x7;
pc1m[j] = ((password[l >>> 3] & (1<<m)) !== 0) ? 1: 0;
}
for (i = 0; i < 16; ++i) {
m = i << 1;
n = m + 1;
for (let i = 0; i < 16; ++i) {
const m = i << 1;
const n = m + 1;
kn[m] = kn[n] = 0;
for (o = 28; o < 59; o += 28) {
for (j = o - 28; j < o; ++j) {
l = j + totrot[i];
if (l < o) {
pcr[j] = pc1m[l];
} else {
pcr[j] = pc1m[l - 28];
}
for (let o = 28; o < 59; o += 28) {
for (let j = o - 28; j < o; ++j) {
const l = j + totrot[i];
pcr[j] = l < o ? pc1m[l] : pc1m[l - 28];
}
}
for (j = 0; j < 24; ++j) {
for (let j = 0; j < 24; ++j) {
if (pcr[PC2[j]] !== 0) {
kn[m] |= 1 << (23 - j);
}
@@ -163,26 +162,26 @@ export default function DES(passwd) {
}
// cookey
for (i = 0, rawi = 0, KnLi = 0; i < 16; ++i) {
raw0 = kn[rawi++];
raw1 = kn[rawi++];
keys[KnLi] = (raw0 & 0x00fc0000) << 6;
keys[KnLi] |= (raw0 & 0x00000fc0) << 10;
keys[KnLi] |= (raw1 & 0x00fc0000) >>> 10;
keys[KnLi] |= (raw1 & 0x00000fc0) >>> 6;
for (let i = 0, rawi = 0, KnLi = 0; i < 16; ++i) {
const raw0 = kn[rawi++];
const raw1 = kn[rawi++];
this.keys[KnLi] = (raw0 & 0x00fc0000) << 6;
this.keys[KnLi] |= (raw0 & 0x00000fc0) << 10;
this.keys[KnLi] |= (raw1 & 0x00fc0000) >>> 10;
this.keys[KnLi] |= (raw1 & 0x00000fc0) >>> 6;
++KnLi;
keys[KnLi] = (raw0 & 0x0003f000) << 12;
keys[KnLi] |= (raw0 & 0x0000003f) << 16;
keys[KnLi] |= (raw1 & 0x0003f000) >>> 4;
keys[KnLi] |= (raw1 & 0x0000003f);
this.keys[KnLi] = (raw0 & 0x0003f000) << 12;
this.keys[KnLi] |= (raw0 & 0x0000003f) << 16;
this.keys[KnLi] |= (raw1 & 0x0003f000) >>> 4;
this.keys[KnLi] |= (raw1 & 0x0000003f);
++KnLi;
}
}
// Encrypt 8 bytes of text
function enc8(text) {
var i = 0, b = text.slice(), fval, keysi = 0,
l, r, x; // left, right, accumulator
enc8(text) {
const b = text.slice();
let i = 0, l, r, x; // left, right, accumulator
// Squash 8 bytes to 2 ints
l = b[i++]<<24 | b[i++]<<16 | b[i++]<<8 | b[i++];
@@ -206,26 +205,26 @@ export default function DES(passwd) {
r ^= x;
l = (l << 1) | ((l >>> 31) & 1);
for (i = 0; i < 8; ++i) {
for (let i = 0, keysi = 0; i < 8; ++i) {
x = (r << 28) | (r >>> 4);
x ^= keys[keysi++];
fval = SP7[x & 0x3f];
x ^= this.keys[keysi++];
let fval = SP7[x & 0x3f];
fval |= SP5[(x >>> 8) & 0x3f];
fval |= SP3[(x >>> 16) & 0x3f];
fval |= SP1[(x >>> 24) & 0x3f];
x = r ^ keys[keysi++];
x = r ^ this.keys[keysi++];
fval |= SP8[x & 0x3f];
fval |= SP6[(x >>> 8) & 0x3f];
fval |= SP4[(x >>> 16) & 0x3f];
fval |= SP2[(x >>> 24) & 0x3f];
l ^= fval;
x = (l << 28) | (l >>> 4);
x ^= keys[keysi++];
x ^= this.keys[keysi++];
fval = SP7[x & 0x3f];
fval |= SP5[(x >>> 8) & 0x3f];
fval |= SP3[(x >>> 16) & 0x3f];
fval |= SP1[(x >>> 24) & 0x3f];
x = l ^ keys[keysi++];
x = l ^ this.keys[keysi++];
fval |= SP8[x & 0x0000003f];
fval |= SP6[(x >>> 8) & 0x3f];
fval |= SP4[(x >>> 16) & 0x3f];
@@ -261,11 +260,7 @@ export default function DES(passwd) {
}
// Encrypt 16 bytes of text using passwd as key
function encrypt(t) {
return enc8(t.slice(0, 8)).concat(enc8(t.slice(8, 16)));
encrypt(t) {
return this.enc8(t.slice(0, 8)).concat(this.enc8(t.slice(8, 16)));
}
setKeys(passwd); // Setup keys
return {'encrypt': encrypt}; // Public interface
}; // function DES
}

View File

@@ -1,7 +1,6 @@
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2012 Joel Martin
* Copyright (C) 2015 Samuel Mannehed for Cendio AB
* Copyright (C) 2019 The noVNC Authors
* Licensed under MPL 2.0 (see LICENSE.txt)
*
* See README.md for usage and integration instructions.
@@ -9,111 +8,104 @@
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 function Display(target) {
this._drawCtx = null;
this._c_forceCanvas = false;
export default class Display {
constructor(target) {
this._drawCtx = null;
this._renderQ = []; // queue drawing actions for in-oder rendering
this._flushing = false;
this._renderQ = []; // queue drawing actions for in-oder rendering
this._flushing = false;
// the full frame buffer (logical canvas) size
this._fb_width = 0;
this._fb_height = 0;
// the full frame buffer (logical canvas) size
this._fbWidth = 0;
this._fbHeight = 0;
this._prevDrawStyle = "";
this._tile = null;
this._tile16x16 = null;
this._tile_x = 0;
this._tile_y = 0;
this._prevDrawStyle = "";
this._tile = null;
this._tile16x16 = null;
this._tileX = 0;
this._tileY = 0;
Log.Debug(">> Display.constructor");
Log.Debug(">> Display.constructor");
// The visible canvas
this._target = target;
// The visible canvas
this._target = target;
if (!this._target) {
throw new Error("Target must be set");
if (!this._target) {
throw new Error("Target must be set");
}
if (typeof this._target === 'string') {
throw new Error('target must be a DOM element');
}
if (!this._target.getContext) {
throw new Error("no getContext method");
}
this._targetCtx = this._target.getContext('2d');
// the visible canvas viewport (i.e. what actually gets seen)
this._viewportLoc = { 'x': 0, 'y': 0, 'w': this._target.width, 'h': this._target.height };
// The hidden canvas, where we do the actual rendering
this._backbuffer = document.createElement('canvas');
this._drawCtx = this._backbuffer.getContext('2d');
this._damageBounds = { left: 0, top: 0,
right: this._backbuffer.width,
bottom: this._backbuffer.height };
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 =====
this._scale = 1.0;
this._clipViewport = false;
// ===== EVENT HANDLERS =====
this.onflush = () => {}; // A flush request has finished
}
if (typeof this._target === 'string') {
throw new Error('target must be a DOM element');
}
if (!this._target.getContext) {
throw new Error("no getContext method");
}
this._targetCtx = this._target.getContext('2d');
// the visible canvas viewport (i.e. what actually gets seen)
this._viewportLoc = { 'x': 0, 'y': 0, 'w': this._target.width, 'h': this._target.height };
// The hidden canvas, where we do the actual rendering
this._backbuffer = document.createElement('canvas');
this._drawCtx = this._backbuffer.getContext('2d');
this._damageBounds = { left:0, top:0,
right: this._backbuffer.width,
bottom: this._backbuffer.height };
Log.Debug("User Agent: " + navigator.userAgent);
this.clear();
// Check canvas features
if (!('createImageData' in this._drawCtx)) {
throw new Error("Canvas does not support createImageData");
}
this._tile16x16 = this._drawCtx.createImageData(16, 16);
Log.Debug("<< Display.constructor");
};
var SUPPORTS_IMAGEDATA_CONSTRUCTOR = false;
try {
new ImageData(new Uint8ClampedArray(4), 1, 1);
SUPPORTS_IMAGEDATA_CONSTRUCTOR = true;
} catch (ex) {
// ignore failure
}
Display.prototype = {
// ===== PROPERTIES =====
_scale: 1.0,
get scale() { return this._scale; },
get scale() { return this._scale; }
set scale(scale) {
this._rescale(scale);
},
}
_clipViewport: false,
get clipViewport() { return this._clipViewport; },
get clipViewport() { return this._clipViewport; }
set clipViewport(viewport) {
this._clipViewport = viewport;
// May need to readjust the viewport dimensions
var vp = this._viewportLoc;
const vp = this._viewportLoc;
this.viewportChangeSize(vp.w, vp.h);
this.viewportChangePos(0, 0);
},
}
get width() {
return this._fb_width;
},
return this._fbWidth;
}
get height() {
return this._fb_height;
},
logo: null,
// ===== EVENT HANDLERS =====
onflush: function () {}, // A flush request has finished
return this._fbHeight;
}
// ===== PUBLIC METHODS =====
viewportChangePos: function (deltaX, deltaY) {
var vp = this._viewportLoc;
viewportChangePos(deltaX, deltaY) {
const vp = this._viewportLoc;
deltaX = Math.floor(deltaX);
deltaY = Math.floor(deltaY);
@@ -122,23 +114,23 @@ Display.prototype = {
deltaY = -vp.h;
}
var vx2 = vp.x + vp.w - 1;
var vy2 = vp.y + vp.h - 1;
const vx2 = vp.x + vp.w - 1;
const vy2 = vp.y + vp.h - 1;
// Position change
if (deltaX < 0 && vp.x + deltaX < 0) {
deltaX = -vp.x;
}
if (vx2 + deltaX >= this._fb_width) {
deltaX -= vx2 + deltaX - this._fb_width + 1;
if (vx2 + deltaX >= this._fbWidth) {
deltaX -= vx2 + deltaX - this._fbWidth + 1;
}
if (vp.y + deltaY < 0) {
deltaY = -vp.y;
}
if (vy2 + deltaY >= this._fb_height) {
deltaY -= (vy2 + deltaY - this._fb_height + 1);
if (vy2 + deltaY >= this._fbHeight) {
deltaY -= (vy2 + deltaY - this._fbHeight + 1);
}
if (deltaX === 0 && deltaY === 0) {
@@ -152,32 +144,35 @@ Display.prototype = {
this._damage(vp.x, vp.y, vp.w, vp.h);
this.flip();
},
}
viewportChangeSize: function(width, height) {
viewportChangeSize(width, height) {
if (!this._clipViewport ||
typeof(width) === "undefined" ||
typeof(height) === "undefined") {
Log.Debug("Setting viewport to full display region");
width = this._fb_width;
height = this._fb_height;
width = this._fbWidth;
height = this._fbHeight;
}
if (width > this._fb_width) {
width = this._fb_width;
width = Math.floor(width);
height = Math.floor(height);
if (width > this._fbWidth) {
width = this._fbWidth;
}
if (height > this._fb_height) {
height = this._fb_height;
if (height > this._fbHeight) {
height = this._fbHeight;
}
var vp = this._viewportLoc;
const vp = this._viewportLoc;
if (vp.w !== width || vp.h !== height) {
vp.w = width;
vp.h = height;
var canvas = this._target;
const canvas = this._target;
canvas.width = width;
canvas.height = height;
@@ -190,27 +185,33 @@ Display.prototype = {
// Update the visible size of the target canvas
this._rescale(this._scale);
}
},
}
absX: function (x) {
return x / this._scale + this._viewportLoc.x;
},
absX(x) {
if (this._scale === 0) {
return 0;
}
return toSigned32bit(x / this._scale + this._viewportLoc.x);
}
absY: function (y) {
return y / this._scale + this._viewportLoc.y;
},
absY(y) {
if (this._scale === 0) {
return 0;
}
return toSigned32bit(y / this._scale + this._viewportLoc.y);
}
resize: function (width, height) {
resize(width, height) {
this._prevDrawStyle = "";
this._fb_width = width;
this._fb_height = height;
this._fbWidth = width;
this._fbHeight = height;
var canvas = this._backbuffer;
const canvas = this._backbuffer;
if (canvas.width !== width || canvas.height !== height) {
// We have to save the canvas data since changing the size will clear it
var saveImg = null;
let saveImg = null;
if (canvas.width > 0 && canvas.height > 0) {
saveImg = this._drawCtx.getImageData(0, 0, canvas.width, canvas.height);
}
@@ -229,13 +230,13 @@ Display.prototype = {
// Readjust the viewport as it may be incorrectly sized
// and positioned
var vp = this._viewportLoc;
const vp = this._viewportLoc;
this.viewportChangeSize(vp.w, vp.h);
this.viewportChangePos(0, 0);
},
}
// Track what parts of the visible canvas that need updating
_damage: function(x, y, w, h) {
_damage(x, y, w, h) {
if (x < this._damageBounds.left) {
this._damageBounds.left = x;
}
@@ -248,25 +249,23 @@ Display.prototype = {
if ((y + h) > this._damageBounds.bottom) {
this._damageBounds.bottom = y + h;
}
},
}
// Update the visible canvas with the contents of the
// rendering canvas
flip: function(from_queue) {
if (this._renderQ.length !== 0 && !from_queue) {
this._renderQ_push({
flip(fromQueue) {
if (this._renderQ.length !== 0 && !fromQueue) {
this._renderQPush({
'type': 'flip'
});
} else {
var x, y, vx, vy, w, h;
let x = this._damageBounds.left;
let y = this._damageBounds.top;
let w = this._damageBounds.right - x;
let h = this._damageBounds.bottom - y;
x = this._damageBounds.left;
y = this._damageBounds.top;
w = this._damageBounds.right - x;
h = this._damageBounds.bottom - y;
vx = x - this._viewportLoc.x;
vy = y - this._viewportLoc.y;
let vx = x - this._viewportLoc.x;
let vy = y - this._viewportLoc.y;
if (vx < 0) {
w += vx;
@@ -298,34 +297,23 @@ Display.prototype = {
this._damageBounds.left = this._damageBounds.top = 65535;
this._damageBounds.right = this._damageBounds.bottom = 0;
}
},
}
clear: function () {
if (this._logo) {
this.resize(this._logo.width, this._logo.height);
this.imageRect(0, 0, this._logo.type, this._logo.data);
} else {
this.resize(240, 20);
this._drawCtx.clearRect(0, 0, this._fb_width, this._fb_height);
}
this.flip();
},
pending: function() {
pending() {
return this._renderQ.length > 0;
},
}
flush: function() {
flush() {
if (this._renderQ.length === 0) {
this.onflush();
} else {
this._flushing = true;
}
},
}
fillRect: function (x, y, width, height, color, from_queue) {
if (this._renderQ.length !== 0 && !from_queue) {
this._renderQ_push({
fillRect(x, y, width, height, color, fromQueue) {
if (this._renderQ.length !== 0 && !fromQueue) {
this._renderQPush({
'type': 'fill',
'x': x,
'y': y,
@@ -338,16 +326,16 @@ Display.prototype = {
this._drawCtx.fillRect(x, y, width, height);
this._damage(x, y, width, height);
}
},
}
copyImage: function (old_x, old_y, new_x, new_y, w, h, from_queue) {
if (this._renderQ.length !== 0 && !from_queue) {
this._renderQ_push({
copyImage(oldX, oldY, newX, newY, w, h, fromQueue) {
if (this._renderQ.length !== 0 && !fromQueue) {
this._renderQPush({
'type': 'copy',
'old_x': old_x,
'old_y': old_y,
'x': new_x,
'y': new_y,
'oldX': oldX,
'oldY': oldY,
'x': newX,
'y': newY,
'width': w,
'height': h,
});
@@ -365,84 +353,92 @@ Display.prototype = {
this._drawCtx.imageSmoothingEnabled = false;
this._drawCtx.drawImage(this._backbuffer,
old_x, old_y, w, h,
new_x, new_y, w, h);
this._damage(new_x, new_y, w, h);
oldX, oldY, w, h,
newX, newY, w, h);
this._damage(newX, newY, w, h);
}
},
}
imageRect: function(x, y, mime, arr) {
var img = new Image();
imageRect(x, y, width, height, mime, arr) {
/* The internal logic cannot handle empty images, so bail early */
if ((width === 0) || (height === 0)) {
return;
}
const img = new Image();
img.src = "data: " + mime + ";base64," + Base64.encode(arr);
this._renderQ_push({
this._renderQPush({
'type': 'img',
'img': img,
'x': x,
'y': y
'y': y,
'width': width,
'height': height
});
},
}
// start updating a tile
startTile: function (x, y, width, height, color) {
this._tile_x = x;
this._tile_y = y;
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);
}
var red = color[2];
var green = color[1];
var blue = color[0];
const red = color[2];
const green = color[1];
const blue = color[0];
var data = this._tile.data;
for (var i = 0; i < width * height * 4; i += 4) {
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: function (x, y, w, h, color) {
var red = color[2];
var green = color[1];
var blue = color[0];
var xend = x + w;
var yend = y + h;
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;
var data = this._tile.data;
var width = this._tile.width;
for (var j = y; j < yend; j++) {
for (var i = x; i < xend; i++) {
var p = (i + (j * width)) * 4;
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: function () {
this._drawCtx.putImageData(this._tile, this._tile_x, this._tile_y);
this._damage(this._tile_x, this._tile_y,
finishTile() {
this._drawCtx.putImageData(this._tile, this._tileX, this._tileY);
this._damage(this._tileX, this._tileY,
this._tile.width, this._tile.height);
},
}
blitImage: function (x, y, width, height, arr, offset, from_queue) {
if (this._renderQ.length !== 0 && !from_queue) {
blitImage(x, y, width, height, arr, offset, fromQueue) {
if (this._renderQ.length !== 0 && !fromQueue) {
// NB(directxman12): it's technically more performant here to use preallocated arrays,
// but it's a lot of extra work for not a lot of payoff -- if we're using the render queue,
// this probably isn't getting called *nearly* as much
var new_arr = new Uint8Array(width * height * 4);
new_arr.set(new Uint8Array(arr.buffer, 0, new_arr.length));
this._renderQ_push({
const newArr = new Uint8Array(width * height * 4);
newArr.set(new Uint8Array(arr.buffer, 0, newArr.length));
this._renderQPush({
'type': 'blit',
'data': new_arr,
'data': newArr,
'x': x,
'y': y,
'width': width,
@@ -451,18 +447,18 @@ Display.prototype = {
} else {
this._bgrxImageData(x, y, width, height, arr, offset);
}
},
}
blitRgbImage: function (x, y , width, height, arr, offset, from_queue) {
if (this._renderQ.length !== 0 && !from_queue) {
blitRgbImage(x, y, width, height, arr, offset, fromQueue) {
if (this._renderQ.length !== 0 && !fromQueue) {
// NB(directxman12): it's technically more performant here to use preallocated arrays,
// but it's a lot of extra work for not a lot of payoff -- if we're using the render queue,
// this probably isn't getting called *nearly* as much
var new_arr = new Uint8Array(width * height * 3);
new_arr.set(new Uint8Array(arr.buffer, 0, new_arr.length));
this._renderQ_push({
const newArr = new Uint8Array(width * height * 3);
newArr.set(new Uint8Array(arr.buffer, 0, newArr.length));
this._renderQPush({
'type': 'blitRgb',
'data': new_arr,
'data': newArr,
'x': x,
'y': y,
'width': width,
@@ -471,18 +467,18 @@ Display.prototype = {
} else {
this._rgbImageData(x, y, width, height, arr, offset);
}
},
}
blitRgbxImage: function (x, y, width, height, arr, offset, from_queue) {
if (this._renderQ.length !== 0 && !from_queue) {
blitRgbxImage(x, y, width, height, arr, offset, fromQueue) {
if (this._renderQ.length !== 0 && !fromQueue) {
// NB(directxman12): it's technically more performant here to use preallocated arrays,
// but it's a lot of extra work for not a lot of payoff -- if we're using the render queue,
// this probably isn't getting called *nearly* as much
var new_arr = new Uint8Array(width * height * 4);
new_arr.set(new Uint8Array(arr.buffer, 0, new_arr.length));
this._renderQ_push({
const newArr = new Uint8Array(width * height * 4);
newArr.set(new Uint8Array(arr.buffer, 0, newArr.length));
this._renderQPush({
'type': 'blitRgbx',
'data': new_arr,
'data': newArr,
'x': x,
'y': y,
'width': width,
@@ -491,72 +487,67 @@ Display.prototype = {
} else {
this._rgbxImageData(x, y, width, height, arr, offset);
}
},
}
drawImage: function (img, x, y) {
drawImage(img, x, y) {
this._drawCtx.drawImage(img, x, y);
this._damage(x, y, img.width, img.height);
},
}
changeCursor: function (pixels, mask, hotx, hoty, w, h) {
Display.changeCursor(this._target, pixels, mask, hotx, hoty, w, h);
},
autoscale(containerWidth, containerHeight) {
let scaleRatio;
defaultCursor: function () {
this._target.style.cursor = "default";
},
if (containerWidth === 0 || containerHeight === 0) {
scaleRatio = 0;
disableLocalCursor: function () {
this._target.style.cursor = "none";
},
autoscale: function (containerWidth, containerHeight) {
var vp = this._viewportLoc;
var targetAspectRatio = containerWidth / containerHeight;
var fbAspectRatio = vp.w / vp.h;
var scaleRatio;
if (fbAspectRatio >= targetAspectRatio) {
scaleRatio = containerWidth / vp.w;
} else {
scaleRatio = containerHeight / vp.h;
const vp = this._viewportLoc;
const targetAspectRatio = containerWidth / containerHeight;
const fbAspectRatio = vp.w / vp.h;
if (fbAspectRatio >= targetAspectRatio) {
scaleRatio = containerWidth / vp.w;
} else {
scaleRatio = containerHeight / vp.h;
}
}
this._rescale(scaleRatio);
},
}
// ===== PRIVATE METHODS =====
_rescale: function (factor) {
_rescale(factor) {
this._scale = factor;
var vp = this._viewportLoc;
const vp = this._viewportLoc;
// NB(directxman12): If you set the width directly, or set the
// style width to a number, the canvas is cleared.
// However, if you set the style width to a string
// ('NNNpx'), the canvas is scaled without clearing.
var width = Math.round(factor * vp.w) + 'px';
var height = Math.round(factor * vp.h) + 'px';
const width = factor * vp.w + 'px';
const height = factor * vp.h + 'px';
if ((this._target.style.width !== width) ||
(this._target.style.height !== height)) {
this._target.style.width = width;
this._target.style.height = height;
}
},
}
_setFillColor: function (color) {
var newStyle = 'rgb(' + color[2] + ',' + color[1] + ',' + color[0] + ')';
_setFillColor(color) {
const newStyle = 'rgb(' + color[2] + ',' + color[1] + ',' + color[0] + ')';
if (newStyle !== this._prevDrawStyle) {
this._drawCtx.fillStyle = newStyle;
this._prevDrawStyle = newStyle;
}
},
}
_rgbImageData: function (x, y, width, height, arr, offset) {
var img = this._drawCtx.createImageData(width, height);
var data = img.data;
for (var i = 0, j = offset; i < width * height * 4; i += 4, j += 3) {
_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];
@@ -564,12 +555,12 @@ Display.prototype = {
}
this._drawCtx.putImageData(img, x, y);
this._damage(x, y, img.width, img.height);
},
}
_bgrxImageData: function (x, y, width, height, arr, offset) {
var img = this._drawCtx.createImageData(width, height);
var data = img.data;
for (var i = 0, j = offset; i < width * height * 4; i += 4, j += 4) {
_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];
@@ -577,12 +568,12 @@ Display.prototype = {
}
this._drawCtx.putImageData(img, x, y);
this._damage(x, y, img.width, img.height);
},
}
_rgbxImageData: function (x, y, width, height, arr, offset) {
_rgbxImageData(x, y, width, height, arr, offset) {
// NB(directxman12): arr must be an Type Array view
var img;
if (SUPPORTS_IMAGEDATA_CONSTRUCTOR) {
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);
@@ -590,34 +581,34 @@ Display.prototype = {
}
this._drawCtx.putImageData(img, x, y);
this._damage(x, y, img.width, img.height);
},
}
_renderQ_push: function (action) {
_renderQPush(action) {
this._renderQ.push(action);
if (this._renderQ.length === 1) {
// If this can be rendered immediately it will be, otherwise
// the scanner will wait for the relevant event
this._scan_renderQ();
this._scanRenderQ();
}
},
}
_resume_renderQ: function() {
_resumeRenderQ() {
// "this" is the object that is ready, not the
// display object
this.removeEventListener('load', this._noVNC_display._resume_renderQ);
this._noVNC_display._scan_renderQ();
},
this.removeEventListener('load', this._noVNCDisplay._resumeRenderQ);
this._noVNCDisplay._scanRenderQ();
}
_scan_renderQ: function () {
var ready = true;
_scanRenderQ() {
let ready = true;
while (ready && this._renderQ.length > 0) {
var a = this._renderQ[0];
const a = this._renderQ[0];
switch (a.type) {
case 'flip':
this.flip(true);
break;
case 'copy':
this.copyImage(a.old_x, a.old_y, a.x, a.y, a.width, a.height, true);
this.copyImage(a.oldX, a.oldY, a.x, a.y, a.width, a.height, true);
break;
case 'fill':
this.fillRect(a.x, a.y, a.width, a.height, a.color, true);
@@ -632,11 +623,18 @@ Display.prototype = {
this.blitRgbxImage(a.x, a.y, a.width, a.height, a.data, 0, true);
break;
case 'img':
if (a.img.complete) {
/* IE tends to set "complete" prematurely, so check dimensions */
if (a.img.complete && (a.img.width !== 0) && (a.img.height !== 0)) {
if (a.img.width !== a.width || a.img.height !== a.height) {
Log.Error("Decoded image has incorrect dimensions. Got " +
a.img.width + "x" + a.img.height + ". Expected " +
a.width + "x" + a.height + ".");
return;
}
this.drawImage(a.img, a.x, a.y);
} else {
a.img._noVNC_display = this;
a.img.addEventListener('load', this._resume_renderQ);
a.img._noVNCDisplay = this;
a.img.addEventListener('load', this._resumeRenderQ);
// We need to wait for this image to 'load'
// to keep things in-order
ready = false;
@@ -653,46 +651,5 @@ Display.prototype = {
this._flushing = false;
this.onflush();
}
},
};
// Class Methods
Display.changeCursor = function (target, pixels, mask, hotx, hoty, w, h) {
if ((w === 0) || (h === 0)) {
target.style.cursor = 'none';
return;
}
var cur = []
var y, x;
for (y = 0; y < h; y++) {
for (x = 0; x < w; x++) {
var idx = y * Math.ceil(w / 8) + Math.floor(x / 8);
var alpha = (mask[idx] << (x % 8)) & 0x80 ? 255 : 0;
idx = ((w * y) + x) * 4;
cur.push(pixels[idx + 2]); // red
cur.push(pixels[idx + 1]); // green
cur.push(pixels[idx]); // blue
cur.push(alpha); // alpha
}
}
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
canvas.width = w;
canvas.height = h;
var img;
if (SUPPORTS_IMAGEDATA_CONSTRUCTOR) {
img = new ImageData(new Uint8ClampedArray(cur), w, h);
} else {
img = ctx.createImageData(w, h);
img.data.set(new Uint8ClampedArray(cur));
}
ctx.clearRect(0, 0, w, h);
ctx.putImageData(img, 0, 0);
var url = canvas.toDataURL();
target.style.cursor = 'url(' + url + ')' + hotx + ' ' + hoty + ', default';
};
}

View File

@@ -1,17 +1,18 @@
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2017 Pierre Ossman for Cendio AB
* Copyright (C) 2019 The noVNC Authors
* Licensed under MPL 2.0 (see LICENSE.txt)
*
* See README.md for usage and integration instructions.
*/
export var encodings = {
export const encodings = {
encodingRaw: 0,
encodingCopyRect: 1,
encodingRRE: 2,
encodingHextile: 5,
encodingTight: 7,
encodingTightPNG: -260,
pseudoEncodingQualityLevel9: -23,
pseudoEncodingQualityLevel0: -32,
@@ -19,13 +20,15 @@ export var encodings = {
pseudoEncodingLastRect: -224,
pseudoEncodingCursor: -239,
pseudoEncodingQEMUExtendedKeyEvent: -258,
pseudoEncodingTightPNG: -260,
pseudoEncodingDesktopName: -307,
pseudoEncodingExtendedDesktopSize: -308,
pseudoEncodingXvp: -309,
pseudoEncodingFence: -312,
pseudoEncodingContinuousUpdates: -313,
pseudoEncodingCompressLevel9: -247,
pseudoEncodingCompressLevel0: -256,
pseudoEncodingVMwareCursor: 0x574d5664,
pseudoEncodingExtendedClipboard: 0xc0a1e5ce
};
export function encodingName(num) {
@@ -35,6 +38,7 @@ export function encodingName(num) {
case encodings.encodingRRE: return "RRE";
case encodings.encodingHextile: return "Hextile";
case encodings.encodingTight: return "Tight";
case encodings.encodingTightPNG: return "TightPNG";
default: return "[unknown encoding " + num + "]";
}
}

View File

@@ -1,13 +1,40 @@
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2020 The noVNC Authors
* Licensed under MPL 2.0 (see LICENSE.txt)
*
* See README.md for usage and integration instructions.
*/
import { inflateInit, inflate, inflateReset } from "../vendor/pako/lib/zlib/inflate.js";
import ZStream from "../vendor/pako/lib/zlib/zstream.js";
Inflate.prototype = {
inflate: function (data, flush, expected) {
this.strm.input = data;
this.strm.avail_in = this.strm.input.length;
this.strm.next_in = 0;
this.strm.next_out = 0;
export default class Inflate {
constructor() {
this.strm = new ZStream();
this.chunkSize = 1024 * 10 * 10;
this.strm.output = new Uint8Array(this.chunkSize);
this.windowBits = 5;
inflateInit(this.strm, this.windowBits);
}
setInput(data) {
if (!data) {
//FIXME: flush remaining data.
/* eslint-disable camelcase */
this.strm.input = null;
this.strm.avail_in = 0;
this.strm.next_in = 0;
} else {
this.strm.input = data;
this.strm.avail_in = this.strm.input.length;
this.strm.next_in = 0;
/* eslint-enable camelcase */
}
}
inflate(expected) {
// resize our output buffer if it's too small
// (we could just use multiple chunks, but that would cause an extra
// allocation each time to flatten the chunks)
@@ -16,23 +43,24 @@ Inflate.prototype = {
this.strm.output = new Uint8Array(this.chunkSize);
}
this.strm.avail_out = this.chunkSize;
/* eslint-disable camelcase */
this.strm.next_out = 0;
this.strm.avail_out = expected;
/* eslint-enable camelcase */
inflate(this.strm, flush);
let ret = inflate(this.strm, 0); // Flush argument not used.
if (ret < 0) {
throw new Error("zlib inflate failed");
}
if (this.strm.next_out != expected) {
throw new Error("Incomplete zlib block");
}
return new Uint8Array(this.strm.output.buffer, 0, this.strm.next_out);
},
}
reset: function () {
reset() {
inflateReset(this.strm);
}
};
export default function Inflate() {
this.strm = new ZStream();
this.chunkSize = 1024 * 10 * 10;
this.strm.output = new Uint8Array(this.chunkSize);
this.windowBits = 5;
inflateInit(this.strm, this.windowBits);
};
}

View File

@@ -1,6 +1,6 @@
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2017 Pierre Ossman for Cendio AB
* Copyright (C) 2018 The noVNC Authors
* Licensed under MPL 2.0 or any later version (see LICENSE.txt)
*/
@@ -13,28 +13,25 @@ import KeyTable from "./keysym.js";
* See https://www.w3.org/TR/uievents-key/ for possible values.
*/
var DOMKeyTable = {};
const DOMKeyTable = {};
function addStandard(key, standard)
{
if (standard === undefined) throw "Undefined keysym for key \"" + key + "\"";
if (key in DOMKeyTable) throw "Duplicate entry for key \"" + key + "\"";
function addStandard(key, standard) {
if (standard === undefined) throw new Error("Undefined keysym for key \"" + key + "\"");
if (key in DOMKeyTable) throw new Error("Duplicate entry for key \"" + key + "\"");
DOMKeyTable[key] = [standard, standard, standard, standard];
}
function addLeftRight(key, left, right)
{
if (left === undefined) throw "Undefined keysym for key \"" + key + "\"";
if (right === undefined) throw "Undefined keysym for key \"" + key + "\"";
if (key in DOMKeyTable) throw "Duplicate entry for key \"" + key + "\"";
function addLeftRight(key, left, right) {
if (left === undefined) throw new Error("Undefined keysym for key \"" + key + "\"");
if (right === undefined) throw new Error("Undefined keysym for key \"" + key + "\"");
if (key in DOMKeyTable) throw new Error("Duplicate entry for key \"" + key + "\"");
DOMKeyTable[key] = [left, left, right, left];
}
function addNumpad(key, standard, numpad)
{
if (standard === undefined) throw "Undefined keysym for key \"" + key + "\"";
if (numpad === undefined) throw "Undefined keysym for key \"" + key + "\"";
if (key in DOMKeyTable) throw "Duplicate entry for key \"" + key + "\"";
function addNumpad(key, standard, numpad) {
if (standard === undefined) throw new Error("Undefined keysym for key \"" + key + "\"");
if (numpad === undefined) throw new Error("Undefined keysym for key \"" + key + "\"");
if (key in DOMKeyTable) throw new Error("Duplicate entry for key \"" + key + "\"");
DOMKeyTable[key] = [standard, standard, standard, numpad];
}
@@ -46,12 +43,10 @@ addStandard("CapsLock", KeyTable.XK_Caps_Lock);
addLeftRight("Control", KeyTable.XK_Control_L, KeyTable.XK_Control_R);
// - Fn
// - FnLock
addLeftRight("Hyper", KeyTable.XK_Super_L, KeyTable.XK_Super_R);
addLeftRight("Meta", KeyTable.XK_Super_L, KeyTable.XK_Super_R);
addStandard("NumLock", KeyTable.XK_Num_Lock);
addStandard("ScrollLock", KeyTable.XK_Scroll_Lock);
addLeftRight("Shift", KeyTable.XK_Shift_L, KeyTable.XK_Shift_R);
addLeftRight("Super", KeyTable.XK_Super_L, KeyTable.XK_Super_R);
// - Symbol
// - SymbolLock
@@ -75,7 +70,10 @@ addNumpad("PageUp", KeyTable.XK_Prior, KeyTable.XK_KP_Prior);
// 2.5. Editing Keys
addStandard("Backspace", KeyTable.XK_BackSpace);
addStandard("Clear", KeyTable.XK_Clear);
// Browsers send "Clear" for the numpad 5 without NumLock because
// Windows uses VK_Clear for that key. But Unix expects KP_Begin for
// that scenario.
addNumpad("Clear", KeyTable.XK_Clear, KeyTable.XK_KP_Begin);
addStandard("Copy", KeyTable.XF86XK_Copy);
// - CrSel
addStandard("Cut", KeyTable.XF86XK_Cut);
@@ -197,7 +195,8 @@ addStandard("F35", KeyTable.XK_F35);
addStandard("Close", KeyTable.XF86XK_Close);
addStandard("MailForward", KeyTable.XF86XK_MailForward);
addStandard("MailReply", KeyTable.XF86XK_Reply);
addStandard("MainSend", KeyTable.XF86XK_Send);
addStandard("MailSend", KeyTable.XF86XK_Send);
// - MediaClose
addStandard("MediaFastForward", KeyTable.XF86XK_AudioForward);
addStandard("MediaPause", KeyTable.XF86XK_AudioPause);
addStandard("MediaPlay", KeyTable.XF86XK_AudioPlay);
@@ -221,11 +220,9 @@ addStandard("SpellCheck", KeyTable.XF86XK_Spell);
// - AudioBalanceLeft
// - AudioBalanceRight
// - AudioBassDown
// - AudioBassBoostDown
// - AudioBassBoostToggle
// - AudioBassBoostUp
// - AudioBassUp
// - AudioFaderFront
// - AudioFaderRear
// - AudioSurroundModeNext
@@ -246,12 +243,12 @@ addStandard("MicrophoneVolumeMute", KeyTable.XF86XK_AudioMicMute);
// 2.14. Application Keys
addStandard("LaunchCalculator", KeyTable.XF86XK_Calculator);
addStandard("LaunchApplication1", KeyTable.XF86XK_MyComputer);
addStandard("LaunchApplication2", KeyTable.XF86XK_Calculator);
addStandard("LaunchCalendar", KeyTable.XF86XK_Calendar);
addStandard("LaunchMail", KeyTable.XF86XK_Mail);
addStandard("LaunchMediaPlayer", KeyTable.XF86XK_AudioMedia);
addStandard("LaunchMusicPlayer", KeyTable.XF86XK_Music);
addStandard("LaunchMyComputer", KeyTable.XF86XK_MyComputer);
addStandard("LaunchPhone", KeyTable.XF86XK_Phone);
addStandard("LaunchScreenSaver", KeyTable.XF86XK_ScreenSaver);
addStandard("LaunchSpreadsheet", KeyTable.XF86XK_Excel);

View File

@@ -1,6 +1,6 @@
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2017 Pierre Ossman for Cendio AB
* Copyright (C) 2018 The noVNC Authors
* Licensed under MPL 2.0 or any later version (see LICENSE.txt)
*/
@@ -14,6 +14,8 @@
* See https://www.w3.org/TR/uievents-key/ for possible values.
*/
/* eslint-disable key-spacing */
export default {
// 3.1.1.1. Writing System Keys

View File

@@ -0,0 +1,567 @@
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2020 The noVNC Authors
* Licensed under MPL 2.0 (see LICENSE.txt)
*
* See README.md for usage and integration instructions.
*
*/
const GH_NOGESTURE = 0;
const GH_ONETAP = 1;
const GH_TWOTAP = 2;
const GH_THREETAP = 4;
const GH_DRAG = 8;
const GH_LONGPRESS = 16;
const GH_TWODRAG = 32;
const GH_PINCH = 64;
const GH_INITSTATE = 127;
const GH_MOVE_THRESHOLD = 50;
const GH_ANGLE_THRESHOLD = 90; // Degrees
// Timeout when waiting for gestures (ms)
const GH_MULTITOUCH_TIMEOUT = 250;
// Maximum time between press and release for a tap (ms)
const GH_TAP_TIMEOUT = 1000;
// Timeout when waiting for longpress (ms)
const GH_LONGPRESS_TIMEOUT = 1000;
// Timeout when waiting to decide between PINCH and TWODRAG (ms)
const GH_TWOTOUCH_TIMEOUT = 50;
export default class GestureHandler {
constructor() {
this._target = null;
this._state = GH_INITSTATE;
this._tracked = [];
this._ignored = [];
this._waitingRelease = false;
this._releaseStart = 0.0;
this._longpressTimeoutId = null;
this._twoTouchTimeoutId = null;
this._boundEventHandler = this._eventHandler.bind(this);
}
attach(target) {
this.detach();
this._target = target;
this._target.addEventListener('touchstart',
this._boundEventHandler);
this._target.addEventListener('touchmove',
this._boundEventHandler);
this._target.addEventListener('touchend',
this._boundEventHandler);
this._target.addEventListener('touchcancel',
this._boundEventHandler);
}
detach() {
if (!this._target) {
return;
}
this._stopLongpressTimeout();
this._stopTwoTouchTimeout();
this._target.removeEventListener('touchstart',
this._boundEventHandler);
this._target.removeEventListener('touchmove',
this._boundEventHandler);
this._target.removeEventListener('touchend',
this._boundEventHandler);
this._target.removeEventListener('touchcancel',
this._boundEventHandler);
this._target = null;
}
_eventHandler(e) {
let fn;
e.stopPropagation();
e.preventDefault();
switch (e.type) {
case 'touchstart':
fn = this._touchStart;
break;
case 'touchmove':
fn = this._touchMove;
break;
case 'touchend':
case 'touchcancel':
fn = this._touchEnd;
break;
}
for (let i = 0; i < e.changedTouches.length; i++) {
let touch = e.changedTouches[i];
fn.call(this, touch.identifier, touch.clientX, touch.clientY);
}
}
_touchStart(id, x, y) {
// Ignore any new touches if there is already an active gesture,
// or we're in a cleanup state
if (this._hasDetectedGesture() || (this._state === GH_NOGESTURE)) {
this._ignored.push(id);
return;
}
// Did it take too long between touches that we should no longer
// consider this a single gesture?
if ((this._tracked.length > 0) &&
((Date.now() - this._tracked[0].started) > GH_MULTITOUCH_TIMEOUT)) {
this._state = GH_NOGESTURE;
this._ignored.push(id);
return;
}
// If we're waiting for fingers to release then we should no longer
// recognize new touches
if (this._waitingRelease) {
this._state = GH_NOGESTURE;
this._ignored.push(id);
return;
}
this._tracked.push({
id: id,
started: Date.now(),
active: true,
firstX: x,
firstY: y,
lastX: x,
lastY: y,
angle: 0
});
switch (this._tracked.length) {
case 1:
this._startLongpressTimeout();
break;
case 2:
this._state &= ~(GH_ONETAP | GH_DRAG | GH_LONGPRESS);
this._stopLongpressTimeout();
break;
case 3:
this._state &= ~(GH_TWOTAP | GH_TWODRAG | GH_PINCH);
break;
default:
this._state = GH_NOGESTURE;
}
}
_touchMove(id, x, y) {
let touch = this._tracked.find(t => t.id === id);
// If this is an update for a touch we're not tracking, ignore it
if (touch === undefined) {
return;
}
// Update the touches last position with the event coordinates
touch.lastX = x;
touch.lastY = y;
let deltaX = x - touch.firstX;
let deltaY = y - touch.firstY;
// Update angle when the touch has moved
if ((touch.firstX !== touch.lastX) ||
(touch.firstY !== touch.lastY)) {
touch.angle = Math.atan2(deltaY, deltaX) * 180 / Math.PI;
}
if (!this._hasDetectedGesture()) {
// Ignore moves smaller than the minimum threshold
if (Math.hypot(deltaX, deltaY) < GH_MOVE_THRESHOLD) {
return;
}
// Can't be a tap or long press as we've seen movement
this._state &= ~(GH_ONETAP | GH_TWOTAP | GH_THREETAP | GH_LONGPRESS);
this._stopLongpressTimeout();
if (this._tracked.length !== 1) {
this._state &= ~(GH_DRAG);
}
if (this._tracked.length !== 2) {
this._state &= ~(GH_TWODRAG | GH_PINCH);
}
// We need to figure out which of our different two touch gestures
// this might be
if (this._tracked.length === 2) {
// The other touch is the one where the id doesn't match
let prevTouch = this._tracked.find(t => t.id !== id);
// How far the previous touch point has moved since start
let prevDeltaMove = Math.hypot(prevTouch.firstX - prevTouch.lastX,
prevTouch.firstY - prevTouch.lastY);
// We know that the current touch moved far enough,
// but unless both touches moved further than their
// threshold we don't want to disqualify any gestures
if (prevDeltaMove > GH_MOVE_THRESHOLD) {
// The angle difference between the direction of the touch points
let deltaAngle = Math.abs(touch.angle - prevTouch.angle);
deltaAngle = Math.abs(((deltaAngle + 180) % 360) - 180);
// PINCH or TWODRAG can be eliminated depending on the angle
if (deltaAngle > GH_ANGLE_THRESHOLD) {
this._state &= ~GH_TWODRAG;
} else {
this._state &= ~GH_PINCH;
}
if (this._isTwoTouchTimeoutRunning()) {
this._stopTwoTouchTimeout();
}
} else if (!this._isTwoTouchTimeoutRunning()) {
// We can't determine the gesture right now, let's
// wait and see if more events are on their way
this._startTwoTouchTimeout();
}
}
if (!this._hasDetectedGesture()) {
return;
}
this._pushEvent('gesturestart');
}
this._pushEvent('gesturemove');
}
_touchEnd(id, x, y) {
// Check if this is an ignored touch
if (this._ignored.indexOf(id) !== -1) {
// Remove this touch from ignored
this._ignored.splice(this._ignored.indexOf(id), 1);
// And reset the state if there are no more touches
if ((this._ignored.length === 0) &&
(this._tracked.length === 0)) {
this._state = GH_INITSTATE;
this._waitingRelease = false;
}
return;
}
// We got a touchend before the timer triggered,
// this cannot result in a gesture anymore.
if (!this._hasDetectedGesture() &&
this._isTwoTouchTimeoutRunning()) {
this._stopTwoTouchTimeout();
this._state = GH_NOGESTURE;
}
// Some gestures don't trigger until a touch is released
if (!this._hasDetectedGesture()) {
// Can't be a gesture that relies on movement
this._state &= ~(GH_DRAG | GH_TWODRAG | GH_PINCH);
// Or something that relies on more time
this._state &= ~GH_LONGPRESS;
this._stopLongpressTimeout();
if (!this._waitingRelease) {
this._releaseStart = Date.now();
this._waitingRelease = true;
// Can't be a tap that requires more touches than we current have
switch (this._tracked.length) {
case 1:
this._state &= ~(GH_TWOTAP | GH_THREETAP);
break;
case 2:
this._state &= ~(GH_ONETAP | GH_THREETAP);
break;
}
}
}
// Waiting for all touches to release? (i.e. some tap)
if (this._waitingRelease) {
// Were all touches released at roughly the same time?
if ((Date.now() - this._releaseStart) > GH_MULTITOUCH_TIMEOUT) {
this._state = GH_NOGESTURE;
}
// Did too long time pass between press and release?
if (this._tracked.some(t => (Date.now() - t.started) > GH_TAP_TIMEOUT)) {
this._state = GH_NOGESTURE;
}
let touch = this._tracked.find(t => t.id === id);
touch.active = false;
// Are we still waiting for more releases?
if (this._hasDetectedGesture()) {
this._pushEvent('gesturestart');
} else {
// Have we reached a dead end?
if (this._state !== GH_NOGESTURE) {
return;
}
}
}
if (this._hasDetectedGesture()) {
this._pushEvent('gestureend');
}
// Ignore any remaining touches until they are ended
for (let i = 0; i < this._tracked.length; i++) {
if (this._tracked[i].active) {
this._ignored.push(this._tracked[i].id);
}
}
this._tracked = [];
this._state = GH_NOGESTURE;
// Remove this touch from ignored if it's in there
if (this._ignored.indexOf(id) !== -1) {
this._ignored.splice(this._ignored.indexOf(id), 1);
}
// We reset the state if ignored is empty
if ((this._ignored.length === 0)) {
this._state = GH_INITSTATE;
this._waitingRelease = false;
}
}
_hasDetectedGesture() {
if (this._state === GH_NOGESTURE) {
return false;
}
// Check to see if the bitmask value is a power of 2
// (i.e. only one bit set). If it is, we have a state.
if (this._state & (this._state - 1)) {
return false;
}
// For taps we also need to have all touches released
// before we've fully detected the gesture
if (this._state & (GH_ONETAP | GH_TWOTAP | GH_THREETAP)) {
if (this._tracked.some(t => t.active)) {
return false;
}
}
return true;
}
_startLongpressTimeout() {
this._stopLongpressTimeout();
this._longpressTimeoutId = setTimeout(() => this._longpressTimeout(),
GH_LONGPRESS_TIMEOUT);
}
_stopLongpressTimeout() {
clearTimeout(this._longpressTimeoutId);
this._longpressTimeoutId = null;
}
_longpressTimeout() {
if (this._hasDetectedGesture()) {
throw new Error("A longpress gesture failed, conflict with a different gesture");
}
this._state = GH_LONGPRESS;
this._pushEvent('gesturestart');
}
_startTwoTouchTimeout() {
this._stopTwoTouchTimeout();
this._twoTouchTimeoutId = setTimeout(() => this._twoTouchTimeout(),
GH_TWOTOUCH_TIMEOUT);
}
_stopTwoTouchTimeout() {
clearTimeout(this._twoTouchTimeoutId);
this._twoTouchTimeoutId = null;
}
_isTwoTouchTimeoutRunning() {
return this._twoTouchTimeoutId !== null;
}
_twoTouchTimeout() {
if (this._tracked.length === 0) {
throw new Error("A pinch or two drag gesture failed, no tracked touches");
}
// How far each touch point has moved since start
let avgM = this._getAverageMovement();
let avgMoveH = Math.abs(avgM.x);
let avgMoveV = Math.abs(avgM.y);
// The difference in the distance between where
// the touch points started and where they are now
let avgD = this._getAverageDistance();
let deltaTouchDistance = Math.abs(Math.hypot(avgD.first.x, avgD.first.y) -
Math.hypot(avgD.last.x, avgD.last.y));
if ((avgMoveV < deltaTouchDistance) &&
(avgMoveH < deltaTouchDistance)) {
this._state = GH_PINCH;
} else {
this._state = GH_TWODRAG;
}
this._pushEvent('gesturestart');
this._pushEvent('gesturemove');
}
_pushEvent(type) {
let detail = { type: this._stateToGesture(this._state) };
// For most gesture events the current (average) position is the
// most useful
let avg = this._getPosition();
let pos = avg.last;
// However we have a slight distance to detect gestures, so for the
// first gesture event we want to use the first positions we saw
if (type === 'gesturestart') {
pos = avg.first;
}
// For these gestures, we always want the event coordinates
// to be where the gesture began, not the current touch location.
switch (this._state) {
case GH_TWODRAG:
case GH_PINCH:
pos = avg.first;
break;
}
detail['clientX'] = pos.x;
detail['clientY'] = pos.y;
// FIXME: other coordinates?
// Some gestures also have a magnitude
if (this._state === GH_PINCH) {
let distance = this._getAverageDistance();
if (type === 'gesturestart') {
detail['magnitudeX'] = distance.first.x;
detail['magnitudeY'] = distance.first.y;
} else {
detail['magnitudeX'] = distance.last.x;
detail['magnitudeY'] = distance.last.y;
}
} else if (this._state === GH_TWODRAG) {
if (type === 'gesturestart') {
detail['magnitudeX'] = 0.0;
detail['magnitudeY'] = 0.0;
} else {
let movement = this._getAverageMovement();
detail['magnitudeX'] = movement.x;
detail['magnitudeY'] = movement.y;
}
}
let gev = new CustomEvent(type, { detail: detail });
this._target.dispatchEvent(gev);
}
_stateToGesture(state) {
switch (state) {
case GH_ONETAP:
return 'onetap';
case GH_TWOTAP:
return 'twotap';
case GH_THREETAP:
return 'threetap';
case GH_DRAG:
return 'drag';
case GH_LONGPRESS:
return 'longpress';
case GH_TWODRAG:
return 'twodrag';
case GH_PINCH:
return 'pinch';
}
throw new Error("Unknown gesture state: " + state);
}
_getPosition() {
if (this._tracked.length === 0) {
throw new Error("Failed to get gesture position, no tracked touches");
}
let size = this._tracked.length;
let fx = 0, fy = 0, lx = 0, ly = 0;
for (let i = 0; i < this._tracked.length; i++) {
fx += this._tracked[i].firstX;
fy += this._tracked[i].firstY;
lx += this._tracked[i].lastX;
ly += this._tracked[i].lastY;
}
return { first: { x: fx / size,
y: fy / size },
last: { x: lx / size,
y: ly / size } };
}
_getAverageMovement() {
if (this._tracked.length === 0) {
throw new Error("Failed to get gesture movement, no tracked touches");
}
let totalH, totalV;
totalH = totalV = 0;
let size = this._tracked.length;
for (let i = 0; i < this._tracked.length; i++) {
totalH += this._tracked[i].lastX - this._tracked[i].firstX;
totalV += this._tracked[i].lastY - this._tracked[i].firstY;
}
return { x: totalH / size,
y: totalV / size };
}
_getAverageDistance() {
if (this._tracked.length === 0) {
throw new Error("Failed to get gesture distance, no tracked touches");
}
// Distance between the first and last tracked touches
let first = this._tracked[0];
let last = this._tracked[this._tracked.length - 1];
let fdx = Math.abs(last.firstX - first.firstX);
let fdy = Math.abs(last.firstY - first.firstY);
let ldx = Math.abs(last.lastX - first.lastX);
let ldy = Math.abs(last.lastY - first.lastY);
return { first: { x: fdx, y: fdy },
last: { x: ldx, y: ldy } };
}
}

View File

@@ -1,7 +1,6 @@
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2012 Joel Martin
* Copyright (C) 2013 Samuel Mannehed for Cendio AB
* Copyright (C) 2019 The noVNC Authors
* Licensed under MPL 2.0 or any later version (see LICENSE.txt)
*/
@@ -15,63 +14,49 @@ import * as browser from "../util/browser.js";
// Keyboard event handler
//
export default function Keyboard(target) {
this._target = target || null;
export default class Keyboard {
constructor(target) {
this._target = target || null;
this._keyDownList = {}; // List of depressed keys
// (even if they are happy)
this._pendingKey = null; // Key waiting for keypress
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)
};
};
// 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),
};
Keyboard.prototype = {
// ===== EVENT HANDLERS =====
// ===== EVENT HANDLERS =====
onkeyevent: function () {}, // Handler for key press/release
this.onkeyevent = () => {}; // Handler for key press/release
}
// ===== PRIVATE METHODS =====
_sendKeyEvent: function (keysym, code, down) {
_sendKeyEvent(keysym, code, down) {
if (down) {
this._keyDownList[code] = keysym;
} else {
// Do we really think this key is down?
if (!(code in this._keyDownList)) {
return;
}
delete this._keyDownList[code];
}
Log.Debug("onkeyevent " + (down ? "down" : "up") +
", keysym: " + keysym, ", code: " + code);
// Windows sends CtrlLeft+AltRight when you press
// AltGraph, which tends to confuse the hell out of
// remote systems. Fake a release of these keys until
// there is a way to detect AltGraph properly.
var fakeAltGraph = false;
if (down && browser.isWindows()) {
if ((code !== 'ControlLeft') &&
(code !== 'AltRight') &&
('ControlLeft' in this._keyDownList) &&
('AltRight' in this._keyDownList)) {
fakeAltGraph = true;
this.onkeyevent(this._keyDownList['AltRight'],
'AltRight', false);
this.onkeyevent(this._keyDownList['ControlLeft'],
'ControlLeft', false);
}
}
this.onkeyevent(keysym, code, down);
}
if (fakeAltGraph) {
this.onkeyevent(this._keyDownList['ControlLeft'],
'ControlLeft', true);
this.onkeyevent(this._keyDownList['AltRight'],
'AltRight', true);
}
},
_getKeyCode: function (e) {
var code = KeyboardUtil.getKeycode(e);
_getKeyCode(e) {
const code = KeyboardUtil.getKeycode(e);
if (code !== 'Unidentified') {
return code;
}
@@ -94,26 +79,46 @@ Keyboard.prototype = {
return e.keyIdentifier;
}
var codepoint = parseInt(e.keyIdentifier.substr(2), 16);
var char = String.fromCharCode(codepoint);
// Some implementations fail to uppercase the symbols
char = char.toUpperCase();
const codepoint = parseInt(e.keyIdentifier.substr(2), 16);
const char = String.fromCharCode(codepoint).toUpperCase();
return 'Platform' + char.charCodeAt();
}
return 'Unidentified';
},
}
_handleKeyDown: function (e) {
var code = this._getKeyCode(e);
var keysym = KeyboardUtil.getKeysym(e);
_handleKeyDown(e) {
const code = this._getKeyCode(e);
let keysym = KeyboardUtil.getKeysym(e);
// Windows doesn't have a proper AltGr, but handles it using
// fake Ctrl+Alt. However the remote end might not be Windows,
// so we need to merge those in to a single AltGr event. We
// detect this case by seeing the two key events directly after
// each other with a very short time between them (<50ms).
if (this._altGrArmed) {
this._altGrArmed = false;
clearTimeout(this._altGrTimeout);
if ((code === "AltRight") &&
((e.timeStamp - this._altGrCtrlTime) < 50)) {
// FIXME: We fail to detect this if either Ctrl key is
// first manually pressed as Windows then no
// longer sends the fake Ctrl down event. It
// does however happily send real Ctrl events
// even when AltGr is already down. Some
// browsers detect this for us though and set the
// key to "AltGraph".
keysym = KeyTable.XK_ISO_Level3_Shift;
} else {
this._sendKeyEvent(KeyTable.XK_Control_L, "ControlLeft", true);
}
}
// We cannot handle keys we cannot track, but we also need
// to deal with virtual keyboards which omit key info
// (iOS omits tracking info on keyup events, which forces us to
// special treat that platform here)
if ((code === 'Unidentified') || browser.isIOS()) {
if (code === 'Unidentified') {
if (keysym) {
// If it's a virtual keyboard then it should be
// sufficient to just send press and release right
@@ -130,20 +135,20 @@ Keyboard.prototype = {
// keys around a bit to make things more sane for the remote
// server. This method is used by RealVNC and TigerVNC (and
// possibly others).
if (browser.isMac()) {
if (browser.isMac() || browser.isIOS()) {
switch (keysym) {
case KeyTable.XK_Super_L:
keysym = KeyTable.XK_Alt_L;
break;
case KeyTable.XK_Super_R:
keysym = KeyTable.XK_Super_L;
break;
case KeyTable.XK_Alt_L:
keysym = KeyTable.XK_Mode_switch;
break;
case KeyTable.XK_Alt_R:
keysym = KeyTable.XK_ISO_Level3_Shift;
break;
case KeyTable.XK_Super_L:
keysym = KeyTable.XK_Alt_L;
break;
case KeyTable.XK_Super_R:
keysym = KeyTable.XK_Super_L;
break;
case KeyTable.XK_Alt_L:
keysym = KeyTable.XK_Mode_switch;
break;
case KeyTable.XK_Alt_R:
keysym = KeyTable.XK_ISO_Level3_Shift;
break;
}
}
@@ -157,7 +162,7 @@ Keyboard.prototype = {
// state change events. That gets extra confusing for CapsLock
// which toggles on each press, but not on release. So pretend
// it was a quick press and release of the button.
if (browser.isMac() && (code === 'CapsLock')) {
if ((browser.isMac() || browser.isIOS()) && (code === 'CapsLock')) {
this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', true);
this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', false);
stopEvent(e);
@@ -180,13 +185,20 @@ Keyboard.prototype = {
this._pendingKey = null;
stopEvent(e);
this._keyDownList[code] = keysym;
// Possible start of AltGr sequence? (see above)
if ((code === "ControlLeft") && browser.isWindows() &&
!("ControlLeft" in this._keyDownList)) {
this._altGrArmed = true;
this._altGrTimeout = setTimeout(this._handleAltGrTimeout.bind(this), 100);
this._altGrCtrlTime = e.timeStamp;
return;
}
this._sendKeyEvent(keysym, code, true);
},
}
// Legacy event for browsers without code/key
_handleKeyPress: function (e) {
_handleKeyPress(e) {
stopEvent(e);
// Are we expecting a keypress?
@@ -194,8 +206,8 @@ Keyboard.prototype = {
return;
}
var code = this._getKeyCode(e);
var keysym = KeyboardUtil.getKeysym(e);
let code = this._getKeyCode(e);
const keysym = KeyboardUtil.getKeysym(e);
// The key we were waiting for?
if ((code !== 'Unidentified') && (code != this._pendingKey)) {
@@ -210,19 +222,18 @@ Keyboard.prototype = {
return;
}
this._keyDownList[code] = keysym;
this._sendKeyEvent(keysym, code, true);
},
_handleKeyPressTimeout: function (e) {
}
_handleKeyPressTimeout(e) {
// Did someone manage to sort out the key already?
if (this._pendingKey === null) {
return;
}
var code, keysym;
let keysym;
code = this._pendingKey;
const code = this._pendingKey;
this._pendingKey = null;
// We have no way of knowing the proper keysym with the
@@ -233,82 +244,145 @@ Keyboard.prototype = {
keysym = e.keyCode;
} else if ((e.keyCode >= 0x41) && (e.keyCode <= 0x5a)) {
// Character (A-Z)
var char = String.fromCharCode(e.keyCode);
let char = String.fromCharCode(e.keyCode);
// A feeble attempt at the correct case
if (e.shiftKey)
if (e.shiftKey) {
char = char.toUpperCase();
else
} else {
char = char.toLowerCase();
}
keysym = char.charCodeAt();
} else {
// Unknown, give up
keysym = 0;
}
this._keyDownList[code] = keysym;
this._sendKeyEvent(keysym, code, true);
},
}
_handleKeyUp: function (e) {
_handleKeyUp(e) {
stopEvent(e);
var code = this._getKeyCode(e);
const code = this._getKeyCode(e);
// We can't get a release in the middle of an AltGr sequence, so
// abort that detection
if (this._altGrArmed) {
this._altGrArmed = false;
clearTimeout(this._altGrTimeout);
this._sendKeyEvent(KeyTable.XK_Control_L, "ControlLeft", true);
}
// See comment in _handleKeyDown()
if (browser.isMac() && (code === 'CapsLock')) {
if ((browser.isMac() || browser.isIOS()) && (code === 'CapsLock')) {
this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', true);
this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', false);
return;
}
// Do we really think this key is down?
if (!(code in this._keyDownList)) {
this._sendKeyEvent(this._keyDownList[code], code, false);
// Windows has a rather nasty bug where it won't send key
// release events for a Shift button if the other Shift is still
// pressed
if (browser.isWindows() && ((code === 'ShiftLeft') ||
(code === 'ShiftRight'))) {
if ('ShiftRight' in this._keyDownList) {
this._sendKeyEvent(this._keyDownList['ShiftRight'],
'ShiftRight', false);
}
if ('ShiftLeft' in this._keyDownList) {
this._sendKeyEvent(this._keyDownList['ShiftLeft'],
'ShiftLeft', false);
}
}
}
_handleAltGrTimeout() {
this._altGrArmed = false;
clearTimeout(this._altGrTimeout);
this._sendKeyEvent(KeyTable.XK_Control_L, "ControlLeft", true);
}
_allKeysUp() {
Log.Debug(">> Keyboard.allKeysUp");
for (let code in this._keyDownList) {
this._sendKeyEvent(this._keyDownList[code], code, false);
}
Log.Debug("<< Keyboard.allKeysUp");
}
// Alt workaround for Firefox on Windows, see below
_checkAlt(e) {
if (e.skipCheckAlt) {
return;
}
if (e.altKey) {
return;
}
this._sendKeyEvent(this._keyDownList[code], code, false);
const target = this._target;
const downList = this._keyDownList;
['AltLeft', 'AltRight'].forEach((code) => {
if (!(code in downList)) {
return;
}
delete this._keyDownList[code];
},
_allKeysUp: function () {
Log.Debug(">> Keyboard.allKeysUp");
for (var code in this._keyDownList) {
this._sendKeyEvent(this._keyDownList[code], code, false);
};
this._keyDownList = {};
Log.Debug("<< Keyboard.allKeysUp");
},
const event = new KeyboardEvent('keyup',
{ key: downList[code],
code: code });
event.skipCheckAlt = true;
target.dispatchEvent(event);
});
}
// ===== PUBLIC METHODS =====
grab: function () {
grab() {
//Log.Debug(">> Keyboard.grab");
var c = this._target;
c.addEventListener('keydown', this._eventHandlers.keydown);
c.addEventListener('keyup', this._eventHandlers.keyup);
c.addEventListener('keypress', this._eventHandlers.keypress);
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: function () {
ungrab() {
//Log.Debug(">> Keyboard.ungrab");
var c = this._target;
c.removeEventListener('keydown', this._eventHandlers.keydown);
c.removeEventListener('keyup', this._eventHandlers.keyup);
c.removeEventListener('keypress', this._eventHandlers.keypress);
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
this._allKeysUp();
//Log.Debug(">> Keyboard.ungrab");
},
};
}
}

View File

@@ -1,3 +1,5 @@
/* eslint-disable key-spacing */
export default {
XK_VoidSymbol: 0xffffff, /* Void symbol */

View File

@@ -7,7 +7,7 @@
/* Functions at the bottom */
var codepoints = {
const codepoints = {
0x0100: 0x03c0, // XK_Amacron
0x0101: 0x03e0, // XK_amacron
0x0102: 0x01c3, // XK_Abreve
@@ -670,14 +670,14 @@ var codepoints = {
};
export default {
lookup : function(u) {
lookup(u) {
// Latin-1 is one-to-one mapping
if ((u >= 0x20) && (u <= 0xff)) {
return u;
}
// Lookup table (fairly random)
var keysym = codepoints[u];
const keysym = codepoints[u];
if (keysym !== undefined) {
return keysym;
}

View File

@@ -1,280 +0,0 @@
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2012 Joel Martin
* Copyright (C) 2013 Samuel Mannehed for Cendio AB
* Licensed under MPL 2.0 or any later version (see LICENSE.txt)
*/
import * as Log from '../util/logging.js';
import { isTouchDevice } from '../util/browser.js';
import { setCapture, stopEvent, getPointerEvent } from '../util/events.js';
var WHEEL_STEP = 10; // Delta threshold for a mouse wheel step
var WHEEL_STEP_TIMEOUT = 50; // ms
var WHEEL_LINE_HEIGHT = 19;
export default function Mouse(target) {
this._target = target || document;
this._doubleClickTimer = null;
this._lastTouchPos = null;
this._pos = null;
this._wheelStepXTimer = null;
this._wheelStepYTimer = null;
this._accumulatedWheelDeltaX = 0;
this._accumulatedWheelDeltaY = 0;
this._eventHandlers = {
'mousedown': this._handleMouseDown.bind(this),
'mouseup': this._handleMouseUp.bind(this),
'mousemove': this._handleMouseMove.bind(this),
'mousewheel': this._handleMouseWheel.bind(this),
'mousedisable': this._handleMouseDisable.bind(this)
};
};
Mouse.prototype = {
// ===== PROPERTIES =====
touchButton: 1, // Button mask (1, 2, 4) for touch devices (0 means ignore clicks)
// ===== EVENT HANDLERS =====
onmousebutton: function () {}, // Handler for mouse button click/release
onmousemove: function () {}, // Handler for mouse movement
// ===== PRIVATE METHODS =====
_resetDoubleClickTimer: function () {
this._doubleClickTimer = null;
},
_handleMouseButton: function (e, down) {
this._updateMousePosition(e);
var pos = this._pos;
var bmask;
if (e.touches || e.changedTouches) {
// Touch device
// When two touches occur within 500 ms of each other and are
// close enough together a double click is triggered.
if (down == 1) {
if (this._doubleClickTimer === null) {
this._lastTouchPos = pos;
} else {
clearTimeout(this._doubleClickTimer);
// When the distance between the two touches is small enough
// force the position of the latter touch to the position of
// the first.
var xs = this._lastTouchPos.x - pos.x;
var ys = this._lastTouchPos.y - pos.y;
var d = Math.sqrt((xs * xs) + (ys * ys));
// The goal is to trigger on a certain physical width, the
// devicePixelRatio brings us a bit closer but is not optimal.
var threshold = 20 * (window.devicePixelRatio || 1);
if (d < threshold) {
pos = this._lastTouchPos;
}
}
this._doubleClickTimer = setTimeout(this._resetDoubleClickTimer.bind(this), 500);
}
bmask = this.touchButton;
// If bmask is set
} else if (e.which) {
/* everything except IE */
bmask = 1 << e.button;
} else {
/* IE including 9 */
bmask = (e.button & 0x1) + // Left
(e.button & 0x2) * 2 + // Right
(e.button & 0x4) / 2; // Middle
}
Log.Debug("onmousebutton " + (down ? "down" : "up") +
", x: " + pos.x + ", y: " + pos.y + ", bmask: " + bmask);
this.onmousebutton(pos.x, pos.y, down, bmask);
stopEvent(e);
},
_handleMouseDown: function (e) {
// Touch events have implicit capture
if (e.type === "mousedown") {
setCapture(this._target);
}
this._handleMouseButton(e, 1);
},
_handleMouseUp: function (e) {
this._handleMouseButton(e, 0);
},
// Mouse wheel events are sent in steps over VNC. This means that the VNC
// protocol can't handle a wheel event with specific distance or speed.
// Therefor, if we get a lot of small mouse wheel events we combine them.
_generateWheelStepX: function () {
if (this._accumulatedWheelDeltaX < 0) {
this.onmousebutton(this._pos.x, this._pos.y, 1, 1 << 5);
this.onmousebutton(this._pos.x, this._pos.y, 0, 1 << 5);
} else if (this._accumulatedWheelDeltaX > 0) {
this.onmousebutton(this._pos.x, this._pos.y, 1, 1 << 6);
this.onmousebutton(this._pos.x, this._pos.y, 0, 1 << 6);
}
this._accumulatedWheelDeltaX = 0;
},
_generateWheelStepY: function () {
if (this._accumulatedWheelDeltaY < 0) {
this.onmousebutton(this._pos.x, this._pos.y, 1, 1 << 3);
this.onmousebutton(this._pos.x, this._pos.y, 0, 1 << 3);
} else if (this._accumulatedWheelDeltaY > 0) {
this.onmousebutton(this._pos.x, this._pos.y, 1, 1 << 4);
this.onmousebutton(this._pos.x, this._pos.y, 0, 1 << 4);
}
this._accumulatedWheelDeltaY = 0;
},
_resetWheelStepTimers: function () {
window.clearTimeout(this._wheelStepXTimer);
window.clearTimeout(this._wheelStepYTimer);
this._wheelStepXTimer = null;
this._wheelStepYTimer = null;
},
_handleMouseWheel: function (e) {
this._resetWheelStepTimers();
this._updateMousePosition(e);
var dX = e.deltaX;
var dY = e.deltaY;
// Pixel units unless it's non-zero.
// Note that if deltamode is line or page won't matter since we aren't
// sending the mouse wheel delta to the server anyway.
// The difference between pixel and line can be important however since
// we have a threshold that can be smaller than the line height.
if (e.deltaMode !== 0) {
dX *= WHEEL_LINE_HEIGHT;
dY *= WHEEL_LINE_HEIGHT;
}
this._accumulatedWheelDeltaX += dX;
this._accumulatedWheelDeltaY += dY;
// Generate a mouse wheel step event when the accumulated delta
// for one of the axes is large enough.
// Small delta events that do not pass the threshold get sent
// after a timeout.
if (Math.abs(this._accumulatedWheelDeltaX) > WHEEL_STEP) {
this._generateWheelStepX();
} else {
this._wheelStepXTimer =
window.setTimeout(this._generateWheelStepX.bind(this),
WHEEL_STEP_TIMEOUT);
}
if (Math.abs(this._accumulatedWheelDeltaY) > WHEEL_STEP) {
this._generateWheelStepY();
} else {
this._wheelStepYTimer =
window.setTimeout(this._generateWheelStepY.bind(this),
WHEEL_STEP_TIMEOUT);
}
stopEvent(e);
},
_handleMouseMove: function (e) {
this._updateMousePosition(e);
this.onmousemove(this._pos.x, this._pos.y);
stopEvent(e);
},
_handleMouseDisable: function (e) {
/*
* Stop propagation if inside canvas area
* Note: This is only needed for the 'click' event as it fails
* to fire properly for the target element so we have
* to listen on the document element instead.
*/
if (e.target == this._target) {
stopEvent(e);
}
},
// Update coordinates relative to target
_updateMousePosition: function(e) {
e = getPointerEvent(e);
var bounds = this._target.getBoundingClientRect();
var x, y;
// Clip to target bounds
if (e.clientX < bounds.left) {
x = 0;
} else if (e.clientX >= bounds.right) {
x = bounds.width - 1;
} else {
x = e.clientX - bounds.left;
}
if (e.clientY < bounds.top) {
y = 0;
} else if (e.clientY >= bounds.bottom) {
y = bounds.height - 1;
} else {
y = e.clientY - bounds.top;
}
this._pos = {x:x, y:y};
},
// ===== PUBLIC METHODS =====
grab: function () {
var c = this._target;
if (isTouchDevice) {
c.addEventListener('touchstart', this._eventHandlers.mousedown);
c.addEventListener('touchend', this._eventHandlers.mouseup);
c.addEventListener('touchmove', this._eventHandlers.mousemove);
}
c.addEventListener('mousedown', this._eventHandlers.mousedown);
c.addEventListener('mouseup', this._eventHandlers.mouseup);
c.addEventListener('mousemove', this._eventHandlers.mousemove);
c.addEventListener('wheel', this._eventHandlers.mousewheel);
/* Prevent middle-click pasting (see above for why we bind to document) */
document.addEventListener('click', this._eventHandlers.mousedisable);
/* preventDefault() on mousedown doesn't stop this event for some
reason so we have to explicitly block it */
c.addEventListener('contextmenu', this._eventHandlers.mousedisable);
},
ungrab: function () {
var c = this._target;
this._resetWheelStepTimers();
if (isTouchDevice) {
c.removeEventListener('touchstart', this._eventHandlers.mousedown);
c.removeEventListener('touchend', this._eventHandlers.mouseup);
c.removeEventListener('touchmove', this._eventHandlers.mousemove);
}
c.removeEventListener('mousedown', this._eventHandlers.mousedown);
c.removeEventListener('mouseup', this._eventHandlers.mouseup);
c.removeEventListener('mousemove', this._eventHandlers.mousemove);
c.removeEventListener('wheel', this._eventHandlers.mousewheel);
document.removeEventListener('click', this._eventHandlers.mousedisable);
c.removeEventListener('contextmenu', this._eventHandlers.mousedisable);
}
};

View File

@@ -6,7 +6,7 @@ import DOMKeyTable from "./domkeytable.js";
import * as browser from "../util/browser.js";
// Get 'KeyboardEvent.code', handling legacy browsers
export function getKeycode(evt){
export function getKeycode(evt) {
// Are we getting proper key identifiers?
// (unfortunately Firefox and Chrome are crappy here and gives
// us an empty string on some platforms, rather than leaving it
@@ -25,7 +25,7 @@ export function getKeycode(evt){
// 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)) {
var code = vkeys[evt.keyCode];
let code = vkeys[evt.keyCode];
// macOS has messed up this code for some reason
if (browser.isMac() && (code === 'ContextMenu')) {
@@ -92,6 +92,8 @@ export function getKey(evt) {
// Mozilla isn't fully in sync with the spec yet
switch (evt.key) {
case 'OS': return 'Meta';
case 'LaunchMyComputer': return 'LaunchApplication1';
case 'LaunchCalculator': return 'LaunchApplication2';
}
// iOS leaks some OS names
@@ -103,15 +105,27 @@ export function getKey(evt) {
case 'UIKeyInputEscape': return 'Escape';
}
// IE and Edge have broken handling of AltGraph so we cannot
// trust them for printable characters
if ((evt.key.length !== 1) || (!browser.isIE() && !browser.isEdge())) {
// Broken behaviour in Chrome
if ((evt.key === '\x00') && (evt.code === 'NumpadDecimal')) {
return 'Delete';
}
// IE and Edge need special handling, but for everyone else we
// can trust the value provided
if (!browser.isIE() && !browser.isEdge()) {
return evt.key;
}
// IE and Edge have broken handling of AltGraph so we can only
// trust them for non-printable characters (and unfortunately
// they also specify 'Unidentified' for some problem keys)
if ((evt.key.length !== 1) && (evt.key !== 'Unidentified')) {
return evt.key;
}
}
// Try to deduce it based on the physical key
var code = getKeycode(evt);
const code = getKeycode(evt);
if (code in fixedkeys) {
return fixedkeys[code];
}
@@ -126,8 +140,8 @@ export function getKey(evt) {
}
// Get the most reliable keysym value we can get from a key event
export function getKeysym(evt){
var key = getKey(evt);
export function getKeysym(evt) {
const key = getKey(evt);
if (key === 'Unidentified') {
return null;
@@ -135,30 +149,57 @@ export function getKeysym(evt){
// First look up special keys
if (key in DOMKeyTable) {
var location = evt.location;
let location = evt.location;
// Safari screws up location for the right cmd key
if ((key === 'Meta') && (location === 0)) {
location = 2;
}
// And for Clear
if ((key === 'Clear') && (location === 3)) {
let code = getKeycode(evt);
if (code === 'NumLock') {
location = 0;
}
}
if ((location === undefined) || (location > 3)) {
location = 0;
}
// The original Meta key now gets confused with the Windows key
// https://bugs.chromium.org/p/chromium/issues/detail?id=1020141
// https://bugzilla.mozilla.org/show_bug.cgi?id=1232918
if (key === 'Meta') {
let code = getKeycode(evt);
if (code === 'AltLeft') {
return KeyTable.XK_Meta_L;
} else if (code === 'AltRight') {
return KeyTable.XK_Meta_R;
}
}
// macOS has Clear instead of NumLock, but the remote system is
// probably not macOS, so lying here is probably best...
if (key === 'Clear') {
let code = getKeycode(evt);
if (code === 'NumLock') {
return KeyTable.XK_Num_Lock;
}
}
return DOMKeyTable[key][location];
}
// Now we need to look at the Unicode symbol instead
var codepoint;
// Special key? (FIXME: Should have been caught earlier)
if (key.length !== 1) {
return null;
}
codepoint = key.charCodeAt();
const codepoint = key.charCodeAt();
if (codepoint) {
return keysyms.lookup(codepoint);
}

View File

@@ -1,6 +1,6 @@
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2017 Pierre Ossman for Cendio AB
* Copyright (C) 2018 The noVNC Authors
* Licensed under MPL 2.0 or any later version (see LICENSE.txt)
*/
@@ -13,6 +13,7 @@ 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',

File diff suppressed because it is too large Load Diff

View File

@@ -1,15 +1,17 @@
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2012 Joel Martin
* Copyright (C) 2019 The noVNC Authors
* Licensed under MPL 2.0 (see LICENSE.txt)
*
* See README.md for usage and integration instructions.
*
* Browser feature support detection
*/
import * as Log from './logging.js';
// Touch detection
export var isTouchDevice = ('ontouchstart' in document.documentElement) ||
export let isTouchDevice = ('ontouchstart' in document.documentElement) ||
// requried for Chrome debugger
(document.ontouchstart !== undefined) ||
// required for MS Surface
@@ -20,42 +22,74 @@ window.addEventListener('touchstart', function onFirstTouch() {
window.removeEventListener('touchstart', onFirstTouch, false);
}, false);
var _cursor_uris_supported = null;
export function supportsCursorURIs () {
if (_cursor_uris_supported === null) {
try {
var target = document.createElement('canvas');
target.style.cursor = 'url("data:image/x-icon;base64,AAACAAEACAgAAAIAAgA4AQAAFgAAACgAAAAIAAAAEAAAAAEAIAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAAAAAAAAAAAAAAAA==") 2 2, default';
// The goal is to find a certain physical width, the devicePixelRatio
// brings us a bit closer but is not optimal.
export let dragThreshold = 10 * (window.devicePixelRatio || 1);
if (target.style.cursor) {
Log.Info("Data URI scheme cursor supported");
_cursor_uris_supported = true;
} else {
Log.Warn("Data URI scheme cursor not supported");
_cursor_uris_supported = false;
}
} catch (exc) {
Log.Error("Data URI scheme cursor test exception: " + exc);
_cursor_uris_supported = false;
}
let _supportsCursorURIs = false;
try {
const target = document.createElement('canvas');
target.style.cursor = 'url("data:image/x-icon;base64,AAACAAEACAgAAAIAAgA4AQAAFgAAACgAAAAIAAAAEAAAAAEAIAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAAAAAAAAAAAAAAAA==") 2 2, default';
if (target.style.cursor.indexOf("url") === 0) {
Log.Info("Data URI scheme cursor supported");
_supportsCursorURIs = true;
} else {
Log.Warn("Data URI scheme cursor not supported");
}
} catch (exc) {
Log.Error("Data URI scheme cursor test exception: " + exc);
}
return _cursor_uris_supported;
};
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
const container = document.createElement('div');
container.style.visibility = 'hidden';
container.style.overflow = 'scroll'; // forcing scrollbars
document.body.appendChild(container);
// Create a div and place it in the container
const child = document.createElement('div');
container.appendChild(child);
// Calculate the difference between the container's full width
// and the child's width - the difference is the scrollbars
const scrollbarWidth = (container.offsetWidth - child.offsetWidth);
// Clean up
container.parentNode.removeChild(container);
_hasScrollbarGutter = scrollbarWidth != 0;
} catch (exc) {
Log.Error("Scrollbar test exception: " + exc);
}
export const hasScrollbarGutter = _hasScrollbarGutter;
/*
* The functions for detection of platforms and browsers below are exported
* but the use of these should be minimized as much as possible.
*
* It's better to use feature detection than platform detection.
*/
export function isMac() {
return navigator && !!(/mac/i).exec(navigator.platform);
}
export function isIE() {
return navigator && !!(/trident/i).exec(navigator.userAgent);
}
export function isEdge() {
return navigator && !!(/edge/i).exec(navigator.userAgent);
}
export function isWindows() {
return navigator && !!(/win/i).exec(navigator.platform);
}
@@ -67,3 +101,20 @@ export function isIOS() {
!!(/ipod/i).exec(navigator.platform));
}
export function isSafari() {
return navigator && (navigator.userAgent.indexOf('Safari') !== -1 &&
navigator.userAgent.indexOf('Chrome') === -1);
}
export function isIE() {
return navigator && !!(/trident/i).exec(navigator.userAgent);
}
export function isEdge() {
return navigator && !!(/edge/i).exec(navigator.userAgent);
}
export function isFirefox() {
return navigator && !!(/firefox/i).exec(navigator.userAgent);
}

253
core/util/cursor.js Normal file
View File

@@ -0,0 +1,253 @@
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2019 The noVNC Authors
* Licensed under MPL 2.0 or any later version (see LICENSE.txt)
*/
import { supportsCursorURIs, isTouchDevice } from './browser.js';
const useFallback = !supportsCursorURIs || isTouchDevice;
export default class Cursor {
constructor() {
this._target = null;
this._canvas = document.createElement('canvas');
if (useFallback) {
this._canvas.style.position = 'fixed';
this._canvas.style.zIndex = '65535';
this._canvas.style.pointerEvents = 'none';
// Can't use "display" because of Firefox bug #1445997
this._canvas.style.visibility = 'hidden';
}
this._position = { x: 0, y: 0 };
this._hotSpot = { x: 0, y: 0 };
this._eventHandlers = {
'mouseover': this._handleMouseOver.bind(this),
'mouseleave': this._handleMouseLeave.bind(this),
'mousemove': this._handleMouseMove.bind(this),
'mouseup': this._handleMouseUp.bind(this),
};
}
attach(target) {
if (this._target) {
this.detach();
}
this._target = target;
if (useFallback) {
document.body.appendChild(this._canvas);
// FIXME: These don't fire properly except for mouse
/// movement in IE. We want to also capture element
// movement, size changes, visibility, etc.
const options = { capture: true, passive: true };
this._target.addEventListener('mouseover', this._eventHandlers.mouseover, options);
this._target.addEventListener('mouseleave', this._eventHandlers.mouseleave, options);
this._target.addEventListener('mousemove', this._eventHandlers.mousemove, options);
this._target.addEventListener('mouseup', this._eventHandlers.mouseup, options);
}
this.clear();
}
detach() {
if (!this._target) {
return;
}
if (useFallback) {
const options = { capture: true, passive: true };
this._target.removeEventListener('mouseover', this._eventHandlers.mouseover, options);
this._target.removeEventListener('mouseleave', this._eventHandlers.mouseleave, options);
this._target.removeEventListener('mousemove', this._eventHandlers.mousemove, options);
this._target.removeEventListener('mouseup', this._eventHandlers.mouseup, options);
document.body.removeChild(this._canvas);
}
this._target = null;
}
change(rgba, hotx, hoty, w, h) {
if ((w === 0) || (h === 0)) {
this.clear();
return;
}
this._position.x = this._position.x + this._hotSpot.x - hotx;
this._position.y = this._position.y + this._hotSpot.y - hoty;
this._hotSpot.x = hotx;
this._hotSpot.y = hoty;
let ctx = this._canvas.getContext('2d');
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));
}
ctx.clearRect(0, 0, w, h);
ctx.putImageData(img, 0, 0);
if (useFallback) {
this._updatePosition();
} else {
let url = this._canvas.toDataURL();
this._target.style.cursor = 'url(' + url + ')' + hotx + ' ' + hoty + ', default';
}
}
clear() {
this._target.style.cursor = 'none';
this._canvas.width = 0;
this._canvas.height = 0;
this._position.x = this._position.x + this._hotSpot.x;
this._position.y = this._position.y + this._hotSpot.y;
this._hotSpot.x = 0;
this._hotSpot.y = 0;
}
// Mouse events might be emulated, this allows
// moving the cursor in such cases
move(clientX, clientY) {
if (!useFallback) {
return;
}
// clientX/clientY are relative the _visual viewport_,
// but our position is relative the _layout viewport_,
// so try to compensate when we can
if (window.visualViewport) {
this._position.x = clientX + window.visualViewport.offsetLeft;
this._position.y = clientY + window.visualViewport.offsetTop;
} else {
this._position.x = clientX;
this._position.y = clientY;
}
this._updatePosition();
let target = document.elementFromPoint(clientX, clientY);
this._updateVisibility(target);
}
_handleMouseOver(event) {
// This event could be because we're entering the target, or
// moving around amongst its sub elements. Let the move handler
// sort things out.
this._handleMouseMove(event);
}
_handleMouseLeave(event) {
// Check if we should show the cursor on the element we are leaving to
this._updateVisibility(event.relatedTarget);
}
_handleMouseMove(event) {
this._updateVisibility(event.target);
this._position.x = event.clientX - this._hotSpot.x;
this._position.y = event.clientY - this._hotSpot.y;
this._updatePosition();
}
_handleMouseUp(event) {
// We might get this event because of a drag operation that
// moved outside of the target. Check what's under the cursor
// now and adjust visibility based on that.
let target = document.elementFromPoint(event.clientX, event.clientY);
this._updateVisibility(target);
// Captures end with a mouseup but we can't know the event order of
// mouseup vs releaseCapture.
//
// In the cases when releaseCapture comes first, the code above is
// enough.
//
// In the cases when the mouseup comes first, we need wait for the
// browser to flush all events and then check again if the cursor
// should be visible.
if (this._captureIsActive()) {
window.setTimeout(() => {
// We might have detached at this point
if (!this._target) {
return;
}
// Refresh the target from elementFromPoint since queued events
// might have altered the DOM
target = document.elementFromPoint(event.clientX,
event.clientY);
this._updateVisibility(target);
}, 0);
}
}
_showCursor() {
if (this._canvas.style.visibility === 'hidden') {
this._canvas.style.visibility = '';
}
}
_hideCursor() {
if (this._canvas.style.visibility !== 'hidden') {
this._canvas.style.visibility = 'hidden';
}
}
// Should we currently display the cursor?
// (i.e. are we over the target, or a child of the target without a
// different cursor set)
_shouldShowCursor(target) {
if (!target) {
return false;
}
// Easy case
if (target === this._target) {
return true;
}
// Other part of the DOM?
if (!this._target.contains(target)) {
return false;
}
// Has the child its own cursor?
// FIXME: How can we tell that a sub element has an
// explicit "cursor: none;"?
if (window.getComputedStyle(target).cursor !== 'none') {
return false;
}
return true;
}
_updateVisibility(target) {
// When the cursor target has capture we want to show the cursor.
// So, if a capture is active - look at the captured element instead.
if (this._captureIsActive()) {
target = document.captureElement;
}
if (this._shouldShowCursor(target)) {
this._showCursor();
} else {
this._hideCursor();
}
}
_updatePosition() {
this._canvas.style.left = this._position.x + "px";
this._canvas.style.top = this._position.y + "px";
}
_captureIsActive() {
return document.captureElement &&
document.documentElement.contains(document.captureElement);
}
}

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

@@ -0,0 +1,32 @@
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2020 The noVNC Authors
* Licensed under MPL 2.0 (see LICENSE.txt)
*
* See README.md for usage and integration instructions.
*/
/*
* HTML element utility functions
*/
export function clientToElement(x, y, elem) {
const bounds = elem.getBoundingClientRect();
let pos = { x: 0, y: 0 };
// Clip to target bounds
if (x < bounds.left) {
pos.x = 0;
} else if (x >= bounds.right) {
pos.x = bounds.width - 1;
} else {
pos.x = x - bounds.left;
}
if (y < bounds.top) {
pos.y = 0;
} else if (y >= bounds.bottom) {
pos.y = bounds.height - 1;
} else {
pos.y = y - bounds.top;
}
return pos;
}

View File

@@ -1,6 +1,6 @@
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2012 Joel Martin
* Copyright (C) 2018 The noVNC Authors
* Licensed under MPL 2.0 (see LICENSE.txt)
*
* See README.md for usage and integration instructions.
@@ -10,27 +10,32 @@
* Cross-browser event and position routines
*/
export function getPointerEvent (e) {
export function getPointerEvent(e) {
return e.changedTouches ? e.changedTouches[0] : e.touches ? e.touches[0] : e;
};
}
export function stopEvent (e) {
export function stopEvent(e) {
e.stopPropagation();
e.preventDefault();
};
}
// Emulate Element.setCapture() when not supported
var _captureRecursion = false;
var _captureElem = null;
let _captureRecursion = false;
let _elementForUnflushedEvents = null;
document.captureElement = null;
function _captureProxy(e) {
// Recursion protection as we'll see our own event
if (_captureRecursion) return;
// Clone the event as we cannot dispatch an already dispatched event
var newEv = new e.constructor(e.type, e);
const newEv = new e.constructor(e.type, e);
_captureRecursion = true;
_captureElem.dispatchEvent(newEv);
if (document.captureElement) {
document.captureElement.dispatchEvent(newEv);
} else {
_elementForUnflushedEvents.dispatchEvent(newEv);
}
_captureRecursion = false;
// Avoid double events
@@ -45,94 +50,93 @@ function _captureProxy(e) {
if (e.type === "mouseup") {
releaseCapture();
}
};
}
// Follow cursor style of target element
function _captureElemChanged() {
var captureElem = document.getElementById("noVNC_mouse_capture_elem");
captureElem.style.cursor = window.getComputedStyle(_captureElem).cursor;
};
var _captureObserver = new MutationObserver(_captureElemChanged);
function _capturedElemChanged() {
const proxyElem = document.getElementById("noVNC_mouse_capture_elem");
proxyElem.style.cursor = window.getComputedStyle(document.captureElement).cursor;
}
var _captureIndex = 0;
const _captureObserver = new MutationObserver(_capturedElemChanged);
export function setCapture (elem) {
if (elem.setCapture) {
export function setCapture(target) {
if (target.setCapture) {
elem.setCapture();
target.setCapture();
document.captureElement = target;
// IE releases capture on 'click' events which might not trigger
elem.addEventListener('mouseup', releaseCapture);
target.addEventListener('mouseup', releaseCapture);
} else {
// Release any existing capture in case this method is
// called multiple times without coordination
releaseCapture();
var captureElem = document.getElementById("noVNC_mouse_capture_elem");
let proxyElem = document.getElementById("noVNC_mouse_capture_elem");
if (captureElem === null) {
captureElem = document.createElement("div");
captureElem.id = "noVNC_mouse_capture_elem";
captureElem.style.position = "fixed";
captureElem.style.top = "0px";
captureElem.style.left = "0px";
captureElem.style.width = "100%";
captureElem.style.height = "100%";
captureElem.style.zIndex = 10000;
captureElem.style.display = "none";
document.body.appendChild(captureElem);
if (proxyElem === null) {
proxyElem = document.createElement("div");
proxyElem.id = "noVNC_mouse_capture_elem";
proxyElem.style.position = "fixed";
proxyElem.style.top = "0px";
proxyElem.style.left = "0px";
proxyElem.style.width = "100%";
proxyElem.style.height = "100%";
proxyElem.style.zIndex = 10000;
proxyElem.style.display = "none";
document.body.appendChild(proxyElem);
// This is to make sure callers don't get confused by having
// our blocking element as the target
captureElem.addEventListener('contextmenu', _captureProxy);
proxyElem.addEventListener('contextmenu', _captureProxy);
captureElem.addEventListener('mousemove', _captureProxy);
captureElem.addEventListener('mouseup', _captureProxy);
proxyElem.addEventListener('mousemove', _captureProxy);
proxyElem.addEventListener('mouseup', _captureProxy);
}
_captureElem = elem;
_captureIndex++;
document.captureElement = target;
// Track cursor and get initial cursor
_captureObserver.observe(elem, {attributes:true});
_captureElemChanged();
_captureObserver.observe(target, {attributes: true});
_capturedElemChanged();
captureElem.style.display = "";
proxyElem.style.display = "";
// We listen to events on window in order to keep tracking if it
// happens to leave the viewport
window.addEventListener('mousemove', _captureProxy);
window.addEventListener('mouseup', _captureProxy);
}
};
}
export function releaseCapture () {
export function releaseCapture() {
if (document.releaseCapture) {
document.releaseCapture();
document.captureElement = null;
} else {
if (!_captureElem) {
if (!document.captureElement) {
return;
}
// There might be events already queued, so we need to wait for
// them to flush. E.g. contextmenu in Microsoft Edge
window.setTimeout(function(expected) {
// Only clear it if it's the expected grab (i.e. no one
// else has initiated a new grab)
if (_captureIndex === expected) {
_captureElem = null;
}
}, 0, _captureIndex);
// There might be events already queued. The event proxy needs
// access to the captured element for these queued events.
// E.g. contextmenu (right-click) in Microsoft Edge
//
// Before removing the capturedElem pointer we save it to a
// temporary variable that the unflushed events can use.
_elementForUnflushedEvents = document.captureElement;
document.captureElement = null;
_captureObserver.disconnect();
var captureElem = document.getElementById("noVNC_mouse_capture_elem");
captureElem.style.display = "none";
const proxyElem = document.getElementById("noVNC_mouse_capture_elem");
proxyElem.style.display = "none";
window.removeEventListener('mousemove', _captureProxy);
window.removeEventListener('mouseup', _captureProxy);
}
};
}

View File

@@ -1,40 +1,35 @@
/*
* noVNC: HTML5 VNC client
* Copyright 2017 Pierre Ossman for Cendio AB
* Copyright (C) 2019 The noVNC Authors
* Licensed under MPL 2.0 (see LICENSE.txt)
*
* See README.md for usage and integration instructions.
*/
var EventTargetMixin = {
_listeners: null,
export default class EventTargetMixin {
constructor() {
this._listeners = new Map();
}
addEventListener: function(type, callback) {
if (!this._listeners) {
this._listeners = new Map();
}
if (!this._listeners.has(type)) {
this._listeners.set(type, new Set());
}
this._listeners.get(type).add(callback);
},
addEventListener(type, callback) {
if (!this._listeners.has(type)) {
this._listeners.set(type, new Set());
}
this._listeners.get(type).add(callback);
}
removeEventListener: function(type, callback) {
if (!this._listeners || !this._listeners.has(type)) {
return;
}
this._listeners.get(type).delete(callback);
},
removeEventListener(type, callback) {
if (this._listeners.has(type)) {
this._listeners.get(type).delete(callback);
}
}
dispatchEvent: function(event) {
if (!this._listeners || !this._listeners.has(event.type)) {
return true;
}
this._listeners.get(event.type).forEach(function (callback) {
callback.call(this, event);
}, this);
return !event.defaultPrevented;
},
};
export default EventTargetMixin;
dispatchEvent(event) {
if (!this._listeners.has(event.type)) {
return true;
}
this._listeners.get(event.type)
.forEach(callback => callback.call(this, event));
return !event.defaultPrevented;
}
}

15
core/util/int.js Normal file
View File

@@ -0,0 +1,15 @@
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2020 The noVNC Authors
* Licensed under MPL 2.0 (see LICENSE.txt)
*
* See README.md for usage and integration instructions.
*/
export function toUnsigned32bit(toConvert) {
return toConvert >>> 0;
}
export function toSigned32bit(toConvert) {
return toConvert | 0;
}

View File

@@ -1,6 +1,6 @@
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2012 Joel Martin
* Copyright (C) 2019 The noVNC Authors
* Licensed under MPL 2.0 (see LICENSE.txt)
*
* See README.md for usage and integration instructions.
@@ -10,22 +10,24 @@
* Logging/debug routines
*/
var _log_level = 'warn';
let _logLevel = 'warn';
var Debug = function (msg) {};
var Info = function (msg) {};
var Warn = function (msg) {};
var Error = function (msg) {};
let Debug = () => {};
let Info = () => {};
let Warn = () => {};
let Error = () => {};
export function init_logging (level) {
export function initLogging(level) {
if (typeof level === 'undefined') {
level = _log_level;
level = _logLevel;
} else {
_log_level = level;
_logLevel = level;
}
Debug = Info = Warn = Error = function (msg) {};
Debug = Info = Warn = Error = () => {};
if (typeof window.console !== "undefined") {
/* eslint-disable no-console, no-fallthrough */
switch (level) {
case 'debug':
Debug = console.debug.bind(window.console);
@@ -38,14 +40,17 @@ export function init_logging (level) {
case 'none':
break;
default:
throw new Error("invalid logging type '" + level + "'");
throw new window.Error("invalid logging type '" + level + "'");
}
/* eslint-enable no-console, no-fallthrough */
}
};
export function get_logging () {
return _log_level;
};
}
export function getLogging() {
return _logLevel;
}
export { Debug, Info, Warn, Error };
// Initialize logging level
init_logging();
initLogging();

View File

@@ -1,6 +1,6 @@
/*
* noVNC: HTML5 VNC client
* Copyright 2017 Pierre Ossman for noVNC
* Copyright (C) 2020 The noVNC Authors
* Licensed under MPL 2.0 or any later version (see LICENSE.txt)
*/
@@ -16,13 +16,13 @@ if (typeof Object.assign != 'function') {
throw new TypeError('Cannot convert undefined or null to object');
}
var to = Object(target);
const to = Object(target);
for (var index = 1; index < arguments.length; index++) {
var nextSource = arguments[index];
for (let index = 1; index < arguments.length; index++) {
const nextSource = arguments[index];
if (nextSource != null) { // Skip over if undefined or null
for (var nextKey in nextSource) {
for (let nextKey in nextSource) {
// Avoid bugs when hasOwnProperty is shadowed
if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
to[nextKey] = nextSource[nextKey];
@@ -38,10 +38,10 @@ if (typeof Object.assign != 'function') {
}
/* CustomEvent constructor (taken from MDN) */
(function () {
function CustomEvent ( event, params ) {
(() => {
function CustomEvent(event, params) {
params = params || { bubbles: false, cancelable: false, detail: undefined };
var evt = document.createEvent( 'CustomEvent' );
const evt = document.createEvent( 'CustomEvent' );
evt.initCustomEvent( event, params.bubbles, params.cancelable, params.detail );
return evt;
}
@@ -52,3 +52,10 @@ if (typeof Object.assign != 'function') {
window.CustomEvent = CustomEvent;
}
})();
/* Number.isInteger() (taken from MDN) */
Number.isInteger = Number.isInteger || function isInteger(value) {
return typeof value === 'number' &&
isFinite(value) &&
Math.floor(value) === value;
};

View File

@@ -1,15 +1,28 @@
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2012 Joel Martin
* Copyright (C) 2019 The noVNC Authors
* Licensed under MPL 2.0 (see LICENSE.txt)
*
* See README.md for usage and integration instructions.
*/
/*
* Decode from UTF-8
*/
export function decodeUTF8 (utf8string) {
"use strict";
return decodeURIComponent(escape(utf8string));
};
// Decode from UTF-8
export function decodeUTF8(utf8string, allowLatin1=false) {
try {
return decodeURIComponent(escape(utf8string));
} catch (e) {
if (e instanceof URIError) {
if (allowLatin1) {
// If we allow Latin1 we can ignore any decoding fails
// and in these cases return the original string
return utf8string;
}
}
throw e;
}
}
// Encode to UTF-8
export function encodeUTF8(DOMString) {
return unescape(encodeURIComponent(DOMString));
}

View File

@@ -1,6 +1,6 @@
/*
* Websock: high-performance binary WebSockets
* Copyright (C) 2012 Joel Martin
* 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
@@ -14,143 +14,121 @@
import * as Log from './util/logging.js';
export default function Websock() {
"use strict";
this._websocket = null; // WebSocket object
this._rQi = 0; // Receive queue index
this._rQlen = 0; // Next write position in the receive queue
this._rQbufferSize = 1024 * 1024 * 4; // Receive queue buffer size (4 MiB)
this._rQmax = this._rQbufferSize / 8;
// called in init: this._rQ = new Uint8Array(this._rQbufferSize);
this._rQ = null; // Receive queue
this._sQbufferSize = 1024 * 10; // 10 KiB
// called in init: this._sQ = new Uint8Array(this._sQbufferSize);
this._sQlen = 0;
this._sQ = null; // Send queue
this._eventHandlers = {
'message': function () {},
'open': function () {},
'close': function () {},
'error': function () {}
};
};
// 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.
var ENABLE_COPYWITHIN = false;
// 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
var MAX_RQ_GROW_SIZE = 40 * 1024 * 1024; // 40 MiB
export default class Websock {
constructor() {
this._websocket = null; // WebSocket object
var typedArrayToString = (function () {
// This is only for PhantomJS, which doesn't like apply-ing
// with Typed Arrays
try {
var arr = new Uint8Array([1, 2, 3]);
String.fromCharCode.apply(null, arr);
return function (a) { return String.fromCharCode.apply(null, a); };
} catch (ex) {
return function (a) {
return String.fromCharCode.apply(
null, Array.prototype.slice.call(a));
this._rQi = 0; // Receive queue index
this._rQlen = 0; // Next write position in the receive queue
this._rQbufferSize = 1024 * 1024 * 4; // Receive queue buffer size (4 MiB)
// called in init: this._rQ = new Uint8Array(this._rQbufferSize);
this._rQ = null; // Receive queue
this._sQbufferSize = 1024 * 10; // 10 KiB
// called in init: this._sQ = new Uint8Array(this._sQbufferSize);
this._sQlen = 0;
this._sQ = null; // Send queue
this._eventHandlers = {
message: () => {},
open: () => {},
close: () => {},
error: () => {}
};
}
})();
Websock.prototype = {
// Getters and Setters
get_sQ: function () {
get sQ() {
return this._sQ;
},
}
get_rQ: function () {
get rQ() {
return this._rQ;
},
}
get_rQi: function () {
get rQi() {
return this._rQi;
},
}
set_rQi: function (val) {
set rQi(val) {
this._rQi = val;
},
}
// Receive Queue
rQlen: function () {
get rQlen() {
return this._rQlen - this._rQi;
},
}
rQpeek8: function () {
rQpeek8() {
return this._rQ[this._rQi];
},
}
rQshift8: function () {
return this._rQ[this._rQi++];
},
rQskipBytes(bytes) {
this._rQi += bytes;
}
rQskip8: function () {
this._rQi++;
},
rQshift8() {
return this._rQshift(1);
}
rQskipBytes: function (num) {
this._rQi += num;
},
rQshift16() {
return this._rQshift(2);
}
rQshift32() {
return this._rQshift(4);
}
// TODO(directxman12): test performance with these vs a DataView
rQshift16: function () {
return (this._rQ[this._rQi++] << 8) +
this._rQ[this._rQi++];
},
_rQshift(bytes) {
let res = 0;
for (let byte = bytes - 1; byte >= 0; byte--) {
res += this._rQ[this._rQi++] << (byte * 8);
}
return res;
}
rQshift32: function () {
return (this._rQ[this._rQi++] << 24) +
(this._rQ[this._rQi++] << 16) +
(this._rQ[this._rQi++] << 8) +
this._rQ[this._rQi++];
},
rQshiftStr(len) {
if (typeof(len) === 'undefined') { len = this.rQlen; }
let str = "";
// Handle large arrays in steps to avoid long strings on the stack
for (let i = 0; i < len; i += 4096) {
let part = this.rQshiftBytes(Math.min(4096, len - i));
str += String.fromCharCode.apply(null, part);
}
return str;
}
rQshiftStr: function (len) {
if (typeof(len) === 'undefined') { len = this.rQlen(); }
var arr = new Uint8Array(this._rQ.buffer, this._rQi, len);
this._rQi += len;
return typedArrayToString(arr);
},
rQshiftBytes: function (len) {
if (typeof(len) === 'undefined') { len = this.rQlen(); }
rQshiftBytes(len) {
if (typeof(len) === 'undefined') { len = this.rQlen; }
this._rQi += len;
return new Uint8Array(this._rQ.buffer, this._rQi - len, len);
},
}
rQshiftTo: function (target, len) {
if (len === undefined) { len = this.rQlen(); }
rQshiftTo(target, len) {
if (len === undefined) { len = this.rQlen; }
// TODO: make this just use set with views when using a ArrayBuffer to store the rQ
target.set(new Uint8Array(this._rQ.buffer, this._rQi, len));
this._rQi += len;
},
}
rQwhole: function () {
return new Uint8Array(this._rQ.buffer, 0, this._rQlen);
},
rQslice: function (start, end) {
if (end) {
return new Uint8Array(this._rQ.buffer, this._rQi + start, end - start);
} else {
return new Uint8Array(this._rQ.buffer, this._rQi + start, this._rQlen - this._rQi - start);
}
},
rQslice(start, end = this.rQlen) {
return new Uint8Array(this._rQ.buffer, this._rQi + start, end - start);
}
// Check to see if we must wait for 'num' bytes (default to FBU.bytes)
// to be available in the receive queue. Return true if we need to
// wait (and possibly print a debug message), otherwise false.
rQwait: function (msg, num, goback) {
var rQlen = this._rQlen - this._rQi; // Skip rQlen() function call
if (rQlen < num) {
rQwait(msg, num, goback) {
if (this.rQlen < num) {
if (goback) {
if (this._rQi < goback) {
throw new Error("rQwait cannot backup " + goback + " bytes");
@@ -160,58 +138,55 @@ Websock.prototype = {
return true; // true means need more data
}
return false;
},
}
// Send Queue
flush: function () {
flush() {
if (this._sQlen > 0 && this._websocket.readyState === WebSocket.OPEN) {
this._websocket.send(this._encode_message());
this._websocket.send(this._encodeMessage());
this._sQlen = 0;
}
},
}
send: function (arr) {
send(arr) {
this._sQ.set(arr, this._sQlen);
this._sQlen += arr.length;
this.flush();
},
}
send_string: function (str) {
this.send(str.split('').map(function (chr) {
return chr.charCodeAt(0);
}));
},
sendString(str) {
this.send(str.split('').map(chr => chr.charCodeAt(0)));
}
// Event Handlers
off: function (evt) {
this._eventHandlers[evt] = function () {};
},
off(evt) {
this._eventHandlers[evt] = () => {};
}
on: function (evt, handler) {
on(evt, handler) {
this._eventHandlers[evt] = handler;
},
}
_allocate_buffers: function () {
_allocateBuffers() {
this._rQ = new Uint8Array(this._rQbufferSize);
this._sQ = new Uint8Array(this._sQbufferSize);
},
}
init: function () {
this._allocate_buffers();
init() {
this._allocateBuffers();
this._rQi = 0;
this._websocket = null;
},
}
open: function (uri, protocols) {
var ws_schema = uri.match(/^([a-z]+):\/\//)[1];
open(uri, protocols) {
this.init();
this._websocket = new WebSocket(uri, protocols);
this._websocket.binaryType = 'arraybuffer';
this._websocket.onmessage = this._recv_message.bind(this);
this._websocket.onopen = (function () {
this._websocket.onmessage = this._recvMessage.bind(this);
this._websocket.onopen = () => {
Log.Debug('>> WebSock.onopen');
if (this._websocket.protocol) {
Log.Info("Server choose sub-protocol: " + this._websocket.protocol);
@@ -219,20 +194,20 @@ Websock.prototype = {
this._eventHandlers.open();
Log.Debug("<< WebSock.onopen");
}).bind(this);
this._websocket.onclose = (function (e) {
};
this._websocket.onclose = (e) => {
Log.Debug(">> WebSock.onclose");
this._eventHandlers.close(e);
Log.Debug("<< WebSock.onclose");
}).bind(this);
this._websocket.onerror = (function (e) {
};
this._websocket.onerror = (e) => {
Log.Debug(">> WebSock.onerror: " + e);
this._eventHandlers.error(e);
Log.Debug("<< WebSock.onerror: " + e);
}).bind(this);
},
};
}
close: function () {
close() {
if (this._websocket) {
if ((this._websocket.readyState === WebSocket.OPEN) ||
(this._websocket.readyState === WebSocket.CONNECTING)) {
@@ -240,77 +215,80 @@ Websock.prototype = {
this._websocket.close();
}
this._websocket.onmessage = function (e) { return; };
this._websocket.onmessage = () => {};
}
},
}
// private methods
_encode_message: function () {
_encodeMessage() {
// Put in a binary arraybuffer
// according to the spec, you can send ArrayBufferViews with the send method
return new Uint8Array(this._sQ.buffer, 0, this._sQlen);
},
}
// We want to move all the unread data to the start of the queue,
// e.g. compacting.
// The function also expands the receive que if needed, and for
// performance reasons we combine these two actions to avoid
// unneccessary copying.
_expandCompactRQ(minFit) {
// if we're using less than 1/8th of the buffer even with the incoming bytes, compact in place
// instead of resizing
const requiredBufferSize = (this._rQlen - this._rQi + minFit) * 8;
const resizeNeeded = this._rQbufferSize < requiredBufferSize;
_expand_compact_rQ: function (min_fit) {
var resizeNeeded = min_fit || this._rQlen - this._rQi > this._rQbufferSize / 2;
if (resizeNeeded) {
if (!min_fit) {
// just double the size if we need to do compaction
this._rQbufferSize *= 2;
} else {
// otherwise, make sure we satisy rQlen - rQi + min_fit < rQbufferSize / 8
this._rQbufferSize = (this._rQlen - this._rQi + min_fit) * 8;
}
// Make sure we always *at least* double the buffer size, and have at least space for 8x
// the current amount of data
this._rQbufferSize = Math.max(this._rQbufferSize * 2, requiredBufferSize);
}
// we don't want to grow unboundedly
if (this._rQbufferSize > MAX_RQ_GROW_SIZE) {
this._rQbufferSize = MAX_RQ_GROW_SIZE;
if (this._rQbufferSize - this._rQlen - this._rQi < min_fit) {
throw new Exception("Receive Queue buffer exceeded " + MAX_RQ_GROW_SIZE + " bytes, and the new message could not fit");
if (this._rQbufferSize - this.rQlen < minFit) {
throw new Error("Receive Queue buffer exceeded " + MAX_RQ_GROW_SIZE + " bytes, and the new message could not fit");
}
}
if (resizeNeeded) {
var old_rQbuffer = this._rQ.buffer;
this._rQmax = this._rQbufferSize / 8;
const oldRQbuffer = this._rQ.buffer;
this._rQ = new Uint8Array(this._rQbufferSize);
this._rQ.set(new Uint8Array(old_rQbuffer, this._rQi));
this._rQ.set(new Uint8Array(oldRQbuffer, this._rQi, this._rQlen - this._rQi));
} else {
if (ENABLE_COPYWITHIN) {
this._rQ.copyWithin(0, this._rQi);
this._rQ.copyWithin(0, this._rQi, this._rQlen);
} else {
this._rQ.set(new Uint8Array(this._rQ.buffer, this._rQi));
this._rQ.set(new Uint8Array(this._rQ.buffer, this._rQi, this._rQlen - this._rQi));
}
}
this._rQlen = this._rQlen - this._rQi;
this._rQi = 0;
},
}
_decode_message: function (data) {
// push arraybuffer values onto the end
var u8 = new Uint8Array(data);
// push arraybuffer values onto the end of the receive que
_DecodeMessage(data) {
const u8 = new Uint8Array(data);
if (u8.length > this._rQbufferSize - this._rQlen) {
this._expand_compact_rQ(u8.length);
this._expandCompactRQ(u8.length);
}
this._rQ.set(u8, this._rQlen);
this._rQlen += u8.length;
},
}
_recv_message: function (e) {
this._decode_message(e.data);
if (this.rQlen() > 0) {
_recvMessage(e) {
this._DecodeMessage(e.data);
if (this.rQlen > 0) {
this._eventHandlers.message();
// Compact the receive queue
if (this._rQlen == this._rQi) {
// All data has now been processed, this means we
// can reset the receive queue.
this._rQlen = 0;
this._rQi = 0;
} else if (this._rQlen > this._rQmax) {
this._expand_compact_rQ();
}
} else {
Log.Debug("Ignoring empty message");
}
}
};
}

View File

@@ -11,9 +11,6 @@ official external API.
## 1.1 Module List
* __Mouse__ (core/input/mouse.js): Mouse input event handler with
limited touch support.
* __Keyboard__ (core/input/keyboard.js): Keyboard input event handler with
non-US keyboard support. Translates keyDown and keyUp events to X11
keysym values.
@@ -23,7 +20,7 @@ layered on the HTML5 canvas element.
* __Websock__ (core/websock.js): Websock client from websockify
with transparent binary data support.
[Websock API](https://github.com/novnc/websockify/wiki/websock.js) wiki page.
[Websock API](https://github.com/novnc/websockify-js/wiki/websock.js) wiki page.
## 1.2 Callbacks
@@ -35,62 +32,38 @@ callback event name, and the callback function.
## 2. Modules
## 2.1 Mouse Module
## 2.1 Keyboard Module
### 2.1.1 Configuration Attributes
| name | type | mode | default | description
| ----------- | ---- | ---- | -------- | ------------
| touchButton | int | RW | 1 | Button mask (1, 2, 4) for which click to send on touch devices. 0 means ignore clicks.
### 2.1.2 Methods
| name | parameters | description
| ------ | ---------- | ------------
| grab | () | Begin capturing mouse events
| ungrab | () | Stop capturing mouse events
### 2.1.2 Callbacks
| name | parameters | description
| ------------- | ------------------- | ------------
| onmousebutton | (x, y, down, bmask) | Handler for mouse button click/release
| onmousemove | (x, y) | Handler for mouse movement
## 2.2 Keyboard Module
### 2.2.1 Configuration Attributes
None
### 2.2.2 Methods
### 2.1.2 Methods
| name | parameters | description
| ------ | ---------- | ------------
| grab | () | Begin capturing keyboard events
| ungrab | () | Stop capturing keyboard events
### 2.2.3 Callbacks
### 2.1.3 Callbacks
| name | parameters | description
| ---------- | -------------------- | ------------
| onkeypress | (keysym, code, down) | Handler for key press/release
## 2.3 Display Module
## 2.2 Display Module
### 2.3.1 Configuration Attributes
### 2.2.1 Configuration Attributes
| name | type | mode | default | description
| ------------ | ----- | ---- | ------- | ------------
| logo | raw | RW | | Logo to display when cleared: {"width": width, "height": height, "type": mime-type, "data": data}
| scale | float | RW | 1.0 | Display area scale factor 0.0 - 1.0
| clipViewport | bool | RW | false | Use viewport clipping
| width | int | RO | | Display area width
| height | int | RO | | Display area height
### 2.3.2 Methods
### 2.2.2 Methods
| name | parameters | description
| ------------------ | ------------------------------------------------------- | ------------
@@ -100,12 +73,11 @@ None
| absY | (y) | Return Y relative to the remote display
| resize | (width, height) | Set width and height
| flip | (from_queue) | Update the visible canvas with the contents of the rendering canvas
| clear | () | Clear the display (show logo if set)
| pending | () | Check if there are waiting items in the render queue
| flush | () | Resume processing the render queue unless it's empty
| fillRect | (x, y, width, height, color, from_queue) | Draw a filled in rectangle
| copyImage | (old_x, old_y, new_x, new_y, width, height, from_queue) | Copy a rectangular area
| imageRect | (x, y, mime, arr) | Draw a rectangle with an image
| imageRect | (x, y, width, height, mime, arr) | Draw a rectangle with an image
| startTile | (x, y, width, height, color) | Begin updating a tile
| subTile | (tile, x, y, w, h, color) | Update a sub-rectangle within the given tile
| finishTile | () | Draw the current tile to the display
@@ -113,12 +85,9 @@ None
| 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
| changeCursor | (pixels, mask, hotx, hoty, w, h) | Change cursor appearance
| defaultCursor | () | Restore default cursor appearance
| disableLocalCursor | () | Disable local (client-side) cursor
| autoscale | (containerWidth, containerHeight) | Scale the display
### 2.3.3 Callbacks
### 2.2.3 Callbacks
| name | parameters | description
| ------- | ---------- | ------------

View File

@@ -24,13 +24,7 @@ protocol stream.
`focusOnClick`
- Is a `boolean` indicating if keyboard focus should automatically be
moved to the remote session when a `mousedown` or `touchstart`
event is received.
`touchButton`
- Is a `long` controlling the button mask that should be simulated
when a touch event is recieved. Uses the same values as
[`MouseEvent.button`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button).
Is set to `1` by default.
event is received. Enabled by default.
`clipViewport`
- Is a `boolean` indicating if the remote session should be clipped
@@ -53,6 +47,30 @@ protocol stream.
should be sent whenever the container changes dimensions. Disabled
by default.
`showDotCursor`
- Is a `boolean` indicating whether a dot cursor should be shown
instead of a zero-sized or fully-transparent cursor if the server
sets such invisible cursor. Disabled by default.
`background`
- Is a valid CSS [background](https://developer.mozilla.org/en-US/docs/Web/CSS/background)
style value indicating which background style should be applied
to the element containing the remote session screen. The default value is `rgb(40, 40, 40)`
(solid gray color).
`qualityLevel`
- Is an `int` in range `[0-9]` controlling the desired JPEG quality.
Value `0` implies low quality and `9` implies high quality.
Default value is `6`.
`compressionLevel`
- Is an `int` in range `[0-9]` controlling the desired compression
level. Value `0` means no compression. Level 1 uses a minimum of CPU
resources and achieves weak compression ratios, while level 9 offers
best compression but is slow in terms of CPU consumption on the server
side. Use high levels with very slow network connections.
Default value is `2`.
`capabilities` *Read only*
- Is an `Object` indicating which optional extensions are available
on the server. Some methods may only be called if the corresponding
@@ -137,7 +155,7 @@ connection to a specified VNC server.
##### Syntax
var rfb = new RFB( target, url [, options] );
let rfb = new RFB( target, url [, options] );
###### Parameters
@@ -176,6 +194,10 @@ connection to a specified VNC server.
- A `DOMString` specifying the ID to provide to any VNC repeater
encountered.
`wsProtocols`
- An `Array` of `DOMString`s specifying the sub-protocols to use
in the WebSocket connection. Empty by default.
#### connect
The `connect` event is fired after all the handshaking with the server
@@ -360,5 +382,4 @@ to the remote server.
###### Parameters
**`text`**
- A `DOMString` specifying the clipboard data to send. Currently only
characters from ISO 8859-1 are supported.
- A `DOMString` specifying the clipboard data to send.

View File

@@ -61,6 +61,13 @@ query string. Currently the following options are available:
* `resize` - How to resize the remote session if it is not the same size as
the browser window. Can be one of `off`, `scale` and `remote`.
* `quality` - The session JPEG quality level. Can be `0` to `9`.
* `compression` - The session compression level. Can be `0` to `9`.
* `show_dot` - If a dot cursor should be shown when the remote server provides
no local cursor, or provides a fully-transparent (invisible) cursor.
* `logging` - The console log level. Can be one of `error`, `warn`, `info` or
`debug`.
@@ -81,3 +88,36 @@ load times. To do this please follow these steps:
This will produce a `build/` directory that includes everything needed to run
the noVNC application.
## HTTP Serving Considerations
### Browser Cache Issue
If you serve noVNC files using a web server that provides an ETag header, and
include any options in the query string, a nasty browser cache issue can bite
you on upgrade, resulting in a red error box. The issue is caused by a mismatch
between the new vnc.html (which is reloaded because the user has used it with
new query string after the upgrade) and the old javascript files (that the
browser reuses from its cache). To avoid this issue, the browser must be told
to always revalidate cached files using conditional requests. The correct
semantics are achieved via the (confusingly named) `Cache-Control: no-cache`
header that needs to be provided in the web server responses.
### Example Server Configurations
Apache:
```
# In the main configuration file
# (Debian/Ubuntu users: use "a2enmod headers" instead)
LoadModule headers_module modules/mod_headers.so
# In the <Directory> or <Location> block related to noVNC
Header set Cache-Control "no-cache"
```
Nginx:
```
# In the location block related to noVNC
add_header Cache-Control no-cache;
```

View File

@@ -1 +0,0 @@
1.0.0-testing.2

View File

@@ -1,34 +0,0 @@
- Decide a new version number X.Y.Z (follow SemVer)
- Update version in package.json
- Update version in docs/VERSION
- Commit the change with a commit like "Release X.Y.Z"
- Add a new release on GitHub called "vX.Y.Z", and populate it with
release notes of the following form (where A.B.C is the last release):
Major Changes Since A.B.C
=========================
*Insert warnings here about incompatibilities*
*Thanks to all the contributors who filed bugs, added features, and fixed bugs
during this release :tada:*
App-visible Changes
-------------------
- *feature* a feature which improves the app usage (#PRNUM)
- *bugfix* a bug fix which fixes the app usage (#PRNUM)
- *refactor* a refactor which changes the app usage (#PRNUM)
Library-visible Changes
-----------------------
- *feature* a feature which improves the noVNC APIs (#PRNUM)
- *bugfix* a bug fix which fixes the noVNC APIs (#PRNUM)
- *refactor* a refactor which changes the noVNC APIs (#PRNUM)
App-internals Changes
---------------------
- *bugfix* a bug fix with affects the internals of noVNC only (#PRNUM)
- *refactor* a refactor which affects the internals of noVNC only (#PRNUM)

View File

@@ -1,72 +1,45 @@
// Karma configuration
module.exports = function(config) {
var customLaunchers = {};
var browsers = [];
var useSauce = false;
// The Safari launcher is broken, so construct our own
function SafariBrowser(id, baseBrowserDecorator, args) {
baseBrowserDecorator(this);
// use Sauce when running on Travis
if (process.env.TRAVIS_JOB_NUMBER) {
useSauce = true;
this._start = function(url) {
this._execCommand('/usr/bin/open', ['-W', '-n', '-a', 'Safari', url]);
}
}
SafariBrowser.prototype = {
name: 'Safari'
}
module.exports = (config) => {
let browsers = [];
if (process.env.TEST_BROWSER_NAME) {
browsers = process.env.TEST_BROWSER_NAME.split(',');
}
if (useSauce && process.env.TEST_BROWSER_NAME && process.env.TEST_BROWSER_NAME != 'PhantomJS') {
var names = process.env.TEST_BROWSER_NAME.split(',');
var platforms = process.env.TEST_BROWSER_OS.split(',');
var versions = [];
if (process.env.TEST_BROWSER_VERSION) {
versions = process.env.TEST_BROWSER_VERSION.split(',');
} else {
versions = [null];
}
for (var i = 0; i < names.length; i++) {
for (var j = 0; j < platforms.length; j++) {
for (var k = 0; k < versions.length; k++) {
var launcher_name = 'sl_' + platforms[j].replace(/[^a-zA-Z0-9]/g, '') + '_' + names[i];
if (versions[k]) {
launcher_name += '_' + versions[k];
}
customLaunchers[launcher_name] = {
base: 'SauceLabs',
browserName: names[i],
platform: platforms[j],
};
if (versions[i]) {
customLaunchers[launcher_name].version = versions[k];
}
}
}
}
browsers = Object.keys(customLaunchers);
} else {
useSauce = false;
//browsers = ['PhantomJS'];
browsers = [];
}
var my_conf = {
const my_conf = {
// base path that will be used to resolve all patterns (eg. files, exclude)
basePath: '',
// frameworks to use
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
frameworks: ['requirejs', 'mocha', 'chai'],
frameworks: ['mocha', 'sinon-chai'],
// list of files / patterns to load in the browser (loaded in order)
files: [
{ pattern: 'vendor/sinon.js', included: false },
{ pattern: 'node_modules/sinon-chai/lib/sinon-chai.js', included: false },
{ 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',
],
@@ -82,44 +55,22 @@ module.exports = function(config) {
exclude: [
],
customLaunchers: customLaunchers,
plugins: [
'karma-*',
'@chiragrupani/karma-chromium-edge-launcher',
{ 'launcher:Safari': [ 'type', SafariBrowser ] },
],
// start these browsers
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
browsers: browsers,
// preprocess matching files before serving them to the browser
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
preprocessors: {
'app/localization.js': ['babel'],
'core/**/*.js': ['babel'],
'tests/test.*.js': ['babel'],
'tests/fake.*.js': ['babel'],
'tests/assertions.js': ['babel'],
'vendor/pako/**/*.js': ['babel'],
},
babelPreprocessor: {
options: {
plugins: ['transform-es2015-modules-amd', 'syntax-dynamic-import'],
sourceMap: 'inline',
},
},
// test results reporter to use
// possible values: 'dots', 'progress'
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
reporters: ['mocha'],
// web server port
port: 9876,
// enable / disable colors in the output (reporters and logs)
colors: true,
// level of logging
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
logLevel: config.LOG_INFO,
@@ -131,24 +82,7 @@ module.exports = function(config) {
// Continuous Integration mode
// if true, Karma captures browsers, runs the tests and exits
singleRun: true,
// Increase timeout in case connection is slow/we run more browsers than possible
// (we currently get 3 for free, and we try to run 7, so it can take a while)
captureTimeout: 240000,
// similarly to above
browserNoActivityTimeout: 100000,
};
if (useSauce) {
my_conf.reporters.push('saucelabs');
my_conf.captureTimeout = 0; // use SL timeout
my_conf.sauceLabs = {
testName: 'noVNC Tests (all)',
startConnect: false,
tunnelIdentifier: process.env.TRAVIS_JOB_NUMBER
};
}
config.set(my_conf);
};

View File

@@ -1,14 +1,27 @@
{
"name": "@novnc/novnc",
"version": "1.0.0-beta",
"version": "1.2.0-beta",
"description": "An HTML5 VNC client",
"browser": "lib/rfb",
"directories": {
"lib": "lib",
"doc": "docs",
"test": "tests"
},
"files": [
"lib",
"AUTHORS",
"VERSION",
"docs/API.md",
"docs/LIBRARY.md",
"docs/LICENSE*",
"core",
"vendor/pako"
],
"scripts": {
"test": "PATH=$PATH:node_modules/karma/bin karma start karma.conf.js",
"prepare": "node ./utils/use_require.js --as commonjs --clean"
"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"
},
"repository": {
"type": "git",
@@ -27,35 +40,42 @@
},
"homepage": "https://github.com/novnc/noVNC",
"devDependencies": {
"babel-core": "^6.22.1",
"babel-plugin-add-module-exports": "^0.2.1",
"@babel/core": "*",
"@babel/plugin-syntax-dynamic-import": "*",
"@babel/plugin-transform-modules-amd": "*",
"@babel/plugin-transform-modules-commonjs": "*",
"@babel/plugin-transform-modules-systemjs": "*",
"@babel/plugin-transform-modules-umd": "*",
"@babel/preset-env": "*",
"@babel/cli": "*",
"babel-plugin-import-redirect": "*",
"babel-plugin-syntax-dynamic-import": "^6.18.0",
"babel-plugin-transform-es2015-modules-amd": "^6.22.0",
"babel-plugin-transform-es2015-modules-commonjs": "^6.18.0",
"babel-plugin-transform-es2015-modules-systemjs": "^6.22.0",
"babel-plugin-transform-es2015-modules-umd": "^6.22.0",
"babelify": "^7.3.0",
"browserify": "^13.1.0",
"chai": "^3.5.0",
"commander": "^2.9.0",
"es-module-loader": "^2.1.0",
"fs-extra": "^1.0.0",
"browserify": "*",
"babelify": "*",
"core-js": "*",
"chai": "*",
"commander": "*",
"es-module-loader": "*",
"eslint": "*",
"fs-extra": "*",
"jsdom": "*",
"karma": "^1.3.0",
"karma-babel-preprocessor": "^6.0.1",
"karma-chai": "^0.1.0",
"karma-mocha": "^1.3.0",
"karma-mocha-reporter": "^2.2.0",
"karma-requirejs": "^1.1.0",
"karma-sauce-launcher": "^1.0.0",
"mocha": "^3.1.2",
"karma": "*",
"karma-mocha": "*",
"karma-chrome-launcher": "*",
"@chiragrupani/karma-chromium-edge-launcher": "*",
"karma-firefox-launcher": "*",
"karma-ie-launcher": "*",
"karma-mocha-reporter": "*",
"karma-safari-launcher": "*",
"karma-script-launcher": "*",
"karma-sinon-chai": "*",
"mocha": "*",
"node-getopt": "*",
"po2json": "*",
"requirejs": "^2.3.2",
"rollup": "^0.41.4",
"rollup-plugin-node-resolve": "^2.0.0",
"sinon-chai": "^2.8.0"
"requirejs": "*",
"rollup": "*",
"rollup-plugin-node-resolve": "*",
"sinon": "*",
"sinon-chai": "*"
},
"dependencies": {},
"keywords": [

5
po/.eslintrc Normal file
View File

@@ -0,0 +1,5 @@
{
"env": {
"node": true,
},
}

View File

@@ -1,7 +1,7 @@
all:
.PHONY: update-po update-js update-pot
LINGUAS := de el es nl pl sv tr zh
LINGUAS := cs de el es ja ko nl pl ru sv tr zh_CN zh_TW
VERSION := $(shell grep '"version"' ../package.json | cut -d '"' -f 4)
@@ -18,7 +18,7 @@ update-js: $(JSONFILES)
update-pot:
xgettext --output=noVNC.js.pot \
--copyright-holder="Various Authors" \
--copyright-holder="The noVNC Authors" \
--package-name="noVNC" \
--package-version="$(VERSION)" \
--msgid-bugs-address="novnc@googlegroups.com" \

294
po/cs.po Normal file
View File

@@ -0,0 +1,294 @@
# Czech translations for noVNC package.
# Copyright (C) 2018 The noVNC Authors
# This file is distributed under the same license as the noVNC package.
# Petr <petr@kle.cz>, 2018.
#
msgid ""
msgstr ""
"Project-Id-Version: noVNC 1.0.0-testing.2\n"
"Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
"POT-Creation-Date: 2018-10-19 12:00+0200\n"
"PO-Revision-Date: 2018-10-19 12:00+0200\n"
"Last-Translator: Petr <petr@kle.cz>\n"
"Language-Team: Czech\n"
"Language: cs\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
#: ../app/ui.js:389
msgid "Connecting..."
msgstr "Připojení..."
#: ../app/ui.js:396
msgid "Disconnecting..."
msgstr "Odpojení..."
#: ../app/ui.js:402
msgid "Reconnecting..."
msgstr "Obnova připojení..."
#: ../app/ui.js:407
msgid "Internal error"
msgstr "Vnitřní chyba"
#: ../app/ui.js:997
msgid "Must set host"
msgstr "Hostitel musí být nastavení"
#: ../app/ui.js:1079
msgid "Connected (encrypted) to "
msgstr "Připojení (šifrované) k "
#: ../app/ui.js:1081
msgid "Connected (unencrypted) to "
msgstr "Připojení (nešifrované) k "
#: ../app/ui.js:1104
msgid "Something went wrong, connection is closed"
msgstr "Něco se pokazilo, odpojeno"
#: ../app/ui.js:1107
msgid "Failed to connect to server"
msgstr "Chyba připojení k serveru"
#: ../app/ui.js:1117
msgid "Disconnected"
msgstr "Odpojeno"
#: ../app/ui.js:1130
msgid "New connection has been rejected with reason: "
msgstr "Nové připojení bylo odmítnuto s odůvodněním: "
#: ../app/ui.js:1133
msgid "New connection has been rejected"
msgstr "Nové připojení bylo odmítnuto"
#: ../app/ui.js:1153
msgid "Password is required"
msgstr "Je vyžadováno heslo"
#: ../vnc.html:84
msgid "noVNC encountered an error:"
msgstr "noVNC narazilo na chybu:"
#: ../vnc.html:94
msgid "Hide/Show the control bar"
msgstr "Skrýt/zobrazit ovládací panel"
#: ../vnc.html:101
msgid "Move/Drag Viewport"
msgstr "Přesunout/přetáhnout výřez"
#: ../vnc.html:101
msgid "viewport drag"
msgstr "přesun výřezu"
#: ../vnc.html:107 ../vnc.html:110 ../vnc.html:113 ../vnc.html:116
msgid "Active Mouse Button"
msgstr "Aktivní tlačítka myši"
#: ../vnc.html:107
msgid "No mousebutton"
msgstr "Žádné"
#: ../vnc.html:110
msgid "Left mousebutton"
msgstr "Levé tlačítko myši"
#: ../vnc.html:113
msgid "Middle mousebutton"
msgstr "Prostřední tlačítko myši"
#: ../vnc.html:116
msgid "Right mousebutton"
msgstr "Pravé tlačítko myši"
#: ../vnc.html:119
msgid "Keyboard"
msgstr "Klávesnice"
#: ../vnc.html:119
msgid "Show Keyboard"
msgstr "Zobrazit klávesnici"
#: ../vnc.html:126
msgid "Extra keys"
msgstr "Extra klávesy"
#: ../vnc.html:126
msgid "Show Extra Keys"
msgstr "Zobrazit extra klávesy"
#: ../vnc.html:131
msgid "Ctrl"
msgstr "Ctrl"
#: ../vnc.html:131
msgid "Toggle Ctrl"
msgstr "Přepnout Ctrl"
#: ../vnc.html:134
msgid "Alt"
msgstr "Alt"
#: ../vnc.html:134
msgid "Toggle Alt"
msgstr "Přepnout Alt"
#: ../vnc.html:137
msgid "Send Tab"
msgstr "Odeslat tabulátor"
#: ../vnc.html:137
msgid "Tab"
msgstr "Tab"
#: ../vnc.html:140
msgid "Esc"
msgstr "Esc"
#: ../vnc.html:140
msgid "Send Escape"
msgstr "Odeslat Esc"
#: ../vnc.html:143
msgid "Ctrl+Alt+Del"
msgstr "Ctrl+Alt+Del"
#: ../vnc.html:143
msgid "Send Ctrl-Alt-Del"
msgstr "Poslat Ctrl-Alt-Del"
#: ../vnc.html:151
msgid "Shutdown/Reboot"
msgstr "Vypnutí/Restart"
#: ../vnc.html:151
msgid "Shutdown/Reboot..."
msgstr "Vypnutí/Restart..."
#: ../vnc.html:157
msgid "Power"
msgstr "Napájení"
#: ../vnc.html:159
msgid "Shutdown"
msgstr "Vypnout"
#: ../vnc.html:160
msgid "Reboot"
msgstr "Restart"
#: ../vnc.html:161
msgid "Reset"
msgstr "Reset"
#: ../vnc.html:166 ../vnc.html:172
msgid "Clipboard"
msgstr "Schránka"
#: ../vnc.html:176
msgid "Clear"
msgstr "Vymazat"
#: ../vnc.html:182
msgid "Fullscreen"
msgstr "Celá obrazovka"
#: ../vnc.html:187 ../vnc.html:194
msgid "Settings"
msgstr "Nastavení"
#: ../vnc.html:197
msgid "Shared Mode"
msgstr "Sdílený režim"
#: ../vnc.html:200
msgid "View Only"
msgstr "Pouze prohlížení"
#: ../vnc.html:204
msgid "Clip to Window"
msgstr "Přizpůsobit oknu"
#: ../vnc.html:207
msgid "Scaling Mode:"
msgstr "Přizpůsobení velikosti"
#: ../vnc.html:209
msgid "None"
msgstr "Žádné"
#: ../vnc.html:210
msgid "Local Scaling"
msgstr "Místní"
#: ../vnc.html:211
msgid "Remote Resizing"
msgstr "Vzdálené"
#: ../vnc.html:216
msgid "Advanced"
msgstr "Pokročilé"
#: ../vnc.html:219
msgid "Repeater ID:"
msgstr "ID opakovače"
#: ../vnc.html:223
msgid "WebSocket"
msgstr "WebSocket"
#: ../vnc.html:226
msgid "Encrypt"
msgstr "Šifrování:"
#: ../vnc.html:229
msgid "Host:"
msgstr "Hostitel:"
#: ../vnc.html:233
msgid "Port:"
msgstr "Port:"
#: ../vnc.html:237
msgid "Path:"
msgstr "Cesta"
#: ../vnc.html:244
msgid "Automatic Reconnect"
msgstr "Automatická obnova připojení"
#: ../vnc.html:247
msgid "Reconnect Delay (ms):"
msgstr "Zpoždění připojení (ms)"
#: ../vnc.html:252
msgid "Show Dot when No Cursor"
msgstr "Tečka místo chybějícího kurzoru myši"
#: ../vnc.html:257
msgid "Logging:"
msgstr "Logování:"
#: ../vnc.html:269
msgid "Disconnect"
msgstr "Odpojit"
#: ../vnc.html:288
msgid "Connect"
msgstr "Připojit"
#: ../vnc.html:298
msgid "Password:"
msgstr "Heslo"
#: ../vnc.html:302
msgid "Send Password"
msgstr "Odeslat heslo"
#: ../vnc.html:312
msgid "Cancel"
msgstr "Zrušit"

View File

@@ -1,6 +1,6 @@
# German translations for noVNC package
# German translation for noVNC.
# Copyright (C) 2016 Various Authors
# Copyright (C) 2018 The noVNC Authors
# This file is distributed under the same license as the noVNC package.
# Loek Janssen <loekjanssen@gmail.com>, 2016.
#

View File

@@ -1,5 +1,5 @@
# Greek translations for noVNC package.
# Copyright (C) 2016 Various Authors
# Copyright (C) 2018 The noVNC Authors
# This file is distributed under the same license as the noVNC package.
# Giannis Kosmas <kosmasgiannis@gmail.com>, 2016.
#

View File

@@ -1,6 +1,6 @@
# Spanish translations for noVNC package
# Traducciones al español para el paquete noVNC.
# Copyright (C) 2018 Various Authors
# 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.
#

303
po/ja.po Normal file
View File

@@ -0,0 +1,303 @@
# Japanese translations for noVNC package
# noVNC パッケージに対する英訳.
# Copyright (C) 2019 The noVNC Authors
# This file is distributed under the same license as the noVNC package.
# nnn1590 <28985763+nnn1590@users.noreply.github.com>, 2019.
#
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: 2019-06-12 04:06+0900\n"
"Last-Translator: nnn1590 <28985763+nnn1590@users.noreply.github.com>\n"
"Language-Team: Japanese\n"
"Language: ja\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
#: ../app/ui.js:387
msgid "Connecting..."
msgstr "接続しています..."
#: ../app/ui.js:394
msgid "Disconnecting..."
msgstr "切断しています..."
#: ../app/ui.js:400
msgid "Reconnecting..."
msgstr "再接続しています..."
#: ../app/ui.js:405
msgid "Internal error"
msgstr "内部エラー"
#: ../app/ui.js:995
msgid "Must set host"
msgstr "ホストを設定する必要があります"
#: ../app/ui.js:1077
msgid "Connected (encrypted) to "
msgstr "接続しました (暗号化済み): "
#: ../app/ui.js:1079
msgid "Connected (unencrypted) to "
msgstr "接続しました (暗号化されていません): "
#: ../app/ui.js:1102
msgid "Something went wrong, connection is closed"
msgstr "何かが問題で、接続が閉じられました"
#: ../app/ui.js:1105
msgid "Failed to connect to server"
msgstr "サーバーへの接続に失敗しました"
#: ../app/ui.js:1115
msgid "Disconnected"
msgstr "切断しました"
#: ../app/ui.js:1128
msgid "New connection has been rejected with reason: "
msgstr "新規接続は次の理由で拒否されました: "
#: ../app/ui.js:1131
msgid "New connection has been rejected"
msgstr "新規接続は拒否されました"
#: ../app/ui.js:1151
msgid "Password is required"
msgstr "パスワードが必要です"
#: ../vnc.html:84
msgid "noVNC encountered an error:"
msgstr "noVNC でエラーが発生しました:"
#: ../vnc.html:94
msgid "Hide/Show the control bar"
msgstr "コントロールバーを隠す/表示する"
#: ../vnc.html:101
msgid "Move/Drag Viewport"
msgstr "ビューポートを移動/ドラッグ"
#: ../vnc.html:101
msgid "viewport drag"
msgstr "ビューポートをドラッグ"
#: ../vnc.html:107 ../vnc.html:110 ../vnc.html:113 ../vnc.html:116
msgid "Active Mouse Button"
msgstr "アクティブなマウスボタン"
#: ../vnc.html:107
msgid "No mousebutton"
msgstr "マウスボタンなし"
#: ../vnc.html:110
msgid "Left mousebutton"
msgstr "左マウスボタン"
#: ../vnc.html:113
msgid "Middle mousebutton"
msgstr "中マウスボタン"
#: ../vnc.html:116
msgid "Right mousebutton"
msgstr "右マウスボタン"
#: ../vnc.html:119
msgid "Keyboard"
msgstr "キーボード"
#: ../vnc.html:119
msgid "Show Keyboard"
msgstr "キーボードを表示"
#: ../vnc.html:126
msgid "Extra keys"
msgstr "追加キー"
#: ../vnc.html:126
msgid "Show Extra Keys"
msgstr "追加キーを表示"
#: ../vnc.html:131
msgid "Ctrl"
msgstr "Ctrl"
#: ../vnc.html:131
msgid "Toggle Ctrl"
msgstr "Ctrl キーを切り替え"
#: ../vnc.html:134
msgid "Alt"
msgstr "Alt"
#: ../vnc.html:134
msgid "Toggle Alt"
msgstr "Alt キーを切り替え"
#: ../vnc.html:137
msgid "Toggle Windows"
msgstr "Windows キーを切り替え"
#: ../vnc.html:137
msgid "Windows"
msgstr "Windows"
#: ../vnc.html:140
msgid "Send Tab"
msgstr "Tab キーを送信"
#: ../vnc.html:140
msgid "Tab"
msgstr "Tab"
#: ../vnc.html:143
msgid "Esc"
msgstr "Esc"
#: ../vnc.html:143
msgid "Send Escape"
msgstr "Escape キーを送信"
#: ../vnc.html:146
msgid "Ctrl+Alt+Del"
msgstr "Ctrl+Alt+Del"
#: ../vnc.html:146
msgid "Send Ctrl-Alt-Del"
msgstr "Ctrl-Alt-Del を送信"
#: ../vnc.html:154
msgid "Shutdown/Reboot"
msgstr "シャットダウン/再起動"
#: ../vnc.html:154
msgid "Shutdown/Reboot..."
msgstr "シャットダウン/再起動..."
#: ../vnc.html:160
msgid "Power"
msgstr "電源"
#: ../vnc.html:162
msgid "Shutdown"
msgstr "シャットダウン"
#: ../vnc.html:163
msgid "Reboot"
msgstr "再起動"
#: ../vnc.html:164
msgid "Reset"
msgstr "リセット"
#: ../vnc.html:169 ../vnc.html:175
msgid "Clipboard"
msgstr "クリップボード"
#: ../vnc.html:179
msgid "Clear"
msgstr "クリア"
#: ../vnc.html:185
msgid "Fullscreen"
msgstr "全画面表示"
#: ../vnc.html:190 ../vnc.html:197
msgid "Settings"
msgstr "設定"
#: ../vnc.html:200
msgid "Shared Mode"
msgstr "共有モード"
#: ../vnc.html:203
msgid "View Only"
msgstr "表示のみ"
#: ../vnc.html:207
msgid "Clip to Window"
msgstr "ウィンドウにクリップ"
#: ../vnc.html:210
msgid "Scaling Mode:"
msgstr "スケーリングモード:"
#: ../vnc.html:212
msgid "None"
msgstr "なし"
#: ../vnc.html:213
msgid "Local Scaling"
msgstr "ローカルスケーリング"
#: ../vnc.html:214
msgid "Remote Resizing"
msgstr "リモートでリサイズ"
#: ../vnc.html:219
msgid "Advanced"
msgstr "高度"
#: ../vnc.html:222
msgid "Repeater ID:"
msgstr "リピーター ID:"
#: ../vnc.html:226
msgid "WebSocket"
msgstr "WebSocket"
#: ../vnc.html:229
msgid "Encrypt"
msgstr "暗号化"
#: ../vnc.html:232
msgid "Host:"
msgstr "ホスト:"
#: ../vnc.html:236
msgid "Port:"
msgstr "ポート:"
#: ../vnc.html:240
msgid "Path:"
msgstr "パス:"
#: ../vnc.html:247
msgid "Automatic Reconnect"
msgstr "自動再接続"
#: ../vnc.html:250
msgid "Reconnect Delay (ms):"
msgstr "再接続する遅延 (ミリ秒):"
#: ../vnc.html:255
msgid "Show Dot when No Cursor"
msgstr "カーソルがないときにドットを表示"
#: ../vnc.html:260
msgid "Logging:"
msgstr "ロギング:"
#: ../vnc.html:272
msgid "Disconnect"
msgstr "切断"
#: ../vnc.html:291
msgid "Connect"
msgstr "接続"
#: ../vnc.html:301
msgid "Password:"
msgstr "パスワード:"
#: ../vnc.html:305
msgid "Send Password"
msgstr "パスワードを送信"
#: ../vnc.html:315
msgid "Cancel"
msgstr "キャンセル"

290
po/ko.po Normal file
View File

@@ -0,0 +1,290 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) 2018 The noVNC Authors
# This file is distributed under the same license as the noVNC package.
# Baw Appie <pp121324@gmail.com>, 2018.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: noVNC 1.0.0-testing.2\n"
"Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
"POT-Creation-Date: 2018-01-31 16:29+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Baw Appie <pp121324@gmail.com>\n"
"Language-Team: Korean\n"
"Language: ko\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: ../app/ui.js:395
msgid "Connecting..."
msgstr "연결중..."
#: ../app/ui.js:402
msgid "Disconnecting..."
msgstr "연결 해제중..."
#: ../app/ui.js:408
msgid "Reconnecting..."
msgstr "재연결중..."
#: ../app/ui.js:413
msgid "Internal error"
msgstr "내부 오류"
#: ../app/ui.js:1002
msgid "Must set host"
msgstr "호스트는 설정되어야 합니다."
#: ../app/ui.js:1083
msgid "Connected (encrypted) to "
msgstr "다음과 (암호화되어) 연결되었습니다:"
#: ../app/ui.js:1085
msgid "Connected (unencrypted) to "
msgstr "다음과 (암호화 없이) 연결되었습니다:"
#: ../app/ui.js:1108
msgid "Something went wrong, connection is closed"
msgstr "무언가 잘못되었습니다, 연결이 닫혔습니다."
#: ../app/ui.js:1111
msgid "Failed to connect to server"
msgstr "서버에 연결하지 못했습니다."
#: ../app/ui.js:1121
msgid "Disconnected"
msgstr "연결이 해제되었습니다."
#: ../app/ui.js:1134
msgid "New connection has been rejected with reason: "
msgstr "새 연결이 다음 이유로 거부되었습니다:"
#: ../app/ui.js:1137
msgid "New connection has been rejected"
msgstr "새 연결이 거부되었습니다."
#: ../app/ui.js:1158
msgid "Password is required"
msgstr "비밀번호가 필요합니다."
#: ../vnc.html:91
msgid "noVNC encountered an error:"
msgstr "noVNC에 오류가 발생했습니다:"
#: ../vnc.html:101
msgid "Hide/Show the control bar"
msgstr "컨트롤 바 숨기기/보이기"
#: ../vnc.html:108
msgid "Move/Drag Viewport"
msgstr "움직이기/드래그 뷰포트"
#: ../vnc.html:108
msgid "viewport drag"
msgstr "뷰포트 드래그"
#: ../vnc.html:114 ../vnc.html:117 ../vnc.html:120 ../vnc.html:123
msgid "Active Mouse Button"
msgstr "마우스 버튼 활성화"
#: ../vnc.html:114
msgid "No mousebutton"
msgstr "마우스 버튼 없음"
#: ../vnc.html:117
msgid "Left mousebutton"
msgstr "왼쪽 마우스 버튼"
#: ../vnc.html:120
msgid "Middle mousebutton"
msgstr "중간 마우스 버튼"
#: ../vnc.html:123
msgid "Right mousebutton"
msgstr "오른쪽 마우스 버튼"
#: ../vnc.html:126
msgid "Keyboard"
msgstr "키보드"
#: ../vnc.html:126
msgid "Show Keyboard"
msgstr "키보드 보이기"
#: ../vnc.html:133
msgid "Extra keys"
msgstr "기타 키들"
#: ../vnc.html:133
msgid "Show Extra Keys"
msgstr "기타 키들 보이기"
#: ../vnc.html:138
msgid "Ctrl"
msgstr "Ctrl"
#: ../vnc.html:138
msgid "Toggle Ctrl"
msgstr "Ctrl 켜기/끄기"
#: ../vnc.html:141
msgid "Alt"
msgstr "Alt"
#: ../vnc.html:141
msgid "Toggle Alt"
msgstr "Alt 켜기/끄기"
#: ../vnc.html:144
msgid "Send Tab"
msgstr "Tab 보내기"
#: ../vnc.html:144
msgid "Tab"
msgstr "Tab"
#: ../vnc.html:147
msgid "Esc"
msgstr "Esc"
#: ../vnc.html:147
msgid "Send Escape"
msgstr "Esc 보내기"
#: ../vnc.html:150
msgid "Ctrl+Alt+Del"
msgstr "Ctrl+Alt+Del"
#: ../vnc.html:150
msgid "Send Ctrl-Alt-Del"
msgstr "Ctrl+Alt+Del 보내기"
#: ../vnc.html:158
msgid "Shutdown/Reboot"
msgstr "셧다운/리붓"
#: ../vnc.html:158
msgid "Shutdown/Reboot..."
msgstr "셧다운/리붓..."
#: ../vnc.html:164
msgid "Power"
msgstr "전원"
#: ../vnc.html:166
msgid "Shutdown"
msgstr "셧다운"
#: ../vnc.html:167
msgid "Reboot"
msgstr "리붓"
#: ../vnc.html:168
msgid "Reset"
msgstr "리셋"
#: ../vnc.html:173 ../vnc.html:179
msgid "Clipboard"
msgstr "클립보드"
#: ../vnc.html:183
msgid "Clear"
msgstr "지우기"
#: ../vnc.html:189
msgid "Fullscreen"
msgstr "전체화면"
#: ../vnc.html:194 ../vnc.html:201
msgid "Settings"
msgstr "설정"
#: ../vnc.html:204
msgid "Shared Mode"
msgstr "공유 모드"
#: ../vnc.html:207
msgid "View Only"
msgstr "보기 전용"
#: ../vnc.html:211
msgid "Clip to Window"
msgstr "창에 클립"
#: ../vnc.html:214
msgid "Scaling Mode:"
msgstr "스케일링 모드:"
#: ../vnc.html:216
msgid "None"
msgstr "없음"
#: ../vnc.html:217
msgid "Local Scaling"
msgstr "로컬 스케일링"
#: ../vnc.html:218
msgid "Remote Resizing"
msgstr "원격 크기 조절"
#: ../vnc.html:223
msgid "Advanced"
msgstr "고급"
#: ../vnc.html:226
msgid "Repeater ID:"
msgstr "중계 ID"
#: ../vnc.html:230
msgid "WebSocket"
msgstr "웹소켓"
#: ../vnc.html:233
msgid "Encrypt"
msgstr "암호화"
#: ../vnc.html:236
msgid "Host:"
msgstr "호스트:"
#: ../vnc.html:240
msgid "Port:"
msgstr "포트:"
#: ../vnc.html:244
msgid "Path:"
msgstr "위치:"
#: ../vnc.html:251
msgid "Automatic Reconnect"
msgstr "자동 재연결"
#: ../vnc.html:254
msgid "Reconnect Delay (ms):"
msgstr "재연결 지연 시간 (ms)"
#: ../vnc.html:260
msgid "Logging:"
msgstr "로깅"
#: ../vnc.html:272
msgid "Disconnect"
msgstr "연결 해제"
#: ../vnc.html:291
msgid "Connect"
msgstr "연결"
#: ../vnc.html:301
msgid "Password:"
msgstr "비밀번호:"
#: ../vnc.html:305
msgid "Send Password"
msgstr "비밀번호 전송"
#: ../vnc.html:315
msgid "Cancel"
msgstr "취소"

224
po/nl.po
View File

@@ -1,16 +1,16 @@
# Dutch translations for noVNC package
# Nederlandse vertalingen voor het pakket noVNC.
# Copyright (C) 2016 Various Authors
# Copyright (C) 2018 The noVNC Authors
# This file is distributed under the same license as the noVNC package.
# Loek Janssen <loekjanssen@gmail.com>, 2016.
#
msgid ""
msgstr ""
"Project-Id-Version: noVNC 0.6.1\n"
"Project-Id-Version: noVNC 1.1.0\n"
"Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
"POT-Creation-Date: 2017-10-06 10:07+0200\n"
"PO-Revision-Date: 2017-10-11 16:16+0200\n"
"Last-Translator: Yuri van Oers <yvanoers@gmail.com>\n"
"POT-Creation-Date: 2019-04-09 11:06+0100\n"
"PO-Revision-Date: 2019-04-09 17:17+0100\n"
"Last-Translator: Arend Lapere <arend.lapere@gmail.com>\n"
"Language-Team: none\n"
"Language: nl\n"
"MIME-Version: 1.0\n"
@@ -18,269 +18,301 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: ../app/ui.js:430
#: ../app/ui.js:383
msgid "Connecting..."
msgstr "Verbinden..."
#: ../app/ui.js:438
msgid "Connected (encrypted) to "
msgstr "Verbonden (versleuteld) met "
#: ../app/ui.js:440
msgid "Connected (unencrypted) to "
msgstr "Verbonden (onversleuteld) met "
#: ../app/ui.js:446
#: ../app/ui.js:390
msgid "Disconnecting..."
msgstr "Verbinding verbreken..."
#: ../app/ui.js:450
msgid "Disconnected"
msgstr "Verbinding verbroken"
#: ../app/ui.js:1052 ../core/rfb.js:248
msgid "Must set host"
msgstr "Host moeten worden ingesteld"
#: ../app/ui.js:1101
#: ../app/ui.js:396
msgid "Reconnecting..."
msgstr "Opnieuw verbinding maken..."
#: ../app/ui.js:1140
#: ../app/ui.js:401
msgid "Internal error"
msgstr "Interne fout"
#: ../app/ui.js:991
msgid "Must set host"
msgstr "Host moeten worden ingesteld"
#: ../app/ui.js:1073
msgid "Connected (encrypted) to "
msgstr "Verbonden (versleuteld) met "
#: ../app/ui.js:1075
msgid "Connected (unencrypted) to "
msgstr "Verbonden (onversleuteld) met "
#: ../app/ui.js:1098
msgid "Something went wrong, connection is closed"
msgstr "Er iets fout gelopen, verbinding werd verbroken"
#: ../app/ui.js:1101
msgid "Failed to connect to server"
msgstr "Verbinding maken met server is mislukt"
#: ../app/ui.js:1111
msgid "Disconnected"
msgstr "Verbinding verbroken"
#: ../app/ui.js:1124
msgid "New connection has been rejected with reason: "
msgstr "Nieuwe verbinding is geweigerd omwille van de volgende reden: "
#: ../app/ui.js:1127
msgid "New connection has been rejected"
msgstr "Nieuwe verbinding is geweigerd"
#: ../app/ui.js:1147
msgid "Password is required"
msgstr "Wachtwoord is vereist"
#: ../core/rfb.js:548
msgid "Disconnect timeout"
msgstr "Timeout tijdens verbreken van verbinding"
#: ../vnc.html:89
#: ../vnc.html:80
msgid "noVNC encountered an error:"
msgstr "noVNC heeft een fout bemerkt:"
#: ../vnc.html:99
#: ../vnc.html:90
msgid "Hide/Show the control bar"
msgstr "Verberg/Toon de bedieningsbalk"
#: ../vnc.html:106
#: ../vnc.html:97
msgid "Move/Drag Viewport"
msgstr "Verplaats/Versleep Kijkvenster"
#: ../vnc.html:106
#: ../vnc.html:97
msgid "viewport drag"
msgstr "kijkvenster slepen"
#: ../vnc.html:112 ../vnc.html:115 ../vnc.html:118 ../vnc.html:121
#: ../vnc.html:103 ../vnc.html:106 ../vnc.html:109 ../vnc.html:112
msgid "Active Mouse Button"
msgstr "Actieve Muisknop"
#: ../vnc.html:112
#: ../vnc.html:103
msgid "No mousebutton"
msgstr "Geen muisknop"
#: ../vnc.html:115
#: ../vnc.html:106
msgid "Left mousebutton"
msgstr "Linker muisknop"
#: ../vnc.html:118
#: ../vnc.html:109
msgid "Middle mousebutton"
msgstr "Middelste muisknop"
#: ../vnc.html:121
#: ../vnc.html:112
msgid "Right mousebutton"
msgstr "Rechter muisknop"
#: ../vnc.html:124
#: ../vnc.html:115
msgid "Keyboard"
msgstr "Toetsenbord"
#: ../vnc.html:124
#: ../vnc.html:115
msgid "Show Keyboard"
msgstr "Toon Toetsenbord"
#: ../vnc.html:131
#: ../vnc.html:121
msgid "Extra keys"
msgstr "Extra toetsen"
#: ../vnc.html:131
#: ../vnc.html:121
msgid "Show Extra Keys"
msgstr "Toon Extra Toetsen"
#: ../vnc.html:136
#: ../vnc.html:126
msgid "Ctrl"
msgstr "Ctrl"
#: ../vnc.html:136
#: ../vnc.html:126
msgid "Toggle Ctrl"
msgstr "Ctrl aan/uitzetten"
msgstr "Ctrl omschakelen"
#: ../vnc.html:139
#: ../vnc.html:129
msgid "Alt"
msgstr "Alt"
#: ../vnc.html:139
#: ../vnc.html:129
msgid "Toggle Alt"
msgstr "Alt aan/uitzetten"
msgstr "Alt omschakelen"
#: ../vnc.html:142
#: ../vnc.html:132
msgid "Toggle Windows"
msgstr "Windows omschakelen"
#: ../vnc.html:132
msgid "Windows"
msgstr "Windows"
#: ../vnc.html:135
msgid "Send Tab"
msgstr "Tab Sturen"
#: ../vnc.html:142
#: ../vnc.html:135
msgid "Tab"
msgstr "Tab"
#: ../vnc.html:145
#: ../vnc.html:138
msgid "Esc"
msgstr "Esc"
#: ../vnc.html:145
#: ../vnc.html:138
msgid "Send Escape"
msgstr "Escape Sturen"
#: ../vnc.html:148
#: ../vnc.html:141
msgid "Ctrl+Alt+Del"
msgstr "Ctrl-Alt-Del"
#: ../vnc.html:148
#: ../vnc.html:141
msgid "Send Ctrl-Alt-Del"
msgstr "Ctrl-Alt-Del Sturen"
#: ../vnc.html:156
#: ../vnc.html:149
msgid "Shutdown/Reboot"
msgstr "Uitschakelen/Herstarten"
#: ../vnc.html:156
#: ../vnc.html:149
msgid "Shutdown/Reboot..."
msgstr "Uitschakelen/Herstarten..."
#: ../vnc.html:162
#: ../vnc.html:155
msgid "Power"
msgstr "Systeem"
#: ../vnc.html:164
#: ../vnc.html:157
msgid "Shutdown"
msgstr "Uitschakelen"
#: ../vnc.html:165
#: ../vnc.html:158
msgid "Reboot"
msgstr "Herstarten"
#: ../vnc.html:166
#: ../vnc.html:159
msgid "Reset"
msgstr "Resetten"
#: ../vnc.html:171 ../vnc.html:177
#: ../vnc.html:164 ../vnc.html:170
msgid "Clipboard"
msgstr "Klembord"
#: ../vnc.html:181
#: ../vnc.html:174
msgid "Clear"
msgstr "Wissen"
#: ../vnc.html:187
#: ../vnc.html:180
msgid "Fullscreen"
msgstr "Volledig Scherm"
#: ../vnc.html:192 ../vnc.html:199
#: ../vnc.html:185 ../vnc.html:192
msgid "Settings"
msgstr "Instellingen"
#: ../vnc.html:202
#: ../vnc.html:195
msgid "Shared Mode"
msgstr "Gedeelde Modus"
#: ../vnc.html:205
#: ../vnc.html:198
msgid "View Only"
msgstr "Alleen Kijken"
#: ../vnc.html:209
#: ../vnc.html:202
msgid "Clip to Window"
msgstr "Randen buiten venster afsnijden"
#: ../vnc.html:212
#: ../vnc.html:205
msgid "Scaling Mode:"
msgstr "Schaalmodus:"
#: ../vnc.html:214
#: ../vnc.html:207
msgid "None"
msgstr "Geen"
#: ../vnc.html:215
#: ../vnc.html:208
msgid "Local Scaling"
msgstr "Lokaal Schalen"
#: ../vnc.html:216
msgid "Local Downscaling"
msgstr "Lokaal Neerschalen"
#: ../vnc.html:217
#: ../vnc.html:209
msgid "Remote Resizing"
msgstr "Op Afstand Formaat Wijzigen"
#: ../vnc.html:222
#: ../vnc.html:214
msgid "Advanced"
msgstr "Geavanceerd"
#: ../vnc.html:225
msgid "Local Cursor"
msgstr "Lokale Cursor"
#: ../vnc.html:229
#: ../vnc.html:217
msgid "Repeater ID:"
msgstr "Repeater ID:"
#: ../vnc.html:233
#: ../vnc.html:221
msgid "WebSocket"
msgstr "WebSocket"
#: ../vnc.html:236
#: ../vnc.html:224
msgid "Encrypt"
msgstr "Versleutelen"
#: ../vnc.html:239
#: ../vnc.html:227
msgid "Host:"
msgstr "Host:"
#: ../vnc.html:243
#: ../vnc.html:231
msgid "Port:"
msgstr "Poort:"
#: ../vnc.html:247
#: ../vnc.html:235
msgid "Path:"
msgstr "Pad:"
#: ../vnc.html:254
#: ../vnc.html:242
msgid "Automatic Reconnect"
msgstr "Automatisch Opnieuw Verbinden"
#: ../vnc.html:257
#: ../vnc.html:245
msgid "Reconnect Delay (ms):"
msgstr "Vertraging voor Opnieuw Verbinden (ms):"
#: ../vnc.html:263
#: ../vnc.html:250
msgid "Show Dot when No Cursor"
msgstr "Geef stip weer indien geen cursor"
#: ../vnc.html:255
msgid "Logging:"
msgstr "Logmeldingen:"
#: ../vnc.html:275
#: ../vnc.html:267
msgid "Disconnect"
msgstr "Verbinding verbreken"
#: ../vnc.html:294
#: ../vnc.html:286
msgid "Connect"
msgstr "Verbinden"
#: ../vnc.html:304
#: ../vnc.html:296
msgid "Password:"
msgstr "Wachtwoord:"
#: ../vnc.html:318
#: ../vnc.html:300
msgid "Send Password"
msgstr "Verzend Wachtwoord:"
#: ../vnc.html:310
msgid "Cancel"
msgstr "Annuleren"
#: ../vnc.html:334
msgid "Canvas not supported."
msgstr "Canvas wordt niet ondersteund."
#~ msgid "Disconnect timeout"
#~ msgstr "Timeout tijdens verbreken van verbinding"
#~ msgid "Local Downscaling"
#~ msgstr "Lokaal Neerschalen"
#~ msgid "Local Cursor"
#~ msgstr "Lokale Cursor"
#~ msgid "Canvas not supported."
#~ msgstr "Canvas wordt niet ondersteund."
#~ msgid ""
#~ "Forcing clipping mode since scrollbars aren't supported by IE in "

View File

@@ -1,14 +1,14 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR Various Authors
# Copyright (C) YEAR The noVNC Authors
# This file is distributed under the same license as the noVNC package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: noVNC 1.0.0-testing.2\n"
"Project-Id-Version: noVNC 1.2.0\n"
"Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
"POT-Creation-Date: 2018-01-31 16:29+0100\n"
"POT-Creation-Date: 2020-07-03 16:11+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -17,274 +17,282 @@ msgstr ""
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"
#: ../app/ui.js:395
#: ../app/ui.js:394
msgid "Connecting..."
msgstr ""
#: ../app/ui.js:402
#: ../app/ui.js:401
msgid "Disconnecting..."
msgstr ""
#: ../app/ui.js:408
#: ../app/ui.js:407
msgid "Reconnecting..."
msgstr ""
#: ../app/ui.js:413
#: ../app/ui.js:412
msgid "Internal error"
msgstr ""
#: ../app/ui.js:1002
#: ../app/ui.js:1008
msgid "Must set host"
msgstr ""
#: ../app/ui.js:1083
#: ../app/ui.js:1090
msgid "Connected (encrypted) to "
msgstr ""
#: ../app/ui.js:1085
#: ../app/ui.js:1092
msgid "Connected (unencrypted) to "
msgstr ""
#: ../app/ui.js:1108
#: ../app/ui.js:1115
msgid "Something went wrong, connection is closed"
msgstr ""
#: ../app/ui.js:1111
#: ../app/ui.js:1118
msgid "Failed to connect to server"
msgstr ""
#: ../app/ui.js:1121
#: ../app/ui.js:1128
msgid "Disconnected"
msgstr ""
#: ../app/ui.js:1134
#: ../app/ui.js:1143
msgid "New connection has been rejected with reason: "
msgstr ""
#: ../app/ui.js:1137
#: ../app/ui.js:1146
msgid "New connection has been rejected"
msgstr ""
#: ../app/ui.js:1158
msgid "Password is required"
#: ../app/ui.js:1181
msgid "Credentials are required"
msgstr ""
#: ../vnc.html:91
#: ../vnc.html:74
msgid "noVNC encountered an error:"
msgstr ""
#: ../vnc.html:101
#: ../vnc.html:84
msgid "Hide/Show the control bar"
msgstr ""
#: ../vnc.html:108
#: ../vnc.html:91
msgid "Drag"
msgstr ""
#: ../vnc.html:91
msgid "Move/Drag Viewport"
msgstr ""
#: ../vnc.html:108
msgid "viewport drag"
msgstr ""
#: ../vnc.html:114 ../vnc.html:117 ../vnc.html:120 ../vnc.html:123
msgid "Active Mouse Button"
msgstr ""
#: ../vnc.html:114
msgid "No mousebutton"
msgstr ""
#: ../vnc.html:117
msgid "Left mousebutton"
msgstr ""
#: ../vnc.html:120
msgid "Middle mousebutton"
msgstr ""
#: ../vnc.html:123
msgid "Right mousebutton"
msgstr ""
#: ../vnc.html:126
#: ../vnc.html:97
msgid "Keyboard"
msgstr ""
#: ../vnc.html:126
#: ../vnc.html:97
msgid "Show Keyboard"
msgstr ""
#: ../vnc.html:133
#: ../vnc.html:102
msgid "Extra keys"
msgstr ""
#: ../vnc.html:133
#: ../vnc.html:102
msgid "Show Extra Keys"
msgstr ""
#: ../vnc.html:138
#: ../vnc.html:107
msgid "Ctrl"
msgstr ""
#: ../vnc.html:138
#: ../vnc.html:107
msgid "Toggle Ctrl"
msgstr ""
#: ../vnc.html:141
#: ../vnc.html:110
msgid "Alt"
msgstr ""
#: ../vnc.html:141
#: ../vnc.html:110
msgid "Toggle Alt"
msgstr ""
#: ../vnc.html:144
#: ../vnc.html:113
msgid "Toggle Windows"
msgstr ""
#: ../vnc.html:113
msgid "Windows"
msgstr ""
#: ../vnc.html:116
msgid "Send Tab"
msgstr ""
#: ../vnc.html:144
#: ../vnc.html:116
msgid "Tab"
msgstr ""
#: ../vnc.html:147
#: ../vnc.html:119
msgid "Esc"
msgstr ""
#: ../vnc.html:147
#: ../vnc.html:119
msgid "Send Escape"
msgstr ""
#: ../vnc.html:150
#: ../vnc.html:122
msgid "Ctrl+Alt+Del"
msgstr ""
#: ../vnc.html:150
#: ../vnc.html:122
msgid "Send Ctrl-Alt-Del"
msgstr ""
#: ../vnc.html:158
#: ../vnc.html:129
msgid "Shutdown/Reboot"
msgstr ""
#: ../vnc.html:158
#: ../vnc.html:129
msgid "Shutdown/Reboot..."
msgstr ""
#: ../vnc.html:164
#: ../vnc.html:135
msgid "Power"
msgstr ""
#: ../vnc.html:166
#: ../vnc.html:137
msgid "Shutdown"
msgstr ""
#: ../vnc.html:167
#: ../vnc.html:138
msgid "Reboot"
msgstr ""
#: ../vnc.html:168
#: ../vnc.html:139
msgid "Reset"
msgstr ""
#: ../vnc.html:173 ../vnc.html:179
#: ../vnc.html:144 ../vnc.html:150
msgid "Clipboard"
msgstr ""
#: ../vnc.html:183
#: ../vnc.html:154
msgid "Clear"
msgstr ""
#: ../vnc.html:189
#: ../vnc.html:160
msgid "Fullscreen"
msgstr ""
#: ../vnc.html:194 ../vnc.html:201
#: ../vnc.html:165 ../vnc.html:172
msgid "Settings"
msgstr ""
#: ../vnc.html:204
#: ../vnc.html:175
msgid "Shared Mode"
msgstr ""
#: ../vnc.html:207
#: ../vnc.html:178
msgid "View Only"
msgstr ""
#: ../vnc.html:211
#: ../vnc.html:182
msgid "Clip to Window"
msgstr ""
#: ../vnc.html:214
#: ../vnc.html:185
msgid "Scaling Mode:"
msgstr ""
#: ../vnc.html:216
#: ../vnc.html:187
msgid "None"
msgstr ""
#: ../vnc.html:217
#: ../vnc.html:188
msgid "Local Scaling"
msgstr ""
#: ../vnc.html:218
#: ../vnc.html:189
msgid "Remote Resizing"
msgstr ""
#: ../vnc.html:223
#: ../vnc.html:194
msgid "Advanced"
msgstr ""
#: ../vnc.html:226
#: ../vnc.html:197
msgid "Quality:"
msgstr ""
#: ../vnc.html:201
msgid "Compression level:"
msgstr ""
#: ../vnc.html:206
msgid "Repeater ID:"
msgstr ""
#: ../vnc.html:230
#: ../vnc.html:210
msgid "WebSocket"
msgstr ""
#: ../vnc.html:233
#: ../vnc.html:213
msgid "Encrypt"
msgstr ""
#: ../vnc.html:236
#: ../vnc.html:216
msgid "Host:"
msgstr ""
#: ../vnc.html:240
#: ../vnc.html:220
msgid "Port:"
msgstr ""
#: ../vnc.html:244
#: ../vnc.html:224
msgid "Path:"
msgstr ""
#: ../vnc.html:251
#: ../vnc.html:231
msgid "Automatic Reconnect"
msgstr ""
#: ../vnc.html:254
#: ../vnc.html:234
msgid "Reconnect Delay (ms):"
msgstr ""
#: ../vnc.html:260
#: ../vnc.html:239
msgid "Show Dot when No Cursor"
msgstr ""
#: ../vnc.html:244
msgid "Logging:"
msgstr ""
#: ../vnc.html:272
#: ../vnc.html:253
msgid "Version:"
msgstr ""
#: ../vnc.html:261
msgid "Disconnect"
msgstr ""
#: ../vnc.html:291
#: ../vnc.html:280
msgid "Connect"
msgstr ""
#: ../vnc.html:301
#: ../vnc.html:290
msgid "Username:"
msgstr ""
#: ../vnc.html:294
msgid "Password:"
msgstr ""
#: ../vnc.html:305
msgid "Send Password"
#: ../vnc.html:298
msgid "Send Credentials"
msgstr ""
#: ../vnc.html:315
#: ../vnc.html:308
msgid "Cancel"
msgstr ""

View File

@@ -1,5 +1,5 @@
# Polish translations for noVNC package.
# Copyright (C) 2017 Various Authors
# Copyright (C) 2018 The noVNC Authors
# This file is distributed under the same license as the noVNC package.
# Mariusz Jamro <mariusz.jamro@gmail.com>, 2017.
#

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env node
/*
* ps2js: gettext .po to noVNC .js converter
* Copyright (C) 2016 Pierre Ossman
* Copyright (C) 2018 The noVNC Authors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -17,27 +17,27 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
var getopt = require('node-getopt');
var fs = require('fs');
var po2json = require("po2json");
const getopt = require('node-getopt');
const fs = require('fs');
const po2json = require("po2json");
opt = getopt.create([
['h' , 'help' , 'display this help'],
const opt = getopt.create([
['h', 'help', 'display this help'],
]).bindHelp().parseSystem();
if (opt.argv.length != 2) {
console.error("Incorrect number of arguments given");
process.exit(1);
console.error("Incorrect number of arguments given");
process.exit(1);
}
var data = po2json.parseFileSync(opt.argv[0]);
const data = po2json.parseFileSync(opt.argv[0]);
var bodyPart = Object.keys(data).filter((msgid) => msgid !== "").map((msgid) => {
const bodyPart = Object.keys(data).filter(msgid => msgid !== "").map((msgid) => {
if (msgid === "") return;
var msgstr = data[msgid][1];
const msgstr = data[msgid][1];
return " " + JSON.stringify(msgid) + ": " + JSON.stringify(msgstr);
}).join(",\n");
var output = "{\n" + bodyPart + "\n}";
const output = "{\n" + bodyPart + "\n}";
fs.writeFileSync(opt.argv[1], output);

306
po/ru.po Normal file
View File

@@ -0,0 +1,306 @@
# Russian translations for noVNC package
# Русский перевод для пакета noVNC.
# Copyright (C) 2019 Dmitriy Shweew
# This file is distributed under the same license as the noVNC package.
# Dmitriy Shweew <shweew@it-advisor.ru>, 2019.
#
msgid ""
msgstr ""
"Project-Id-Version: noVNC 1.1.0\n"
"Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
"POT-Creation-Date: 2019-02-26 14:53+0400\n"
"PO-Revision-Date: 2019-02-17 17:29+0400\n"
"Last-Translator: Dmitriy Shweew <shweew@it-advisor.ru>\n"
"Language-Team: Russian\n"
"Language: ru\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
"X-Generator: Poedit 2.2.1\n"
"X-Poedit-Flags-xgettext: --add-comments\n"
#: ../app/ui.js:387
msgid "Connecting..."
msgstr "Подключение..."
#: ../app/ui.js:394
msgid "Disconnecting..."
msgstr "Отключение..."
#: ../app/ui.js:400
msgid "Reconnecting..."
msgstr "Переподключение..."
#: ../app/ui.js:405
msgid "Internal error"
msgstr "Внутренняя ошибка"
#: ../app/ui.js:995
msgid "Must set host"
msgstr "Задайте имя сервера или IP"
#: ../app/ui.js:1077
msgid "Connected (encrypted) to "
msgstr "Подключено (с шифрованием) к "
#: ../app/ui.js:1079
msgid "Connected (unencrypted) to "
msgstr "Подключено (без шифрования) к "
#: ../app/ui.js:1102
msgid "Something went wrong, connection is closed"
msgstr "Что-то пошло не так, подключение разорвано"
#: ../app/ui.js:1105
msgid "Failed to connect to server"
msgstr "Ошибка подключения к серверу"
#: ../app/ui.js:1115
msgid "Disconnected"
msgstr "Отключено"
#: ../app/ui.js:1128
msgid "New connection has been rejected with reason: "
msgstr "Подключиться не удалось: "
#: ../app/ui.js:1131
msgid "New connection has been rejected"
msgstr "Подключиться не удалось"
#: ../app/ui.js:1151
msgid "Password is required"
msgstr "Требуется пароль"
#: ../vnc.html:84
msgid "noVNC encountered an error:"
msgstr "Ошибка noVNC: "
#: ../vnc.html:94
msgid "Hide/Show the control bar"
msgstr "Скрыть/Показать контрольную панель"
#: ../vnc.html:101
msgid "Move/Drag Viewport"
msgstr "Переместить окно"
#: ../vnc.html:101
msgid "viewport drag"
msgstr "Переместить окно"
#: ../vnc.html:107 ../vnc.html:110 ../vnc.html:113 ../vnc.html:116
msgid "Active Mouse Button"
msgstr "Активировать кнопки мыши"
#: ../vnc.html:107
msgid "No mousebutton"
msgstr "Отключить кнопки мыши"
#: ../vnc.html:110
msgid "Left mousebutton"
msgstr "Левая кнопка мыши"
#: ../vnc.html:113
msgid "Middle mousebutton"
msgstr "Средняя кнопка мыши"
#: ../vnc.html:116
msgid "Right mousebutton"
msgstr "Правая кнопка мыши"
#: ../vnc.html:119
msgid "Keyboard"
msgstr "Клавиатура"
#: ../vnc.html:119
msgid "Show Keyboard"
msgstr "Показать клавиатуру"
#: ../vnc.html:126
msgid "Extra keys"
msgstr "Доп. кнопки"
#: ../vnc.html:126
msgid "Show Extra Keys"
msgstr "Показать дополнительные кнопки"
#: ../vnc.html:131
msgid "Ctrl"
msgstr "Ctrl"
#: ../vnc.html:131
msgid "Toggle Ctrl"
msgstr "Передать нажатие Ctrl"
#: ../vnc.html:134
msgid "Alt"
msgstr "Alt"
#: ../vnc.html:134
msgid "Toggle Alt"
msgstr "Передать нажатие Alt"
#: ../vnc.html:137
msgid "Toggle Windows"
msgstr "Переключение вкладок"
#: ../vnc.html:137
msgid "Windows"
msgstr "Вкладка"
#: ../vnc.html:140
msgid "Send Tab"
msgstr "Передать нажатие Tab"
#: ../vnc.html:140
msgid "Tab"
msgstr "Tab"
#: ../vnc.html:143
msgid "Esc"
msgstr "Esc"
#: ../vnc.html:143
msgid "Send Escape"
msgstr "Передать нажатие Escape"
#: ../vnc.html:146
msgid "Ctrl+Alt+Del"
msgstr "Ctrl+Alt+Del"
#: ../vnc.html:146
msgid "Send Ctrl-Alt-Del"
msgstr "Передать нажатие Ctrl-Alt-Del"
#: ../vnc.html:154
msgid "Shutdown/Reboot"
msgstr "Выключить/Перезагрузить"
#: ../vnc.html:154
msgid "Shutdown/Reboot..."
msgstr "Выключить/Перезагрузить..."
#: ../vnc.html:160
msgid "Power"
msgstr "Питание"
#: ../vnc.html:162
msgid "Shutdown"
msgstr "Выключить"
#: ../vnc.html:163
msgid "Reboot"
msgstr "Перезагрузить"
#: ../vnc.html:164
msgid "Reset"
msgstr "Сброс"
#: ../vnc.html:169 ../vnc.html:175
msgid "Clipboard"
msgstr "Буфер обмена"
#: ../vnc.html:179
msgid "Clear"
msgstr "Очистить"
#: ../vnc.html:185
msgid "Fullscreen"
msgstr "Во весь экран"
#: ../vnc.html:190 ../vnc.html:197
msgid "Settings"
msgstr "Настройки"
#: ../vnc.html:200
msgid "Shared Mode"
msgstr "Общий режим"
#: ../vnc.html:203
msgid "View Only"
msgstr "Просмотр"
#: ../vnc.html:207
msgid "Clip to Window"
msgstr "В окно"
#: ../vnc.html:210
msgid "Scaling Mode:"
msgstr "Масштаб:"
#: ../vnc.html:212
msgid "None"
msgstr "Нет"
#: ../vnc.html:213
msgid "Local Scaling"
msgstr "Локльный масштаб"
#: ../vnc.html:214
msgid "Remote Resizing"
msgstr "Удаленный масштаб"
#: ../vnc.html:219
msgid "Advanced"
msgstr "Дополнительно"
#: ../vnc.html:222
msgid "Repeater ID:"
msgstr "Идентификатор ID:"
#: ../vnc.html:226
msgid "WebSocket"
msgstr "WebSocket"
#: ../vnc.html:229
msgid "Encrypt"
msgstr "Шифрование"
#: ../vnc.html:232
msgid "Host:"
msgstr "Сервер:"
#: ../vnc.html:236
msgid "Port:"
msgstr "Порт:"
#: ../vnc.html:240
msgid "Path:"
msgstr "Путь:"
#: ../vnc.html:247
msgid "Automatic Reconnect"
msgstr "Автоматическое переподключение"
#: ../vnc.html:250
msgid "Reconnect Delay (ms):"
msgstr "Задержка переподключения (мс):"
#: ../vnc.html:255
msgid "Show Dot when No Cursor"
msgstr "Показать точку вместо курсора"
#: ../vnc.html:260
msgid "Logging:"
msgstr "Лог:"
#: ../vnc.html:272
msgid "Disconnect"
msgstr "Отключение"
#: ../vnc.html:291
msgid "Connect"
msgstr "Подключение"
#: ../vnc.html:301
msgid "Password:"
msgstr "Пароль:"
#: ../vnc.html:305
msgid "Send Password"
msgstr "Пароль: "
#: ../vnc.html:315
msgid "Cancel"
msgstr "Выход"

222
po/sv.po
View File

@@ -1,16 +1,16 @@
# Swedish translations for noVNC package
# Svenska översättningar för paket noVNC.
# Copyright (C) 2016 Various Authors
# Copyright (C) 2018 The noVNC Authors
# This file is distributed under the same license as the noVNC package.
# Samuel Mannehed <samuel@cendio.se>, 2016.
# Samuel Mannehed <samuel@cendio.se>, 2019.
#
msgid ""
msgstr ""
"Project-Id-Version: noVNC 0.6.1\n"
"Project-Id-Version: noVNC 1.1.0\n"
"Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
"POT-Creation-Date: 2017-10-06 10:07+0200\n"
"PO-Revision-Date: 2017-10-06 10:18+0200\n"
"Last-Translator: Pierre Ossman <pierre@ossman.eu>\n"
"POT-Creation-Date: 2019-01-16 11:06+0100\n"
"PO-Revision-Date: 2019-04-08 10:18+0200\n"
"Last-Translator: Samuel Mannehed <samuel@cendio.se>\n"
"Language-Team: none\n"
"Language: sv\n"
"MIME-Version: 1.0\n"
@@ -19,266 +19,298 @@ msgstr ""
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 2.0.3\n"
#: ../app/ui.js:430
#: ../app/ui.js:387
msgid "Connecting..."
msgstr "Ansluter..."
#: ../app/ui.js:438
msgid "Connected (encrypted) to "
msgstr "Ansluten (krypterat) till "
#: ../app/ui.js:440
msgid "Connected (unencrypted) to "
msgstr "Ansluten (okrypterat) till "
#: ../app/ui.js:446
#: ../app/ui.js:394
msgid "Disconnecting..."
msgstr "Kopplar ner..."
#: ../app/ui.js:450
msgid "Disconnected"
msgstr "Frånkopplad"
#: ../app/ui.js:1052 ../core/rfb.js:248
msgid "Must set host"
msgstr "Du måste specifiera en värd"
#: ../app/ui.js:1101
#: ../app/ui.js:400
msgid "Reconnecting..."
msgstr "Återansluter..."
#: ../app/ui.js:1140
#: ../app/ui.js:405
msgid "Internal error"
msgstr "Internt fel"
#: ../app/ui.js:995
msgid "Must set host"
msgstr "Du måste specifiera en värd"
#: ../app/ui.js:1077
msgid "Connected (encrypted) to "
msgstr "Ansluten (krypterat) till "
#: ../app/ui.js:1079
msgid "Connected (unencrypted) to "
msgstr "Ansluten (okrypterat) till "
#: ../app/ui.js:1102
msgid "Something went wrong, connection is closed"
msgstr "Något gick fel, anslutningen avslutades"
#: ../app/ui.js:1105
msgid "Failed to connect to server"
msgstr "Misslyckades att ansluta till servern"
#: ../app/ui.js:1115
msgid "Disconnected"
msgstr "Frånkopplad"
#: ../app/ui.js:1128
msgid "New connection has been rejected with reason: "
msgstr "Ny anslutning har blivit nekad med följande skäl: "
#: ../app/ui.js:1131
msgid "New connection has been rejected"
msgstr "Ny anslutning har blivit nekad"
#: ../app/ui.js:1151
msgid "Password is required"
msgstr "Lösenord krävs"
#: ../core/rfb.js:548
msgid "Disconnect timeout"
msgstr "Det tog för lång tid att koppla ner"
#: ../vnc.html:89
#: ../vnc.html:84
msgid "noVNC encountered an error:"
msgstr "noVNC stötte på ett problem:"
#: ../vnc.html:99
#: ../vnc.html:94
msgid "Hide/Show the control bar"
msgstr "Göm/Visa kontrollbaren"
#: ../vnc.html:106
#: ../vnc.html:101
msgid "Move/Drag Viewport"
msgstr "Flytta/Dra Vyn"
#: ../vnc.html:106
#: ../vnc.html:101
msgid "viewport drag"
msgstr "dra vy"
#: ../vnc.html:112 ../vnc.html:115 ../vnc.html:118 ../vnc.html:121
#: ../vnc.html:107 ../vnc.html:110 ../vnc.html:113 ../vnc.html:116
msgid "Active Mouse Button"
msgstr "Aktiv musknapp"
#: ../vnc.html:112
#: ../vnc.html:107
msgid "No mousebutton"
msgstr "Ingen musknapp"
#: ../vnc.html:115
#: ../vnc.html:110
msgid "Left mousebutton"
msgstr "Vänster musknapp"
#: ../vnc.html:118
#: ../vnc.html:113
msgid "Middle mousebutton"
msgstr "Mitten-musknapp"
#: ../vnc.html:121
#: ../vnc.html:116
msgid "Right mousebutton"
msgstr "Höger musknapp"
#: ../vnc.html:124
#: ../vnc.html:119
msgid "Keyboard"
msgstr "Tangentbord"
#: ../vnc.html:124
#: ../vnc.html:119
msgid "Show Keyboard"
msgstr "Visa Tangentbord"
#: ../vnc.html:131
#: ../vnc.html:126
msgid "Extra keys"
msgstr "Extraknappar"
#: ../vnc.html:131
#: ../vnc.html:126
msgid "Show Extra Keys"
msgstr "Visa Extraknappar"
#: ../vnc.html:136
#: ../vnc.html:131
msgid "Ctrl"
msgstr "Ctrl"
#: ../vnc.html:136
#: ../vnc.html:131
msgid "Toggle Ctrl"
msgstr "Växla Ctrl"
#: ../vnc.html:139
#: ../vnc.html:134
msgid "Alt"
msgstr "Alt"
#: ../vnc.html:139
#: ../vnc.html:134
msgid "Toggle Alt"
msgstr "Växla Alt"
#: ../vnc.html:142
#: ../vnc.html:137
msgid "Toggle Windows"
msgstr "Växla Windows"
#: ../vnc.html:137
msgid "Windows"
msgstr "Windows"
#: ../vnc.html:140
msgid "Send Tab"
msgstr "Skicka Tab"
#: ../vnc.html:142
#: ../vnc.html:140
msgid "Tab"
msgstr "Tab"
#: ../vnc.html:145
#: ../vnc.html:143
msgid "Esc"
msgstr "Esc"
#: ../vnc.html:145
#: ../vnc.html:143
msgid "Send Escape"
msgstr "Skicka Escape"
#: ../vnc.html:148
#: ../vnc.html:146
msgid "Ctrl+Alt+Del"
msgstr "Ctrl+Alt+Del"
#: ../vnc.html:148
#: ../vnc.html:146
msgid "Send Ctrl-Alt-Del"
msgstr "Skicka Ctrl-Alt-Del"
#: ../vnc.html:156
#: ../vnc.html:154
msgid "Shutdown/Reboot"
msgstr "Stäng av/Boota om"
#: ../vnc.html:156
#: ../vnc.html:154
msgid "Shutdown/Reboot..."
msgstr "Stäng av/Boota om..."
#: ../vnc.html:162
#: ../vnc.html:160
msgid "Power"
msgstr "Ström"
#: ../vnc.html:164
#: ../vnc.html:162
msgid "Shutdown"
msgstr "Stäng av"
#: ../vnc.html:165
#: ../vnc.html:163
msgid "Reboot"
msgstr "Boota om"
#: ../vnc.html:166
#: ../vnc.html:164
msgid "Reset"
msgstr "Återställ"
#: ../vnc.html:171 ../vnc.html:177
#: ../vnc.html:169 ../vnc.html:175
msgid "Clipboard"
msgstr "Urklipp"
#: ../vnc.html:181
#: ../vnc.html:179
msgid "Clear"
msgstr "Rensa"
#: ../vnc.html:187
#: ../vnc.html:185
msgid "Fullscreen"
msgstr "Fullskärm"
#: ../vnc.html:192 ../vnc.html:199
#: ../vnc.html:190 ../vnc.html:197
msgid "Settings"
msgstr "Inställningar"
#: ../vnc.html:202
#: ../vnc.html:200
msgid "Shared Mode"
msgstr "Delat Läge"
#: ../vnc.html:205
#: ../vnc.html:203
msgid "View Only"
msgstr "Endast Visning"
#: ../vnc.html:209
#: ../vnc.html:207
msgid "Clip to Window"
msgstr "Begränsa till Fönster"
#: ../vnc.html:212
#: ../vnc.html:210
msgid "Scaling Mode:"
msgstr "Skalningsläge:"
#: ../vnc.html:214
#: ../vnc.html:212
msgid "None"
msgstr "Ingen"
#: ../vnc.html:215
#: ../vnc.html:213
msgid "Local Scaling"
msgstr "Lokal Skalning"
#: ../vnc.html:216
msgid "Local Downscaling"
msgstr "Lokal Nedskalning"
#: ../vnc.html:217
#: ../vnc.html:214
msgid "Remote Resizing"
msgstr "Ändra Storlek"
#: ../vnc.html:222
#: ../vnc.html:219
msgid "Advanced"
msgstr "Avancerat"
#: ../vnc.html:225
msgid "Local Cursor"
msgstr "Lokal Muspekare"
#: ../vnc.html:229
#: ../vnc.html:222
msgid "Repeater ID:"
msgstr "Repeater-ID:"
#: ../vnc.html:233
#: ../vnc.html:226
msgid "WebSocket"
msgstr "WebSocket"
#: ../vnc.html:236
#: ../vnc.html:229
msgid "Encrypt"
msgstr "Kryptera"
#: ../vnc.html:239
#: ../vnc.html:232
msgid "Host:"
msgstr "Värd:"
#: ../vnc.html:243
#: ../vnc.html:236
msgid "Port:"
msgstr "Port:"
#: ../vnc.html:247
#: ../vnc.html:240
msgid "Path:"
msgstr "Sökväg:"
#: ../vnc.html:254
#: ../vnc.html:247
msgid "Automatic Reconnect"
msgstr "Automatisk Återanslutning"
#: ../vnc.html:257
#: ../vnc.html:250
msgid "Reconnect Delay (ms):"
msgstr "Fördröjning (ms):"
#: ../vnc.html:263
#: ../vnc.html:255
msgid "Show Dot when No Cursor"
msgstr "Visa prick när ingen muspekare finns"
#: ../vnc.html:260
msgid "Logging:"
msgstr "Loggning:"
#: ../vnc.html:275
#: ../vnc.html:272
msgid "Disconnect"
msgstr "Koppla från"
#: ../vnc.html:294
#: ../vnc.html:291
msgid "Connect"
msgstr "Anslut"
#: ../vnc.html:304
#: ../vnc.html:301
msgid "Password:"
msgstr "Lösenord:"
#: ../vnc.html:318
#: ../vnc.html:305
msgid "Send Password"
msgstr "Skicka lösenord"
#: ../vnc.html:315
msgid "Cancel"
msgstr "Avbryt"
#: ../vnc.html:334
msgid "Canvas not supported."
msgstr "Canvas stöds ej"
#~ msgid "Disconnect timeout"
#~ msgstr "Det tog för lång tid att koppla ner"
#~ msgid "Local Downscaling"
#~ msgstr "Lokal Nedskalning"
#~ msgid "Local Cursor"
#~ msgstr "Lokal Muspekare"
#~ msgid "Canvas not supported."
#~ msgstr "Canvas stöds ej"

View File

@@ -1,6 +1,6 @@
# Turkish translations for noVNC package
# Turkish translation for noVNC.
# Copyright (C) 2016 Various Authors
# Copyright (C) 2018 The noVNC Authors
# This file is distributed under the same license as the noVNC package.
# Ömer ÇAKMAK <farukomercakmak@gmail.com>, 2018.
#

View File

@@ -1,21 +1,20 @@
#!/usr/bin/env node
/*
* xgettext-html: HTML gettext parser
* Copyright (C) 2016 Pierre Ossman
* Copyright (C) 2018 The noVNC Authors
* Licensed under MPL 2.0 (see LICENSE.txt)
*/
var getopt = require('node-getopt');
const getopt = require('node-getopt');
const jsdom = require("jsdom");
const fs = require("fs");
var jsdom = require("jsdom");
var fs = require("fs");
opt = getopt.create([
['o' , 'output=FILE' , 'write output to specified file'],
['h' , 'help' , 'display this help'],
const opt = getopt.create([
['o', 'output=FILE', 'write output to specified file'],
['h', 'help', 'display this help'],
]).bindHelp().parseSystem();
var strings = {};
const strings = {};
function addString(str, location) {
if (str.length == 0) {
@@ -23,7 +22,7 @@ function addString(str, location) {
}
if (strings[str] === undefined) {
strings[str] = {}
strings[str] = {};
}
strings[str][location] = null;
}
@@ -74,8 +73,8 @@ function process(elem, locator, enabled) {
}
}
for (var i = 0;i < elem.childNodes.length;i++) {
node = elem.childNodes[i];
for (let i = 0; i < elem.childNodes.length; i++) {
let node = elem.childNodes[i];
if (node.nodeType === node.ELEMENT_NODE) {
process(node, locator, enabled);
} else if (node.nodeType === node.TEXT_NODE && enabled) {
@@ -84,26 +83,24 @@ function process(elem, locator, enabled) {
}
}
for (var i = 0;i < opt.argv.length;i++) {
var file;
for (let i = 0; i < opt.argv.length; i++) {
const fn = opt.argv[i];
const file = fs.readFileSync(fn, "utf8");
const dom = new jsdom.JSDOM(file, { includeNodeLocations: true });
const body = dom.window.document.body;
fn = opt.argv[i];
file = fs.readFileSync(fn, "utf8");
dom = new jsdom.JSDOM(file, { includeNodeLocations: true });
body = dom.window.document.body;
locator = function (elem) {
offset = dom.nodeLocation(elem).startOffset;
line = file.slice(0, offset).split("\n").length;
let locator = (elem) => {
const offset = dom.nodeLocation(elem).startOffset;
const line = file.slice(0, offset).split("\n").length;
return fn + ":" + line;
};
process(body, locator, true);
}
var output = "";
let output = "";
for (str in strings) {
for (let str in strings) {
output += "#:";
for (location in strings[str]) {
output += " " + location;

284
po/zh_CN.po Normal file
View File

@@ -0,0 +1,284 @@
# Simplified Chinese translations for noVNC package.
# Copyright (C) 2020 The noVNC Authors
# This file is distributed under the same license as the noVNC package.
# Peter Dave Hello <hsu@peterdavehello.org>, 2018.
#
msgid ""
msgstr ""
"Project-Id-Version: noVNC 1.1.0\n"
"Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
"POT-Creation-Date: 2018-01-10 00:53+0800\n"
"PO-Revision-Date: 2020-01-02 13:19+0800\n"
"Last-Translator: CUI Wei <ghostplant@qq.com>\n"
"Language: zh_CN\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: ../app/ui.js:395
msgid "Connecting..."
msgstr "连接中..."
#: ../app/ui.js:402
msgid "Disconnecting..."
msgstr "正在断开连接..."
#: ../app/ui.js:408
msgid "Reconnecting..."
msgstr "重新连接中..."
#: ../app/ui.js:413
msgid "Internal error"
msgstr "内部错误"
#: ../app/ui.js:1015
msgid "Must set host"
msgstr "请提供主机名"
#: ../app/ui.js:1097
msgid "Connected (encrypted) to "
msgstr "已连接到(加密)"
#: ../app/ui.js:1099
msgid "Connected (unencrypted) to "
msgstr "已连接到(未加密)"
#: ../app/ui.js:1120
msgid "Something went wrong, connection is closed"
msgstr "发生错误,连接已关闭"
#: ../app/ui.js:1123
msgid "Failed to connect to server"
msgstr "无法连接到服务器"
#: ../app/ui.js:1133
msgid "Disconnected"
msgstr "已断开连接"
#: ../app/ui.js:1146
msgid "New connection has been rejected with reason: "
msgstr "连接被拒绝,原因:"
#: ../app/ui.js:1149
msgid "New connection has been rejected"
msgstr "连接被拒绝"
#: ../app/ui.js:1170
msgid "Password is required"
msgstr "请提供密码"
#: ../vnc.html:89
msgid "noVNC encountered an error:"
msgstr "noVNC 遇到一个错误:"
#: ../vnc.html:99
msgid "Hide/Show the control bar"
msgstr "显示/隐藏控制栏"
#: ../vnc.html:106
msgid "Move/Drag Viewport"
msgstr "拖放显示范围"
#: ../vnc.html:106
msgid "viewport drag"
msgstr "显示范围拖放"
#: ../vnc.html:112 ../vnc.html:115 ../vnc.html:118 ../vnc.html:121
msgid "Active Mouse Button"
msgstr "启动鼠标按鍵"
#: ../vnc.html:112
msgid "No mousebutton"
msgstr "禁用鼠标按鍵"
#: ../vnc.html:115
msgid "Left mousebutton"
msgstr "鼠标左鍵"
#: ../vnc.html:118
msgid "Middle mousebutton"
msgstr "鼠标中鍵"
#: ../vnc.html:121
msgid "Right mousebutton"
msgstr "鼠标右鍵"
#: ../vnc.html:124
msgid "Keyboard"
msgstr "键盘"
#: ../vnc.html:124
msgid "Show Keyboard"
msgstr "显示键盘"
#: ../vnc.html:131
msgid "Extra keys"
msgstr "额外按键"
#: ../vnc.html:131
msgid "Show Extra Keys"
msgstr "显示额外按键"
#: ../vnc.html:136
msgid "Ctrl"
msgstr "Ctrl"
#: ../vnc.html:136
msgid "Toggle Ctrl"
msgstr "切换 Ctrl"
#: ../vnc.html:139
msgid "Alt"
msgstr "Alt"
#: ../vnc.html:139
msgid "Toggle Alt"
msgstr "切换 Alt"
#: ../vnc.html:142
msgid "Send Tab"
msgstr "发送 Tab 键"
#: ../vnc.html:142
msgid "Tab"
msgstr "Tab"
#: ../vnc.html:145
msgid "Esc"
msgstr "Esc"
#: ../vnc.html:145
msgid "Send Escape"
msgstr "发送 Escape 键"
#: ../vnc.html:148
msgid "Ctrl+Alt+Del"
msgstr "Ctrl-Alt-Del"
#: ../vnc.html:148
msgid "Send Ctrl-Alt-Del"
msgstr "发送 Ctrl-Alt-Del 键"
#: ../vnc.html:156
msgid "Shutdown/Reboot"
msgstr "关机/重新启动"
#: ../vnc.html:156
msgid "Shutdown/Reboot..."
msgstr "关机/重新启动..."
#: ../vnc.html:162
msgid "Power"
msgstr "电源"
#: ../vnc.html:164
msgid "Shutdown"
msgstr "关机"
#: ../vnc.html:165
msgid "Reboot"
msgstr "重新启动"
#: ../vnc.html:166
msgid "Reset"
msgstr "重置"
#: ../vnc.html:171 ../vnc.html:177
msgid "Clipboard"
msgstr "剪贴板"
#: ../vnc.html:181
msgid "Clear"
msgstr "清除"
#: ../vnc.html:187
msgid "Fullscreen"
msgstr "全屏"
#: ../vnc.html:192 ../vnc.html:199
msgid "Settings"
msgstr "设置"
#: ../vnc.html:202
msgid "Shared Mode"
msgstr "分享模式"
#: ../vnc.html:205
msgid "View Only"
msgstr "仅查看"
#: ../vnc.html:209
msgid "Clip to Window"
msgstr "限制/裁切窗口大小"
#: ../vnc.html:212
msgid "Scaling Mode:"
msgstr "缩放模式:"
#: ../vnc.html:214
msgid "None"
msgstr "无"
#: ../vnc.html:215
msgid "Local Scaling"
msgstr "本地缩放"
#: ../vnc.html:216
msgid "Remote Resizing"
msgstr "远程调整大小"
#: ../vnc.html:221
msgid "Advanced"
msgstr "高级"
#: ../vnc.html:224
msgid "Repeater ID:"
msgstr "中继站 ID"
#: ../vnc.html:228
msgid "WebSocket"
msgstr "WebSocket"
#: ../vnc.html:231
msgid "Encrypt"
msgstr "加密"
#: ../vnc.html:234
msgid "Host:"
msgstr "主机:"
#: ../vnc.html:238
msgid "Port:"
msgstr "端口:"
#: ../vnc.html:242
msgid "Path:"
msgstr "路径:"
#: ../vnc.html:249
msgid "Automatic Reconnect"
msgstr "自动重新连接"
#: ../vnc.html:252
msgid "Reconnect Delay (ms):"
msgstr "重新连接间隔 (ms)"
#: ../vnc.html:258
msgid "Logging:"
msgstr "日志级别:"
#: ../vnc.html:270
msgid "Disconnect"
msgstr "中断连接"
#: ../vnc.html:289
msgid "Connect"
msgstr "连接"
#: ../vnc.html:299
msgid "Password:"
msgstr "密码:"
#: ../vnc.html:313
msgid "Cancel"
msgstr "取消"

View File

@@ -1,5 +1,5 @@
# Traditional Chinese translations for noVNC package.
# Copyright (C) 2018 Various Authors
# Copyright (C) 2018 The noVNC Authors
# This file is distributed under the same license as the noVNC package.
# Peter Dave Hello <hsu@peterdavehello.org>, 2018.
#

3
snap/hooks/configure vendored Normal file
View File

@@ -0,0 +1,3 @@
#!/bin/sh -e
snapctl restart novnc.novncsvc

29
snap/local/svc_wrapper.sh Executable file
View File

@@ -0,0 +1,29 @@
#!/bin/bash
# `snapctl get services` returns a JSON array, example:
#{
#"n6801": {
# "listen": 6801,
# "vnc": "localhost:5901"
#},
#"n6802": {
# "listen": 6802,
# "vnc": "localhost:5902"
#}
#}
snapctl get services | jq -c '.[]' | while read service; do # for each service the user sepcified..
# get the important data for the service (listen port, VNC host:port)
listen_port="$(echo $service | jq --raw-output '.listen')"
vnc_host_port="$(echo $service | jq --raw-output '.vnc')" # --raw-output removes any quotation marks from the output
# check whether those values are valid
expr "$listen_port" : '^[0-9]\+$' > /dev/null
listen_port_valid=$?
if [ ! $listen_port_valid ] || [ -z "$vnc_host_port" ]; then
# invalid values mean the service is disabled, do nothing except for printing a message (logged in /var/log/system or systemd journal)
echo "novnc: not starting service ${service} with listen_port ${listen_port} and vnc_host_port ${vnc_host_port}"
else
# start (and fork with '&') the service using the specified listen port and VNC host:port
$SNAP/launch.sh --listen $listen_port --vnc $vnc_host_port &
fi
done

36
snap/snapcraft.yaml Normal file
View File

@@ -0,0 +1,36 @@
name: novnc
base: core18 # the base snap is the execution environment for this snap
version: '@VERSION@'
summary: Open Source VNC client using HTML5 (WebSockets, Canvas)
description: |
Open Source VNC client using HTML5 (WebSockets, Canvas).
noVNC is both a VNC client JavaScript library as well as an
application built on top of that library. noVNC runs well in any
modern browser including mobile browsers (iOS and Android).
grade: stable
confinement: strict
parts:
novnc:
source: build/
plugin: dump
stage-packages:
- websockify
- bash
- jq
- python-numpy
- python3-numpy
hooks:
configure:
plugs: [network, network-bind]
apps:
novnc:
command: ./launch.sh
plugs: [network, network-bind]
novncsvc:
command: ./svc_wrapper.sh
daemon: forking
plugs: [network, network-bind]

15
tests/.eslintrc Normal file
View File

@@ -0,0 +1,15 @@
{
"env": {
"node": true,
"mocha": true
},
"globals": {
"chai": false,
"sinon": false
},
"rules": {
"prefer-arrow-callback": 0,
// Too many anonymous callbacks
"func-names": "off",
}
}

View File

@@ -1,62 +1,60 @@
// Assertions that make it easier to use sinon
import sinonChai from '../node_modules/sinon-chai/lib/sinon-chai.js';
chai.use(sinonChai);
// noVNC specific assertions
chai.use(function (_chai, utils) {
_chai.Assertion.addMethod('displayed', function (target_data) {
var obj = this._obj;
var ctx = obj._target.getContext('2d');
var data_cl = ctx.getImageData(0, 0, obj._target.width, obj._target.height).data;
_chai.Assertion.addMethod('displayed', function (targetData) {
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
var data = new Uint8Array(data_cl);
var len = data_cl.length;
new chai.Assertion(len).to.be.equal(target_data.length, "unexpected display size");
var same = true;
for (var i = 0; i < len; i++) {
if (data[i] != target_data[i]) {
const data = new Uint8Array(dataCl);
const len = dataCl.length;
new chai.Assertion(len).to.be.equal(targetData.length, "unexpected display size");
let same = true;
for (let i = 0; i < len; i++) {
if (data[i] != targetData[i]) {
same = false;
break;
}
}
if (!same) {
console.log("expected data: %o, actual data: %o", target_data, data);
// eslint-disable-next-line no-console
console.log("expected data: %o, actual data: %o", targetData, data);
}
this.assert(same,
"expected #{this} to have displayed the image #{exp}, but instead it displayed #{act}",
"expected #{this} not to have displayed the image #{act}",
target_data,
data);
"expected #{this} to have displayed the image #{exp}, but instead it displayed #{act}",
"expected #{this} not to have displayed the image #{act}",
targetData,
data);
});
_chai.Assertion.addMethod('sent', function (target_data) {
var obj = this._obj;
obj.inspect = function () {
var res = { _websocket: obj._websocket, rQi: obj._rQi, _rQ: new Uint8Array(obj._rQ.buffer, 0, obj._rQlen),
_sQ: new Uint8Array(obj._sQ.buffer, 0, obj._sQlen) };
_chai.Assertion.addMethod('sent', function (targetData) {
const obj = this._obj;
obj.inspect = () => {
const res = { _websocket: obj._websocket, rQi: obj._rQi, _rQ: new Uint8Array(obj._rQ.buffer, 0, obj._rQlen),
_sQ: new Uint8Array(obj._sQ.buffer, 0, obj._sQlen) };
res.prototype = obj;
return res;
};
var data = obj._websocket._get_sent_data();
var same = true;
if (data.length != target_data.length) {
const data = obj._websocket._getSentData();
let same = true;
if (data.length != targetData.length) {
same = false;
} else {
for (var i = 0; i < data.length; i++) {
if (data[i] != target_data[i]) {
for (let i = 0; i < data.length; i++) {
if (data[i] != targetData[i]) {
same = false;
break;
}
}
}
if (!same) {
console.log("expected data: %o, actual data: %o", target_data, data);
// eslint-disable-next-line no-console
console.log("expected data: %o, actual data: %o", targetData, data);
}
this.assert(same,
"expected #{this} to have sent the data #{exp}, but it actually sent #{act}",
"expected #{this} not to have sent the data #{act}",
Array.prototype.slice.call(target_data),
Array.prototype.slice.call(data));
"expected #{this} to have sent the data #{exp}, but it actually sent #{act}",
"expected #{this} not to have sent the data #{act}",
Array.prototype.slice.call(targetData),
Array.prototype.slice.call(data));
});
_chai.Assertion.addProperty('array', function () {
@@ -66,13 +64,12 @@ chai.use(function (_chai, utils) {
_chai.Assertion.overwriteMethod('equal', function (_super) {
return function assertArrayEqual(target) {
if (utils.flag(this, 'array')) {
var obj = this._obj;
const obj = this._obj;
var i;
var same = true;
let same = true;
if (utils.flag(this, 'deep')) {
for (i = 0; i < obj.length; i++) {
for (let i = 0; i < obj.length; i++) {
if (!utils.eql(obj[i], target[i])) {
same = false;
break;
@@ -80,11 +77,11 @@ chai.use(function (_chai, utils) {
}
this.assert(same,
"expected #{this} to have elements deeply equal to #{exp}",
"expected #{this} not to have elements deeply equal to #{exp}",
Array.prototype.slice.call(target));
"expected #{this} to have elements deeply equal to #{exp}",
"expected #{this} not to have elements deeply equal to #{exp}",
Array.prototype.slice.call(target));
} else {
for (i = 0; i < obj.length; i++) {
for (let i = 0; i < obj.length; i++) {
if (obj[i] != target[i]) {
same = false;
break;
@@ -92,9 +89,9 @@ chai.use(function (_chai, utils) {
}
this.assert(same,
"expected #{this} to have elements equal to #{exp}",
"expected #{this} not to have elements equal to #{exp}",
Array.prototype.slice.call(target));
"expected #{this} to have elements equal to #{exp}",
"expected #{this} not to have elements equal to #{exp}",
Array.prototype.slice.call(target));
}
} else {
_super.apply(this, arguments);

View File

@@ -1,87 +1,96 @@
import Base64 from '../core/base64.js';
// PhantomJS can't create Event objects directly, so we need to use this
function make_event(name, props) {
var evt = document.createEvent('Event');
function makeEvent(name, props) {
const evt = document.createEvent('Event');
evt.initEvent(name, true, true);
if (props) {
for (var prop in props) {
for (let prop in props) {
evt[prop] = props[prop];
}
}
return evt;
}
export default function FakeWebSocket (uri, protocols) {
this.url = uri;
this.binaryType = "arraybuffer";
this.extensions = "";
export default class FakeWebSocket {
constructor(uri, protocols) {
this.url = uri;
this.binaryType = "arraybuffer";
this.extensions = "";
if (!protocols || typeof protocols === 'string') {
this.protocol = protocols;
} else {
this.protocol = protocols[0];
if (!protocols || typeof protocols === 'string') {
this.protocol = protocols;
} else {
this.protocol = protocols[0];
}
this._sendQueue = new Uint8Array(20000);
this.readyState = FakeWebSocket.CONNECTING;
this.bufferedAmount = 0;
this._isFake = true;
}
this._send_queue = new Uint8Array(20000);
this.readyState = FakeWebSocket.CONNECTING;
this.bufferedAmount = 0;
this.__is_fake = true;
};
FakeWebSocket.prototype = {
close: function (code, reason) {
close(code, reason) {
this.readyState = FakeWebSocket.CLOSED;
if (this.onclose) {
this.onclose(make_event("close", { 'code': code, 'reason': reason, 'wasClean': true }));
this.onclose(makeEvent("close", { 'code': code, 'reason': reason, 'wasClean': true }));
}
},
}
send: function (data) {
send(data) {
if (this.protocol == 'base64') {
data = Base64.decode(data);
} else {
data = new Uint8Array(data);
}
this._send_queue.set(data, this.bufferedAmount);
this._sendQueue.set(data, this.bufferedAmount);
this.bufferedAmount += data.length;
},
}
_get_sent_data: function () {
var res = new Uint8Array(this._send_queue.buffer, 0, this.bufferedAmount);
_getSentData() {
const res = new Uint8Array(this._sendQueue.buffer, 0, this.bufferedAmount);
this.bufferedAmount = 0;
return res;
},
}
_open: function (data) {
_open() {
this.readyState = FakeWebSocket.OPEN;
if (this.onopen) {
this.onopen(make_event('open'));
this.onopen(makeEvent('open'));
}
},
_receive_data: function (data) {
this.onmessage(make_event("message", { 'data': data }));
}
};
_receiveData(data) {
// Break apart the data to expose bugs where we assume data is
// neatly packaged
for (let i = 0;i < data.length;i++) {
let buf = data.subarray(i, i+1);
this.onmessage(makeEvent("message", { 'data': buf }));
}
}
}
FakeWebSocket.OPEN = WebSocket.OPEN;
FakeWebSocket.CONNECTING = WebSocket.CONNECTING;
FakeWebSocket.CLOSING = WebSocket.CLOSING;
FakeWebSocket.CLOSED = WebSocket.CLOSED;
FakeWebSocket.__is_fake = true;
FakeWebSocket._isFake = true;
FakeWebSocket.replace = function () {
if (!WebSocket.__is_fake) {
var real_version = WebSocket;
FakeWebSocket.replace = () => {
if (!WebSocket._isFake) {
const realVersion = WebSocket;
// eslint-disable-next-line no-global-assign
WebSocket = FakeWebSocket;
FakeWebSocket.__real_version = real_version;
FakeWebSocket._realVersion = realVersion;
}
};
FakeWebSocket.restore = function () {
if (WebSocket.__is_fake) {
WebSocket = WebSocket.__real_version;
FakeWebSocket.restore = () => {
if (WebSocket._isFake) {
// eslint-disable-next-line no-global-assign
WebSocket = WebSocket._realVersion;
}
};

View File

@@ -1,6 +1,6 @@
var TEST_REGEXP = /test\..*\.js/;
var allTestFiles = [];
var extraFiles = ['/base/tests/assertions.js'];
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)) {
@@ -9,8 +9,40 @@ Object.keys(window.__karma__.files).forEach(function (file) {
}
});
require.config({
baseUrl: '/base',
deps: allTestFiles.concat(extraFiles),
callback: window.__karma__.start,
// Stub out mocha's start function so we can run it once we're done loading
mocha.origRun = mocha.run;
mocha.run = function () {};
let script;
// Script to import all our tests
script = document.createElement("script");
script.type = "module";
script.text = "";
let allModules = allTestFiles.concat(extraFiles);
allModules.forEach(function (file) {
script.text += "import \"" + file + "\";\n";
});
script.text += "\nmocha.origRun();\n";
document.body.appendChild(script);
// Fallback code for browsers that don't support modules (IE)
script = document.createElement("script");
script.type = "module";
script.text = "window._noVNC_has_module_support = true;\n";
document.body.appendChild(script);
function fallback() {
if (!window._noVNC_has_module_support) {
/* eslint-disable no-console */
if (console) {
console.log("No module support detected. Loading fallback...");
}
/* eslint-enable no-console */
let loader = document.createElement("script");
loader.src = "base/vendor/browser-es-module-loader/dist/browser-es-module-loader.js";
document.body.appendChild(loader);
}
}
setTimeout(fallback, 500);

View File

@@ -1,12 +1,13 @@
/* global vncFrameData, vncFrameEncoding */
import * as WebUtil from '../app/webutil.js';
import RecordingPlayer from './playback.js';
import Base64 from '../core/base64.js';
var frames = null;
var encoding = null;
let frames = null;
function message(str) {
console.log(str);
var cell = document.getElementById('messages');
const cell = document.getElementById('messages');
cell.textContent += str + "\n";
cell.scrollTop = cell.scrollHeight;
}
@@ -18,10 +19,10 @@ function loadFile() {
return Promise.reject("Must specify data=FOO in query string.");
}
message("Loading " + fname);
message("Loading " + fname + "...");
return new Promise(function (resolve, reject) {
var script = document.createElement("script");
return new Promise((resolve, reject) => {
const script = document.createElement("script");
script.onload = resolve;
script.onerror = reject;
document.body.appendChild(script);
@@ -30,59 +31,99 @@ function loadFile() {
}
function enableUI() {
var iterations = WebUtil.getQueryVar('iterations', 3);
const iterations = WebUtil.getQueryVar('iterations', 3);
document.getElementById('iterations').value = iterations;
var mode = WebUtil.getQueryVar('mode', 3);
const mode = WebUtil.getQueryVar('mode', 3);
if (mode === 'realtime') {
document.getElementById('mode2').checked = true;
} else {
document.getElementById('mode1').checked = true;
}
message("VNC_frame_data.length: " + VNC_frame_data.length);
message("Loaded " + vncFrameData.length + " frames");
const startButton = document.getElementById('startButton');
startButton.disabled = false;
startButton.addEventListener('click', start);
frames = VNC_frame_data;
message("Converting...");
frames = vncFrameData;
let encoding;
// Only present in older recordings
if (window.VNC_frame_encoding)
encoding = VNC_frame_encoding;
if (window.vncFrameEncoding) {
encoding = vncFrameEncoding;
} else {
let frame = frames[0];
let start = frame.indexOf('{', 1) + 1;
if (frame.slice(start, start+4) === 'UkZC') {
encoding = 'base64';
} else {
encoding = 'binary';
}
}
for (let i = 0;i < frames.length;i++) {
let frame = frames[i];
if (frame === "EOF") {
frames.splice(i);
break;
}
let dataIdx = frame.indexOf('{', 1) + 1;
let time = parseInt(frame.slice(1, dataIdx - 1));
let u8;
if (encoding === 'base64') {
u8 = Base64.decode(frame.slice(dataIdx));
} else {
u8 = new Uint8Array(frame.length - dataIdx);
for (let j = 0; j < frame.length - dataIdx; j++) {
u8[j] = frame.charCodeAt(dataIdx + j);
}
}
frames[i] = { fromClient: frame[0] === '}',
timestamp: time,
data: u8 };
}
message("Ready");
}
function IterationPlayer (iterations, frames, encoding) {
this._iterations = iterations;
class IterationPlayer {
constructor(iterations, frames) {
this._iterations = iterations;
this._iteration = undefined;
this._player = undefined;
this._iteration = undefined;
this._player = undefined;
this._start_time = undefined;
this._startTime = undefined;
this._frames = frames;
this._encoding = encoding;
this._frames = frames;
this._state = 'running';
this._state = 'running';
this.onfinish = function() {};
this.oniterationfinish = function() {};
this.rfbdisconnected = function() {};
}
this.onfinish = () => {};
this.oniterationfinish = () => {};
this.rfbdisconnected = () => {};
}
IterationPlayer.prototype = {
start: function (mode) {
start(realtime) {
this._iteration = 0;
this._start_time = (new Date()).getTime();
this._startTime = (new Date()).getTime();
this._realtime = mode.startsWith('realtime');
this._trafficMgmt = !mode.endsWith('-no-mgmt');
this._realtime = realtime;
this._nextIteration();
},
}
_nextIteration: function () {
const player = new RecordingPlayer(this._frames, this._encoding, this._disconnected.bind(this));
_nextIteration() {
const player = new RecordingPlayer(this._frames, this._disconnected.bind(this));
player.onfinish = this._iterationFinish.bind(this);
if (this._state !== 'running') { return; }
@@ -93,41 +134,43 @@ IterationPlayer.prototype = {
return;
}
player.run(this._realtime, this._trafficMgmt);
},
player.run(this._realtime, false);
}
_finish: function () {
_finish() {
const endTime = (new Date()).getTime();
const totalDuration = endTime - this._start_time;
const totalDuration = endTime - this._startTime;
const evt = new Event('finish');
evt.duration = totalDuration;
evt.iterations = this._iterations;
const evt = new CustomEvent('finish',
{ detail:
{ duration: totalDuration,
iterations: this._iterations } } );
this.onfinish(evt);
},
}
_iterationFinish: function (duration) {
const evt = new Event('iterationfinish');
evt.duration = duration;
evt.number = this._iteration;
_iterationFinish(duration) {
const evt = new CustomEvent('iterationfinish',
{ detail:
{ duration: duration,
number: this._iteration } } );
this.oniterationfinish(evt);
this._nextIteration();
},
}
_disconnected: function (clean, frame) {
_disconnected(clean, frame) {
if (!clean) {
this._state = 'failed';
}
var evt = new Event('rfbdisconnected');
evt.clean = clean;
evt.frame = frame;
evt.iteration = this._iteration;
const evt = new CustomEvent('rfbdisconnected',
{ detail:
{ clean: clean,
frame: frame,
iteration: this._iteration } } );
this.onrfbdisconnected(evt);
},
};
}
}
function start() {
document.getElementById('startButton').value = "Running";
@@ -135,33 +178,33 @@ function start() {
const iterations = document.getElementById('iterations').value;
var mode;
let realtime;
if (document.getElementById('mode1').checked) {
message(`Starting performance playback (fullspeed) [${iterations} iteration(s)]`);
mode = 'perftest';
realtime = false;
} else {
message(`Starting realtime playback [${iterations} iteration(s)]`);
mode = 'realtime';
realtime = true;
}
const player = new IterationPlayer(iterations, frames, encoding);
player.oniterationfinish = function (evt) {
message(`Iteration ${evt.number} took ${evt.duration}ms`);
const player = new IterationPlayer(iterations, frames);
player.oniterationfinish = (evt) => {
message(`Iteration ${evt.detail.number} took ${evt.detail.duration}ms`);
};
player.onrfbdisconnected = function (evt) {
if (!evt.clean) {
message(`noVNC sent disconnected during iteration ${evt.iteration} frame ${evt.frame}`);
player.onrfbdisconnected = (evt) => {
if (!evt.detail.clean) {
message(`noVNC sent disconnected during iteration ${evt.detail.iteration} frame ${evt.detail.frame}`);
}
};
player.onfinish = function (evt) {
const iterTime = parseInt(evt.duration / evt.iterations, 10);
message(`${evt.iterations} iterations took ${evt.duration}ms (average ${iterTime}ms / iteration)`);
player.onfinish = (evt) => {
const iterTime = parseInt(evt.detail.duration / evt.detail.iterations, 10);
message(`${evt.detail.iterations} iterations took ${evt.detail.duration}ms (average ${iterTime}ms / iteration)`);
document.getElementById('startButton').disabled = false;
document.getElementById('startButton').value = "Start";
};
player.start(mode);
player.start(realtime);
}
loadFile().then(enableUI).catch(function (e) { message("Error loading recording: " + e); });
loadFile().then(enableUI).catch(e => message("Error loading recording: " + e));

View File

@@ -1,38 +1,37 @@
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2012 Joel Martin
* Copyright (C) 2018 The noVNC Authors
* Licensed under MPL 2.0 (see LICENSE.txt)
*/
import RFB from '../core/rfb.js';
import * as Log from '../core/util/logging.js';
import Base64 from '../core/base64.js';
// Immediate polyfill
if (setImmediate === undefined) {
var _immediateIdCounter = 1;
var _immediateFuncs = {};
if (window.setImmediate === undefined) {
let _immediateIdCounter = 1;
const _immediateFuncs = {};
var setImmediate = function (func) {
var index = _immediateIdCounter++;
window.setImmediate = (func) => {
const index = _immediateIdCounter++;
_immediateFuncs[index] = func;
window.postMessage("noVNC immediate trigger:" + index, "*");
return index;
};
window.clearImmediate = function (id) {
window.clearImmediate = (id) => {
_immediateFuncs[id];
};
var _onMessage = function (event) {
window.addEventListener("message", (event) => {
if ((typeof event.data !== "string") ||
(event.data.indexOf("noVNC immediate trigger:") !== 0)) {
return;
}
var index = event.data.slice("noVNC immediate trigger:".length);
const index = event.data.slice("noVNC immediate trigger:".length);
var callback = _immediateFuncs[index];
const callback = _immediateFuncs[index];
if (callback === undefined) {
return;
}
@@ -40,157 +39,134 @@ if (setImmediate === undefined) {
delete _immediateFuncs[index];
callback();
};
window.addEventListener("message", _onMessage);
});
}
export default function RecordingPlayer (frames, encoding, disconnected) {
this._frames = frames;
this._encoding = encoding;
export default class RecordingPlayer {
constructor(frames, disconnected) {
this._frames = frames;
this._disconnected = disconnected;
this._disconnected = disconnected;
if (this._encoding === undefined) {
let frame = this._frames[0];
let start = frame.indexOf('{', 1) + 1;
if (frame.slice(start).startsWith('UkZC')) {
this._encoding = 'base64';
} else {
this._encoding = 'binary';
}
this._rfb = undefined;
this._frameLength = this._frames.length;
this._frameIndex = 0;
this._startTime = undefined;
this._realtime = true;
this._trafficManagement = true;
this._running = false;
this.onfinish = () => {};
}
this._rfb = undefined;
this._frame_length = this._frames.length;
this._frame_index = 0;
this._start_time = undefined;
this._realtime = true;
this._trafficManagement = true;
this._running = false;
this.onfinish = function () {};
}
RecordingPlayer.prototype = {
run: function (realtime, trafficManagement) {
run(realtime, trafficManagement) {
// initialize a new RFB
this._rfb = new RFB(document.getElementById('VNC_screen'), 'wss://test');
this._rfb.viewOnly = true;
this._rfb.addEventListener("disconnect",
this._handleDisconnect.bind(this));
this._rfb.addEventListener("credentialsrequired",
this._handleCredentials.bind(this));
this._enablePlaybackMode();
// reset the frame index and timer
this._frame_index = 0;
this._start_time = (new Date()).getTime();
this._frameIndex = 0;
this._startTime = (new Date()).getTime();
this._realtime = realtime;
this._trafficManagement = (trafficManagement === undefined) ? !realtime : trafficManagement;
this._running = true;
this._queueNextPacket();
},
}
// _enablePlaybackMode mocks out things not required for running playback
_enablePlaybackMode: function () {
this._rfb._sock.send = function (arr) {};
this._rfb._sock.close = function () {};
this._rfb._sock.flush = function () {};
_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();
};
},
}
_queueNextPacket: function () {
_queueNextPacket() {
if (!this._running) { return; }
var frame = this._frames[this._frame_index];
let frame = this._frames[this._frameIndex];
// skip send frames
while (this._frame_index < this._frame_length && frame.charAt(0) === "}") {
this._frame_index++;
frame = this._frames[this._frame_index];
while (this._frameIndex < this._frameLength && frame.fromClient) {
this._frameIndex++;
frame = this._frames[this._frameIndex];
}
if (frame === 'EOF') {
Log.Debug('Finished, found EOF');
this._finish();
return;
}
if (this._frame_index >= this._frame_length) {
if (this._frameIndex >= this._frameLength) {
Log.Debug('Finished, no more frames');
this._finish();
return;
}
if (this._realtime) {
let foffset = frame.slice(1, frame.indexOf('{', 1));
let toffset = (new Date()).getTime() - this._start_time;
let delay = foffset - toffset;
const toffset = (new Date()).getTime() - this._startTime;
let delay = frame.timestamp - toffset;
if (delay < 1) delay = 1;
setTimeout(this._doPacket.bind(this), delay);
} else {
setImmediate(this._doPacket.bind(this));
}
},
}
_doPacket: function () {
_doPacket() {
// Avoid having excessive queue buildup in non-realtime mode
if (this._trafficManagement && this._rfb._flushing) {
let player = this;
let orig = this._rfb._display.onflush;
this._rfb._display.onflush = function () {
player._rfb._display.onflush = orig;
player._rfb._onFlush();
player._doPacket();
const orig = this._rfb._display.onflush;
this._rfb._display.onflush = () => {
this._rfb._display.onflush = orig;
this._rfb._onFlush();
this._doPacket();
};
return;
}
const frame = this._frames[this._frame_index];
var start = frame.indexOf('{', 1) + 1;
if (this._encoding === 'base64') {
var u8 = Base64.decode(frame.slice(start));
start = 0;
} else {
var u8 = new Uint8Array(frame.length - start);
for (let i = 0; i < frame.length - start; i++) {
u8[i] = frame.charCodeAt(start + i);
}
}
const frame = this._frames[this._frameIndex];
this._rfb._sock._recv_message({'data': u8});
this._frame_index++;
this._rfb._sock._recv_message({'data': frame.data});
this._frameIndex++;
this._queueNextPacket();
},
}
_finish() {
if (this._rfb._display.pending()) {
var player = this;
this._rfb._display.onflush = function () {
if (player._rfb._flushing) {
player._rfb._onFlush();
this._rfb._display.onflush = () => {
if (this._rfb._flushing) {
this._rfb._onFlush();
}
player._finish();
this._finish();
};
this._rfb._display.flush();
} else {
this._running = false;
this._rfb._sock._eventHandlers.close({code: 1000, reason: ""});
delete this._rfb;
this.onfinish((new Date()).getTime() - this._start_time);
this.onfinish((new Date()).getTime() - this._startTime);
}
},
}
_handleDisconnect(evt) {
this._running = false;
this._disconnected(evt.detail.clean, this._frame_index);
this._disconnected(evt.detail.clean, this._frameIndex);
}
};
_handleCredentials(evt) {
this._rfb.sendCredentials({"username": "Foo",
"password": "Bar",
"target": "Baz"});
}
}

Some files were not shown because too many files have changed in this diff Show More