311 Commits

Author SHA1 Message Date
Pierre Ossman d3aaf4d5b3 Upgrade base snap to Ubuntu 22.04
Ubuntu 18.04 base snap is no longer supported, so switch to the
currently newest one.
2024-01-10 14:44:44 +01:00
Pierre Ossman 796e924e47 Remove unused npm dependencies
These should have been removed as part of 890cff9.
2024-01-10 14:41:52 +01:00
Pierre Ossman 829725b30e Handle relative paths in novnc_proxy
websockify changes the working directory before it starts looking for
files, so we must give it relative paths for things to work reliably.
2023-12-05 11:33:15 +01:00
Pierre Ossman 9ac632deee Handle immediate connection errors
The browser might throw an exception right away if there is something it
doesn't like with our connect attempt. E.g. using a non-TLS WebSocket
from a TLS web page.
2023-12-05 11:30:30 +01:00
Pierre Ossman 7d2dad0f9e Merge branch 'listen-host' of https://github.com/afbjorklund/noVNC 2023-10-27 12:57:09 +02:00
Anders F Björklund 5ebc297164 Remove comment about websockify command arguments 2023-10-14 09:55:25 +02:00
Anders F Björklund a792b7f39e Document default port applies to all interfaces 2023-10-14 09:55:25 +02:00
Anders F Björklund f1174023c1 Add the possibility to listen on a specific host
For instance, for listening only on "localhost"

That is, bind on 127.0.0.1 instead of 0.0.0.0
2023-10-14 09:55:24 +02:00
Pierre Ossman 85a465288b Merge branch 'qemu_ledstate_pseudo_encoding' of https://github.com/otthou/noVNC 2023-09-29 14:18:02 +02:00
Otto van Houten a0b7c0dac5 Add QEMU Led Pseudo encoding support
Previously, num-lock and caps-lock syncing was performed on a best effort basis by qemu.
Now, the syncing is performed by no-vnc instead. This allows the led state syncing to work
in cases where it previously couldn't, since no-vnc has with this extension knowledge of both
the remote and local capslock and numlock status, which QEMU doesn't have.
2023-09-29 13:58:55 +02:00
Pierre Ossman bf12c24f4c Fix bad indentation 2023-09-07 15:35:20 +02:00
Pierre Ossman 370f21b117 Correctly handle legacy security rejections
The code comment of this code was entirely incorrect, but the commit
message for 5671072 when it was added was correct. I.e. there is a
result, but not a reason.

Adjust the unit tests to make sure this doesn't regress again.
2023-09-07 14:59:36 +02:00
Pierre Ossman 72f6810797 Correctly handle "none" auth on old servers
There is no security result for the "none" authentication until RFB 3.8.
This got broken by mistake in 5671072.
2023-09-07 14:38:04 +02:00
Pierre Ossman e81602d705 Fix zlib level change in clipboard tests
The compression level got changed in 01bb36d4, but the tests weren't
updated to follow this change.
2023-08-29 17:38:44 +02:00
Pierre Ossman b40a45a11b Remove unused argument to inflateInit()
There is just one argument to inflateInit(). It is inflateInit2() that
takes two arguments.

Since this argument was never used, let's just remove it and keep the
existing behaviour.
2023-08-29 17:30:00 +02:00
Pierre Ossman 01bb36d431 Use proper argument to deflateInit()
This was an accidental copy error from inflator.js. The second argument
to deflateInit() is the compression level, not the window bits.

We have not strong opinions on an appropriate level, so stick to the
default.
2023-08-29 17:28:54 +02:00
Samuel Mannehed 295004cabe Merge pull request #1710 from novnc/localStorage
Don't crash if we can't use localStorage
2023-07-18 09:42:01 +02:00
Samuel Mannehed a30f609de4 Don't crash if we can't use localStorage
Our settings are not a fatal requirement, we can fall back on the
default values if they can't be accessed. A scenario where we've seen
this happen is when cookies are disabled in the browser. It seems
localStorage is disabled along with cookies in these settings.

So, lets log a message about the failure and otherwise silently
continue in this case.

Fixes issue #1577.
2023-07-13 14:35:07 +02:00
Pierre Ossman ca6527c1bf Merge branch 'websock' of https://github.com/CendioOssman/noVNC 2023-06-30 18:20:44 +02:00
Pierre Ossman ccef89f556 Implicitly flush Websock if needed
Callers shouldn't have to deal with the internal buffering limits of
Websock, so implicitly flush the buffer if more room is needed.
2023-06-04 22:32:43 +02:00
Pierre Ossman f8b65f9fe1 Add Websock send queue helpers
Callers shouldn't be poking around directly in to the send queue, but
should use accessor functions like for the read queue.
2023-06-04 22:32:43 +02:00
Pierre Ossman 7356d4e60b Move WebSocket queue index reset to receive
It's more robust to do this just before we need the space, rather than
assume when the queue will be read and adjust things right after.
2023-06-04 22:32:43 +02:00
Pierre Ossman 3fc0cb0cb7 Remove Base64 WebSocket remnants
There is no encoding/decoding in modern WebSockets, so let's clean up
some of the old crud that no longer serves a purpose.
2023-06-04 22:32:43 +02:00
Pierre Ossman b146de6d69 Avoid internal variables in recv queue tests
Makes for more robust and realistic tests.
2023-06-04 22:32:43 +02:00
Pierre Ossman b298bf9e90 Don't split large WebSocket data in tests
It takes too much time and can make the tests fail.
2023-06-04 22:32:43 +02:00
Pierre Ossman 2a7db6f647 Make ExtendedDesktopSize tests more realistic
Send real messages and avoid poking around in internals, as we weren't
testing things correctly that way.
2023-06-04 19:12:02 +02:00
Pierre Ossman 45cedea78f Don't send SetDesktopSize too early
We don't know the server layout yet, so we can't preserve the screen id
or flags yet at this point. Move it until after we've parsed everything.
2023-06-04 19:12:02 +02:00
Pierre Ossman 12d2e7832d Properly decode ExtendedDesktopSize fields
We are expected to preserve these and use them in our requests back to
the server. We can't do that if we don't actually decode them correctly.
2023-06-04 19:12:02 +02:00
Pierre Ossman 0ccc679d32 Return unsigned values from rQshift32()
This is what we almost always want, and this makes it consistent with
rQshift8() and rQshift16().
2023-06-04 19:12:02 +02:00
Pierre Ossman d0203a5995 Always return copy of data from socket
We don't know how long the caller will hang on to this data, so we need
to be safe by default and assume it will kept indefinitely. That means
we can't return a reference to the internal buffer, as that will get
overwritten with future messages.

We want to avoid unnecessary copying in performance critical code,
though. So allow code to explicitly ask for a shared buffer, assuming
they know the data needs to be consumed immediately.
2023-06-04 19:12:02 +02:00
Pierre Ossman aaa4eb8c3c Use proper socket helpers for FBU header
Let's not duplicate this stuff when we have convenience functions.
2023-06-04 19:00:33 +02:00
Pierre Ossman e01dd27be4 Remove Websock implicit read length
Callers should be properly aware of how much data they need, as they
need to call rQwait() first to ensure the data is present.
2023-06-04 19:00:33 +02:00
Pierre Ossman 55ffe8fc51 Stop exposing Websock queue length
Callers should be using rQwait() to ensure sufficient data is present,
and not poke around in the internal buffering.
2023-06-04 19:00:33 +02:00
Pierre Ossman 0180bc81c1 Stop direct access to socket buffer
Use proper accessor functions instead of poking around in internal
buffers.
2023-06-04 19:00:33 +02:00
Pierre Ossman fb3c8f64e9 Switch Display.flush() to use a promise
That is the modern way to handle operations that cannot complete
immediately.
2023-06-04 19:00:33 +02:00
Pierre Ossman ae9b042df1 Change rQslice() to rQpeekBytes()
We don't need any full slice functionality, so let's change this to
better march rQpeek8() and rQshiftBytes().
2023-06-04 19:00:33 +02:00
Pierre Ossman 87143b361e Reduce kept state in JPEG decoder
We don't have to keep track of this much data between rects, so
restructure things to make it more simple. This allows the JPEG parsing
code to be a pure function which only depends on the input.
2023-06-04 19:00:33 +02:00
Pierre Ossman e8ad466e45 Merge branch 'testsfix' of github.com:CendioOssman/noVNC 2023-06-04 18:59:38 +02:00
Pierre Ossman eb0ad829d2 Check that decoders consume all data
This is extra important in the tests where we expect no changes to the
display, as otherwise we can't tell the difference between success and a
decoder that is simply waiting for more data.
2023-06-03 15:36:29 +02:00
Pierre Ossman d33f5ce77f Make extended clipboard tests independent
Let's test the full final result instead of assuming specific internal
calls.
2023-05-30 20:48:26 +02:00
Pierre Ossman 8ae789daf0 Add missing tests for message encodings
All of these functions should have units tests, even if they are fairly
minimal.
2023-05-30 20:48:26 +02:00
Pierre Ossman 9e02f4d01d Return a copy of the data from FakeWebSocket
The caller might hang on to the data for multiple calls, so we make sure
the returned buffer might not get overwritten.
2023-05-30 20:48:26 +02:00
Pierre Ossman 9c7576a587 Remove bad Websock mock in tests
This small object will not properly fake a Websock in more complex
cases, so let's avoid it and create a real Websock instead.
2023-05-30 20:48:26 +02:00
Pierre Ossman e07ca6a8e2 Fix Websock send tests
Avoid poking around in the internals and instead test what is actually
sent out on the WebSocket.
2023-05-30 20:48:24 +02:00
Pierre Ossman 336ec86997 Remove internal monitoring from Plain tests
Tests should avoid poking in to the internals and should only look at
external behaviour.
2023-05-30 20:11:51 +02:00
Pierre Ossman 0c80c68e92 Avoid hooking in to RFB._fail for tests
This is an internal function so we should not be examining it in the
tests. Instead use the well defined public APIs to check for correct
behaviour.
2023-05-30 20:11:51 +02:00
Pierre Ossman 13fa6b5908 Fix last rect test
Avoid poking in to internals and instead test that the RFB object
responds correctly to new messages.
2023-05-30 20:11:51 +02:00
Pierre Ossman 549ccc7121 Split RSA-AES test data
Make the tests more clear what data is expected in the different stages
of the handshake.
2023-05-30 20:11:51 +02:00
Pierre Ossman 42bc251eb4 Make RSA-AES tests more asynchronous
The code tested here makes heavy use of promises, so it is easier to
test things also using promise centric code.
2023-05-30 20:11:51 +02:00
Pierre Ossman afbb1da4d5 Remove custom RSA-AES event
We shouldn't add extra, undocumented, API just for the tests. They need
to figure out less invasive way to probe things.
2023-05-30 20:11:51 +02:00
Pierre Ossman 458405e05d Merge RSA-AES tests in to RFB tests
These test the RFB class, so they should be with all other tests for
that class.
2023-05-30 20:11:51 +02:00
Pierre Ossman 0ee0e96f34 Fix ARD authentication test to send real data
Stop bypassing the data handling steps in the test as that means those
parts don't get tested.
2023-05-30 20:11:51 +02:00
Pierre Ossman 71bb6f02cd Fix Plain authentication test checks
We should have constants local for the test function when doing
comparisons or we might have false positives because we compare with
buggy values in the code under test.
2023-05-30 20:11:51 +02:00
Pierre Ossman 79f099108f Split Plain authentication tests to own section
VeNCrypt is a multiplexer for many authentication methods, not just
Plain. So let's split it to its own section, just like other types.
2023-05-30 20:11:51 +02:00
Pierre Ossman 0679c8a801 Test credentials using normal API
Avoid poking around in the internals and instead test things using the
official methods and events. This should give us more realistic and
robust tests.
2023-05-30 20:11:51 +02:00
Pierre Ossman 29a50620ff Avoid touching internals in Tight auth tests
We should test using only external manipulation so we don't assume a
specific implementation.
2023-05-30 20:11:51 +02:00
Pierre Ossman c7c293279b Remove commented out Tight test case
This is not something we intend to implement, so remove this never used
test case.
2023-05-30 20:11:51 +02:00
Pierre Ossman cd231e53ed Don't overwrite methods with spies
Spies should just attach without modifying the real method, or we might
get unwanted side effects.
2023-05-30 20:11:51 +02:00
Pierre Ossman 3ef57d1600 Fix security to authentication state test
The "None" authentication will directly progress past authentication, so
it's not a good type for this test.
2023-05-30 20:11:51 +02:00
Pierre Ossman da75689f4c Fix data for empty RRE rect test
The given data was not a correct RRE rect.
2023-05-30 20:11:51 +02:00
Pierre Ossman 9b115a4485 Send ArrayBuffer, not Uint8Array in tests
This matches the true contents of a WebSocket 'message' event, so should
be a more realistic test.
2023-05-30 20:11:51 +02:00
Pierre Ossman 775ccaa74c Handle immediate responses in RSA-AES authentication
The event handlers might provide the needed response right away, before
even existing the event handler. We should be prepared to handle this
case.
2023-05-30 20:11:51 +02:00
Pierre Ossman 0dd9678e64 Harmonise extended clipboard tests
Let them all follow the same pattern to make things more clear.
2023-05-30 20:11:51 +02:00
Pierre Ossman 91307951d3 Fix cached JPEG test
This test didn't really check anything useful as the end result would be
the same if the second JPEG failed to render.

Fix this by clearing the canvas between the images, so we can tell if
the second image actually rendered or not.
2023-05-30 20:11:51 +02:00
Pierre Ossman c2d6a06d6d Merge branch 'master' of https://github.com/lewayotte/noVNC 2023-05-11 12:20:18 +02:00
Pierre Ossman a565ae559f Merge branches 'l10n' and 'fragment' of github.com:CendioOssman/noVNC 2023-05-10 13:23:34 +02:00
Pierre Ossman 0374b4c0fc Handle translation loading in translation class
Let's try to keep as much as possible of the translation handling in a
single place for clarity.
2023-05-10 13:11:51 +02:00
Pierre Ossman cd1a63b737 Restore history state after tests
We don't want to mess up anything permanent in each test or the tests
might start affecting each other.
2023-05-10 12:25:46 +02:00
Pierre Ossman 05b6d2ad67 Fix typos in query variable comment 2023-05-10 12:25:46 +02:00
Pierre Ossman 2a21bee245 Revert broken Add support for URL fragment parameters
This is a revert of the code changes in commit
f796b05e42 as it served no functional
purpose.

Fragments were already respected for setting parameters, via a different
function. Thus it is unclear what that commit tried to fix. It also
complicated things by mixing the document location with the window
location.

The comment changes are useful, though, so those are kept.
2023-05-10 12:25:46 +02:00
Samuel Mannehed cbbd9ab069 Merge pull request #1777 from nwtgck/patch-1
fix typo
2023-04-30 02:36:05 +02:00
Ryo Ota 2a675b3394 fix typo 2023-04-30 02:04:00 +09:00
Lew Ayotte b16f19f9ce Set _rfbVeNCryptState = 4 not == 4 2023-04-06 15:00:21 -05:00
Pierre Ossman 681632bc9f Avoid running tests on l10n singleton
We want tests to be independent, so we cannot have them modify a shared
state, such as the l10n singleton. Make sure each test instantiates its
own object instead.
2023-04-06 11:11:47 +02:00
Pierre Ossman a4453c9a26 Special case English translation fallback
We should not be listing this in LINGUAS as that gives the impression
that English has en explicit translation. Instead, it is a special case
that the code needs to be explicitly aware of.

This reverts 9a06058 in favour of a more robust fix.
2023-04-05 12:46:17 +02:00
Pierre Ossman 747603c0d5 Also re-enable playback on failures
Allows easier testing by being able to run the test multiple times.
2023-04-04 17:02:57 +02:00
Pierre Ossman c1d2449fb8 Fix playback error message for load failure
We expect the promise to be rejected with a string for display to the
user.
2023-04-04 17:02:21 +02:00
Samuel Mannehed 46292477c8 Merge pull request #1768 from mathis-marcotte/add-english-to-supported-langs
Add english to list of supported languages
2023-04-02 20:16:48 +02:00
Mathis Marcotte 9a06058f66 Added english to list of supported languages 2023-03-27 14:23:09 +00:00
Samuel Mannehed 8decca7353 Use unitless numbers for line-height values
Using <length> type values for line-height can give unexpected
inheritance behaviors. If using <length> values, the inherited
line-height on children is calculated using the font-size of the parent.
What we want is for the line-height of children to be calculated using
it's own font-size.

By instead using a unitless number, we get the behavior we want. Note
that this bug has no effects right now since no children to any of the
related elements have different font-sizes.
2023-03-23 11:06:03 +01:00
Samuel Mannehed 4558104196 Properly center the checkbox checkmark
Using a flexbox we can easily center the checkmark without using hard
coded positions.
2023-03-23 11:06:03 +01:00
NNN1590 6751cc1236 Update Japanese translation 2023-03-21 13:15:26 +09:00
Pierre Ossman 9985950bfa Upgrade to latest websockify in snap package 2023-01-26 10:45:26 +01:00
Pierre Ossman a0e6e7b1d8 Merge branch 'crypto-cleanup-fallback' of https://github.com/pdlan/noVNC 2023-01-20 16:52:32 +01:00
Pierre Ossman 90455eef06 noVNC 1.4.0 2023-01-20 13:58:48 +01:00
Pierre Ossman 51677f5c70 Update json files for new translations 2023-01-20 13:57:20 +01:00
Pierre Ossman 823e7cfca3 Update Swedish translation 2023-01-20 13:56:16 +01:00
pdlan f974b73137 Cleanup for the cryptographic algorithms that are not supported by SubtleCrypto 2023-01-20 05:54:00 -05:00
Samuel Mannehed 5b7d2a622e Fix positioning of checkbox checkmark
Changing the ::after element to be displayed as 'block' lets it be
positioned using relative. This means we can remove the confusing
"position: relative" from the checkbox.
2023-01-02 14:46:33 +01:00
Pierre Ossman 3553a451d8 Remove redundant meta charset
UTF-8 is the default for HTML5 pages anyway.
2022-12-29 13:26:41 +01:00
Pierre Ossman b76358e9bf noVNC 1.4.0 beta 2022-12-27 15:39:52 +01:00
Pierre Ossman 5f689f9bc8 Update translation template file 2022-12-27 15:39:11 +01:00
Pierre Ossman 022fc8c374 Improve whitespace handling in translations
The HTML source will include line breaks and indentation that is only
for source formatting, and will not be displayed.
2022-12-27 15:39:11 +01:00
Pierre Ossman 367bfd2962 Use JavaScript highlighting for Synax sections
Follows what MDN does, and makes things a bit easier to read.
2022-12-27 15:03:32 +01:00
Pierre Ossman 934e3de356 Follow current MDN for syntax examples
The now avoid brackets for optional arguments, so let's try to have the
same style.
2022-12-27 15:00:39 +01:00
Pierre Ossman 74fe694cc4 Fix toBlob() documentation
These are copy-and-paste errors from the toDataURL() section.
2022-12-27 15:00:13 +01:00
Pierre Ossman ce534b85c1 Fix indentation of toBlob()/toImage() docs
It isn't considered a code block if it isn't indented properly.
2022-12-27 14:58:16 +01:00
Pierre Ossman caf0ecc99b Consistent naming of RFB arguments
Make sure we call the arguments the same everywhere. Follow-up to commit
44d384b.
2022-12-27 14:55:06 +01:00
Pierre Ossman ceadcd6e83 Use reference style links in API docs
Makes everything a bit more readable.
2022-12-27 14:04:37 +01:00
Pierre Ossman e16b3b8620 Update feature list
Make sure everything is in sync with the current state of things.
2022-12-27 14:04:20 +01:00
Pierre Ossman d4197932d6 Update copyright year to 2022 2022-12-27 14:03:16 +01:00
Pierre Ossman 7e7e3ac07d List Joel and Solly as previous members
They are no longer active in the project, so list them under a different
section in relevant documents.
2022-12-27 14:02:44 +01:00
Pierre Ossman 1ff2ecd9f0 Merge branch 'ffscroll' of https://github.com/CendioOssman/noVNC 2022-12-27 13:13:48 +01:00
Pierre Ossman 5de478d6e7 Restrict forced panning to known bad platforms
Let's not punish systems that implement overlay scrollbars in a
functional way. The only current example is Firefox on Windows 11 and on
Linux.
2022-12-27 12:50:57 +01:00
Pierre Ossman 12a7c6f0de Check for Android using userAgent
Modern Android systems seem to report "Linux" for navigator.platform, so
we can no longer rely on that.
2022-12-27 12:50:57 +01:00
Pierre Ossman a187821e4f Add OS checks for Android and ChromeOS 2022-12-27 12:50:57 +01:00
Pierre Ossman 8fb30fb9dc Add unit tests for OS detection 2022-12-27 12:50:57 +01:00
Pierre Ossman ee5e3c5fa3 Refine browser detection
Try to follow the principle outlined by Mozilla when detecting browsers
and engines.
2022-12-27 12:50:57 +01:00
Pierre Ossman 4a34ee4b1e Remove navigator check from browser tests
This is a fundamental object that should always be present.
2022-12-27 12:50:57 +01:00
Pierre Ossman 88a36370a9 Add unit tests for browser detection 2022-12-27 12:50:57 +01:00
Pierre Ossman 28c9670427 Remove test code for old Chrome
We don't care about ancient versions of Chrome anyway, so let's keep
things simple.
2022-12-27 12:50:57 +01:00
Pierre Ossman 262a90b0e0 Consistently use "first" indentation
We already enforced this for most things, so let's fix up the last few
variants as well.
2022-12-27 12:50:57 +01:00
Pierre Ossman 7f4a9eebc8 Export clipping state externally
So that UI can reflect if it is currently possible to drag the viewport
or not.
2022-12-27 12:50:57 +01:00
Pierre Ossman f172633715 Sort API alphabetically
So it is easier to find things as the API grows.
2022-12-27 12:20:40 +01:00
Pierre Ossman e6fce71d6a Merge branch 'add-mslogonii' of https://github.com/pdlan/noVNC 2022-12-23 14:43:06 +01:00
Samuel Mannehed 820b39c7d3 Reinstate outer div of noVNC_connect_button
There were two issues with removing the outer div of the connect button.

Firstly, rounded outlines don't work in WebKit browsers like Safari or
Epiphany (https://bugs.webkit.org/show_bug.cgi?id=20807) and this makes
the outline look completely square.

Secondly the code became too complex.

This reverts most of commit 05baf14256.
2022-12-23 13:44:58 +01:00
Samuel Mannehed bd2d3a58b0 Change element type of noVNC_logo
This is text, a <p> is better suited.
2022-12-23 13:44:58 +01:00
Samuel Mannehed 2b449b208e Get rid of WebKit's button top margin
It conflicts with our :active styling since it changes margin-top.
2022-12-23 13:44:57 +01:00
Samuel Mannehed 30f230b74c Merge pull request #1732 from novnc/favicons
Favicon cleanups and fixes
2022-12-23 10:22:12 +01:00
Samuel Mannehed 6e1d842850 Use an ICO file for favicons
The browsers have been choosing very poorly and have a lot of bugs when
it comes to favicons. Using an ICO makes many browsers choose better in
most cases. Most large websites use ICO files.

The icons in the ICO file needs to be ordered largest to the smallest
icon, and due to a Chrome bug we are limited to 8 icons. This
unfortunately means we couldn't fit one of the Android sizes. The 72x72
icon was removed since testing showed that it was used the least.
2022-12-23 09:05:12 +01:00
Samuel Mannehed 139f087187 Explicitly specify icon size instead of density
Instead of calculating a density that we hope results in the correct
size, we can specify what size we want. This is more robust and easier
to understand. This also allows us to simplify the Makefile quite a bit.

Note that Fedora's packaging of ImageMagick has a bug here:
https://bugzilla.redhat.com/show_bug.cgi?id=2140018
2022-12-23 09:05:12 +01:00
Samuel Mannehed 416e21151b Create specific apple-touch-icons
These icons shouldn't have any transparancy. Instead, we remove the
rounded corners and let iOS handle that.
2022-12-23 09:05:12 +01:00
Samuel Mannehed 9e9d5ef17d Simplify names of favicons
All of the icons are square, only providing the size in one direction is
enough. This change lets us avoid some unnecessary complexity in the
Makefile.
2022-12-22 16:50:11 +01:00
Samuel Mannehed c8d37ae8bd Provide up to date set of apple-touch-icons
Apple requires a different set of icons now-a-days. This change involves
removing the 76x76 icon and adding icons with the following sizes;
40, 58, 80, 87, 167 and 180.
2022-12-22 13:15:25 +01:00
Samuel Mannehed 079889a13a Stop including apple-touch-icons as regular icons
These icons are used differently and don't belong in the list of regular
browser icons.
2022-12-22 13:04:03 +01:00
Samuel Mannehed 9649b8ee25 Remove duplicate 48 icon from Android sizes
This size is already specified under BROWSER_SIZES.
2022-12-22 13:04:03 +01:00
Samuel Mannehed 034fd376ac Simplify icon variables by removing the filename
The filename is the same for all of these, lets break out that part to
simplify things.
2022-12-22 11:29:10 +01:00
Samuel Mannehed 99a9c03f3c Rename browser icon variable to reflect use
The other variables in the Makefile are named according to how the icons
are used, lets do the same for the variable for the browser icons.
2022-12-22 11:13:20 +01:00
Samuel Mannehed ec45911456 Don't show virtual keyboard button in webkit
Webkit browsers don't support Media Queries 4, which means we have to
use a slightly convoluted syntax when writing "@media not...". Otherwise
the "(any-pointer: coarse)" part evaluates as the device part of the
query.
2022-12-22 10:22:34 +01:00
Dinglan Peng b776e1495e Add MSLogonII security type 2022-12-21 15:52:31 -05:00
Samuel Mannehed 4fb2d6c497 Add FIXME for virtual keyboard button on touch
The way we decide whether to show the keyboard button or not is not
ideal, let's add a FIXME for that.
2022-12-15 14:33:12 +01:00
Samuel Mannehed d8b3ec99fa Use "initial" for displaying handle touch area
Our intentions are clearer if we set "display" to "initial" rather than
"unset" when we want to enable the touch area for the control bar
handle.
2022-12-15 14:09:56 +01:00
Samuel Mannehed 05baf14256 Remove outer div from noVNC_connect_button
Instead of having an outer "box", we can use an outline on the button
itself to create this "platform". Since the outline isn't part of the
size of the element, it will appear wider than before, when compared to
the logo. To counteract that we remove the left and right padding from
the logo to make the entire noVNC_connect_dlg more narrow.

We also had to slightly adjust the :active style since we don't want the
entire "platform" to move when the button is clicked.
2022-12-15 10:44:27 +01:00
Samuel Mannehed e7ef963a8f Merge pull request #1730 from novnc/media_touch
Replace JavaScript .noVNC_touch with CSS @media (any-pointer: coarse)
2022-12-14 15:50:12 +01:00
Samuel Mannehed f1550c69d9 Get rid of noVNC_touch in favor for @media queries
This commit removes our dependency on the class "noVNC_touch" which was
set by Javascript. Instead, we can use the CSS media query
"any-pointer: coarse", which means that any pointing device that isn't
accurate is available. In practice this seems to basically be equal to
that a touch screen is available.

This change lets us simplify the selectors in many cases as well, which
is a nice bonus.
2022-12-14 14:06:16 +01:00
Samuel Mannehed 6d7d45ba08 Ensure arrow doesn't change on disabled <select>
We can't just modify the CSS variable here, since that is also used in
the style for :disabled. We need to change the entire "background-image"
in order for :disabled to be able to override it.
2022-12-14 13:58:49 +01:00
Fredrik Kortetjärvi 90f120c139 Added none user select on the cursor
This is because, when double-clicking with the trackpad, it will not
highlight the mouse. And this only happened on the iOS but the decision
on adding it a normal user select comes from the other commits that it
looks like it elsewhere.
2022-12-14 13:47:01 +01:00
Samuel Mannehed f983c78d17 Make connect button a regular <button>
It is a button, let the HTML element reflect that. And instead of
having the outer div being clickable, lets only make the inner one
work like a button. Because of that, this commit renames the outer div
to "connect_box" instead of "connect_button".

Note that we remove the disabled :hover-effect for touch on this button.
It doesn't make much difference since this button is one of a kind.
2022-12-14 13:28:27 +01:00
Pierre Ossman 156b9a99e2 Set snap credentials via environment
The old method of using "with" is no longer supported.
2022-12-14 12:50:51 +01:00
Samuel Mannehed 1ff035c330 Add comment explaining the handle touch area 2022-12-14 11:18:46 +01:00
Samuel Mannehed 333e075d7b Get rid of Chrome's blue touch tap highlight
When tapping our buttons using a touch screen in Chrome, we get an ugly
blue overlay. Let's remove this since we have our own :active styles.
2022-12-13 15:38:25 +01:00
Samuel Mannehed 6e1eec3025 Separate the disabling of :hover for touch
This is a corner case which shouldn't need to complicate things for the
regular usecases.
2022-12-13 15:23:31 +01:00
Samuel Mannehed 98364c3daa Use the same background gradient on all buttons
Before, we have had two different gradiant versions, one where the two
colors meet in the middle, and one where only the top part of the
element was the darker shade. This was easily missed. Let's standardize
on the latter alternative. This commit introduces a variable to make it
easier.
2022-12-13 14:43:03 +01:00
Samuel Mannehed 837cc75a8c Add FIXME for select:active on Firefox
The :active state is only active in Firefox during the click on the
<select>, not while the dropdown is opened, like in Chrome.
2022-12-13 14:01:24 +01:00
Samuel Mannehed 6a650ade2d Merge pull request #1729 from novnc/css_cleanup
Add styling for checkboxes and range-sliders
2022-12-12 15:44:17 +01:00
Samuel Mannehed d083ba269e Buttons shouldn't react to clicks when disabled
Counteract the border-width and margin set by :active in the rules for
:disabled buttons.
2022-12-12 15:35:36 +01:00
Samuel Mannehed 69c1d8a1b9 Add section comments to input.css
Makes stuff easier to find.
2022-12-12 15:32:07 +01:00
Samuel Mannehed b676122642 Simplify :focus-visible & :disabled selectors
Since these were the same for all <input> types now, we can omit the
type from the selector.
2022-12-12 15:31:00 +01:00
Samuel Mannehed 9107ae3a10 Share some properties between all input elements
Some properties, like 'font', 'appearance', and 'color' are shared
between all our input elements. Let's reflect that in our rules.
2022-12-12 15:31:00 +01:00
Samuel Mannehed 8c1b6e19c7 Combine rules for buttons in input.css
It was completely unnecessary that these two were separate, lets combine
them. The only difference was that the lower rule didn't apply for
<select>. That doesn't matter though, since padding-left and
padding-right are specifically set for <select> elements anyway.
2022-12-12 15:31:00 +01:00
Samuel Mannehed 3cf2bb9b59 Change arrow direction of active select button
When the select-dropdown is open, let's use an arrow pointing upwards
isntead. Note that we can't easily animate the change in
background-image.
2022-12-12 15:31:00 +01:00
Samuel Mannehed 80897091e0 Put all styling for selected buttons in one place
Let's not have the rules for noVNC_selected spread out.
2022-12-12 15:31:00 +01:00
Samuel Mannehed 36510f7d16 Clarify comment about hover state on touch devices 2022-12-12 15:31:00 +01:00
Samuel Mannehed cc703babcb Remove duplicate opacity for :disable
The control bar buttons can fall back on the :disable opacity from
input.css.
2022-12-12 15:31:00 +01:00
Samuel Mannehed da4f3f30ea Move workaround for Firefox bug to input.css
This applies to all input[type=image]:disabled elements, not only
control bar buttons.
2022-12-12 15:31:00 +01:00
Samuel Mannehed 654066f2c4 Be more specific for control bar button background
Use the more specific background-color, and background-image properties
when setting the state backgrounds for the control bar buttons. This way
we no longer pollute all background related properties. It makes things
easier if we need to replace them in some states in the future.
2022-12-12 15:31:00 +01:00
Samuel Mannehed 4050f0e248 Break out properties for disabled buttons
Instead of marking the hover selector with ":not(:disabled)" we can
break out this into its own section. This makes things easier to read.
In order to ensure the correct selector prioritization we also reorder
the file a bit.
2022-12-12 15:31:00 +01:00
Samuel Mannehed 629a6cacb9 Add styling for input[type=file] elements
The last remaining input element we didn't have styling for (aside from
input[type=hidden] which can't be shown).
2022-12-12 15:30:02 +01:00
Samuel Mannehed 63528570bc Respect standard font settings for buttons as well
This should not only be done for input, select and textareas.
2022-12-12 14:50:14 +01:00
Samuel Mannehed 80ea7e17ec Add styling for radio buttons
One of the few remaining items we didn't have styling for.
2022-12-12 14:50:14 +01:00
Samuel Mannehed 564a89bcb9 Add small margin to the right of checkboxes
Makes things look less cramped.
2022-12-12 14:50:14 +01:00
Samuel Mannehed a714e1b003 Add small animation for checkboxes
Makes it look quite nice.
2022-12-12 14:50:14 +01:00
Samuel Mannehed fa8ff5e09d Set checkbox size using px rather than em
We don't set other sizes using em, it makes this stand out.
2022-12-12 14:50:14 +01:00
Samuel Mannehed 7519f2d4ad Set a white background-color on checkboxes
Otherwise they appear with the same color as the background, which is
not what we want. They should always be white.
2022-12-12 14:50:14 +01:00
Samuel Mannehed faf921b023 Set background gradients using background-image
Use the more specific background-image property when setting
linear-gradient backgrounds for input elements. This way we no longer
pollute all background related properties. It makes things easier if we
need to replace it in some states in the future.
2022-12-12 14:50:14 +01:00
Samuel Mannehed 2ff09d6f10 Unify element's :disabled styles to use opacity
Some elements used grey text and background when disabled, and some used
opacity. It looked a bit old school to make the elements grey when
disabled. Let's use opacity for all input elements when disabled.
2022-12-12 14:50:14 +01:00
Samuel Mannehed 1e500883f6 Move <select>:hover to other hover styles
Lets keep this file organized.
2022-12-12 14:50:14 +01:00
Samuel Mannehed 86adcdd3a3 Reorder selectors alphabetically in input.css 2022-12-12 14:49:52 +01:00
Samuel Mannehed 9c13ea3dd2 Use a outline instead of border for focus-visible
This is the more common way to do it, and allows us to use a offset.
2022-12-07 15:15:07 +01:00
Samuel Mannehed ac6adc61d5 Replace :focus styling with :focus-visible
Use the new modern :focus-visible instead of :focus. This is only shown
when navigating using the keyboard.

And in the case of the control bar buttons, This means we can separate
the :focus and :hover styles. Instead of showing a lighter overlay (or
darker for selected) like we use for hover, lets use a more common
blue outline for focus-visible. This also means we can re-use the common
focus-visible from input.css instead of having a special one for control
bar buttons.
2022-12-07 15:15:07 +01:00
Samuel Mannehed 8c961ab7c6 Add styling for input[type=range]
This makes them fit in better in our settings GUI, especially when it
comes to coloring.
2022-12-07 15:15:07 +01:00
Samuel Mannehed f820ec86f0 Add styling for checkboxes
This makes them fit in better in our settings GUI, especially when it
comes to coloring.
2022-12-07 14:52:15 +01:00
Samuel Mannehed c43e499357 Include input[type=image] in input.css
There's no real reason this shouldn't be in here, lets include it.
2022-12-07 11:16:06 +01:00
Samuel Mannehed 52178e9381 Set min-height on control bar panel 2022-12-07 08:56:21 +01:00
Samuel Mannehed 2d6302e359 Tone down comment about user-select on container
The issue with the selection prior to the fix can't be reproduced to
the same degree. It may have been some other bug that caused interaction
with the remote to be blocked.
2022-11-17 10:14:30 +01:00
Samuel Mannehed fc5bb6dab6 Add "arrow" to <select> elements
Since we are setting "appearance: none" on our <select> elements, the
drop down arrow from the browser is hidden. This arrow doesn't fit in
visually though. This commit adds a new arrow from a simple data url
SVG. Its a dark triangle "pointing" downwards.

Note that we need to set the background to both the gradient and the
image here. Both use the "background-image" property for the graphic,
but since they are positioned differently we must use the general
"background" shorthand.
2022-11-17 08:34:50 +01:00
Samuel Mannehed 4a0999a34e Slightly increase the padding on <select> elements
Our select elements look more like buttons than they look like text
inputs, this means we should use slightly larger padding.
2022-11-16 16:46:17 +01:00
Samuel Mannehed f19e328dce Add explanation text to clipboard panel
Hopefully makes it a bit easier to understand what you as a user are
supposed to do with the textarea.
2022-11-14 17:17:01 +01:00
Samuel Mannehed 2825529a13 Add horizontal rule after logo in control bar
This differentiates the logo from the buttons in a clear way.
2022-11-14 17:08:54 +01:00
Samuel Mannehed 0cb5f2341c Fix indentation of comment in CSS 2022-11-11 10:23:10 +01:00
Samuel Mannehed 429a08da89 Make control bar button selectors more specific
The class "noVNC_button" is only used for control bar buttons. Lets
clarify this in the CSS selectors by only applying styles to elements
with this class that are children of "#noVNC_control_bar".
2022-11-11 10:15:55 +01:00
Pierre Ossman 64d3d60120 Use "npm update" to install dependencies
We don't want to build our npm package, just get our development
dependencies.
2022-11-07 17:24:26 +01:00
Pierre Ossman e674ee4d8e Add translation workflow file
Just to make sure we continuously test that the tools work. This won't
actually update any translations.
2022-11-07 17:23:54 +01:00
Pierre Ossman d9b2606d8c Use latest versions of development dependencies
We thought we already did this in e24b501, but instead we would
basically get random versions as npm would pick some version already
available from whatever was already downloaded.

New attempt, this time being very explicit that we want the version that
has been tagged as "latest".
2022-11-07 17:05:05 +01:00
Pierre Ossman 64ffdc18e0 Merge branch 'busybox' of https://github.com/nggit/noVNC 2022-11-07 16:28:14 +01:00
Pierre Ossman 4cfe0fffcd Merge branch 'manoj/dirs' of https://github.com/msays2000/noVNC 2022-11-07 16:19:12 +01:00
nggit 2dd5600f3d Fix unrecognized option "p" in busybox ps 2022-11-07 21:25:50 +07:00
Samuel Mannehed 081f9d2a13 Disable iOS long-press popup for sidebar images
In order to make the sidebar feel more like a GUI element from a real
application, we can disable the long-press image popup on iOS. Note that
this only has an effect on iOS devices.
2022-11-02 16:38:12 +01:00
Samuel Mannehed 7e29e02ce4 Prevent accidental selection of the container
When long pressing stuff in the sidebar on iOS, you can sometimes
accidentally select the container or the canvas. This results in a
broken state where the user can't interact with the session anymore.
This commit prevents this from happening.
2022-11-02 16:36:50 +01:00
Samuel Mannehed 584ce06698 Disable selection globally on sidebar
We want to disable selections in the sidebar because when users drag
the handle, they could otherwise accidentally select stuff. This results
in a very broken state.

When selections are disabled, the sidebar also feels more like a GUI
element from a real application, and less like part of a webpage.
2022-11-02 16:22:54 +01:00
Samuel Mannehed 0ef75824a4 Ensure the correct cursor on disabled buttons
Without this fix we still get a "pointer" cursor on disabled inputs of
type "image" in Firefox. Currently, all our noVNC_buttons are
<input type="image">. Reported to firefox here:

https://bugzilla.mozilla.org/show_bug.cgi?id=1798304
2022-10-31 13:13:23 +01:00
Samuel Mannehed 138df46825 Remove unnecessary legacy CSS properties
We depend un such modern things anyway, having these kinds of properties
are more confusing than helpful. Let's not give the impression that we
make any attempt to work in old browsers.
2022-10-31 10:44:01 +01:00
Samuel Mannehed 5c684cce2a Change default dimensions of clipboard textarea
Make it slightly taller and not as wide, this makes it stand out less
compared to the other panels.
2022-10-28 16:15:35 +02:00
Samuel Mannehed d3913c0dde Limit webaccess clipboard textarea min width
The clipboard textarea could potentially shrink further than what was
possible for the header text elements, which looked a bit broken. In
that regard, a min width is introduced for the textarea.
2022-10-28 16:12:29 +02:00
Samuel Mannehed 82253c1f1a Don't let the clipboard textarea grow too high
There are scrollbars inside the textarea in case there's a lot of text
in there. We can limit the height of the element, it looks better.
2022-10-28 16:06:34 +02:00
Samuel Mannehed f0fea1fccd Fix max-width of clipboard textarea
It should not be able to "eat" its parent-panel's padding. By setting
box-sizing: border-box we can prevent this.
2022-10-28 16:04:45 +02:00
Samuel Mannehed 6b2357061e Use border-box for noVNC panels
If we use box-sizing: border-box we can avoid having to account for the
padding when calculcating the max-width.
2022-10-28 16:04:06 +02:00
Samuel Mannehed dd713bee63 Set max-width on all noVNC panels
All panels should be limited in this way, not just the clipboard panel.
One additional upside of this is that the numbers used to calculate the
max-width are closer by, in the code. This hopefully makes it easier to
avoid mistakes in the future.
2022-10-28 15:54:28 +02:00
Pierre Ossman 2d559fb2e1 Merge branch 'latin1' of https://github.com/CendioOssman/noVNC 2022-10-27 16:29:38 +02:00
Pierre Ossman 6eb17b27a0 Correctly mask non-BMP clipboard characters
JavaScript strings use UTF-16 encoding under the hood, but we only want
a single '?' per character we replace. So we need to be more careful
which methods we use when iterating over the clipboard string.
2022-10-27 16:24:27 +02:00
Pierre Ossman 6b555f1f74 Mask unsupported clipboard characters
Add a more explicit '?' for characters that the clipboard cannot handle,
instead of getting random junk.
2022-10-27 16:03:22 +02:00
Pierre Ossman 0410cbc190 Remove redundant inspect() override
We do this for all RFB tests now, not just these specific assertions.
2022-10-27 16:03:01 +02:00
Pierre Ossman 337fb06535 Restore Websock.allocateBuffers() after tests
This was accidentally removed in 0a6aec3578.
2022-10-27 16:02:02 +02:00
Pierre Ossman fee115b13f Update method to limit assertion output
Newer versions of the test framework use the inspect() method instead of
toString() for overriding the default output.
2022-10-27 15:59:48 +02:00
Samuel Mannehed f59be0586f Move control bar hint outside of the control bar
Makes it a more independent element responsible for it's own positioning
and vertical centering. This makes the hint easier to adapt for external
CSS styles and makes it possible to remote the fixed size if needed.
2022-10-24 15:29:27 +02:00
Samuel Mannehed 3141c0e01b Only show the control bar hint once per "move"
After the user has "followed" the hint by dragging the handle to the
other side, the control bar will switch to that side. Once this has
happened, we will now hide the hint until the user starts over by
dragging the handle again.
This change was added to make the hint feel more like a "hint" and less
like a permanent GUI element. It isn't as persistent and intrusive now.

Note that we don't want the act of hiding the hint to result in a
transition animation here.
2022-10-24 13:03:50 +02:00
Manoj Ghosh 8e660ba3e8 expose --file-only option to disable dir listing 2022-10-20 10:11:36 -07:00
Pierre Ossman cd94c2aed2 Merge branch 'fix-1695' of https://github.com/m1k1o/noVNC 2022-10-19 14:16:29 +02:00
Miroslav Šedivý 1971823a4f auto release keys while meta is held down. 2022-10-15 14:48:39 +02:00
Samuel Mannehed 8715ed9e70 Match touch area height with height of handle
Instead of hard coding the height of this touch area we can just use
its parent's height.
2022-10-14 18:13:19 +02:00
Samuel Mannehed f0c3af3c67 Move <input> and <button> styles to its own file
This makes it easier for integrators of vnc.html to write their own
input and button styles.
It's also positive to cut a bit off from the size of the large base.css.
2022-10-14 11:27:42 +02:00
Pierre Ossman 88ccfdc193 Clean up control bar padding/margins
Try to make it a bit less messy by trying to get more general rules in
place.
2022-10-13 16:20:28 +02:00
Pierre Ossman a0c4214823 Use latest GitHub actions
The older ones are getting deprecated, so make the switch.
2022-10-13 16:20:28 +02:00
Samuel Mannehed 9761278df8 Style <button> the same as <input type="button">
These styles are meant to be complete, that we didn't have a style for
<button> was a mistake.
2022-10-13 10:25:36 +02:00
Samuel Mannehed 9a6e0d47d0 Rename CSS section to better reflect contents
These buttons only exist in the control bar.
2022-10-12 16:50:28 +02:00
Samuel Mannehed 145d235094 Make error handler's focus changes best-effort
When the error handler itself causes an exception, it falls back to a
simple document.write(). This means the proper error dialog isn't shown
when this happens.

The focus changes that were added to the error handler in e1f8232b are
not crucial for its function. If these focus changes causes an exception
we can just ignore that.
2022-10-12 13:01:23 +02:00
Samuel Mannehed 4ecb44111d Check if activeElement exists before using it
According to MDN, document.activeElement can be null if there is no
focused element.
2022-10-12 12:39:49 +02:00
Pierre Ossman bdc0bbbb4f Respect font settings on input elements
The browsers override these instead of using the normal inheritance. So
make sure our global font settings are respected.
2022-10-11 13:51:57 +02:00
Pierre Ossman a8488d5b32 Add more air to settings panel
We want some space between elements to avoid things getting cramped, so
add some minimal margins.
2022-10-11 13:50:40 +02:00
Pierre Ossman f887abdb38 Increase input element padding
The text gets a bit cramped otherwise. Extra so at larger font sizes.
2022-10-11 13:50:09 +02:00
Pierre Ossman a1e11e6d00 Stop setting margin on input elements
Margins behave badly on inline elements, so let's try to avoid using
them. Margins should be handled by the block elements anyway.
2022-10-11 13:49:11 +02:00
Pierre Ossman 615b36a067 Handle crash dialog overflow better
Avoid making assumptions on how much space is available for the stack
dump, and instead handle the overflow on the top element.
2022-10-11 13:47:53 +02:00
Samuel Mannehed 6f55527514 Fix typo in fallback_error CSS comment
Typo from commit e1f8232bc9
2022-10-07 15:24:11 +02:00
Samuel Mannehed e1f8232bc9 Block user interaction when fallback error shows
When this error is shown, something has gone very wrong. It shows when
a bug in the JavaScript causes an uncaught error. In these scenarios we
dont want the user to be able to interact with the GUI or the remote
session, since we can't guarantee that things work.
2022-10-07 14:55:00 +02:00
Samuel Mannehed 58dfb7df45 Don't react to interactions with disabled buttons
Disabled buttons should not change appearance on mouse hover, click or
on keyboard focus. Doing so destroys the "disabled" impression.
2022-10-04 15:47:25 +02:00
Pierre Ossman c101a31520 Don't use explicitly Helvetica
On many systems you get a poor substitute, so let's instead instruct the
browser that we merely want a sans serif font for our interface.
2022-09-23 13:59:39 +02:00
Pierre Ossman 1a101443a7 Set font family on root element
This is a very global setting, so let's put it on the top node for
clarity.
2022-09-23 13:58:50 +02:00
Pierre Ossman af10b0c5e4 Avoid using translate() for positioning
It often results in a blurry result on WebKit based browsers.
2022-09-16 13:18:06 +02:00
Pierre Ossman 32f9033863 Document state classes uses in CSS
Makes it a bit easier to understand all the magic in this CSS.
2022-09-15 10:39:21 +02:00
Pierre Ossman efb2400833 Merge branch 'italian' of https://github.com/M2Rbiz/noVNC 2022-09-14 10:36:27 +02:00
Pierre Ossman d5b8425d42 Use automatic version when building snap directly
Make it easier to build a snap from your working copy by removing the
restriction of having to modify the version field first.
2022-09-14 10:34:01 +02:00
Samuel Mannehed 69e0f0f5db Remove unnecessary clipboard clear button
This button fills no real purpose. It's easy to mark everything and
delete with either "Ctrl + A -> Delete" or, on touch devices, "long
press -> mark everything -> Delete".
2022-09-09 16:03:49 +02:00
Fabio Fantoni 5d8ede61f9 Add italian translation 2022-09-08 14:56:04 +02:00
Frederik Fix edc7520e27 access to raw image data 2022-09-07 13:26:10 +02:00
nickcFRU 2f1e11b54a add support for for enabling authentication 2022-08-23 16:22:54 -04:00
Pierre Ossman 832937292e Merge branch 'patch-1' of https://github.com/VibroAxe/noVNC 2022-08-19 10:24:11 +02:00
Pierre Ossman 1d148a8478 Merge branch 'authprio' of https://github.com/CendioOssman/noVNC 2022-08-19 10:10:10 +02:00
Pierre Ossman df8d005de9 VeNCrypt should handle classical types
VeNCrypt is a superset of the original security types, so it should be
fine to send any of the classical values here as well.
2022-08-18 16:26:33 +02:00
Pierre Ossman 795494ade1 Prefer security types in the server's order
This is how TigerVNC has been behaving for years and has worked well
there, so let's follow them.
2022-08-18 16:26:27 +02:00
Pierre Ossman e1174e813b Use constants for security types
Makes everything much more readable.
2022-08-18 16:26:19 +02:00
Pierre Ossman 6719b932cf Avoiding internal variables for security tests
A good test uses only input and output, so let's avoid assuming internal
variable names or behaviours.
2022-08-18 16:26:09 +02:00
Pierre Ossman 5671072dfe Expect security result for RFB 3.7
The cut off was wrong here. 3.7 will send a security result, but not a
security reason. It also fixes the issue that < 3.7 (e.g. 3.3) supports
VNC authentication as well.
2022-08-18 16:25:59 +02:00
Pierre Ossman 084030fe68 Handle connection init loop at the top
Avoid the mess of having lots of functions call back to _initMsg() just
because they might be able to continue right away. Instead loop at the
top level until we're either done, or we need more data.
2022-08-18 16:24:55 +02:00
Pierre Ossman 05d68e118d Abstract resuming the authentication
We now do this in multiple places, so make sure things are handled the
same way in all cases.
2022-08-18 16:24:45 +02:00
Pierre Ossman 8a7089c0c6 Remove redundant security result tests
The event is the desired behaviour. RFB._fail() being called is just an
internal detail that we shouldn't care about.
2022-08-18 16:24:24 +02:00
James Kinsman faedcd0210 Allow continued reconnect tries
Currently novnc will only retry once (assuming the server is unavailable) and then stop (as the detail from is unclean, usually "failed to connect"). Minor change will continue to reconnect every reconnect_delay seconds until either reconnected or user intervention cancels the attempt.
2022-08-08 14:22:41 +01:00
Pierre Ossman cdfb336651 Add warnings about insecure context
Most (all?) new APIs will require a "secure context", which generally
means served over TLS. We can expect crashes because of missing
functions if this requirement isn't fulfilled, so try to warn the user.
2022-05-12 15:48:46 +02:00
Samuel Mannehed 658e415796 Merge pull request #1647 from Lowxorx/master
Update French translation
2022-04-28 18:51:35 +02:00
Lowxorx 97f6657146 Update French translation
- Some syntax adjustments
- Correction of terminology used
- Use of non-breaking space before ':'
2022-04-25 23:41:06 +02:00
Samuel Mannehed 1075cd8e19 Merge pull request #1644 from USTC-vlab/remove-bigint-mod-arith
Remove bigint-mod-arith.js
2022-04-06 09:31:15 +02:00
pdlan 19aa9ad6a3 Remove bigint-mod-arith.js 2022-04-05 02:40:47 -04:00
Pierre Ossman 42ec5f3321 Merge branch 'appleremotedesktop' of https://github.com/pauldumais/noVNC 2022-04-05 07:55:14 +02:00
Paul Dumais e21ed2e689 Added support for Apple Remote Desktop authentication
Fixed eslint warnings

Fixing tests that failed

Added unit tests for ARD authentication

Fixed an issue with the ARD rfb version number in the unit tests

Fixed issue with username/password lengths

Username and password lengths are now capped at 63 characters each.  Improved code for sign bit on public key bytes.

UTF Encoder username and password before packing it

Change UTF encoding to encode the username and password before packing it to prevent it from being expanded beyond the allowed size.  Public key is truncated to proper key length.

Replaced forge with web crypto for ARD authentication

Changed the way in which the async methods are handled, added unit tests to verify ARD encryption output.

Update .eslintignore
2022-04-04 11:40:19 -04:00
Pierre Ossman 98664c7887 Handle correct data offset in raw decoder
There is often buffered data ahead of the pixel data so we need to take
this in account when making sure pixels are opaque.
2022-03-28 08:45:40 +02:00
Pierre Ossman 7730814b8d Set label "for" attributes for credentials dialog
Label tags should always indicate which input they are associated with.
2022-03-10 16:29:57 +01:00
Pierre Ossman da623156d3 Adjust wording and style of server verify dialog
Try to be a bit more verbose about what this dialog means and what the
user should do.
2022-03-10 16:29:24 +01:00
Pierre Ossman 7be06b4d7d Add headings for dialogs
Keep them in the same style as the panels.
2022-03-10 16:28:54 +01:00
Pierre Ossman 15a0608e04 Make sure server verification dialog is closed
It should be closed at this point just like all other dialogs and
panels.
2022-03-10 16:28:15 +01:00
Pierre Ossman ced6431ac5 Make credentials rules more specific
This was a very broad selector, so it does not belong in a specific
section like this. Do what all similar rules do and make it very
targeted.
2022-03-10 16:27:17 +01:00
Pierre Ossman a73b5acfbb Stop abusing lists for dialogs
Use some more sane elements for these things.
2022-03-10 16:26:38 +01:00
Pierre Ossman cbe54acd1f Fix bad links in API documentation 2022-03-10 15:39:49 +01:00
Pierre Ossman cf7f7b57c5 Document new API for server verification
The new RSA AES security types have a mechanism for authenticating the
server that needs to be properly specified.
2022-03-10 15:39:12 +01:00
Pierre Ossman 240efb94da Note contribution from USTC Vlab Team 2022-03-10 15:38:50 +01:00
Pierre Ossman eac11d5799 Merge branch 'add-ra2ne-security-type' of https://github.com/pdlan/noVNC 2022-03-10 12:37:34 +01:00
pdlan a1709b999e Added support for RSA-AES Unencrypted Security Type 2022-03-08 13:24:26 -05:00
Tim Edwards 80a7c1dbf1 Update README.md 2022-03-04 16:31:21 +01:00
Samuel Mannehed 679b45fa3b Merge pull request #1617 from novnc/resizeObserverScrollbars
Fix issue with ResizeObservers and scrollbars
2021-12-15 16:01:36 +01:00
Samuel Mannehed 0ff0844a14 Ignore resize observation caused by server resizes
If we increase the remote screen size from the server in such a way that
it no longer fits the browser window, the browser will probably want to
show scrollbars. The same happens if you enable 'clipping' while the
remote is larger than the browser window. These scrollbars do, in turn,
decrease the available space in the browser window. This causes our
ResizeObserver to trigger.

If the resize observation triggers a requestRemoteResize() we will
overwrite the size and request a new one just because scrollbars have
appeared. We don't want that.

We can save the expected client size after resizing, and then compare
the current client size with the expected one. If there is no change
compared to the expected size, we shouldn't send the request.

Fixes issue #1616.
2021-12-13 11:20:14 +01:00
Samuel Mannehed 6cd69705d6 Make sure we wait for the resizeTimeout in tests
Not waiting for the full timeout can obscure future bugs.
2021-12-13 11:20:14 +01:00
Samuel Mannehed acc30093ad Replace resize events with observations in tests
This was missed in commit 375f36c575,
probably because these unit tests still passed (due to the expectancy
was for the code to not act on the resize events).
2021-12-13 11:20:14 +01:00
Samuel Mannehed c0d4dc8eb3 Breakdown of ExtendedDesktopSize message in tests
Saves time by not requiring the developer to look up the RFB protocol
each time viewing these tests.
2021-12-13 11:20:14 +01:00
Samuel Mannehed a7b96087d7 Add some explanatory comments to test.rfb.js 2021-12-13 11:20:13 +01:00
Samuel Mannehed a78a7bf8aa Update comment for scrollbar workaround
This is no longer an issue on Google Chrome, tested on Chrome 96 on
Fedora 34, Windows 10, macOS 12 and Android 12. It is however an issue
on Safari on macOS 12.

Without this workaround we get scrollbars when making the browser
window smaller, despite remote resize being enabled.
2021-12-13 10:07:16 +01:00
Samuel Mannehed 78eda3c040 Merge pull request #1615 from novnc/useRequireRename
Rename use_require.js to js2common.js
2021-12-01 09:22:10 +01:00
Samuel Mannehed 44c10255ad Rename use_require.js to convert.js
This script only has one purpose now, let the name reflect that. It
converts to CommonJS for NPM.
2021-12-01 09:17:41 +01:00
Samuel Mannehed e965832e0a Remove unused dependencies from package.json
These were used by the, now removed, legacy transpilation steps, which
were removed in commit 890cff921d.
2021-11-30 11:07:55 +01:00
Pierre Ossman 6710410356 Merge branch 'add-jpeg-encoding' of https://github.com/pdlan/noVNC 2021-11-29 09:18:50 +01:00
Pierre Ossman 721eaa4f50 Fix lint error in encodings test 2021-11-26 11:38:01 +01:00
Pierre Ossman 65d6357cdf Add missing ZRLE encoding constant
Also add a unit test to catch omissions like this in the future.
2021-11-26 11:13:06 +01:00
pdlan 7f84160147 Add RealVNC's JPEG encoding
Add support for RealVNC's JPEG encoding.

Add tests for JPEGDecoder. Fix the corner case of caching Huffman or quantization tables.
2021-11-26 03:59:19 -05:00
Pierre Ossman bfb6ac259d Merge branch 'zrle' of https://github.com/pauldumais/noVNC 2021-11-26 09:27:08 +01:00
Pierre Ossman 1691617f39 Merge branch 'patch-1' of https://github.com/pykgi6/noVNC 2021-11-26 08:36:49 +01:00
Samuel Mannehed c278b24eb4 Merge pull request #1612 from williamsjoblom/master
Use "Full Screen" instead of "Fullscreen"
2021-11-24 16:48:13 +01:00
William Sjöblom c88083b86a Use "Full Screen" instead of "Fullscreen"
"Fullscreen", or more correctly "Full-screen", refers to the
adjective. In this case, we want the tooltip of the full-screen button
to refer to the noun "Full Screen" as this seems to be the convention.
2021-11-24 16:31:47 +01:00
pykgi6 466f1f9af6 Document binding to localhost in Quick Start 2021-11-24 07:33:08 +00:00
Paul Dumais d4c887e23f Added support for ZRLE encoding
Fixed eslint warnings

Improved memory usage of zrle decoding.  Added unit tests for zrle decoding.

Added support for ZRLE encoding

Fixed eslint warnings

Reverted allowIncomplete changes to Inflator

Fixed failing tests for zrle decoder.
2021-11-23 12:02:42 -05:00
Pierre Ossman c143a852b1 Convert error handler to ES6 module
We no longer support older browsers, so this is not allowed to use
modern features.
2021-11-22 14:03:59 +01:00
Pierre Ossman 2f602da961 Ignore ResizeObserver errors
It seems that Firefox has a bug where these are fired incorrectly when
we are in an <iframe>. The events also contain no useful details, so we
can't really do anything useful with them anyway.
2021-11-22 13:53:05 +01:00
Pierre Ossman 7ad4e60df6 Add transition animation to transition screen
Avoid a harsh switch to the transition screen (loading, connecting) by
using some CSS transition animation.
2021-11-19 16:18:51 +01:00
Pierre Ossman 301714928b Avoid scrolling on RFB object focus
Chrome scrolls the view to show as much as possible of the canvas when
we call focus(), which is likely not the desired behaviour.

This also exposes the ability to pass on future options when focusing
the RFB object manually.
2021-11-16 09:38:14 +01:00
Pierre Ossman 096449da35 Add unit tests for Inflator 2021-11-16 09:37:56 +01:00
Pierre Ossman 99cf540e1a Set a git version number on most builds
If it isn't a release then it is some form of development build and
should have a version that reflects that.
2021-11-09 16:32:49 +01:00
Pierre Ossman c2980d15e9 Only publish if we are in the original repo
This avoids people having failing actions in forks of our repo since
they don't have permission to publish things.
2021-11-09 16:32:34 +01:00
Pierre Ossman bbbcab692a Make workflow if expressions multi line
To make them easier to read
2021-11-09 16:20:41 +01:00
Pierre Ossman 8c09b99b4e Fix typo in deploy workflow 2021-11-09 16:05:17 +01:00
Pierre Ossman a012f98b61 Merge branch 'snap' of https://github.com/CendioOssman/noVNC 2021-11-09 15:59:44 +01:00
Pierre Ossman 98243fc68f Move snap dependencies to separate parts
You can't include dependencies if you use the "stage:" or "prime:"
filters as they will also filter the files from your dependencies. This
is apparently per design and not a bug...
2021-11-09 15:51:39 +01:00
Pierre Ossman 303e5ef87b Publish development builds to npm and snap
Gives us early warnings about problems, and allows people to test any
committed version.
2021-11-08 16:36:40 +01:00
Samuel Mannehed 2c48df4560 Merge pull request #1601 from Matir/patch-1
Tiny typo fix in README.md
2021-10-28 11:16:28 +02:00
David Tomaschik 98cdc076a0 Tiny typo fix
Fix `novnc_procy` to `novnc_proxy` in README.md.
2021-10-27 15:57:36 -07:00
Samuel Mannehed a5499bbffe Bump up node version for lint github action
Apparently the new eslint version doesn't work with the older version of
node that we were using. Asking for '@v2' seems to help. It's unclear
what version of node we get now though since v2 isn't as verbose in its
output.
2021-10-22 13:42:41 +02:00
Samuel Mannehed 463c39e4af noVNC 1.3.0 2021-10-22 10:40:13 +02:00
Samuel Mannehed 6f0eb2b01a Remove inactive maintainers from package.json 2021-10-22 10:39:26 +02:00
Samuel Mannehed 22fe8e383f Update json files for new translations 2021-10-22 10:36:07 +02:00
Samuel Mannehed 264a6d82ea Stop chained builds of .po and .json files
The way we work with these translation files means that we only care
about one step at a time, we don't want to update the .po files when
building the 'update-js' target.

Also, always force rebuilds of the .po and .json files.
2021-10-22 09:42:17 +02:00
Pierre Ossman a85c85fb5f Follow API changes in commander 7.0.0+
Options now have to be exlicitly requested.
2021-09-28 10:46:41 +02:00
Samuel Mannehed d971c0fe55 Merge pull request #1582 from CendioOssman/preload
Preload status bar images
2021-09-24 16:45:11 +02:00
Pierre Ossman ff077f4656 Preload status bar images
These are used via CSS, which means the browser doesn't load them until
an element actually gets those CSS rules. There can be some delay to
this loading which causes visual glitches. By preloading we can make
sure those images are cached and ready when the status bar appears.
2021-09-24 16:15:31 +02:00
Nia Remez bfefd81d4c Fix typos in Russian translation 2021-09-09 12:46:22 +02:00
Samuel Mannehed 0fd0d57fcd Fix snapcraft publish step
A typo caused it to use the incorrect path
2021-09-09 11:27:51 +02:00
Nia Remez a98d72e2e9 Update Russian translation 2021-09-09 11:25:43 +02:00
104 changed files with 7786 additions and 2467 deletions
+6 -2
View File
@@ -1,10 +1,11 @@
{
"env": {
"browser": true,
"es6": true
"es2020": true
},
"parserOptions": {
"sourceType": "module"
"sourceType": "module",
"ecmaVersion": 2020
},
"extends": "eslint:recommended",
"rules": {
@@ -25,10 +26,13 @@
"brace-style": ["error", "1tbs", { "allowSingleLine": true }],
"indent": ["error", 4, { "SwitchCase": 1,
"VariableDeclarator": "first",
"FunctionDeclaration": { "parameters": "first" },
"FunctionExpression": { "parameters": "first" },
"CallExpression": { "arguments": "first" },
"ArrayExpression": "first",
"ObjectExpression": "first",
"ImportDeclaration": "first",
"ignoreComments": true }],
"comma-spacing": ["error"],
"comma-style": ["error"],
+55 -14
View File
@@ -10,47 +10,88 @@ jobs:
npm:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
- uses: actions/checkout@v3
- run: |
GITREV=$(git rev-parse --short HEAD)
echo $GITREV
sed -i "s/^\(.*\"version\".*\)\"\([^\"]\+\)\"\(.*\)\$/\1\"\2-g$GITREV\"\3/" package.json
if: github.event_name != 'release'
- uses: actions/setup-node@v3
with:
# Needs to be explicitly specified for auth to work
registry-url: 'https://registry.npmjs.org'
- run: npm install
- uses: actions/upload-artifact@v2
- uses: actions/upload-artifact@v3
with:
name: npm
path: lib
- run: npm publish --access public
env:
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
if: ${{ github.event_name == 'release' && !github.event.release.prerelease }}
if: |
github.repository == 'novnc/noVNC' &&
github.event_name == 'release' &&
!github.event.release.prerelease
- run: npm publish --access public --tag beta
env:
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
if: ${{ github.event_name == 'release' && github.event.release.prerelease }}
if: |
github.repository == 'novnc/noVNC' &&
github.event_name == 'release' &&
github.event.release.prerelease
- run: npm publish --access public --tag dev
env:
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
if: |
github.repository == 'novnc/noVNC' &&
github.event_name == 'push' &&
github.event.ref == 'refs/heads/master'
snap:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- run: |
GITREV=$(git rev-parse --short HEAD)
echo $GITREV
sed -i "s/^\(.*\"version\".*\)\"\([^\"]\+\)\"\(.*\)\$/\1\"\2-g$GITREV\"\3/" package.json
if: github.event_name != 'release'
- run: |
VERSION=$(grep '"version"' package.json | cut -d '"' -f 4)
echo $VERSION
sed -i "s/@VERSION@/$VERSION/g" snap/snapcraft.yaml
sed -i "s/^version:.*/version: '$VERSION'/" snap/snapcraft.yaml
- uses: snapcore/action-build@v1
id: snapcraft
- uses: actions/upload-artifact@v2
- uses: actions/upload-artifact@v3
with:
name: snap
path: ${{ steps.snapcraft.outputs.snap }}
- uses: snapcore/action-publish@v1
with:
store_login: ${{ secrets.SNAPCRAFT_LOGIN }}
snap: ${{ steps.build.outputs.snap }}
snap: ${{ steps.snapcraft.outputs.snap }}
release: stable
if: ${{ github.event_name == 'release' && !github.event.release.prerelease }}
env:
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_LOGIN }}
if: |
github.repository == 'novnc/noVNC' &&
github.event_name == 'release' &&
!github.event.release.prerelease
- uses: snapcore/action-publish@v1
with:
store_login: ${{ secrets.SNAPCRAFT_LOGIN }}
snap: ${{ steps.build.outputs.snap }}
snap: ${{ steps.snapcraft.outputs.snap }}
release: beta
if: ${{ github.event_name == 'release' && github.event.release.prerelease }}
env:
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_LOGIN }}
if: |
github.repository == 'novnc/noVNC' &&
github.event_name == 'release' &&
github.event.release.prerelease
- uses: snapcore/action-publish@v1
with:
snap: ${{ steps.snapcraft.outputs.snap }}
release: edge
env:
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_LOGIN }}
if: |
github.repository == 'novnc/noVNC' &&
github.event_name == 'push' &&
github.event.ref == 'refs/heads/master'
+6 -6
View File
@@ -6,14 +6,14 @@ jobs:
eslint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
- run: npm install
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- run: npm update
- run: npm run lint
html:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
- run: npm install
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- run: npm update
- run: git ls-tree --name-only -r HEAD | grep -E "[.](html|css)$" | xargs ./utils/validate
+3 -3
View File
@@ -20,9 +20,9 @@ jobs:
fail-fast: false
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
- run: npm install
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- run: npm update
- run: npm run test
env:
TEST_BROWSER_NAME: ${{ matrix.browser }}
+15
View File
@@ -0,0 +1,15 @@
name: Translate
on: [push, pull_request]
jobs:
translate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- run: npm update
- run: sudo apt-get install gettext
- run: make -C po update-pot
- run: make -C po update-po
- run: make -C po update-js
+2 -2
View File
@@ -1,9 +1,9 @@
maintainers:
- Joel Martin (@kanaka)
- Solly Ross (@directxman12)
- Samuel Mannehed for Cendio AB (@samhed)
- Pierre Ossman for Cendio AB (@CendioOssman)
maintainersEmeritus:
- Joel Martin (@kanaka)
- Solly Ross (@directxman12)
- @astrand
contributors:
# There are a bunch of people that should be here.
+1 -1
View File
@@ -1,4 +1,4 @@
noVNC is Copyright (C) 2019 The noVNC Authors
noVNC is Copyright (C) 2022 The noVNC Authors
(./AUTHORS)
The noVNC core library files are licensed under the MPL 2.0 (Mozilla
+23 -7
View File
@@ -65,10 +65,14 @@ Please tweet [@noVNC](http://www.twitter.com/noVNC) if you do.
### Features
* Supports all modern browsers including mobile (iOS, Android)
* Supported VNC encodings: raw, copyrect, rre, hextile, tight, tightPNG
* Supported authentication methods: none, classical VNC, RealVNC's
RSA-AES, Tight, VeNCrypt Plain, XVP, Apple's Diffie-Hellman,
UltraVNC's MSLogonII
* Supported VNC encodings: raw, copyrect, rre, hextile, tight, tightPNG,
ZRLE, JPEG
* Supports scaling, clipping and resizing the desktop
* Local cursor rendering
* Clipboard copy/paste
* Clipboard copy/paste with full Unicode support
* Translations
* Touch gestures for emulating common mouse actions
* Licensed mainly under the [MPL 2.0](http://www.mozilla.org/MPL/2.0/), see
@@ -113,8 +117,13 @@ proxy.
used to specify the location of a running VNC server:
`./utils/novnc_proxy --vnc localhost:5901`
* If you don't need to expose the web server to public internet, you can
bind to localhost:
`./utils/novnc_proxy --vnc localhost:5901 --listen localhost:6081`
* Point your browser to the cut-and-paste URL that is output by the `novnc_procy`
* Point your browser to the cut-and-paste URL that is output by the `novnc_proxy`
script. Hit the Connect button, enter a password if the VNC server has one
configured, and enjoy!
@@ -123,13 +132,17 @@ Running the command below will install the latest release of noVNC from Snap:
`sudo snap install novnc`
#### Running noVNC
#### Running noVNC from Snap Directly
You can run the Snap-package installed novnc directly with, for example:
`novnc --listen 6081 --vnc localhost:5901 # /snap/bin/novnc if /snap/bin is not in your PATH`
#### Running as a Service (Daemon)
If you want to use certificate files, due to standard Snap confinement restrictions you need to have them in the /home/\<user\>/snap/novnc/current/ directory. If your username is jsmith an example command would be:
`novnc --listen 8443 --cert ~jsmith/snap/novnc/current/self.crt --key ~jsmith/snap/novnc/current/self.key --vnc ubuntu.example.com:5901`
#### Running noVNC from Snap as a Service (Daemon)
The Snap package also has the capability to run a 'novnc' service which can be
configured to listen on multiple ports connecting to multiple VNC servers
(effectively a service runing multiple instances of novnc).
@@ -194,15 +207,18 @@ See [AUTHORS](AUTHORS) for a (full-ish) list of authors. If you're not on
that list and you think you should be, feel free to send a PR to fix that.
* Core team:
* [Joel Martin](https://github.com/kanaka)
* [Samuel Mannehed](https://github.com/samhed) (Cendio)
* [Solly Ross](https://github.com/DirectXMan12) (Red Hat / OpenStack)
* [Pierre Ossman](https://github.com/CendioOssman) (Cendio)
* Previous core contributors:
* [Joel Martin](https://github.com/kanaka) (Project founder)
* [Solly Ross](https://github.com/DirectXMan12) (Red Hat / OpenStack)
* Notable contributions:
* UI and Icons : Pierre Ossman, Chris Gordon
* Original Logo : Michael Sersen
* tight encoding : Michael Tinglof (Mercuri.ca)
* RealVNC RSA AES authentication : USTC Vlab Team
* Included libraries:
* base64 : Martijn Pieters (Digital Creations 2), Samuel Sieb (sieb.net)
+68 -55
View File
@@ -6,61 +6,74 @@
* See README.md for usage and integration instructions.
*/
// NB: this should *not* be included as a module until we have
// native support in the browsers, so that our error handler
// can catch script-loading errors.
// Fallback for all uncought errors
function handleError(event, err) {
try {
const msg = document.getElementById('noVNC_fallback_errormsg');
// No ES6 can be used in this file since it's used for the translation
/* eslint-disable prefer-arrow-callback */
(function _scope() {
"use strict";
// Fallback for all uncought errors
function handleError(event, err) {
try {
const msg = document.getElementById('noVNC_fallback_errormsg');
// Only show the initial error
if (msg.hasChildNodes()) {
return false;
}
let div = document.createElement("div");
div.classList.add('noVNC_message');
div.appendChild(document.createTextNode(event.message));
msg.appendChild(div);
if (event.filename) {
div = document.createElement("div");
div.className = 'noVNC_location';
let text = event.filename;
if (event.lineno !== undefined) {
text += ":" + event.lineno;
if (event.colno !== undefined) {
text += ":" + event.colno;
}
}
div.appendChild(document.createTextNode(text));
msg.appendChild(div);
}
if (err && err.stack) {
div = document.createElement("div");
div.className = 'noVNC_stack';
div.appendChild(document.createTextNode(err.stack));
msg.appendChild(div);
}
document.getElementById('noVNC_fallback_error')
.classList.add("noVNC_open");
} catch (exc) {
document.write("noVNC encountered an error.");
// Work around Firefox bug:
// https://bugzilla.mozilla.org/show_bug.cgi?id=1685038
if (event.message === "ResizeObserver loop completed with undelivered notifications.") {
return false;
}
// Don't return true since this would prevent the error
// from being printed to the browser console.
return false;
// Only show the initial error
if (msg.hasChildNodes()) {
return false;
}
let div = document.createElement("div");
div.classList.add('noVNC_message');
div.appendChild(document.createTextNode(event.message));
msg.appendChild(div);
if (event.filename) {
div = document.createElement("div");
div.className = 'noVNC_location';
let text = event.filename;
if (event.lineno !== undefined) {
text += ":" + event.lineno;
if (event.colno !== undefined) {
text += ":" + event.colno;
}
}
div.appendChild(document.createTextNode(text));
msg.appendChild(div);
}
if (err && err.stack) {
div = document.createElement("div");
div.className = 'noVNC_stack';
div.appendChild(document.createTextNode(err.stack));
msg.appendChild(div);
}
document.getElementById('noVNC_fallback_error')
.classList.add("noVNC_open");
} catch (exc) {
document.write("noVNC encountered an error.");
}
window.addEventListener('error', function onerror(evt) { handleError(evt, evt.error); });
window.addEventListener('unhandledrejection', function onreject(evt) { handleError(evt.reason, evt.reason); });
})();
// Try to disable keyboard interaction, best effort
try {
// Remove focus from the currently focused element in order to
// prevent keyboard interaction from continuing
if (document.activeElement) { document.activeElement.blur(); }
// Don't let any element be focusable when showing the error
let keyboardFocusable = 'a[href], button, input, textarea, select, details, [tabindex]';
document.querySelectorAll(keyboardFocusable).forEach((elem) => {
elem.setAttribute("tabindex", "-1");
});
} catch (exc) {
// Do nothing
}
// Don't return true since this would prevent the error
// from being printed to the browser console.
return false;
}
window.addEventListener('error', evt => handleError(evt, evt.error));
window.addEventListener('unhandledrejection', evt => handleError(evt.reason, evt.reason));
+32 -32
View File
@@ -1,42 +1,42 @@
ICONS := \
novnc-16x16.png \
novnc-24x24.png \
novnc-32x32.png \
novnc-48x48.png \
novnc-64x64.png
BROWSER_SIZES := 16 24 32 48 64
#ANDROID_SIZES := 72 96 144 192
# FIXME: The ICO is limited to 8 icons due to a Chrome bug:
# https://bugs.chromium.org/p/chromium/issues/detail?id=1381393
ANDROID_SIZES := 96 144 192
WEB_ICON_SIZES := $(BROWSER_SIZES) $(ANDROID_SIZES)
ANDROID_LAUNCHER := \
novnc-48x48.png \
novnc-72x72.png \
novnc-96x96.png \
novnc-144x144.png \
novnc-192x192.png
#IOS_1X_SIZES := 20 29 40 76 # No such devices exist anymore
IOS_2X_SIZES := 40 58 80 120 152 167
IOS_3X_SIZES := 60 87 120 180
ALL_IOS_SIZES := $(IOS_1X_SIZES) $(IOS_2X_SIZES) $(IOS_3X_SIZES)
IPHONE_LAUNCHER := \
novnc-60x60.png \
novnc-120x120.png
IPAD_LAUNCHER := \
novnc-76x76.png \
novnc-152x152.png
ALL_ICONS := $(ICONS) $(ANDROID_LAUNCHER) $(IPHONE_LAUNCHER) $(IPAD_LAUNCHER)
ALL_ICONS := \
$(ALL_IOS_SIZES:%=novnc-ios-%.png) \
novnc.ico
all: $(ALL_ICONS)
novnc-16x16.png: novnc-icon-sm.svg
convert -density 90 \
-background transparent "$<" "$@"
novnc-24x24.png: novnc-icon-sm.svg
convert -density 135 \
-background transparent "$<" "$@"
novnc-32x32.png: novnc-icon-sm.svg
convert -density 180 \
-background transparent "$<" "$@"
# Our testing shows that the ICO file need to be sorted in largest to
# smallest to get the apporpriate behviour
WEB_ICON_SIZES_REVERSE := $(shell echo $(WEB_ICON_SIZES) | tr ' ' '\n' | sort -nr | tr '\n' ' ')
WEB_BASE_ICONS := $(WEB_ICON_SIZES_REVERSE:%=novnc-%.png)
.INTERMEDIATE: $(WEB_BASE_ICONS)
novnc.ico: $(WEB_BASE_ICONS)
convert $(WEB_BASE_ICONS) "$@"
# General conversion
novnc-%.png: novnc-icon.svg
convert -density $$[`echo $* | cut -d x -f 1` * 90 / 48] \
-background transparent "$<" "$@"
convert -depth 8 -background transparent \
-size $*x$* "$(lastword $^)" "$@"
# iOS icons use their own SVG
novnc-ios-%.png: novnc-ios-icon.svg
convert -depth 8 -background transparent \
-size $*x$* "$(lastword $^)" "$@"
# The smallest sizes are generated using a different SVG
novnc-16.png novnc-24.png novnc-32.png: novnc-icon-sm.svg
clean:
rm -f *.png
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 675 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1000 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

+183
View File
@@ -0,0 +1,183 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="48"
height="48"
viewBox="0 0 48 48.000001"
id="svg2"
version="1.1"
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
sodipodi:docname="novnc-ios-icon.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs
id="defs4" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="11.313708"
inkscape:cx="27.356195"
inkscape:cy="17.810253"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
units="px"
inkscape:object-nodes="true"
inkscape:snap-smooth-nodes="true"
inkscape:snap-midpoints="true"
inkscape:window-width="2560"
inkscape:window-height="1371"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:showpageshadow="2"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1">
<inkscape:grid
type="xygrid"
id="grid4169" />
</sodipodi:namedview>
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-1004.3621)">
<rect
style="opacity:1;fill:#494949;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="rect4167"
width="48"
height="48"
x="0"
y="1004.3621"
inkscape:label="background" />
<path
style="opacity:1;fill:#313131;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
d="m 0,1004.3621 v 48 h 20 c 15.512,0 28,-16.948 28,-38 v -10 z"
id="rect4173"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccccc"
inkscape:label="darker_grey_plate" />
<g
id="g4300"
style="display:inline;fill:#000000;fill-opacity:1;stroke:none"
transform="translate(0.5,0.5)"
inkscape:label="shadows">
<g
id="g4302"
style="fill:#000000;fill-opacity:1;stroke:none"
inkscape:label="no">
<path
sodipodi:nodetypes="scsccsssscccs"
d="m 11.986926,1016.3621 c 0.554325,0 1.025987,0.2121 1.414987,0.6362 0.398725,0.4138 0.600909,0.9155 0.598087,1.5052 v 6.8586 h -2 v -6.8914 c 0,-0.072 -0.03404,-0.1086 -0.102113,-0.1086 H 7.1021125 C 7.0340375,1018.3621 7,1018.3983 7,1018.4707 v 6.8914 H 5 v -9 z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="path4304"
inkscape:connector-curvature="0"
inkscape:label="n" />
<path
sodipodi:nodetypes="sscsscsscsscssssssssss"
d="m 17.013073,1016.3621 h 4.973854 c 0.554325,0 1.025987,0.2121 1.414986,0.6362 0.398725,0.4138 0.598087,0.9155 0.598087,1.5052 v 4.7172 c 0,0.5897 -0.199362,1.0966 -0.598087,1.5207 -0.388999,0.4138 -0.860661,0.6207 -1.414986,0.6207 h -4.973854 c -0.554325,0 -1.030849,-0.2069 -1.429574,-0.6207 C 15.1945,1024.3173 15,1023.8104 15,1023.2207 v -4.7172 c 0,-0.5897 0.1945,-1.0914 0.583499,-1.5052 0.398725,-0.4241 0.875249,-0.6362 1.429574,-0.6362 z m 4.884815,2 h -4.795776 c -0.06808,0 -0.102112,0.036 -0.102112,0.1086 v 4.7828 c 0,0.072 0.03404,0.1086 0.102112,0.1086 h 4.795776 c 0.06807,0 0.102112,-0.036 0.102112,-0.1086 v -4.7828 c 0,-0.072 -0.03404,-0.1086 -0.102112,-0.1086 z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="path4306"
inkscape:connector-curvature="0"
inkscape:label="o" />
</g>
<g
id="g4308"
style="fill:#000000;fill-opacity:1;stroke:none"
inkscape:label="VNC">
<path
sodipodi:nodetypes="cccccccc"
d="m 12,1036.9177 4.768114,-8.5556 H 19 l -6,11 h -2 l -6,-11 h 2.2318854 z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="path4310"
inkscape:connector-curvature="0"
inkscape:label="V" />
<path
sodipodi:nodetypes="ccccccccccc"
d="m 29,1036.3621 v -8 h 2 v 11 h -2 l -7,-8 v 8 h -2 v -11 h 2 z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="path4312"
inkscape:connector-curvature="0"
inkscape:label="N" />
<path
sodipodi:nodetypes="cssssccscsscscc"
d="m 43,1030.3621 h -8.897887 c -0.06808,0 -0.102113,0.036 -0.102113,0.1069 v 6.7862 c 0,0.071 0.03404,0.1069 0.102113,0.1069 H 43 v 2 h -8.972339 c -0.56405,0 -1.045437,-0.2037 -1.444162,-0.6111 C 32.1945,1038.3334 32,1037.8292 32,1037.2385 v -6.7528 c 0,-0.5907 0.1945,-1.0898 0.583499,-1.4972 0.398725,-0.4176 0.880112,-0.6264 1.444162,-0.6264 H 43 Z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="path4314"
inkscape:connector-curvature="0"
inkscape:label="C" />
</g>
</g>
<g
id="g4291"
style="stroke:none"
inkscape:label="noVNC">
<g
id="g4282"
style="stroke:none"
inkscape:label="no">
<path
inkscape:connector-curvature="0"
id="path4143"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#008000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 11.986926,1016.3621 c 0.554325,0 1.025987,0.2121 1.414987,0.6362 0.398725,0.4138 0.600909,0.9155 0.598087,1.5052 l 0,6.8586 -2,0 0,-6.8914 c 0,-0.072 -0.03404,-0.1086 -0.102113,-0.1086 l -4.7957745,0 C 7.0340375,1018.3621 7,1018.3983 7,1018.4707 l 0,6.8914 -2,0 0,-9 z"
sodipodi:nodetypes="scsccsssscccs"
inkscape:label="n" />
<path
inkscape:connector-curvature="0"
id="path4145"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#008000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 17.013073,1016.3621 4.973854,0 c 0.554325,0 1.025987,0.2121 1.414986,0.6362 0.398725,0.4138 0.598087,0.9155 0.598087,1.5052 l 0,4.7172 c 0,0.5897 -0.199362,1.0966 -0.598087,1.5207 -0.388999,0.4138 -0.860661,0.6207 -1.414986,0.6207 l -4.973854,0 c -0.554325,0 -1.030849,-0.2069 -1.429574,-0.6207 C 15.1945,1024.3173 15,1023.8104 15,1023.2207 l 0,-4.7172 c 0,-0.5897 0.1945,-1.0914 0.583499,-1.5052 0.398725,-0.4241 0.875249,-0.6362 1.429574,-0.6362 z m 4.884815,2 -4.795776,0 c -0.06808,0 -0.102112,0.036 -0.102112,0.1086 l 0,4.7828 c 0,0.072 0.03404,0.1086 0.102112,0.1086 l 4.795776,0 c 0.06807,0 0.102112,-0.036 0.102112,-0.1086 l 0,-4.7828 c 0,-0.072 -0.03404,-0.1086 -0.102112,-0.1086 z"
sodipodi:nodetypes="sscsscsscsscssssssssss"
inkscape:label="o" />
</g>
<g
id="g4286"
style="stroke:none"
inkscape:label="VNC">
<path
inkscape:connector-curvature="0"
id="path4147"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffff00;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 12,1036.9177 4.768114,-8.5556 2.231886,0 -6,11 -2,0 -6,-11 2.2318854,0 z"
sodipodi:nodetypes="cccccccc"
inkscape:label="V" />
<path
inkscape:connector-curvature="0"
id="path4149"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffff00;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 29,1036.3621 0,-8 2,0 0,11 -2,0 -7,-8 0,8 -2,0 0,-11 2,0 z"
sodipodi:nodetypes="ccccccccccc"
inkscape:label="N" />
<path
inkscape:connector-curvature="0"
id="path4151"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffff00;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 43,1030.3621 -8.897887,0 c -0.06808,0 -0.102113,0.036 -0.102113,0.1069 l 0,6.7862 c 0,0.071 0.03404,0.1069 0.102113,0.1069 l 8.897887,0 0,2 -8.972339,0 c -0.56405,0 -1.045437,-0.2037 -1.444162,-0.6111 C 32.1945,1038.3334 32,1037.8292 32,1037.2385 l 0,-6.7528 c 0,-0.5907 0.1945,-1.0898 0.583499,-1.4972 0.398725,-0.4176 0.880112,-0.6264 1.444162,-0.6264 l 8.972339,0 z"
sodipodi:nodetypes="cssssccscsscscc"
inkscape:label="C" />
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 KiB

+11 -11
View File
@@ -4,9 +4,9 @@
"Connected (unencrypted) to ": "Conectado (sin encriptación) a",
"Disconnecting...": "Desconectando...",
"Disconnected": "Desconectado",
"Must set host": "Debes configurar el host",
"Must set host": "Se debe configurar el host",
"Reconnecting...": "Reconectando...",
"Password is required": "Contraseña es obligatoria",
"Password is required": "La contraseña es obligatoria",
"Disconnect timeout": "Tiempo de desconexión agotado",
"noVNC encountered an error:": "noVNC ha encontrado un error:",
"Hide/Show the control bar": "Ocultar/Mostrar la barra de control",
@@ -41,6 +41,7 @@
"Clear": "Vaciar",
"Fullscreen": "Pantalla Completa",
"Settings": "Configuraciones",
"Encrypt": "Encriptar",
"Shared Mode": "Modo Compartido",
"View Only": "Solo visualización",
"Clip to Window": "Recortar al tamaño de la ventana",
@@ -51,18 +52,17 @@
"Remote Resizing": "Cambio de tamaño remoto",
"Advanced": "Avanzado",
"Local Cursor": "Cursor Local",
"Repeater ID:": "ID del Repetidor",
"Repeater ID:": "ID del Repetidor:",
"WebSocket": "WebSocket",
"Encrypt": "",
"Host:": "Host",
"Port:": "Puesto",
"Path:": "Ruta",
"Host:": "Host:",
"Port:": "Puerto:",
"Path:": "Ruta:",
"Automatic Reconnect": "Reconexión automática",
"Reconnect Delay (ms):": "Retraso en la reconexión (ms)",
"Logging:": "Logging",
"Reconnect Delay (ms):": "Retraso en la reconexión (ms):",
"Logging:": "Registrando:",
"Disconnect": "Desconectar",
"Connect": "Conectar",
"Password:": "Contraseña",
"Password:": "Contraseña:",
"Cancel": "Cancelar",
"Canvas not supported.": "Canvas no está soportado"
"Canvas not supported.": "Canvas no soportado."
}
+78
View File
@@ -0,0 +1,78 @@
{
"HTTPS is required for full functionality": "",
"Connecting...": "En cours de connexion...",
"Disconnecting...": "Déconnexion en cours...",
"Reconnecting...": "Reconnexion en cours...",
"Internal error": "Erreur interne",
"Must set host": "Doit définir l'hôte",
"Connected (encrypted) to ": "Connecté (chiffré) à ",
"Connected (unencrypted) to ": "Connecté (non chiffré) à ",
"Something went wrong, connection is closed": "Quelque chose s'est mal passé, la connexion a été fermée",
"Failed to connect to server": "Échec de connexion au serveur",
"Disconnected": "Déconnecté",
"New connection has been rejected with reason: ": "Une nouvelle connexion a été rejetée avec motif : ",
"New connection has been rejected": "Une nouvelle connexion a été rejetée",
"Credentials are required": "Les identifiants sont requis",
"noVNC encountered an error:": "noVNC a rencontré une erreur :",
"Hide/Show the control bar": "Masquer/Afficher la barre de contrôle",
"Drag": "Faire glisser",
"Move/Drag Viewport": "Déplacer/faire glisser le Viewport",
"Keyboard": "Clavier",
"Show Keyboard": "Afficher le clavier",
"Extra keys": "Touches supplémentaires",
"Show Extra Keys": "Afficher les touches supplémentaires",
"Ctrl": "Ctrl",
"Toggle Ctrl": "Basculer Ctrl",
"Alt": "Alt",
"Toggle Alt": "Basculer Alt",
"Toggle Windows": "Basculer Windows",
"Windows": "Windows",
"Send Tab": "Envoyer l'onglet",
"Tab": "l'onglet",
"Esc": "Esc",
"Send Escape": "Envoyer Escape",
"Ctrl+Alt+Del": "Ctrl+Alt+Del",
"Send Ctrl-Alt-Del": "Envoyer Ctrl-Alt-Del",
"Shutdown/Reboot": "Arrêter/Redémarrer",
"Shutdown/Reboot...": "Arrêter/Redémarrer...",
"Power": "Alimentation",
"Shutdown": "Arrêter",
"Reboot": "Redémarrer",
"Reset": "Réinitialiser",
"Clipboard": "Presse-papiers",
"Edit clipboard content in the textarea below.": "",
"Settings": "Paramètres",
"Shared Mode": "Mode partagé",
"View Only": "Afficher uniquement",
"Clip to Window": "Clip à fenêtre",
"Scaling Mode:": "Mode mise à l'échelle :",
"None": "Aucun",
"Local Scaling": "Mise à l'échelle locale",
"Remote Resizing": "Redimensionnement à distance",
"Advanced": "Avancé",
"Quality:": "Qualité :",
"Compression level:": "Niveau de compression :",
"Repeater ID:": "ID Répéteur :",
"WebSocket": "WebSocket",
"Encrypt": "Chiffrer",
"Host:": "Hôte :",
"Port:": "Port :",
"Path:": "Chemin :",
"Automatic Reconnect": "Reconnecter automatiquemen",
"Reconnect Delay (ms):": "Délai de reconnexion (ms) :",
"Show Dot when No Cursor": "Afficher le point lorsqu'il n'y a pas de curseur",
"Logging:": "Se connecter :",
"Version:": "Version :",
"Disconnect": "Déconnecter",
"Connect": "Connecter",
"Server identity": "",
"The server has provided the following identifying information:": "",
"Fingerprint:": "",
"Please verify that the information is correct and press \"Approve\". Otherwise press \"Reject\".": "",
"Approve": "",
"Reject": "",
"Username:": "Nom d'utilisateur :",
"Password:": "Mot de passe :",
"Send Credentials": "Envoyer les identifiants",
"Cancel": "Annuler"
}
+72
View File
@@ -0,0 +1,72 @@
{
"Connecting...": "Connessione in corso...",
"Disconnecting...": "Disconnessione...",
"Reconnecting...": "Riconnessione...",
"Internal error": "Errore interno",
"Must set host": "Devi impostare l'host",
"Connected (encrypted) to ": "Connesso (crittografato) a ",
"Connected (unencrypted) to ": "Connesso (non crittografato) a",
"Something went wrong, connection is closed": "Qualcosa è andato storto, la connessione è stata chiusa",
"Failed to connect to server": "Impossibile connettersi al server",
"Disconnected": "Disconnesso",
"New connection has been rejected with reason: ": "La nuova connessione è stata rifiutata con motivo: ",
"New connection has been rejected": "La nuova connessione è stata rifiutata",
"Credentials are required": "Le credenziali sono obbligatorie",
"noVNC encountered an error:": "noVNC ha riscontrato un errore:",
"Hide/Show the control bar": "Nascondi/Mostra la barra di controllo",
"Drag": "",
"Move/Drag Viewport": "",
"Keyboard": "Tastiera",
"Show Keyboard": "Mostra tastiera",
"Extra keys": "Tasti Aggiuntivi",
"Show Extra Keys": "Mostra Tasti Aggiuntivi",
"Ctrl": "Ctrl",
"Toggle Ctrl": "Tieni premuto Ctrl",
"Alt": "Alt",
"Toggle Alt": "Tieni premuto Alt",
"Toggle Windows": "Tieni premuto Windows",
"Windows": "Windows",
"Send Tab": "Invia Tab",
"Tab": "Tab",
"Esc": "Esc",
"Send Escape": "Invia Esc",
"Ctrl+Alt+Del": "Ctrl+Alt+Canc",
"Send Ctrl-Alt-Del": "Invia Ctrl-Alt-Canc",
"Shutdown/Reboot": "Spegnimento/Riavvio",
"Shutdown/Reboot...": "Spegnimento/Riavvio...",
"Power": "Alimentazione",
"Shutdown": "Spegnimento",
"Reboot": "Riavvio",
"Reset": "Reset",
"Clipboard": "Clipboard",
"Clear": "Pulisci",
"Fullscreen": "Schermo intero",
"Settings": "Impostazioni",
"Shared Mode": "Modalità condivisa",
"View Only": "Sola Visualizzazione",
"Clip to Window": "",
"Scaling Mode:": "Modalità di ridimensionamento:",
"None": "Nessuna",
"Local Scaling": "Ridimensionamento Locale",
"Remote Resizing": "Ridimensionamento Remoto",
"Advanced": "Avanzate",
"Quality:": "Qualità:",
"Compression level:": "Livello Compressione:",
"Repeater ID:": "ID Ripetitore:",
"WebSocket": "WebSocket",
"Encrypt": "Crittografa",
"Host:": "Host:",
"Port:": "Porta:",
"Path:": "Percorso:",
"Automatic Reconnect": "Riconnessione Automatica",
"Reconnect Delay (ms):": "Ritardo Riconnessione (ms):",
"Show Dot when No Cursor": "Mostra Punto quando Nessun Cursore",
"Logging:": "",
"Version:": "Versione:",
"Disconnect": "Disconnetti",
"Connect": "Connetti",
"Username:": "Utente:",
"Password:": "Password:",
"Send Credentials": "Invia Credenziale",
"Cancel": "Annulla"
}
+15 -16
View File
@@ -9,26 +9,21 @@
"Something went wrong, connection is closed": "Что-то пошло не так, подключение разорвано",
"Failed to connect to server": "Ошибка подключения к серверу",
"Disconnected": "Отключено",
"New connection has been rejected with reason: ": "Подключиться не удалось: ",
"New connection has been rejected": "Подключиться не удалось",
"Password is required": "Требуется пароль",
"New connection has been rejected with reason: ": "Новое соединение отклонено по причине: ",
"New connection has been rejected": "Новое соединение отклонено",
"Credentials are required": "Требуются учетные данные",
"noVNC encountered an error:": "Ошибка noVNC: ",
"Hide/Show the control bar": "Скрыть/Показать контрольную панель",
"Drag": "Переместить",
"Move/Drag Viewport": "Переместить окно",
"viewport drag": "Переместить окно",
"Active Mouse Button": "Активировать кнопки мыши",
"No mousebutton": "Отключить кнопки мыши",
"Left mousebutton": "Левая кнопка мыши",
"Middle mousebutton": "Средняя кнопка мыши",
"Right mousebutton": "Правая кнопка мыши",
"Keyboard": "Клавиатура",
"Show Keyboard": "Показать клавиатуру",
"Extra keys": "Доп. кнопки",
"Show Extra Keys": "Показать дополнительные кнопки",
"Extra keys": "Дополнительные Кнопки",
"Show Extra Keys": "Показать Дополнительные Кнопки",
"Ctrl": "Ctrl",
"Toggle Ctrl": "Передать нажатие Ctrl",
"Toggle Ctrl": "Переключение нажатия Ctrl",
"Alt": "Alt",
"Toggle Alt": "Передать нажатие Alt",
"Toggle Alt": "Переключение нажатия Alt",
"Toggle Windows": "Переключение вкладок",
"Windows": "Вкладка",
"Send Tab": "Передать нажатие Tab",
@@ -48,13 +43,15 @@
"Fullscreen": "Во весь экран",
"Settings": "Настройки",
"Shared Mode": "Общий режим",
"View Only": "Просмотр",
"View Only": "Только Просмотр",
"Clip to Window": "В окно",
"Scaling Mode:": "Масштаб:",
"None": "Нет",
"Local Scaling": "Локльный масштаб",
"Remote Resizing": "Удаленный масштаб",
"Remote Resizing": "Удаленная перенастройка размера",
"Advanced": "Дополнительно",
"Quality:": "Качество",
"Compression level:": "Уровень Сжатия",
"Repeater ID:": "Идентификатор ID:",
"WebSocket": "WebSocket",
"Encrypt": "Шифрование",
@@ -65,9 +62,11 @@
"Reconnect Delay (ms):": "Задержка переподключения (мс):",
"Show Dot when No Cursor": "Показать точку вместо курсора",
"Logging:": "Лог:",
"Version:": "Версия",
"Disconnect": "Отключение",
"Connect": "Подключение",
"Username:": "Имя Пользователя",
"Password:": "Пароль:",
"Send Password": "Пароль: ",
"Send Credentials": "Передача Учетных Данных",
"Cancel": "Выход"
}
+10 -2
View File
@@ -1,4 +1,5 @@
{
"HTTPS is required for full functionality": "HTTPS krävs för full funktionalitet",
"Connecting...": "Ansluter...",
"Disconnecting...": "Kopplar ner...",
"Reconnecting...": "Återansluter...",
@@ -39,8 +40,8 @@
"Reboot": "Boota om",
"Reset": "Återställ",
"Clipboard": "Urklipp",
"Clear": "Rensa",
"Fullscreen": "Fullskärm",
"Edit clipboard content in the textarea below.": "Redigera urklippets innehåll i fältet nedan.",
"Full Screen": "Fullskärm",
"Settings": "Inställningar",
"Shared Mode": "Delat Läge",
"View Only": "Endast Visning",
@@ -65,6 +66,13 @@
"Version:": "Version:",
"Disconnect": "Koppla från",
"Connect": "Anslut",
"Server identity": "Server-identitet",
"The server has provided the following identifying information:": "Servern har gett följande identifierande information:",
"Fingerprint:": "Fingeravtryck:",
"Please verify that the information is correct and press \"Approve\". Otherwise press \"Reject\".": "Kontrollera att informationen är korrekt och tryck sedan \"Godkänn\". Tryck annars \"Neka\".",
"Approve": "Godkänn",
"Reject": "Neka",
"Credentials": "Användaruppgifter",
"Username:": "Användarnamn:",
"Password:": "Lösenord:",
"Send Credentials": "Skicka Användaruppgifter",
+47 -13
View File
@@ -16,13 +16,19 @@ export class Localizer {
this.language = 'en';
// Current dictionary of translations
this.dictionary = undefined;
this._dictionary = undefined;
}
// Configure suitable language based on user preferences
setup(supportedLanguages) {
async setup(supportedLanguages, baseURL) {
this.language = 'en'; // Default: US English
this._dictionary = undefined;
this._setupLanguage(supportedLanguages);
await this._setupDictionary(baseURL);
}
_setupLanguage(supportedLanguages) {
/*
* Navigator.languages only available in Chrome (32+) and FireFox (32+)
* Fall back to navigator.language for other browsers
@@ -40,12 +46,6 @@ export class Localizer {
.replace("_", "-")
.split("-");
// Built-in default?
if ((userLang[0] === 'en') &&
((userLang[1] === undefined) || (userLang[1] === 'us'))) {
return;
}
// First pass: perfect match
for (let j = 0; j < supportedLanguages.length; j++) {
const supLang = supportedLanguages[j]
@@ -64,7 +64,12 @@ export class Localizer {
return;
}
// Second pass: fallback
// Second pass: English fallback
if (userLang[0] === 'en') {
return;
}
// Third pass pass: other fallback
for (let j = 0;j < supportedLanguages.length;j++) {
const supLang = supportedLanguages[j]
.toLowerCase()
@@ -84,10 +89,32 @@ export class Localizer {
}
}
async _setupDictionary(baseURL) {
if (baseURL) {
if (!baseURL.endsWith("/")) {
baseURL = baseURL + "/";
}
} else {
baseURL = "";
}
if (this.language === "en") {
return;
}
let response = await fetch(baseURL + this.language + ".json");
if (!response.ok) {
throw Error("" + response.status + " " + response.statusText);
}
this._dictionary = await response.json();
}
// Retrieve localised text
get(id) {
if (typeof this.dictionary !== 'undefined' && this.dictionary[id]) {
return this.dictionary[id];
if (typeof this._dictionary !== 'undefined' &&
this._dictionary[id]) {
return this._dictionary[id];
} else {
return id;
}
@@ -103,13 +130,20 @@ export class Localizer {
return items.indexOf(searchElement) !== -1;
}
function translateString(str) {
// We assume surrounding whitespace, and whitespace around line
// breaks is just for source formatting
str = str.split("\n").map(s => s.trim()).join(" ").trim();
return self.get(str);
}
function translateAttribute(elem, attr) {
const str = self.get(elem.getAttribute(attr));
const str = translateString(elem.getAttribute(attr));
elem.setAttribute(attr, str);
}
function translateTextNode(node) {
const str = self.get(node.data.trim());
const str = translateString(node.data);
node.data = str;
}
+168 -216
View File
@@ -19,10 +19,23 @@
* 10000: Max (used for polyfills)
*/
/*
* State variables (set on :root):
*
* noVNC_loading: Page is still loading
* noVNC_connecting: Connecting to server
* noVNC_reconnecting: Re-establishing a connection
* noVNC_connected: Connected to server (most common state)
* noVNC_disconnecting: Disconnecting from server
*/
:root {
font-family: sans-serif;
}
body {
margin:0;
padding:0;
font-family: Helvetica;
/*Background image with light grey curve.*/
background-color:#494949;
background-repeat:no-repeat;
@@ -78,144 +91,6 @@ html {
50% { box-shadow: 60px 10px 0 rgba(255, 255, 255, 0); width: 10px; }
}
/* ----------------------------------------
* Input Elements
* ----------------------------------------
*/
input:not([type]),
input[type=date],
input[type=datetime-local],
input[type=email],
input[type=month],
input[type=number],
input[type=password],
input[type=search],
input[type=tel],
input[type=text],
input[type=time],
input[type=url],
input[type=week],
textarea {
/* Disable default rendering */
-webkit-appearance: none;
-moz-appearance: none;
background: none;
margin: 2px;
padding: 2px;
border: 1px solid rgb(192, 192, 192);
border-radius: 5px;
color: black;
background: linear-gradient(to top, rgb(255, 255, 255) 80%, rgb(240, 240, 240));
}
input[type=button],
input[type=color],
input[type=reset],
input[type=submit],
select {
/* Disable default rendering */
-webkit-appearance: none;
-moz-appearance: none;
background: none;
margin: 2px;
padding: 2px;
border: 1px solid rgb(192, 192, 192);
border-bottom-width: 2px;
border-radius: 5px;
color: black;
background: linear-gradient(to top, rgb(255, 255, 255), rgb(240, 240, 240));
/* This avoids it jumping around when :active */
vertical-align: middle;
}
input[type=button],
input[type=color],
input[type=reset],
input[type=submit] {
padding-left: 20px;
padding-right: 20px;
}
option {
color: black;
background: white;
}
input:not([type]):focus,
input[type=button]:focus,
input[type=color]:focus,
input[type=date]:focus,
input[type=datetime-local]:focus,
input[type=email]:focus,
input[type=month]:focus,
input[type=number]:focus,
input[type=password]:focus,
input[type=reset]:focus,
input[type=search]:focus,
input[type=submit]:focus,
input[type=tel]:focus,
input[type=text]:focus,
input[type=time]:focus,
input[type=url]:focus,
input[type=week]:focus,
select:focus,
textarea:focus {
box-shadow: 0px 0px 3px rgba(74, 144, 217, 0.5);
border-color: rgb(74, 144, 217);
outline: none;
}
input[type=button]::-moz-focus-inner,
input[type=color]::-moz-focus-inner,
input[type=reset]::-moz-focus-inner,
input[type=submit]::-moz-focus-inner {
border: none;
}
input:not([type]):disabled,
input[type=button]:disabled,
input[type=color]:disabled,
input[type=date]:disabled,
input[type=datetime-local]:disabled,
input[type=email]:disabled,
input[type=month]:disabled,
input[type=number]:disabled,
input[type=password]:disabled,
input[type=reset]:disabled,
input[type=search]:disabled,
input[type=submit]:disabled,
input[type=tel]:disabled,
input[type=text]:disabled,
input[type=time]:disabled,
input[type=url]:disabled,
input[type=week]:disabled,
select:disabled,
textarea:disabled {
color: rgb(128, 128, 128);
background: rgb(240, 240, 240);
}
input[type=button]:active,
input[type=color]:active,
input[type=reset]:active,
input[type=submit]:active,
select:active {
border-bottom-width: 1px;
margin-top: 3px;
}
:root:not(.noVNC_touch) input[type=button]:hover:not(:disabled),
:root:not(.noVNC_touch) input[type=color]:hover:not(:disabled),
:root:not(.noVNC_touch) input[type=reset]:hover:not(:disabled),
:root:not(.noVNC_touch) input[type=submit]:hover:not(:disabled),
:root:not(.noVNC_touch) select:hover:not(:disabled) {
background: linear-gradient(to top, rgb(255, 255, 255), rgb(250, 250, 250));
}
/* ----------------------------------------
* WebKit centering hacks
* ----------------------------------------
@@ -242,13 +117,15 @@ select:active {
pointer-events: auto;
}
.noVNC_vcenter {
display: flex;
display: flex !important;
flex-direction: column;
justify-content: center;
position: fixed;
top: 0;
left: 0;
height: 100%;
margin: 0 !important;
padding: 0 !important;
pointer-events: none;
}
.noVNC_vcenter > * {
@@ -272,13 +149,20 @@ select:active {
#noVNC_fallback_error {
z-index: 1000;
visibility: hidden;
/* Put a dark background in front of everything but the error,
and don't let mouse events pass through */
background: rgba(0, 0, 0, 0.8);
pointer-events: all;
}
#noVNC_fallback_error.noVNC_open {
visibility: visible;
}
#noVNC_fallback_error > div {
max-width: 90%;
max-width: calc(100vw - 30px - 30px);
max-height: calc(100vh - 30px - 30px);
overflow: auto;
padding: 15px;
transition: 0.5s ease-in-out;
@@ -317,7 +201,6 @@ select:active {
}
#noVNC_fallback_error .noVNC_stack {
max-height: 50vh;
padding: 10px;
margin: 10px;
font-size: 0.8em;
@@ -361,6 +244,9 @@ select:active {
background-color: rgb(110, 132, 163);
border-radius: 0 10px 10px 0;
user-select: none;
-webkit-user-select: none;
-webkit-touch-callout: none; /* Disable iOS image long-press popup */
}
#noVNC_control_bar.noVNC_open {
box-shadow: 6px 6px 0px rgba(0, 0, 0, 0.5);
@@ -433,38 +319,50 @@ select:active {
.noVNC_right #noVNC_control_bar.noVNC_open #noVNC_control_bar_handle:after {
transform: none;
}
/* Larger touch area for the handle, used when a touch screen is available */
#noVNC_control_bar_handle div {
position: absolute;
right: -35px;
top: 0;
width: 50px;
height: 50px;
}
:root:not(.noVNC_touch) #noVNC_control_bar_handle div {
height: 100%;
display: none;
}
@media (any-pointer: coarse) {
#noVNC_control_bar_handle div {
display: initial;
}
}
.noVNC_right #noVNC_control_bar_handle div {
left: -35px;
right: auto;
}
#noVNC_control_bar .noVNC_scroll {
#noVNC_control_bar > .noVNC_scroll {
max-height: 100vh; /* Chrome is buggy with 100% */
overflow-x: hidden;
overflow-y: auto;
padding: 0 10px 0 5px;
padding: 0 10px;
}
.noVNC_right #noVNC_control_bar .noVNC_scroll {
padding: 0 5px 0 10px;
#noVNC_control_bar > .noVNC_scroll > * {
display: block;
margin: 10px auto;
}
/* Control bar hint */
#noVNC_control_bar_hint {
#noVNC_hint_anchor {
position: fixed;
left: calc(100vw - 50px);
right: -50px;
left: auto;
}
#noVNC_control_bar_anchor.noVNC_right + #noVNC_hint_anchor {
left: -50px;
right: auto;
top: 50%;
transform: translateY(-50%) scale(0);
}
#noVNC_control_bar_hint {
position: relative;
transform: scale(0);
width: 100px;
height: 50%;
max-height: 600px;
@@ -477,61 +375,65 @@ select:active {
border-radius: 10px;
transition-delay: 0s;
}
#noVNC_control_bar_anchor.noVNC_right #noVNC_control_bar_hint{
left: auto;
right: calc(100vw - 50px);
}
#noVNC_control_bar_hint.noVNC_active {
visibility: visible;
opacity: 1;
transition-delay: 0.2s;
transform: translateY(-50%) scale(1);
transform: scale(1);
}
#noVNC_control_bar_hint.noVNC_notransition {
transition: none !important;
}
/* General button style */
.noVNC_button {
display: block;
/* Control bar buttons */
#noVNC_control_bar .noVNC_button {
padding: 4px 4px;
margin: 10px 0;
vertical-align: middle;
border:1px solid rgba(255, 255, 255, 0.2);
border-radius: 6px;
background-color: transparent;
background-image: unset; /* we don't want the gradiant from input.css */
}
.noVNC_button.noVNC_selected {
#noVNC_control_bar .noVNC_button.noVNC_selected {
border-color: rgba(0, 0, 0, 0.8);
background: rgba(0, 0, 0, 0.5);
background-color: rgba(0, 0, 0, 0.5);
}
.noVNC_button:disabled {
opacity: 0.4;
#noVNC_control_bar .noVNC_button.noVNC_selected:not(:disabled):hover {
border-color: rgba(0, 0, 0, 0.4);
background-color: rgba(0, 0, 0, 0.2);
}
.noVNC_button:focus {
outline: none;
#noVNC_control_bar .noVNC_button:not(:disabled):hover {
background-color: rgba(255, 255, 255, 0.2);
}
.noVNC_button:active {
#noVNC_control_bar .noVNC_button:not(:disabled):active {
padding-top: 5px;
padding-bottom: 3px;
}
/* Android browsers don't properly update hover state if touch events
* are intercepted, but focus should be safe to display */
:root:not(.noVNC_touch) .noVNC_button.noVNC_selected:hover,
.noVNC_button.noVNC_selected:focus {
border-color: rgba(0, 0, 0, 0.4);
background: rgba(0, 0, 0, 0.2);
#noVNC_control_bar .noVNC_button.noVNC_hidden {
display: none !important;
}
:root:not(.noVNC_touch) .noVNC_button:hover,
.noVNC_button:focus {
background: rgba(255, 255, 255, 0.2);
}
.noVNC_button.noVNC_hidden {
display: none;
/* Android browsers don't properly update hover state if touch events are
* intercepted, like they are when clicking on the remote screen. */
@media (any-pointer: coarse) {
#noVNC_control_bar .noVNC_button:not(:disabled):hover {
background-color: transparent;
}
#noVNC_control_bar .noVNC_button.noVNC_selected:not(:disabled):hover {
border-color: rgba(0, 0, 0, 0.8);
background-color: rgba(0, 0, 0, 0.5);
}
}
/* Panels */
.noVNC_panel {
transform: translateX(25px);
transition: 0.5s ease-in-out;
box-sizing: border-box; /* so max-width don't have to care about padding */
max-width: calc(100vw - 75px - 25px); /* minus left and right margins */
max-height: 100vh; /* Chrome is buggy with 100% */
overflow-x: hidden;
overflow-y: auto;
@@ -563,6 +465,17 @@ select:active {
transform: translateX(-75px);
}
.noVNC_panel > * {
display: block;
margin: 10px auto;
}
.noVNC_panel > *:first-child {
margin-top: 0 !important;
}
.noVNC_panel > *:last-child {
margin-bottom: 0 !important;
}
.noVNC_panel hr {
border: none;
border-top: 1px solid rgb(192, 192, 192);
@@ -571,6 +484,11 @@ select:active {
.noVNC_panel label {
display: block;
white-space: nowrap;
margin: 5px;
}
.noVNC_panel li {
margin: 5px;
}
.noVNC_panel .noVNC_heading {
@@ -581,7 +499,6 @@ select:active {
padding-right: 8px;
color: white;
font-size: 20px;
margin-bottom: 10px;
white-space: nowrap;
}
.noVNC_panel .noVNC_heading img {
@@ -622,6 +539,12 @@ select:active {
font-size: 13px;
}
.noVNC_logo + hr {
/* Remove all but top border */
border: none;
border-top: 1px solid rgba(255, 255, 255, 0.2);
}
:root:not(.noVNC_connected) #noVNC_view_drag_button {
display: none;
}
@@ -630,8 +553,15 @@ select:active {
:root:not(.noVNC_connected) #noVNC_mobile_buttons {
display: none;
}
:root:not(.noVNC_touch) #noVNC_mobile_buttons {
display: none;
@media not all and (any-pointer: coarse) {
/* FIXME: The button for the virtual keyboard is the only button in this
group of "mobile buttons". It is bad to assume that no touch
devices have physical keyboards available. Hopefully we can get
a media query for this:
https://github.com/w3c/csswg-drafts/issues/3871 */
:root.noVNC_connected #noVNC_mobile_buttons {
display: none;
}
}
/* Extra manual keys */
@@ -642,7 +572,7 @@ select:active {
#noVNC_modifiers {
background-color: rgb(92, 92, 92);
border: none;
padding: 0 10px;
padding: 10px;
}
/* Shutdown/Reboot */
@@ -663,13 +593,16 @@ select:active {
:root:not(.noVNC_connected) #noVNC_clipboard_button {
display: none;
}
#noVNC_clipboard {
/* Full screen, minus padding and left and right margins */
max-width: calc(100vw - 2*15px - 75px - 25px);
}
#noVNC_clipboard_text {
width: 500px;
width: 360px;
min-width: 150px;
height: 160px;
min-height: 70px;
box-sizing: border-box;
max-width: 100%;
/* minus approximate height of title, height of subtitle, and margin */
max-height: calc(100vh - 10em - 25px);
}
/* Settings */
@@ -677,7 +610,6 @@ select:active {
}
#noVNC_settings ul {
list-style: none;
margin: 0px;
padding: 0px;
}
#noVNC_setting_port {
@@ -729,7 +661,7 @@ select:active {
justify-content: center;
align-content: center;
line-height: 25px;
line-height: 1.6;
word-wrap: break-word;
color: #fff;
@@ -803,36 +735,32 @@ select:active {
font-size: calc(25vw - 30px);
}
}
#noVNC_connect_button {
cursor: pointer;
#noVNC_connect_dlg div {
padding: 12px;
padding: 10px;
color: white;
background-color: rgb(110, 132, 163);
border-radius: 12px;
text-align: center;
font-size: 20px;
box-shadow: 6px 6px 0px rgba(0, 0, 0, 0.5);
}
#noVNC_connect_button div {
margin: 2px;
#noVNC_connect_button {
width: 100%;
padding: 5px 30px;
border: 1px solid rgb(83, 99, 122);
border-bottom-width: 2px;
cursor: pointer;
border-color: rgb(83, 99, 122);
border-radius: 5px;
background: linear-gradient(to top, rgb(110, 132, 163), rgb(99, 119, 147));
color: white;
/* This avoids it jumping around when :active */
vertical-align: middle;
}
#noVNC_connect_button div:active {
border-bottom-width: 1px;
margin-top: 3px;
}
:root:not(.noVNC_touch) #noVNC_connect_button div:hover {
#noVNC_connect_button:hover {
background: linear-gradient(to top, rgb(110, 132, 163), rgb(105, 125, 155));
}
@@ -841,6 +769,23 @@ select:active {
height: 1.3em;
}
/* ----------------------------------------
* Server verification Dialog
* ----------------------------------------
*/
#noVNC_verify_server_dlg {
position: relative;
transform: translateY(-50px);
}
#noVNC_verify_server_dlg.noVNC_open {
transform: translateY(0);
}
#noVNC_fingerprint_block {
margin: 10px;
}
/* ----------------------------------------
* Password Dialog
* ----------------------------------------
@@ -854,12 +799,8 @@ select:active {
#noVNC_credentials_dlg.noVNC_open {
transform: translateY(0);
}
#noVNC_credentials_dlg ul {
list-style: none;
margin: 0px;
padding: 0px;
}
.noVNC_hidden {
#noVNC_username_block.noVNC_hidden,
#noVNC_password_block.noVNC_hidden {
display: none;
}
@@ -871,7 +812,11 @@ select:active {
/* Transition screen */
#noVNC_transition {
display: none;
transition: 0.5s ease-in-out;
display: flex;
opacity: 0;
visibility: hidden;
position: fixed;
top: 0;
@@ -892,7 +837,8 @@ select:active {
:root.noVNC_connecting #noVNC_transition,
:root.noVNC_disconnecting #noVNC_transition,
:root.noVNC_reconnecting #noVNC_transition {
display: flex;
opacity: 1;
visibility: visible;
}
:root:not(.noVNC_reconnecting) #noVNC_cancel_reconnect_button {
display: none;
@@ -908,6 +854,12 @@ select:active {
background-color: #313131;
border-bottom-right-radius: 800px 600px;
/*border-top-left-radius: 800px 600px;*/
/* If selection isn't disabled, long-pressing stuff in the sidebar
can accidentally select the container or the canvas. This can
happen when attempting to move the handle. */
user-select: none;
-webkit-user-select: none;
}
#noVNC_keyboardinput {
@@ -935,7 +887,7 @@ select:active {
.noVNC_logo {
color:yellow;
font-family: 'Orbitron', 'OrbitronTTF', sans-serif;
line-height:90%;
line-height: 0.9;
text-shadow: 0.1em 0.1em 0 black;
}
.noVNC_logo span{
+281
View File
@@ -0,0 +1,281 @@
/*
* noVNC general input element CSS
* Copyright (C) 2022 The noVNC Authors
* noVNC is licensed under the MPL 2.0 (see LICENSE.txt)
* This file is licensed under the 2-Clause BSD license (see LICENSE.txt).
*/
/*
* Common for all inputs
*/
input, input::file-selector-button, button, select, textarea {
/* Respect standard font settings */
font: inherit;
/* Disable default rendering */
appearance: none;
background: none;
padding: 5px;
border: 1px solid rgb(192, 192, 192);
border-radius: 5px;
color: black;
--bg-gradient: linear-gradient(to top, rgb(255, 255, 255) 80%, rgb(240, 240, 240));
background-image: var(--bg-gradient);
}
/*
* Buttons
*/
input[type=button],
input[type=color],
input[type=image],
input[type=reset],
input[type=submit],
input::file-selector-button,
button,
select {
border-bottom-width: 2px;
/* This avoids it jumping around when :active */
vertical-align: middle;
margin-top: 0;
padding-left: 20px;
padding-right: 20px;
/* Disable Chrome's touch tap highlight */
-webkit-tap-highlight-color: transparent;
}
/*
* Select dropdowns
*/
select {
--select-arrow: url('data:image/svg+xml;utf8, \
<svg width="8" height="6" version="1.1" viewBox="0 0 8 6" \
xmlns="http://www.w3.org/2000/svg"> \
<path d="m6.5 1.5 -2.5 3 -2.5 -3 5 0" stroke-width="3" \
stroke="rgb(31,31,31)" fill="none" \
stroke-linecap="round" stroke-linejoin="round" /> \
</svg>');
background-image: var(--select-arrow), var(--bg-gradient);
background-position: calc(100% - 7px), left top;
background-repeat: no-repeat;
padding-right: calc(2*7px + 8px);
padding-left: 7px;
}
/* FIXME: :active isn't set when the <select> is opened in Firefox:
https://bugzilla.mozilla.org/show_bug.cgi?id=1805406 */
select:active {
/* Rotated arrow */
background-image: url('data:image/svg+xml;utf8, \
<svg width="8" height="6" version="1.1" viewBox="0 0 8 6" \
xmlns="http://www.w3.org/2000/svg" transform="rotate(180)" > \
<path d="m6.5 1.5 -2.5 3 -2.5 -3 5 0" stroke-width="3" \
stroke="rgb(31,31,31)" fill="none" \
stroke-linecap="round" stroke-linejoin="round" /> \
</svg>'), var(--bg-gradient);
}
option {
color: black;
background: white;
}
/*
* Checkboxes
*/
input[type=checkbox] {
display: inline-flex;
justify-content: center;
align-items: center;
background-color: white;
background-image: unset;
border: 1px solid dimgrey;
border-radius: 3px;
width: 13px;
height: 13px;
padding: 0;
margin-right: 6px;
vertical-align: bottom;
transition: 0.2s background-color linear;
}
input[type=checkbox]:checked {
background-color: rgb(110, 132, 163);
border-color: rgb(110, 132, 163);
}
input[type=checkbox]:checked::after {
content: "";
display: block; /* width & height doesn't work on inline elements */
width: 3px;
height: 7px;
border: 1px solid white;
border-width: 0 2px 2px 0;
transform: rotate(40deg) translateY(-1px);
}
/*
* Radiobuttons
*/
input[type=radio] {
border-radius: 50%;
border: 1px solid dimgrey;
width: 12px;
height: 12px;
padding: 0;
margin-right: 6px;
transition: 0.2s border linear;
}
input[type=radio]:checked {
border: 6px solid rgb(110, 132, 163);
}
/*
* Range sliders
*/
input[type=range] {
border: unset;
border-radius: 3px;
height: 20px;
padding: 0;
background: transparent;
}
/* -webkit-slider.. & -moz-range.. cant be in selector lists:
https://bugs.chromium.org/p/chromium/issues/detail?id=1154623 */
input[type=range]::-webkit-slider-runnable-track {
background-color: rgb(110, 132, 163);
height: 6px;
border-radius: 3px;
}
input[type=range]::-moz-range-track {
background-color: rgb(110, 132, 163);
height: 6px;
border-radius: 3px;
}
input[type=range]::-webkit-slider-thumb {
appearance: none;
width: 18px;
height: 20px;
border-radius: 5px;
background-color: white;
border: 1px solid dimgray;
margin-top: -7px;
}
input[type=range]::-moz-range-thumb {
appearance: none;
width: 18px;
height: 20px;
border-radius: 5px;
background-color: white;
border: 1px solid dimgray;
margin-top: -7px;
}
/*
* File choosers
*/
input[type=file] {
background-image: none;
border: none;
}
input::file-selector-button {
margin-right: 6px;
}
/*
* Hover
*/
input[type=button]:hover,
input[type=color]:hover,
input[type=image]:hover,
input[type=reset]:hover,
input[type=submit]:hover,
input::file-selector-button:hover,
button:hover {
background-image: linear-gradient(to top, rgb(255, 255, 255), rgb(250, 250, 250));
}
select:hover {
background-image: var(--select-arrow),
linear-gradient(to top, rgb(255, 255, 255), rgb(250, 250, 250));
background-position: calc(100% - 7px), left top;
background-repeat: no-repeat;
}
@media (any-pointer: coarse) {
/* We don't want a hover style after touch input */
input[type=button]:hover,
input[type=color]:hover,
input[type=image]:hover,
input[type=reset]:hover,
input[type=submit]:hover,
input::file-selector-button:hover,
button:hover {
background-image: var(--bg-gradient);
}
select:hover {
background-image: var(--select-arrow), var(--bg-gradient);
}
}
/*
* Active (clicked)
*/
input[type=button]:active,
input[type=color]:active,
input[type=image]:active,
input[type=reset]:active,
input[type=submit]:active,
input::file-selector-button:active,
button:active,
select:active {
border-bottom-width: 1px;
margin-top: 1px;
}
/*
* Focus (tab)
*/
input:focus-visible,
input:focus-visible::file-selector-button,
button:focus-visible,
select:focus-visible,
textarea:focus-visible {
outline: 2px solid rgb(74, 144, 217);
outline-offset: 1px;
}
input[type=file]:focus-visible {
outline: none; /* We outline the button instead of the entire element */
}
/*
* Disabled
*/
input:disabled,
input:disabled::file-selector-button,
button:disabled,
select:disabled,
textarea:disabled {
opacity: 0.4;
}
input[type=button]:disabled,
input[type=color]:disabled,
input[type=image]:disabled,
input[type=reset]:disabled,
input[type=submit]:disabled,
input:disabled::file-selector-button,
button:disabled,
select:disabled {
background-image: var(--bg-gradient);
border-bottom-width: 2px;
margin-top: 0;
}
input[type=file]:disabled {
background-image: none;
}
select:disabled {
background-image: var(--select-arrow), var(--bg-gradient);
}
input[type=image]:disabled {
/* See Firefox bug:
https://bugzilla.mozilla.org/show_bug.cgi?id=1798304 */
cursor: default;
}
+98 -35
View File
@@ -8,7 +8,8 @@
import * as Log from '../core/util/logging.js';
import _, { l10n } from './localization.js';
import { isTouchDevice, isSafari, hasScrollbarGutter, dragThreshold }
import { isTouchDevice, isMac, isIOS, isAndroid, isChromeOS, isSafari,
hasScrollbarGutter, dragThreshold }
from '../core/util/browser.js';
import { setCapture, getPointerEvent } from '../core/util/events.js';
import KeyTable from "../core/input/keysym.js";
@@ -61,6 +62,14 @@ const UI = {
// Translate the DOM
l10n.translateDOM();
// We rely on modern APIs which might not be available in an
// insecure context
if (!window.isSecureContext) {
// FIXME: This gets hidden when connecting
UI.showStatus(_("HTTPS is required for full functionality"), 'error');
}
// Try to fetch version number
fetch('./package.json')
.then((response) => {
if (!response.ok) {
@@ -80,7 +89,6 @@ const UI = {
// Adapt the interface for touch screen devices
if (isTouchDevice) {
document.documentElement.classList.add("noVNC_touch");
// Remove the address bar
setTimeout(() => window.scrollTo(0, 1), 100);
}
@@ -316,6 +324,10 @@ const UI = {
document.getElementById("noVNC_cancel_reconnect_button")
.addEventListener('click', UI.cancelReconnect);
document.getElementById("noVNC_approve_server_button")
.addEventListener('click', UI.approveServer);
document.getElementById("noVNC_reject_server_button")
.addEventListener('click', UI.rejectServer);
document.getElementById("noVNC_credentials_button")
.addEventListener('click', UI.setCredentials);
},
@@ -325,8 +337,6 @@ const UI = {
.addEventListener('click', UI.toggleClipboardPanel);
document.getElementById("noVNC_clipboard_text")
.addEventListener('change', UI.clipboardSend);
document.getElementById("noVNC_clipboard_clear_button")
.addEventListener('click', UI.clipboardClear);
},
// Add a call to save settings when the element changes,
@@ -445,6 +455,8 @@ const UI = {
// State change closes dialogs as they may not be relevant
// anymore
UI.closeAllPanels();
document.getElementById('noVNC_verify_server_dlg')
.classList.remove('noVNC_open');
document.getElementById('noVNC_credentials_dlg')
.classList.remove('noVNC_open');
},
@@ -577,10 +589,20 @@ const UI = {
// Consider this a movement of the handle
UI.controlbarDrag = true;
// The user has "followed" hint, let's hide it until the next drag
UI.showControlbarHint(false, false);
},
showControlbarHint(show) {
showControlbarHint(show, animate=true) {
const hint = document.getElementById('noVNC_control_bar_hint');
if (animate) {
hint.classList.remove("noVNC_notransition");
} else {
hint.classList.add("noVNC_notransition");
}
if (show) {
hint.classList.add("noVNC_active");
} else {
@@ -954,11 +976,6 @@ const UI = {
Log.Debug("<< UI.clipboardReceive");
},
clipboardClear() {
document.getElementById('noVNC_clipboard_text').value = "";
UI.rfb.clipboardPasteFrom("");
},
clipboardSend() {
const text = document.getElementById('noVNC_clipboard_text').value;
Log.Debug(">> UI.clipboardSend: " + text.substr(0, 40) + "...");
@@ -1024,14 +1041,24 @@ const UI = {
}
url += '/' + path;
UI.rfb = new RFB(document.getElementById('noVNC_container'), url,
{ shared: UI.getSetting('shared'),
repeaterID: UI.getSetting('repeaterID'),
credentials: { password: password } });
try {
UI.rfb = new RFB(document.getElementById('noVNC_container'), url,
{ shared: UI.getSetting('shared'),
repeaterID: UI.getSetting('repeaterID'),
credentials: { password: password } });
} catch (exc) {
Log.Error("Failed to connect to server: " + exc);
UI.updateVisualState('disconnected');
UI.showStatus(_("Failed to connect to server: ") + exc, 'error');
return;
}
UI.rfb.addEventListener("connect", UI.connectFinished);
UI.rfb.addEventListener("disconnect", UI.disconnectFinished);
UI.rfb.addEventListener("serververification", UI.serverVerify);
UI.rfb.addEventListener("credentialsrequired", UI.credentials);
UI.rfb.addEventListener("securityfailure", UI.securityFailed);
UI.rfb.addEventListener("clippingviewport", UI.updateViewDrag);
UI.rfb.addEventListener("capabilities", UI.updatePowerButton);
UI.rfb.addEventListener("clipboard", UI.clipboardReceive);
UI.rfb.addEventListener("bell", UI.bell);
@@ -1118,7 +1145,9 @@ const UI = {
} else {
UI.showStatus(_("Failed to connect to server"), 'error');
}
} else if (UI.getSetting('reconnect', false) === true && !UI.inhibitReconnect) {
}
// If reconnecting is allowed process it now
if (UI.getSetting('reconnect', false) === true && !UI.inhibitReconnect) {
UI.updateVisualState('reconnecting');
const delay = parseInt(UI.getSetting('reconnect_delay'));
@@ -1152,6 +1181,37 @@ const UI = {
/* ------^-------
* /CONNECTION
* ==============
* SERVER VERIFY
* ------v------*/
async serverVerify(e) {
const type = e.detail.type;
if (type === 'RSA') {
const publickey = e.detail.publickey;
let fingerprint = await window.crypto.subtle.digest("SHA-1", publickey);
// The same fingerprint format as RealVNC
fingerprint = Array.from(new Uint8Array(fingerprint).slice(0, 8)).map(
x => x.toString(16).padStart(2, '0')).join('-');
document.getElementById('noVNC_verify_server_dlg').classList.add('noVNC_open');
document.getElementById('noVNC_fingerprint').innerHTML = fingerprint;
}
},
approveServer(e) {
e.preventDefault();
document.getElementById('noVNC_verify_server_dlg').classList.remove('noVNC_open');
UI.rfb.approveServer();
},
rejectServer(e) {
e.preventDefault();
document.getElementById('noVNC_verify_server_dlg').classList.remove('noVNC_open');
UI.disconnect();
},
/* ------^-------
* /SERVER VERIFY
* ==============
* PASSWORD
* ------v------*/
@@ -1275,13 +1335,25 @@ const UI = {
const scaling = UI.getSetting('resize') === 'scale';
// Some platforms have overlay scrollbars that are difficult
// to use in our case, which means we have to force panning
// FIXME: Working scrollbars can still be annoying to use with
// touch, so we should ideally be able to have both
// panning and scrollbars at the same time
let brokenScrollbars = false;
if (!hasScrollbarGutter) {
if (isIOS() || isAndroid() || isMac() || isChromeOS()) {
brokenScrollbars = true;
}
}
if (scaling) {
// Can't be clipping if viewport is scaled to fit
UI.forceSetting('view_clip', false);
UI.rfb.clipViewport = false;
} else if (!hasScrollbarGutter) {
// Some platforms have scrollbars that are difficult
// to use in our case, so we always use our own panning
} else if (brokenScrollbars) {
UI.forceSetting('view_clip', true);
UI.rfb.clipViewport = true;
} else {
@@ -1312,7 +1384,8 @@ const UI = {
const viewDragButton = document.getElementById('noVNC_view_drag_button');
if (!UI.rfb.clipViewport && UI.rfb.dragViewport) {
if ((!UI.rfb.clipViewport || !UI.rfb.clippingViewport) &&
UI.rfb.dragViewport) {
// We are no longer clipping the viewport. Make sure
// viewport drag isn't active when it can't be used.
UI.rfb.dragViewport = false;
@@ -1329,6 +1402,8 @@ const UI = {
} else {
viewDragButton.classList.add("noVNC_hidden");
}
viewDragButton.disabled = !UI.rfb.clippingViewport;
},
/* ------^-------
@@ -1695,21 +1770,9 @@ const UI = {
};
// Set up translations
const LINGUAS = ["cs", "de", "el", "es", "fr", "ja", "ko", "nl", "pl", "pt_BR", "ru", "sv", "tr", "zh_CN", "zh_TW"];
l10n.setup(LINGUAS);
if (l10n.language === "en" || l10n.dictionary !== undefined) {
UI.prime();
} else {
fetch('app/locale/' + l10n.language + '.json')
.then((response) => {
if (!response.ok) {
throw Error("" + response.status + " " + response.statusText);
}
return response.json();
})
.then((translations) => { l10n.dictionary = translations; })
.catch(err => Log.Error("Failed to load translations: " + err))
.then(UI.prime);
}
const LINGUAS = ["cs", "de", "el", "es", "fr", "it", "ja", "ko", "nl", "pl", "pt_BR", "ru", "sv", "tr", "zh_CN", "zh_TW"];
l10n.setup(LINGUAS, "app/locale/")
.catch(err => Log.Error("Failed to load translations: " + err))
.then(UI.prime);
export default UI;
+74 -10
View File
@@ -6,16 +6,16 @@
* See README.md for usage and integration instructions.
*/
import { initLogging as mainInitLogging } from '../core/util/logging.js';
import * as Log from '../core/util/logging.js';
// init log level reading the logging HTTP param
export function initLogging(level) {
"use strict";
if (typeof level !== "undefined") {
mainInitLogging(level);
Log.initLogging(level);
} else {
const param = document.location.href.match(/logging=([A-Za-z0-9._-]*)/);
mainInitLogging(param || undefined);
Log.initLogging(param || undefined);
}
}
@@ -25,14 +25,14 @@ export function initLogging(level) {
//
// For privacy (Using a hastag #, the parameters will not be sent to the server)
// the url can be requested in the following way:
// https://www.example.com#myqueryparam=myvalue&password=secreatvalue
// https://www.example.com#myqueryparam=myvalue&password=secretvalue
//
// Even Mixing public and non public parameters will work:
// https://www.example.com?nonsecretparam=example.com#password=secreatvalue
// https://www.example.com?nonsecretparam=example.com#password=secretvalue
export function getQueryVar(name, defVal) {
"use strict";
const re = new RegExp('.*[?&]' + name + '=([^&#]*)'),
match = ''.concat(document.location.href, window.location.hash).match(re);
match = document.location.href.match(re);
if (typeof defVal === 'undefined') { defVal = null; }
if (match) {
@@ -46,7 +46,7 @@ export function getQueryVar(name, defVal) {
export function getHashVar(name, defVal) {
"use strict";
const re = new RegExp('.*[&#]' + name + '=([^&]*)'),
match = document.location.hash.match(re);
match = document.location.hash.match(re);
if (typeof defVal === 'undefined') { defVal = null; }
if (match) {
@@ -146,7 +146,7 @@ export function writeSetting(name, value) {
if (window.chrome && window.chrome.storage) {
window.chrome.storage.sync.set(settings);
} else {
localStorage.setItem(name, value);
localStorageSet(name, value);
}
}
@@ -156,7 +156,7 @@ export function readSetting(name, defaultValue) {
if ((name in settings) || (window.chrome && window.chrome.storage)) {
value = settings[name];
} else {
value = localStorage.getItem(name);
value = localStorageGet(name);
settings[name] = value;
}
if (typeof value === "undefined") {
@@ -181,6 +181,70 @@ export function eraseSetting(name) {
if (window.chrome && window.chrome.storage) {
window.chrome.storage.sync.remove(name);
} else {
localStorage.removeItem(name);
localStorageRemove(name);
}
}
let loggedMsgs = [];
function logOnce(msg, level = "warn") {
if (!loggedMsgs.includes(msg)) {
switch (level) {
case "error":
Log.Error(msg);
break;
case "warn":
Log.Warn(msg);
break;
case "debug":
Log.Debug(msg);
break;
default:
Log.Info(msg);
}
loggedMsgs.push(msg);
}
}
let cookiesMsg = "Couldn't access noVNC settings, are cookies disabled?";
function localStorageGet(name) {
let r;
try {
r = localStorage.getItem(name);
} catch (e) {
if (e instanceof DOMException) {
logOnce(cookiesMsg);
logOnce("'localStorage.getItem(" + name + ")' failed: " + e,
"debug");
} else {
throw e;
}
}
return r;
}
function localStorageSet(name, value) {
try {
localStorage.setItem(name, value);
} catch (e) {
if (e instanceof DOMException) {
logOnce(cookiesMsg);
logOnce("'localStorage.setItem(" + name + "," + value +
")' failed: " + e, "debug");
} else {
throw e;
}
}
}
function localStorageRemove(name) {
try {
localStorage.removeItem(name);
} catch (e) {
if (e instanceof DOMException) {
logOnce(cookiesMsg);
logOnce("'localStorage.removeItem(" + name + ")' failed: " + e,
"debug");
} else {
throw e;
}
}
}
+178
View File
@@ -0,0 +1,178 @@
export class AESECBCipher {
constructor() {
this._key = null;
}
get algorithm() {
return { name: "AES-ECB" };
}
static async importKey(key, _algorithm, extractable, keyUsages) {
const cipher = new AESECBCipher;
await cipher._importKey(key, extractable, keyUsages);
return cipher;
}
async _importKey(key, extractable, keyUsages) {
this._key = await window.crypto.subtle.importKey(
"raw", key, {name: "AES-CBC"}, extractable, keyUsages);
}
async encrypt(_algorithm, plaintext) {
const x = new Uint8Array(plaintext);
if (x.length % 16 !== 0 || this._key === null) {
return null;
}
const n = x.length / 16;
for (let i = 0; i < n; i++) {
const y = new Uint8Array(await window.crypto.subtle.encrypt({
name: "AES-CBC",
iv: new Uint8Array(16),
}, this._key, x.slice(i * 16, i * 16 + 16))).slice(0, 16);
x.set(y, i * 16);
}
return x;
}
}
export class AESEAXCipher {
constructor() {
this._rawKey = null;
this._ctrKey = null;
this._cbcKey = null;
this._zeroBlock = new Uint8Array(16);
this._prefixBlock0 = this._zeroBlock;
this._prefixBlock1 = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);
this._prefixBlock2 = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2]);
}
get algorithm() {
return { name: "AES-EAX" };
}
async _encryptBlock(block) {
const encrypted = await window.crypto.subtle.encrypt({
name: "AES-CBC",
iv: this._zeroBlock,
}, this._cbcKey, block);
return new Uint8Array(encrypted).slice(0, 16);
}
async _initCMAC() {
const k1 = await this._encryptBlock(this._zeroBlock);
const k2 = new Uint8Array(16);
const v = k1[0] >>> 6;
for (let i = 0; i < 15; i++) {
k2[i] = (k1[i + 1] >> 6) | (k1[i] << 2);
k1[i] = (k1[i + 1] >> 7) | (k1[i] << 1);
}
const lut = [0x0, 0x87, 0x0e, 0x89];
k2[14] ^= v >>> 1;
k2[15] = (k1[15] << 2) ^ lut[v];
k1[15] = (k1[15] << 1) ^ lut[v >> 1];
this._k1 = k1;
this._k2 = k2;
}
async _encryptCTR(data, counter) {
const encrypted = await window.crypto.subtle.encrypt({
name: "AES-CTR",
counter: counter,
length: 128
}, this._ctrKey, data);
return new Uint8Array(encrypted);
}
async _decryptCTR(data, counter) {
const decrypted = await window.crypto.subtle.decrypt({
name: "AES-CTR",
counter: counter,
length: 128
}, this._ctrKey, data);
return new Uint8Array(decrypted);
}
async _computeCMAC(data, prefixBlock) {
if (prefixBlock.length !== 16) {
return null;
}
const n = Math.floor(data.length / 16);
const m = Math.ceil(data.length / 16);
const r = data.length - n * 16;
const cbcData = new Uint8Array((m + 1) * 16);
cbcData.set(prefixBlock);
cbcData.set(data, 16);
if (r === 0) {
for (let i = 0; i < 16; i++) {
cbcData[n * 16 + i] ^= this._k1[i];
}
} else {
cbcData[(n + 1) * 16 + r] = 0x80;
for (let i = 0; i < 16; i++) {
cbcData[(n + 1) * 16 + i] ^= this._k2[i];
}
}
let cbcEncrypted = await window.crypto.subtle.encrypt({
name: "AES-CBC",
iv: this._zeroBlock,
}, this._cbcKey, cbcData);
cbcEncrypted = new Uint8Array(cbcEncrypted);
const mac = cbcEncrypted.slice(cbcEncrypted.length - 32, cbcEncrypted.length - 16);
return mac;
}
static async importKey(key, _algorithm, _extractable, _keyUsages) {
const cipher = new AESEAXCipher;
await cipher._importKey(key);
return cipher;
}
async _importKey(key) {
this._rawKey = key;
this._ctrKey = await window.crypto.subtle.importKey(
"raw", key, {name: "AES-CTR"}, false, ["encrypt", "decrypt"]);
this._cbcKey = await window.crypto.subtle.importKey(
"raw", key, {name: "AES-CBC"}, false, ["encrypt"]);
await this._initCMAC();
}
async encrypt(algorithm, message) {
const ad = algorithm.additionalData;
const nonce = algorithm.iv;
const nCMAC = await this._computeCMAC(nonce, this._prefixBlock0);
const encrypted = await this._encryptCTR(message, nCMAC);
const adCMAC = await this._computeCMAC(ad, this._prefixBlock1);
const mac = await this._computeCMAC(encrypted, this._prefixBlock2);
for (let i = 0; i < 16; i++) {
mac[i] ^= nCMAC[i] ^ adCMAC[i];
}
const res = new Uint8Array(16 + encrypted.length);
res.set(encrypted);
res.set(mac, encrypted.length);
return res;
}
async decrypt(algorithm, data) {
const encrypted = data.slice(0, data.length - 16);
const ad = algorithm.additionalData;
const nonce = algorithm.iv;
const mac = data.slice(data.length - 16);
const nCMAC = await this._computeCMAC(nonce, this._prefixBlock0);
const adCMAC = await this._computeCMAC(ad, this._prefixBlock1);
const computedMac = await this._computeCMAC(encrypted, this._prefixBlock2);
for (let i = 0; i < 16; i++) {
computedMac[i] ^= nCMAC[i] ^ adCMAC[i];
}
if (computedMac.length !== mac.length) {
return null;
}
for (let i = 0; i < mac.length; i++) {
if (computedMac[i] !== mac[i]) {
return null;
}
}
const res = await this._decryptCTR(encrypted, nCMAC);
return res;
}
}
+34
View File
@@ -0,0 +1,34 @@
export function modPow(b, e, m) {
let r = 1n;
b = b % m;
while (e > 0n) {
if ((e & 1n) === 1n) {
r = (r * b) % m;
}
e = e >> 1n;
b = (b * b) % m;
}
return r;
}
export function bigIntToU8Array(bigint, padLength=0) {
let hex = bigint.toString(16);
if (padLength === 0) {
padLength = Math.ceil(hex.length / 2);
}
hex = hex.padStart(padLength * 2, '0');
const length = hex.length / 2;
const arr = new Uint8Array(length);
for (let i = 0; i < length; i++) {
arr[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
}
return arr;
}
export function u8ArrayToBigInt(arr) {
let hex = '0x';
for (let i = 0; i < arr.length; i++) {
hex += arr[i].toString(16).padStart(2, '0');
}
return BigInt(hex);
}
+90
View File
@@ -0,0 +1,90 @@
import { AESECBCipher, AESEAXCipher } from "./aes.js";
import { DESCBCCipher, DESECBCipher } from "./des.js";
import { RSACipher } from "./rsa.js";
import { DHCipher } from "./dh.js";
import { MD5 } from "./md5.js";
// A single interface for the cryptographic algorithms not supported by SubtleCrypto.
// Both synchronous and asynchronous implmentations are allowed.
class LegacyCrypto {
constructor() {
this._algorithms = {
"AES-ECB": AESECBCipher,
"AES-EAX": AESEAXCipher,
"DES-ECB": DESECBCipher,
"DES-CBC": DESCBCCipher,
"RSA-PKCS1-v1_5": RSACipher,
"DH": DHCipher,
"MD5": MD5,
};
}
encrypt(algorithm, key, data) {
if (key.algorithm.name !== algorithm.name) {
throw new Error("algorithm does not match");
}
if (typeof key.encrypt !== "function") {
throw new Error("key does not support encryption");
}
return key.encrypt(algorithm, data);
}
decrypt(algorithm, key, data) {
if (key.algorithm.name !== algorithm.name) {
throw new Error("algorithm does not match");
}
if (typeof key.decrypt !== "function") {
throw new Error("key does not support encryption");
}
return key.decrypt(algorithm, data);
}
importKey(format, keyData, algorithm, extractable, keyUsages) {
if (format !== "raw") {
throw new Error("key format is not supported");
}
const alg = this._algorithms[algorithm.name];
if (typeof alg === "undefined" || typeof alg.importKey !== "function") {
throw new Error("algorithm is not supported");
}
return alg.importKey(keyData, algorithm, extractable, keyUsages);
}
generateKey(algorithm, extractable, keyUsages) {
const alg = this._algorithms[algorithm.name];
if (typeof alg === "undefined" || typeof alg.generateKey !== "function") {
throw new Error("algorithm is not supported");
}
return alg.generateKey(algorithm, extractable, keyUsages);
}
exportKey(format, key) {
if (format !== "raw") {
throw new Error("key format is not supported");
}
if (typeof key.exportKey !== "function") {
throw new Error("key does not support exportKey");
}
return key.exportKey();
}
digest(algorithm, data) {
const alg = this._algorithms[algorithm];
if (typeof alg !== "function") {
throw new Error("algorithm is not supported");
}
return alg(data);
}
deriveBits(algorithm, key, length) {
if (key.algorithm.name !== algorithm.name) {
throw new Error("algorithm does not match");
}
if (typeof key.deriveBits !== "function") {
throw new Error("key does not support deriveBits");
}
return key.deriveBits(algorithm, length);
}
}
export default new LegacyCrypto;
+69 -5
View File
@@ -81,7 +81,7 @@
const PC2 = [13,16,10,23, 0, 4, 2,27,14, 5,20, 9,22,18,11, 3,
25, 7,15, 6,26,19,12, 1,40,51,30,36,46,54,29,39,
50,44,32,47,43,48,38,55,33,52,45,41,49,35,28,31 ],
totrot = [ 1, 2, 4, 6, 8,10,12,14,15,17,19,21,23,25,27,28];
totrot = [ 1, 2, 4, 6, 8,10,12,14,15,17,19,21,23,25,27,28];
const z = 0x0;
let a,b,c,d,e,f;
@@ -128,7 +128,7 @@ const SP8 = [b|f,z|e,a|z,c|f,b|z,b|f,z|d,b|z,a|d,c|z,c|f,a|e,c|e,a|f,z|e,z|d,
/* eslint-enable comma-spacing */
export default class DES {
class DES {
constructor(password) {
this.keys = [];
@@ -258,9 +258,73 @@ export default class DES {
}
return b;
}
}
// Encrypt 16 bytes of text using passwd as key
encrypt(t) {
return this.enc8(t.slice(0, 8)).concat(this.enc8(t.slice(8, 16)));
export class DESECBCipher {
constructor() {
this._cipher = null;
}
get algorithm() {
return { name: "DES-ECB" };
}
static importKey(key, _algorithm, _extractable, _keyUsages) {
const cipher = new DESECBCipher;
cipher._importKey(key);
return cipher;
}
_importKey(key, _extractable, _keyUsages) {
this._cipher = new DES(key);
}
encrypt(_algorithm, plaintext) {
const x = new Uint8Array(plaintext);
if (x.length % 8 !== 0 || this._cipher === null) {
return null;
}
const n = x.length / 8;
for (let i = 0; i < n; i++) {
x.set(this._cipher.enc8(x.slice(i * 8, i * 8 + 8)), i * 8);
}
return x;
}
}
export class DESCBCCipher {
constructor() {
this._cipher = null;
}
get algorithm() {
return { name: "DES-CBC" };
}
static importKey(key, _algorithm, _extractable, _keyUsages) {
const cipher = new DESCBCCipher;
cipher._importKey(key);
return cipher;
}
_importKey(key) {
this._cipher = new DES(key);
}
encrypt(algorithm, plaintext) {
const x = new Uint8Array(plaintext);
let y = new Uint8Array(algorithm.iv);
if (x.length % 8 !== 0 || this._cipher === null) {
return null;
}
const n = x.length / 8;
for (let i = 0; i < n; i++) {
for (let j = 0; j < 8; j++) {
y[j] ^= plaintext[i * 8 + j];
}
y = this._cipher.enc8(y);
x.set(y, i * 8);
}
return x;
}
}
+55
View File
@@ -0,0 +1,55 @@
import { modPow, bigIntToU8Array, u8ArrayToBigInt } from "./bigint.js";
class DHPublicKey {
constructor(key) {
this._key = key;
}
get algorithm() {
return { name: "DH" };
}
exportKey() {
return this._key;
}
}
export class DHCipher {
constructor() {
this._g = null;
this._p = null;
this._gBigInt = null;
this._pBigInt = null;
this._privateKey = null;
}
get algorithm() {
return { name: "DH" };
}
static generateKey(algorithm, _extractable) {
const cipher = new DHCipher;
cipher._generateKey(algorithm);
return { privateKey: cipher, publicKey: new DHPublicKey(cipher._publicKey) };
}
_generateKey(algorithm) {
const g = algorithm.g;
const p = algorithm.p;
this._keyBytes = p.length;
this._gBigInt = u8ArrayToBigInt(g);
this._pBigInt = u8ArrayToBigInt(p);
this._privateKey = window.crypto.getRandomValues(new Uint8Array(this._keyBytes));
this._privateKeyBigInt = u8ArrayToBigInt(this._privateKey);
this._publicKey = bigIntToU8Array(modPow(
this._gBigInt, this._privateKeyBigInt, this._pBigInt), this._keyBytes);
}
deriveBits(algorithm, length) {
const bytes = Math.ceil(length / 8);
const pkey = new Uint8Array(algorithm.public);
const len = bytes > this._keyBytes ? bytes : this._keyBytes;
const secret = modPow(u8ArrayToBigInt(pkey), this._privateKeyBigInt, this._pBigInt);
return bigIntToU8Array(secret, len).slice(0, len);
}
}
+82
View File
@@ -0,0 +1,82 @@
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2021 The noVNC Authors
* Licensed under MPL 2.0 (see LICENSE.txt)
*
* See README.md for usage and integration instructions.
*/
/*
* Performs MD5 hashing on an array of bytes, returns an array of bytes
*/
export async function MD5(d) {
let s = "";
for (let i = 0; i < d.length; i++) {
s += String.fromCharCode(d[i]);
}
return M(V(Y(X(s), 8 * s.length)));
}
function M(d) {
let f = new Uint8Array(d.length);
for (let i=0;i<d.length;i++) {
f[i] = d.charCodeAt(i);
}
return f;
}
function X(d) {
let r = Array(d.length >> 2);
for (let m = 0; m < r.length; m++) r[m] = 0;
for (let m = 0; m < 8 * d.length; m += 8) r[m >> 5] |= (255 & d.charCodeAt(m / 8)) << m % 32;
return r;
}
function V(d) {
let r = "";
for (let m = 0; m < 32 * d.length; m += 8) r += String.fromCharCode(d[m >> 5] >>> m % 32 & 255);
return r;
}
function Y(d, g) {
d[g >> 5] |= 128 << g % 32, d[14 + (g + 64 >>> 9 << 4)] = g;
let m = 1732584193, f = -271733879, r = -1732584194, i = 271733878;
for (let n = 0; n < d.length; n += 16) {
let h = m,
t = f,
g = r,
e = i;
f = ii(f = ii(f = ii(f = ii(f = hh(f = hh(f = hh(f = hh(f = gg(f = gg(f = gg(f = gg(f = ff(f = ff(f = ff(f = ff(f, r = ff(r, i = ff(i, m = ff(m, f, r, i, d[n + 0], 7, -680876936), f, r, d[n + 1], 12, -389564586), m, f, d[n + 2], 17, 606105819), i, m, d[n + 3], 22, -1044525330), r = ff(r, i = ff(i, m = ff(m, f, r, i, d[n + 4], 7, -176418897), f, r, d[n + 5], 12, 1200080426), m, f, d[n + 6], 17, -1473231341), i, m, d[n + 7], 22, -45705983), r = ff(r, i = ff(i, m = ff(m, f, r, i, d[n + 8], 7, 1770035416), f, r, d[n + 9], 12, -1958414417), m, f, d[n + 10], 17, -42063), i, m, d[n + 11], 22, -1990404162), r = ff(r, i = ff(i, m = ff(m, f, r, i, d[n + 12], 7, 1804603682), f, r, d[n + 13], 12, -40341101), m, f, d[n + 14], 17, -1502002290), i, m, d[n + 15], 22, 1236535329), r = gg(r, i = gg(i, m = gg(m, f, r, i, d[n + 1], 5, -165796510), f, r, d[n + 6], 9, -1069501632), m, f, d[n + 11], 14, 643717713), i, m, d[n + 0], 20, -373897302), r = gg(r, i = gg(i, m = gg(m, f, r, i, d[n + 5], 5, -701558691), f, r, d[n + 10], 9, 38016083), m, f, d[n + 15], 14, -660478335), i, m, d[n + 4], 20, -405537848), r = gg(r, i = gg(i, m = gg(m, f, r, i, d[n + 9], 5, 568446438), f, r, d[n + 14], 9, -1019803690), m, f, d[n + 3], 14, -187363961), i, m, d[n + 8], 20, 1163531501), r = gg(r, i = gg(i, m = gg(m, f, r, i, d[n + 13], 5, -1444681467), f, r, d[n + 2], 9, -51403784), m, f, d[n + 7], 14, 1735328473), i, m, d[n + 12], 20, -1926607734), r = hh(r, i = hh(i, m = hh(m, f, r, i, d[n + 5], 4, -378558), f, r, d[n + 8], 11, -2022574463), m, f, d[n + 11], 16, 1839030562), i, m, d[n + 14], 23, -35309556), r = hh(r, i = hh(i, m = hh(m, f, r, i, d[n + 1], 4, -1530992060), f, r, d[n + 4], 11, 1272893353), m, f, d[n + 7], 16, -155497632), i, m, d[n + 10], 23, -1094730640), r = hh(r, i = hh(i, m = hh(m, f, r, i, d[n + 13], 4, 681279174), f, r, d[n + 0], 11, -358537222), m, f, d[n + 3], 16, -722521979), i, m, d[n + 6], 23, 76029189), r = hh(r, i = hh(i, m = hh(m, f, r, i, d[n + 9], 4, -640364487), f, r, d[n + 12], 11, -421815835), m, f, d[n + 15], 16, 530742520), i, m, d[n + 2], 23, -995338651), r = ii(r, i = ii(i, m = ii(m, f, r, i, d[n + 0], 6, -198630844), f, r, d[n + 7], 10, 1126891415), m, f, d[n + 14], 15, -1416354905), i, m, d[n + 5], 21, -57434055), r = ii(r, i = ii(i, m = ii(m, f, r, i, d[n + 12], 6, 1700485571), f, r, d[n + 3], 10, -1894986606), m, f, d[n + 10], 15, -1051523), i, m, d[n + 1], 21, -2054922799), r = ii(r, i = ii(i, m = ii(m, f, r, i, d[n + 8], 6, 1873313359), f, r, d[n + 15], 10, -30611744), m, f, d[n + 6], 15, -1560198380), i, m, d[n + 13], 21, 1309151649), r = ii(r, i = ii(i, m = ii(m, f, r, i, d[n + 4], 6, -145523070), f, r, d[n + 11], 10, -1120210379), m, f, d[n + 2], 15, 718787259), i, m, d[n + 9], 21, -343485551), m = add(m, h), f = add(f, t), r = add(r, g), i = add(i, e);
}
return Array(m, f, r, i);
}
function cmn(d, g, m, f, r, i) {
return add(rol(add(add(g, d), add(f, i)), r), m);
}
function ff(d, g, m, f, r, i, n) {
return cmn(g & m | ~g & f, d, g, r, i, n);
}
function gg(d, g, m, f, r, i, n) {
return cmn(g & f | m & ~f, d, g, r, i, n);
}
function hh(d, g, m, f, r, i, n) {
return cmn(g ^ m ^ f, d, g, r, i, n);
}
function ii(d, g, m, f, r, i, n) {
return cmn(m ^ (g | ~f), d, g, r, i, n);
}
function add(d, g) {
let m = (65535 & d) + (65535 & g);
return (d >> 16) + (g >> 16) + (m >> 16) << 16 | 65535 & m;
}
function rol(d, g) {
return d << g | d >>> 32 - g;
}
+132
View File
@@ -0,0 +1,132 @@
import Base64 from "../base64.js";
import { modPow, bigIntToU8Array, u8ArrayToBigInt } from "./bigint.js";
export class RSACipher {
constructor() {
this._keyLength = 0;
this._keyBytes = 0;
this._n = null;
this._e = null;
this._d = null;
this._nBigInt = null;
this._eBigInt = null;
this._dBigInt = null;
this._extractable = false;
}
get algorithm() {
return { name: "RSA-PKCS1-v1_5" };
}
_base64urlDecode(data) {
data = data.replace(/-/g, "+").replace(/_/g, "/");
data = data.padEnd(Math.ceil(data.length / 4) * 4, "=");
return Base64.decode(data);
}
_padArray(arr, length) {
const res = new Uint8Array(length);
res.set(arr, length - arr.length);
return res;
}
static async generateKey(algorithm, extractable, _keyUsages) {
const cipher = new RSACipher;
await cipher._generateKey(algorithm, extractable);
return { privateKey: cipher };
}
async _generateKey(algorithm, extractable) {
this._keyLength = algorithm.modulusLength;
this._keyBytes = Math.ceil(this._keyLength / 8);
const key = await window.crypto.subtle.generateKey(
{
name: "RSA-OAEP",
modulusLength: algorithm.modulusLength,
publicExponent: algorithm.publicExponent,
hash: {name: "SHA-256"},
},
true, ["encrypt", "decrypt"]);
const privateKey = await window.crypto.subtle.exportKey("jwk", key.privateKey);
this._n = this._padArray(this._base64urlDecode(privateKey.n), this._keyBytes);
this._nBigInt = u8ArrayToBigInt(this._n);
this._e = this._padArray(this._base64urlDecode(privateKey.e), this._keyBytes);
this._eBigInt = u8ArrayToBigInt(this._e);
this._d = this._padArray(this._base64urlDecode(privateKey.d), this._keyBytes);
this._dBigInt = u8ArrayToBigInt(this._d);
this._extractable = extractable;
}
static async importKey(key, _algorithm, extractable, keyUsages) {
if (keyUsages.length !== 1 || keyUsages[0] !== "encrypt") {
throw new Error("only support importing RSA public key");
}
const cipher = new RSACipher;
await cipher._importKey(key, extractable);
return cipher;
}
async _importKey(key, extractable) {
const n = key.n;
const e = key.e;
if (n.length !== e.length) {
throw new Error("the sizes of modulus and public exponent do not match");
}
this._keyBytes = n.length;
this._keyLength = this._keyBytes * 8;
this._n = new Uint8Array(this._keyBytes);
this._e = new Uint8Array(this._keyBytes);
this._n.set(n);
this._e.set(e);
this._nBigInt = u8ArrayToBigInt(this._n);
this._eBigInt = u8ArrayToBigInt(this._e);
this._extractable = extractable;
}
async encrypt(_algorithm, message) {
if (message.length > this._keyBytes - 11) {
return null;
}
const ps = new Uint8Array(this._keyBytes - message.length - 3);
window.crypto.getRandomValues(ps);
for (let i = 0; i < ps.length; i++) {
ps[i] = Math.floor(ps[i] * 254 / 255 + 1);
}
const em = new Uint8Array(this._keyBytes);
em[1] = 0x02;
em.set(ps, 2);
em.set(message, ps.length + 3);
const emBigInt = u8ArrayToBigInt(em);
const c = modPow(emBigInt, this._eBigInt, this._nBigInt);
return bigIntToU8Array(c, this._keyBytes);
}
async decrypt(_algorithm, message) {
if (message.length !== this._keyBytes) {
return null;
}
const msgBigInt = u8ArrayToBigInt(message);
const emBigInt = modPow(msgBigInt, this._dBigInt, this._nBigInt);
const em = bigIntToU8Array(emBigInt, this._keyBytes);
if (em[0] !== 0x00 || em[1] !== 0x02) {
return null;
}
let i = 2;
for (; i < em.length; i++) {
if (em[i] === 0x00) {
break;
}
}
if (i === em.length) {
return null;
}
return em.slice(i + 1, em.length);
}
async exportKey() {
if (!this._extractable) {
throw new Error("key is not extractable");
}
return { n: this._n, e: this._e, d: this._d };
}
}
+12 -22
View File
@@ -31,10 +31,7 @@ export default class HextileDecoder {
return false;
}
let rQ = sock.rQ;
let rQi = sock.rQi;
let subencoding = rQ[rQi]; // Peek
let subencoding = sock.rQpeek8();
if (subencoding > 30) { // Raw
throw new Error("Illegal hextile subencoding (subencoding: " +
subencoding + ")");
@@ -65,7 +62,7 @@ export default class HextileDecoder {
return false;
}
let subrects = rQ[rQi + bytes - 1]; // Peek
let subrects = sock.rQpeekBytes(bytes).at(-1);
if (subencoding & 0x10) { // SubrectsColoured
bytes += subrects * (4 + 2);
} else {
@@ -79,7 +76,7 @@ export default class HextileDecoder {
}
// We know the encoding and have a whole tile
rQi++;
sock.rQshift8();
if (subencoding === 0) {
if (this._lastsubencoding & 0x01) {
// Weird: ignore blanks are RAW
@@ -89,42 +86,36 @@ export default class HextileDecoder {
}
} else if (subencoding & 0x01) { // Raw
let pixels = tw * th;
let data = sock.rQshiftBytes(pixels * 4, false);
// Max sure the image is fully opaque
for (let i = 0;i < pixels;i++) {
rQ[rQi + i * 4 + 3] = 255;
data[i * 4 + 3] = 255;
}
display.blitImage(tx, ty, tw, th, rQ, rQi);
rQi += bytes - 1;
display.blitImage(tx, ty, tw, th, data, 0);
} else {
if (subencoding & 0x02) { // Background
this._background = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]];
rQi += 4;
this._background = new Uint8Array(sock.rQshiftBytes(4));
}
if (subencoding & 0x04) { // Foreground
this._foreground = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]];
rQi += 4;
this._foreground = new Uint8Array(sock.rQshiftBytes(4));
}
this._startTile(tx, ty, tw, th, this._background);
if (subencoding & 0x08) { // AnySubrects
let subrects = rQ[rQi];
rQi++;
let subrects = sock.rQshift8();
for (let s = 0; s < subrects; s++) {
let color;
if (subencoding & 0x10) { // SubrectsColoured
color = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]];
rQi += 4;
color = sock.rQshiftBytes(4);
} else {
color = this._foreground;
}
const xy = rQ[rQi];
rQi++;
const xy = sock.rQshift8();
const sx = (xy >> 4);
const sy = (xy & 0x0f);
const wh = rQ[rQi];
rQi++;
const wh = sock.rQshift8();
const sw = (wh >> 4) + 1;
const sh = (wh & 0x0f) + 1;
@@ -133,7 +124,6 @@ export default class HextileDecoder {
}
this._finishTile(display);
}
sock.rQi = rQi;
this._lastsubencoding = subencoding;
this._tiles--;
}
+146
View File
@@ -0,0 +1,146 @@
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2019 The noVNC Authors
* Licensed under MPL 2.0 (see LICENSE.txt)
*
* See README.md for usage and integration instructions.
*
*/
export default class JPEGDecoder {
constructor() {
// RealVNC will reuse the quantization tables
// and Huffman tables, so we need to cache them.
this._cachedQuantTables = [];
this._cachedHuffmanTables = [];
this._segments = [];
}
decodeRect(x, y, width, height, sock, display, depth) {
// A rect of JPEG encodings is simply a JPEG file
while (true) {
let segment = this._readSegment(sock);
if (segment === null) {
return false;
}
this._segments.push(segment);
// End of image?
if (segment[1] === 0xD9) {
break;
}
}
let huffmanTables = [];
let quantTables = [];
for (let segment of this._segments) {
let type = segment[1];
if (type === 0xC4) {
// Huffman tables
huffmanTables.push(segment);
} else if (type === 0xDB) {
// Quantization tables
quantTables.push(segment);
}
}
const sofIndex = this._segments.findIndex(
x => x[1] == 0xC0 || x[1] == 0xC2
);
if (sofIndex == -1) {
throw new Error("Illegal JPEG image without SOF");
}
if (quantTables.length === 0) {
this._segments.splice(sofIndex+1, 0,
...this._cachedQuantTables);
}
if (huffmanTables.length === 0) {
this._segments.splice(sofIndex+1, 0,
...this._cachedHuffmanTables);
}
let length = 0;
for (let segment of this._segments) {
length += segment.length;
}
let data = new Uint8Array(length);
length = 0;
for (let segment of this._segments) {
data.set(segment, length);
length += segment.length;
}
display.imageRect(x, y, width, height, "image/jpeg", data);
if (huffmanTables.length !== 0) {
this._cachedHuffmanTables = huffmanTables;
}
if (quantTables.length !== 0) {
this._cachedQuantTables = quantTables;
}
this._segments = [];
return true;
}
_readSegment(sock) {
if (sock.rQwait("JPEG", 2)) {
return null;
}
let marker = sock.rQshift8();
if (marker != 0xFF) {
throw new Error("Illegal JPEG marker received (byte: " +
marker + ")");
}
let type = sock.rQshift8();
if (type >= 0xD0 && type <= 0xD9 || type == 0x01) {
// No length after marker
return new Uint8Array([marker, type]);
}
if (sock.rQwait("JPEG", 2, 2)) {
return null;
}
let length = sock.rQshift16();
if (length < 2) {
throw new Error("Illegal JPEG length received (length: " +
length + ")");
}
if (sock.rQwait("JPEG", length-2, 4)) {
return null;
}
let extra = 0;
if (type === 0xDA) {
// start of scan
extra += 2;
while (true) {
if (sock.rQwait("JPEG", length-2+extra, 4)) {
return null;
}
let data = sock.rQpeekBytes(length-2+extra, false);
if (data.at(-2) === 0xFF && data.at(-1) !== 0x00 &&
!(data.at(-1) >= 0xD0 && data.at(-1) <= 0xD7)) {
extra -= 2;
break;
}
extra++;
}
}
let segment = new Uint8Array(2 + length + extra);
segment[0] = marker;
segment[1] = type;
segment[2] = length >> 8;
segment[3] = length;
segment.set(sock.rQshiftBytes(length-2+extra, false), 4);
return segment;
}
}
+25 -32
View File
@@ -24,41 +24,34 @@ export default class RawDecoder {
const pixelSize = depth == 8 ? 1 : 4;
const bytesPerLine = width * pixelSize;
if (sock.rQwait("RAW", bytesPerLine)) {
return false;
}
const curY = y + (height - this._lines);
const currHeight = Math.min(this._lines,
Math.floor(sock.rQlen / bytesPerLine));
const pixels = width * currHeight;
let data = sock.rQ;
let index = sock.rQi;
// Convert data if needed
if (depth == 8) {
const newdata = new Uint8Array(pixels * 4);
for (let i = 0; i < pixels; i++) {
newdata[i * 4 + 0] = ((data[index + i] >> 0) & 0x3) * 255 / 3;
newdata[i * 4 + 1] = ((data[index + i] >> 2) & 0x3) * 255 / 3;
newdata[i * 4 + 2] = ((data[index + i] >> 4) & 0x3) * 255 / 3;
newdata[i * 4 + 3] = 255;
while (this._lines > 0) {
if (sock.rQwait("RAW", bytesPerLine)) {
return false;
}
data = newdata;
index = 0;
}
// Max sure the image is fully opaque
for (let i = 0; i < pixels; i++) {
data[i * 4 + 3] = 255;
}
const curY = y + (height - this._lines);
display.blitImage(x, curY, width, currHeight, data, index);
sock.rQskipBytes(currHeight * bytesPerLine);
this._lines -= currHeight;
if (this._lines > 0) {
return false;
let data = sock.rQshiftBytes(bytesPerLine, false);
// Convert data if needed
if (depth == 8) {
const newdata = new Uint8Array(width * 4);
for (let i = 0; i < width; i++) {
newdata[i * 4 + 0] = ((data[i] >> 0) & 0x3) * 255 / 3;
newdata[i * 4 + 1] = ((data[i] >> 2) & 0x3) * 255 / 3;
newdata[i * 4 + 2] = ((data[i] >> 4) & 0x3) * 255 / 3;
newdata[i * 4 + 3] = 255;
}
data = newdata;
}
// Max sure the image is fully opaque
for (let i = 0; i < width; i++) {
data[i * 4 + 3] = 255;
}
display.blitImage(x, curY, width, 1, data, 0);
this._lines--;
}
return true;
+3 -7
View File
@@ -76,12 +76,8 @@ export default class TightDecoder {
return false;
}
const rQi = sock.rQi;
const rQ = sock.rQ;
display.fillRect(x, y, width, height,
[rQ[rQi], rQ[rQi + 1], rQ[rQi + 2]], false);
sock.rQskipBytes(3);
let pixel = sock.rQshiftBytes(3);
display.fillRect(x, y, width, height, pixel, false);
return true;
}
@@ -316,7 +312,7 @@ export default class TightDecoder {
return null;
}
let data = sock.rQshiftBytes(this._len);
let data = sock.rQshiftBytes(this._len, false);
this._len = 0;
return data;
+185
View File
@@ -0,0 +1,185 @@
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2021 The noVNC Authors
* Licensed under MPL 2.0 (see LICENSE.txt)
*
* See README.md for usage and integration instructions.
*
*/
import Inflate from "../inflator.js";
const ZRLE_TILE_WIDTH = 64;
const ZRLE_TILE_HEIGHT = 64;
export default class ZRLEDecoder {
constructor() {
this._length = 0;
this._inflator = new Inflate();
this._pixelBuffer = new Uint8Array(ZRLE_TILE_WIDTH * ZRLE_TILE_HEIGHT * 4);
this._tileBuffer = new Uint8Array(ZRLE_TILE_WIDTH * ZRLE_TILE_HEIGHT * 4);
}
decodeRect(x, y, width, height, sock, display, depth) {
if (this._length === 0) {
if (sock.rQwait("ZLib data length", 4)) {
return false;
}
this._length = sock.rQshift32();
}
if (sock.rQwait("Zlib data", this._length)) {
return false;
}
const data = sock.rQshiftBytes(this._length, false);
this._inflator.setInput(data);
for (let ty = y; ty < y + height; ty += ZRLE_TILE_HEIGHT) {
let th = Math.min(ZRLE_TILE_HEIGHT, y + height - ty);
for (let tx = x; tx < x + width; tx += ZRLE_TILE_WIDTH) {
let tw = Math.min(ZRLE_TILE_WIDTH, x + width - tx);
const tileSize = tw * th;
const subencoding = this._inflator.inflate(1)[0];
if (subencoding === 0) {
// raw data
const data = this._readPixels(tileSize);
display.blitImage(tx, ty, tw, th, data, 0, false);
} else if (subencoding === 1) {
// solid
const background = this._readPixels(1);
display.fillRect(tx, ty, tw, th, [background[0], background[1], background[2]]);
} else if (subencoding >= 2 && subencoding <= 16) {
const data = this._decodePaletteTile(subencoding, tileSize, tw, th);
display.blitImage(tx, ty, tw, th, data, 0, false);
} else if (subencoding === 128) {
const data = this._decodeRLETile(tileSize);
display.blitImage(tx, ty, tw, th, data, 0, false);
} else if (subencoding >= 130 && subencoding <= 255) {
const data = this._decodeRLEPaletteTile(subencoding - 128, tileSize);
display.blitImage(tx, ty, tw, th, data, 0, false);
} else {
throw new Error('Unknown subencoding: ' + subencoding);
}
}
}
this._length = 0;
return true;
}
_getBitsPerPixelInPalette(paletteSize) {
if (paletteSize <= 2) {
return 1;
} else if (paletteSize <= 4) {
return 2;
} else if (paletteSize <= 16) {
return 4;
}
}
_readPixels(pixels) {
let data = this._pixelBuffer;
const buffer = this._inflator.inflate(3*pixels);
for (let i = 0, j = 0; i < pixels*4; i += 4, j += 3) {
data[i] = buffer[j];
data[i + 1] = buffer[j + 1];
data[i + 2] = buffer[j + 2];
data[i + 3] = 255; // Add the Alpha
}
return data;
}
_decodePaletteTile(paletteSize, tileSize, tilew, tileh) {
const data = this._tileBuffer;
const palette = this._readPixels(paletteSize);
const bitsPerPixel = this._getBitsPerPixelInPalette(paletteSize);
const mask = (1 << bitsPerPixel) - 1;
let offset = 0;
let encoded = this._inflator.inflate(1)[0];
for (let y=0; y<tileh; y++) {
let shift = 8-bitsPerPixel;
for (let x=0; x<tilew; x++) {
if (shift<0) {
shift=8-bitsPerPixel;
encoded = this._inflator.inflate(1)[0];
}
let indexInPalette = (encoded>>shift) & mask;
data[offset] = palette[indexInPalette * 4];
data[offset + 1] = palette[indexInPalette * 4 + 1];
data[offset + 2] = palette[indexInPalette * 4 + 2];
data[offset + 3] = palette[indexInPalette * 4 + 3];
offset += 4;
shift-=bitsPerPixel;
}
if (shift<8-bitsPerPixel && y<tileh-1) {
encoded = this._inflator.inflate(1)[0];
}
}
return data;
}
_decodeRLETile(tileSize) {
const data = this._tileBuffer;
let i = 0;
while (i < tileSize) {
const pixel = this._readPixels(1);
const length = this._readRLELength();
for (let j = 0; j < length; j++) {
data[i * 4] = pixel[0];
data[i * 4 + 1] = pixel[1];
data[i * 4 + 2] = pixel[2];
data[i * 4 + 3] = pixel[3];
i++;
}
}
return data;
}
_decodeRLEPaletteTile(paletteSize, tileSize) {
const data = this._tileBuffer;
// palette
const palette = this._readPixels(paletteSize);
let offset = 0;
while (offset < tileSize) {
let indexInPalette = this._inflator.inflate(1)[0];
let length = 1;
if (indexInPalette >= 128) {
indexInPalette -= 128;
length = this._readRLELength();
}
if (indexInPalette > paletteSize) {
throw new Error('Too big index in palette: ' + indexInPalette + ', palette size: ' + paletteSize);
}
if (offset + length > tileSize) {
throw new Error('Too big rle length in palette mode: ' + length + ', allowed length is: ' + (tileSize - offset));
}
for (let j = 0; j < length; j++) {
data[offset * 4] = palette[indexInPalette * 4];
data[offset * 4 + 1] = palette[indexInPalette * 4 + 1];
data[offset * 4 + 2] = palette[indexInPalette * 4 + 2];
data[offset * 4 + 3] = palette[indexInPalette * 4 + 3];
offset++;
}
}
return data;
}
_readRLELength() {
let length = 0;
let current = 0;
do {
current = this._inflator.inflate(1)[0];
length += current;
} while (current === 255);
return length + 1;
}
}
+2 -3
View File
@@ -7,7 +7,7 @@
*/
import { deflateInit, deflate } from "../vendor/pako/lib/zlib/deflate.js";
import { Z_FULL_FLUSH } from "../vendor/pako/lib/zlib/deflate.js";
import { Z_FULL_FLUSH, Z_DEFAULT_COMPRESSION } from "../vendor/pako/lib/zlib/deflate.js";
import ZStream from "../vendor/pako/lib/zlib/zstream.js";
export default class Deflator {
@@ -15,9 +15,8 @@ export default class Deflator {
this.strm = new ZStream();
this.chunkSize = 1024 * 10 * 10;
this.outputBuffer = new Uint8Array(this.chunkSize);
this.windowBits = 5;
deflateInit(this.strm, this.windowBits);
deflateInit(this.strm, Z_DEFAULT_COMPRESSION);
}
deflate(inData) {
+25 -10
View File
@@ -15,7 +15,7 @@ export default class Display {
this._drawCtx = null;
this._renderQ = []; // queue drawing actions for in-oder rendering
this._flushing = false;
this._flushPromise = null;
// the full frame buffer (logical canvas) size
this._fbWidth = 0;
@@ -61,10 +61,6 @@ export default class Display {
this._scale = 1.0;
this._clipViewport = false;
// ===== EVENT HANDLERS =====
this.onflush = () => {}; // A flush request has finished
}
// ===== PROPERTIES =====
@@ -224,6 +220,18 @@ export default class Display {
this.viewportChangePos(0, 0);
}
getImageData() {
return this._drawCtx.getImageData(0, 0, this.width, this.height);
}
toDataURL(type, encoderOptions) {
return this._backbuffer.toDataURL(type, encoderOptions);
}
toBlob(callback, type, quality) {
return this._backbuffer.toBlob(callback, type, quality);
}
// Track what parts of the visible canvas that need updating
_damage(x, y, w, h) {
if (x < this._damageBounds.left) {
@@ -294,9 +302,14 @@ export default class Display {
flush() {
if (this._renderQ.length === 0) {
this.onflush();
return Promise.resolve();
} else {
this._flushing = true;
if (this._flushPromise === null) {
this._flushPromise = new Promise((resolve) => {
this._flushResolve = resolve;
});
}
return this._flushPromise;
}
}
@@ -505,9 +518,11 @@ export default class Display {
}
}
if (this._renderQ.length === 0 && this._flushing) {
this._flushing = false;
this.onflush();
if (this._renderQ.length === 0 &&
this._flushPromise !== null) {
this._flushResolve();
this._flushPromise = null;
this._flushResolve = null;
}
}
}
+5
View File
@@ -12,7 +12,9 @@ export const encodings = {
encodingRRE: 2,
encodingHextile: 5,
encodingTight: 7,
encodingZRLE: 16,
encodingTightPNG: -260,
encodingJPEG: 21,
pseudoEncodingQualityLevel9: -23,
pseudoEncodingQualityLevel0: -32,
@@ -20,6 +22,7 @@ export const encodings = {
pseudoEncodingLastRect: -224,
pseudoEncodingCursor: -239,
pseudoEncodingQEMUExtendedKeyEvent: -258,
pseudoEncodingQEMULedEvent: -261,
pseudoEncodingDesktopName: -307,
pseudoEncodingExtendedDesktopSize: -308,
pseudoEncodingXvp: -309,
@@ -38,7 +41,9 @@ export function encodingName(num) {
case encodings.encodingRRE: return "RRE";
case encodings.encodingHextile: return "Hextile";
case encodings.encodingTight: return "Tight";
case encodings.encodingZRLE: return "ZRLE";
case encodings.encodingTightPNG: return "TightPNG";
case encodings.encodingJPEG: return "JPEG";
default: return "[unknown encoding " + num + "]";
}
}
+1 -2
View File
@@ -14,9 +14,8 @@ export default class Inflate {
this.strm = new ZStream();
this.chunkSize = 1024 * 10 * 10;
this.strm.output = new Uint8Array(this.chunkSize);
this.windowBits = 5;
inflateInit(this.strm, this.windowBits);
inflateInit(this.strm);
}
setInput(data) {
+29 -11
View File
@@ -36,7 +36,7 @@ export default class Keyboard {
// ===== PRIVATE METHODS =====
_sendKeyEvent(keysym, code, down) {
_sendKeyEvent(keysym, code, down, numlock = null, capslock = null) {
if (down) {
this._keyDownList[code] = keysym;
} else {
@@ -48,8 +48,8 @@ export default class Keyboard {
}
Log.Debug("onkeyevent " + (down ? "down" : "up") +
", keysym: " + keysym, ", code: " + code);
this.onkeyevent(keysym, code, down);
", keysym: " + keysym, ", code: " + code, + ", numlock: " + numlock + ", capslock: " + capslock);
this.onkeyevent(keysym, code, down, numlock, capslock);
}
_getKeyCode(e) {
@@ -86,6 +86,14 @@ export default class Keyboard {
_handleKeyDown(e) {
const code = this._getKeyCode(e);
let keysym = KeyboardUtil.getKeysym(e);
let numlock = e.getModifierState('NumLock');
let capslock = e.getModifierState('CapsLock');
// getModifierState for NumLock is not supported on mac and ios and always returns false.
// Set to null to indicate unknown/unsupported instead.
if (browser.isMac() || browser.isIOS()) {
numlock = null;
}
// Windows doesn't have a proper AltGr, but handles it using
// fake Ctrl+Alt. However the remote end might not be Windows,
@@ -107,7 +115,7 @@ export default class Keyboard {
// key to "AltGraph".
keysym = KeyTable.XK_ISO_Level3_Shift;
} else {
this._sendKeyEvent(KeyTable.XK_Control_L, "ControlLeft", true);
this._sendKeyEvent(KeyTable.XK_Control_L, "ControlLeft", true, numlock, capslock);
}
}
@@ -118,8 +126,8 @@ export default class Keyboard {
// If it's a virtual keyboard then it should be
// sufficient to just send press and release right
// after each other
this._sendKeyEvent(keysym, code, true);
this._sendKeyEvent(keysym, code, false);
this._sendKeyEvent(keysym, code, true, numlock, capslock);
this._sendKeyEvent(keysym, code, false, numlock, capslock);
}
stopEvent(e);
@@ -153,13 +161,23 @@ export default class Keyboard {
keysym = this._keyDownList[code];
}
// macOS doesn't send proper key releases if a key is pressed
// while meta is held down
if ((browser.isMac() || browser.isIOS()) &&
(e.metaKey && code !== 'MetaLeft' && code !== 'MetaRight')) {
this._sendKeyEvent(keysym, code, true, numlock, capslock);
this._sendKeyEvent(keysym, code, false, numlock, capslock);
stopEvent(e);
return;
}
// macOS doesn't send proper key events for modifiers, only
// state change events. That gets extra confusing for CapsLock
// which toggles on each press, but not on release. So pretend
// it was a quick press and release of the button.
if ((browser.isMac() || browser.isIOS()) && (code === 'CapsLock')) {
this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', true);
this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', false);
this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', true, numlock, capslock);
this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', false, numlock, capslock);
stopEvent(e);
return;
}
@@ -172,8 +190,8 @@ export default class Keyboard {
KeyTable.XK_Hiragana,
KeyTable.XK_Romaji ];
if (browser.isWindows() && jpBadKeys.includes(keysym)) {
this._sendKeyEvent(keysym, code, true);
this._sendKeyEvent(keysym, code, false);
this._sendKeyEvent(keysym, code, true, numlock, capslock);
this._sendKeyEvent(keysym, code, false, numlock, capslock);
stopEvent(e);
return;
}
@@ -189,7 +207,7 @@ export default class Keyboard {
return;
}
this._sendKeyEvent(keysym, code, true);
this._sendKeyEvent(keysym, code, true, numlock, capslock);
}
_handleKeyUp(e) {
+312
View File
@@ -0,0 +1,312 @@
import { encodeUTF8 } from './util/strings.js';
import EventTargetMixin from './util/eventtarget.js';
import legacyCrypto from './crypto/crypto.js';
class RA2Cipher {
constructor() {
this._cipher = null;
this._counter = new Uint8Array(16);
}
async setKey(key) {
this._cipher = await legacyCrypto.importKey(
"raw", key, { name: "AES-EAX" }, false, ["encrypt, decrypt"]);
}
async makeMessage(message) {
const ad = new Uint8Array([(message.length & 0xff00) >>> 8, message.length & 0xff]);
const encrypted = await legacyCrypto.encrypt({
name: "AES-EAX",
iv: this._counter,
additionalData: ad,
}, this._cipher, message);
for (let i = 0; i < 16 && this._counter[i]++ === 255; i++);
const res = new Uint8Array(message.length + 2 + 16);
res.set(ad);
res.set(encrypted, 2);
return res;
}
async receiveMessage(length, encrypted) {
const ad = new Uint8Array([(length & 0xff00) >>> 8, length & 0xff]);
const res = await legacyCrypto.decrypt({
name: "AES-EAX",
iv: this._counter,
additionalData: ad,
}, this._cipher, encrypted);
for (let i = 0; i < 16 && this._counter[i]++ === 255; i++);
return res;
}
}
export default class RSAAESAuthenticationState extends EventTargetMixin {
constructor(sock, getCredentials) {
super();
this._hasStarted = false;
this._checkSock = null;
this._checkCredentials = null;
this._approveServerResolve = null;
this._sockReject = null;
this._credentialsReject = null;
this._approveServerReject = null;
this._sock = sock;
this._getCredentials = getCredentials;
}
_waitSockAsync(len) {
return new Promise((resolve, reject) => {
const hasData = () => !this._sock.rQwait('RA2', len);
if (hasData()) {
resolve();
} else {
this._checkSock = () => {
if (hasData()) {
resolve();
this._checkSock = null;
this._sockReject = null;
}
};
this._sockReject = reject;
}
});
}
_waitApproveKeyAsync() {
return new Promise((resolve, reject) => {
this._approveServerResolve = resolve;
this._approveServerReject = reject;
});
}
_waitCredentialsAsync(subtype) {
const hasCredentials = () => {
if (subtype === 1 && this._getCredentials().username !== undefined &&
this._getCredentials().password !== undefined) {
return true;
} else if (subtype === 2 && this._getCredentials().password !== undefined) {
return true;
}
return false;
};
return new Promise((resolve, reject) => {
if (hasCredentials()) {
resolve();
} else {
this._checkCredentials = () => {
if (hasCredentials()) {
resolve();
this._checkCredentials = null;
this._credentialsReject = null;
}
};
this._credentialsReject = reject;
}
});
}
checkInternalEvents() {
if (this._checkSock !== null) {
this._checkSock();
}
if (this._checkCredentials !== null) {
this._checkCredentials();
}
}
approveServer() {
if (this._approveServerResolve !== null) {
this._approveServerResolve();
this._approveServerResolve = null;
}
}
disconnect() {
if (this._sockReject !== null) {
this._sockReject(new Error("disconnect normally"));
this._sockReject = null;
}
if (this._credentialsReject !== null) {
this._credentialsReject(new Error("disconnect normally"));
this._credentialsReject = null;
}
if (this._approveServerReject !== null) {
this._approveServerReject(new Error("disconnect normally"));
this._approveServerReject = null;
}
}
async negotiateRA2neAuthAsync() {
this._hasStarted = true;
// 1: Receive server public key
await this._waitSockAsync(4);
const serverKeyLengthBuffer = this._sock.rQpeekBytes(4);
const serverKeyLength = this._sock.rQshift32();
if (serverKeyLength < 1024) {
throw new Error("RA2: server public key is too short: " + serverKeyLength);
} else if (serverKeyLength > 8192) {
throw new Error("RA2: server public key is too long: " + serverKeyLength);
}
const serverKeyBytes = Math.ceil(serverKeyLength / 8);
await this._waitSockAsync(serverKeyBytes * 2);
const serverN = this._sock.rQshiftBytes(serverKeyBytes);
const serverE = this._sock.rQshiftBytes(serverKeyBytes);
const serverRSACipher = await legacyCrypto.importKey(
"raw", { n: serverN, e: serverE }, { name: "RSA-PKCS1-v1_5" }, false, ["encrypt"]);
const serverPublickey = new Uint8Array(4 + serverKeyBytes * 2);
serverPublickey.set(serverKeyLengthBuffer);
serverPublickey.set(serverN, 4);
serverPublickey.set(serverE, 4 + serverKeyBytes);
// verify server public key
let approveKey = this._waitApproveKeyAsync();
this.dispatchEvent(new CustomEvent("serververification", {
detail: { type: "RSA", publickey: serverPublickey }
}));
await approveKey;
// 2: Send client public key
const clientKeyLength = 2048;
const clientKeyBytes = Math.ceil(clientKeyLength / 8);
const clientRSACipher = (await legacyCrypto.generateKey({
name: "RSA-PKCS1-v1_5",
modulusLength: clientKeyLength,
publicExponent: new Uint8Array([1, 0, 1]),
}, true, ["encrypt"])).privateKey;
const clientExportedRSAKey = await legacyCrypto.exportKey("raw", clientRSACipher);
const clientN = clientExportedRSAKey.n;
const clientE = clientExportedRSAKey.e;
const clientPublicKey = new Uint8Array(4 + clientKeyBytes * 2);
clientPublicKey[0] = (clientKeyLength & 0xff000000) >>> 24;
clientPublicKey[1] = (clientKeyLength & 0xff0000) >>> 16;
clientPublicKey[2] = (clientKeyLength & 0xff00) >>> 8;
clientPublicKey[3] = clientKeyLength & 0xff;
clientPublicKey.set(clientN, 4);
clientPublicKey.set(clientE, 4 + clientKeyBytes);
this._sock.sQpushBytes(clientPublicKey);
this._sock.flush();
// 3: Send client random
const clientRandom = new Uint8Array(16);
window.crypto.getRandomValues(clientRandom);
const clientEncryptedRandom = await legacyCrypto.encrypt(
{ name: "RSA-PKCS1-v1_5" }, serverRSACipher, clientRandom);
const clientRandomMessage = new Uint8Array(2 + serverKeyBytes);
clientRandomMessage[0] = (serverKeyBytes & 0xff00) >>> 8;
clientRandomMessage[1] = serverKeyBytes & 0xff;
clientRandomMessage.set(clientEncryptedRandom, 2);
this._sock.sQpushBytes(clientRandomMessage);
this._sock.flush();
// 4: Receive server random
await this._waitSockAsync(2);
if (this._sock.rQshift16() !== clientKeyBytes) {
throw new Error("RA2: wrong encrypted message length");
}
const serverEncryptedRandom = this._sock.rQshiftBytes(clientKeyBytes);
const serverRandom = await legacyCrypto.decrypt(
{ name: "RSA-PKCS1-v1_5" }, clientRSACipher, serverEncryptedRandom);
if (serverRandom === null || serverRandom.length !== 16) {
throw new Error("RA2: corrupted server encrypted random");
}
// 5: Compute session keys and set ciphers
let clientSessionKey = new Uint8Array(32);
let serverSessionKey = new Uint8Array(32);
clientSessionKey.set(serverRandom);
clientSessionKey.set(clientRandom, 16);
serverSessionKey.set(clientRandom);
serverSessionKey.set(serverRandom, 16);
clientSessionKey = await window.crypto.subtle.digest("SHA-1", clientSessionKey);
clientSessionKey = new Uint8Array(clientSessionKey).slice(0, 16);
serverSessionKey = await window.crypto.subtle.digest("SHA-1", serverSessionKey);
serverSessionKey = new Uint8Array(serverSessionKey).slice(0, 16);
const clientCipher = new RA2Cipher();
await clientCipher.setKey(clientSessionKey);
const serverCipher = new RA2Cipher();
await serverCipher.setKey(serverSessionKey);
// 6: Compute and exchange hashes
let serverHash = new Uint8Array(8 + serverKeyBytes * 2 + clientKeyBytes * 2);
let clientHash = new Uint8Array(8 + serverKeyBytes * 2 + clientKeyBytes * 2);
serverHash.set(serverPublickey);
serverHash.set(clientPublicKey, 4 + serverKeyBytes * 2);
clientHash.set(clientPublicKey);
clientHash.set(serverPublickey, 4 + clientKeyBytes * 2);
serverHash = await window.crypto.subtle.digest("SHA-1", serverHash);
clientHash = await window.crypto.subtle.digest("SHA-1", clientHash);
serverHash = new Uint8Array(serverHash);
clientHash = new Uint8Array(clientHash);
this._sock.sQpushBytes(await clientCipher.makeMessage(clientHash));
this._sock.flush();
await this._waitSockAsync(2 + 20 + 16);
if (this._sock.rQshift16() !== 20) {
throw new Error("RA2: wrong server hash");
}
const serverHashReceived = await serverCipher.receiveMessage(
20, this._sock.rQshiftBytes(20 + 16));
if (serverHashReceived === null) {
throw new Error("RA2: failed to authenticate the message");
}
for (let i = 0; i < 20; i++) {
if (serverHashReceived[i] !== serverHash[i]) {
throw new Error("RA2: wrong server hash");
}
}
// 7: Receive subtype
await this._waitSockAsync(2 + 1 + 16);
if (this._sock.rQshift16() !== 1) {
throw new Error("RA2: wrong subtype");
}
let subtype = (await serverCipher.receiveMessage(
1, this._sock.rQshiftBytes(1 + 16)));
if (subtype === null) {
throw new Error("RA2: failed to authenticate the message");
}
subtype = subtype[0];
let waitCredentials = this._waitCredentialsAsync(subtype);
if (subtype === 1) {
if (this._getCredentials().username === undefined ||
this._getCredentials().password === undefined) {
this.dispatchEvent(new CustomEvent(
"credentialsrequired",
{ detail: { types: ["username", "password"] } }));
}
} else if (subtype === 2) {
if (this._getCredentials().password === undefined) {
this.dispatchEvent(new CustomEvent(
"credentialsrequired",
{ detail: { types: ["password"] } }));
}
} else {
throw new Error("RA2: wrong subtype");
}
await waitCredentials;
let username;
if (subtype === 1) {
username = encodeUTF8(this._getCredentials().username).slice(0, 255);
} else {
username = "";
}
const password = encodeUTF8(this._getCredentials().password).slice(0, 255);
const credentials = new Uint8Array(username.length + password.length + 2);
credentials[0] = username.length;
credentials[username.length + 1] = password.length;
for (let i = 0; i < username.length; i++) {
credentials[i + 1] = username.charCodeAt(i);
}
for (let i = 0; i < password.length; i++) {
credentials[username.length + 2 + i] = password.charCodeAt(i);
}
this._sock.sQpushBytes(await clientCipher.makeMessage(credentials));
this._sock.flush();
}
get hasStarted() {
return this._hasStarted;
}
set hasStarted(s) {
this._hasStarted = s;
}
}
+592 -331
View File
File diff suppressed because it is too large Load Diff
+56 -7
View File
@@ -77,27 +77,76 @@ export const hasScrollbarGutter = _hasScrollbarGutter;
* It's better to use feature detection than platform detection.
*/
/* OS */
export function isMac() {
return navigator && !!(/mac/i).exec(navigator.platform);
return !!(/mac/i).exec(navigator.platform);
}
export function isWindows() {
return navigator && !!(/win/i).exec(navigator.platform);
return !!(/win/i).exec(navigator.platform);
}
export function isIOS() {
return navigator &&
(!!(/ipad/i).exec(navigator.platform) ||
return (!!(/ipad/i).exec(navigator.platform) ||
!!(/iphone/i).exec(navigator.platform) ||
!!(/ipod/i).exec(navigator.platform));
}
export function isAndroid() {
/* Android sets navigator.platform to Linux :/ */
return !!navigator.userAgent.match('Android ');
}
export function isChromeOS() {
/* ChromeOS sets navigator.platform to Linux :/ */
return !!navigator.userAgent.match(' CrOS ');
}
/* Browser */
export function isSafari() {
return navigator && (navigator.userAgent.indexOf('Safari') !== -1 &&
navigator.userAgent.indexOf('Chrome') === -1);
return !!navigator.userAgent.match('Safari/...') &&
!navigator.userAgent.match('Chrome/...') &&
!navigator.userAgent.match('Chromium/...') &&
!navigator.userAgent.match('Epiphany/...');
}
export function isFirefox() {
return navigator && !!(/firefox/i).exec(navigator.userAgent);
return !!navigator.userAgent.match('Firefox/...') &&
!navigator.userAgent.match('Seamonkey/...');
}
export function isChrome() {
return !!navigator.userAgent.match('Chrome/...') &&
!navigator.userAgent.match('Chromium/...') &&
!navigator.userAgent.match('Edg/...') &&
!navigator.userAgent.match('OPR/...');
}
export function isChromium() {
return !!navigator.userAgent.match('Chromium/...');
}
export function isOpera() {
return !!navigator.userAgent.match('OPR/...');
}
export function isEdge() {
return !!navigator.userAgent.match('Edg/...');
}
/* Engine */
export function isGecko() {
return !!navigator.userAgent.match('Gecko/...');
}
export function isWebKit() {
return !!navigator.userAgent.match('AppleWebKit/...') &&
!navigator.userAgent.match('Chrome/...');
}
export function isBlink() {
return !!navigator.userAgent.match('Chrome/...');
}
+4
View File
@@ -18,6 +18,10 @@ export default class Cursor {
this._canvas.style.position = 'fixed';
this._canvas.style.zIndex = '65535';
this._canvas.style.pointerEvents = 'none';
// Safari on iOS can select the cursor image
// https://bugs.webkit.org/show_bug.cgi?id=249223
this._canvas.style.userSelect = 'none';
this._canvas.style.WebkitUserSelect = 'none';
// Can't use "display" because of Firefox bug #1445997
this._canvas.style.visibility = 'hidden';
}
+70 -58
View File
@@ -94,27 +94,7 @@ export default class Websock {
return "unknown";
}
get sQ() {
return this._sQ;
}
get rQ() {
return this._rQ;
}
get rQi() {
return this._rQi;
}
set rQi(val) {
this._rQi = val;
}
// Receive Queue
get rQlen() {
return this._rQlen - this._rQi;
}
rQpeek8() {
return this._rQ[this._rQi];
}
@@ -141,42 +121,47 @@ export default class Websock {
for (let byte = bytes - 1; byte >= 0; byte--) {
res += this._rQ[this._rQi++] << (byte * 8);
}
return res;
return res >>> 0;
}
rQshiftStr(len) {
if (typeof(len) === 'undefined') { len = this.rQlen; }
let str = "";
// Handle large arrays in steps to avoid long strings on the stack
for (let i = 0; i < len; i += 4096) {
let part = this.rQshiftBytes(Math.min(4096, len - i));
let part = this.rQshiftBytes(Math.min(4096, len - i), false);
str += String.fromCharCode.apply(null, part);
}
return str;
}
rQshiftBytes(len) {
if (typeof(len) === 'undefined') { len = this.rQlen; }
rQshiftBytes(len, copy=true) {
this._rQi += len;
return new Uint8Array(this._rQ.buffer, this._rQi - len, len);
if (copy) {
return this._rQ.slice(this._rQi - len, this._rQi);
} else {
return this._rQ.subarray(this._rQi - len, this._rQi);
}
}
rQshiftTo(target, len) {
if (len === undefined) { len = this.rQlen; }
// TODO: make this just use set with views when using a ArrayBuffer to store the rQ
target.set(new Uint8Array(this._rQ.buffer, this._rQi, len));
this._rQi += len;
}
rQslice(start, end = this.rQlen) {
return new Uint8Array(this._rQ.buffer, this._rQi + start, end - start);
rQpeekBytes(len, copy=true) {
if (copy) {
return this._rQ.slice(this._rQi, this._rQi + len);
} else {
return this._rQ.subarray(this._rQi, this._rQi + len);
}
}
// Check to see if we must wait for 'num' bytes (default to FBU.bytes)
// to be available in the receive queue. Return true if we need to
// wait (and possibly print a debug message), otherwise false.
rQwait(msg, num, goback) {
if (this.rQlen < num) {
if (this._rQlen - this._rQi < num) {
if (goback) {
if (this._rQi < goback) {
throw new Error("rQwait cannot backup " + goback + " bytes");
@@ -190,21 +175,56 @@ export default class Websock {
// Send Queue
sQpush8(num) {
this._sQensureSpace(1);
this._sQ[this._sQlen++] = num;
}
sQpush16(num) {
this._sQensureSpace(2);
this._sQ[this._sQlen++] = (num >> 8) & 0xff;
this._sQ[this._sQlen++] = (num >> 0) & 0xff;
}
sQpush32(num) {
this._sQensureSpace(4);
this._sQ[this._sQlen++] = (num >> 24) & 0xff;
this._sQ[this._sQlen++] = (num >> 16) & 0xff;
this._sQ[this._sQlen++] = (num >> 8) & 0xff;
this._sQ[this._sQlen++] = (num >> 0) & 0xff;
}
sQpushString(str) {
let bytes = str.split('').map(chr => chr.charCodeAt(0));
this.sQpushBytes(new Uint8Array(bytes));
}
sQpushBytes(bytes) {
for (let offset = 0;offset < bytes.length;) {
this._sQensureSpace(1);
let chunkSize = this._sQbufferSize - this._sQlen;
if (chunkSize > bytes.length - offset) {
chunkSize = bytes.length - offset;
}
this._sQ.set(bytes.subarray(offset, chunkSize), this._sQlen);
this._sQlen += chunkSize;
offset += chunkSize;
}
}
flush() {
if (this._sQlen > 0 && this.readyState === 'open') {
this._websocket.send(this._encodeMessage());
this._websocket.send(new Uint8Array(this._sQ.buffer, 0, this._sQlen));
this._sQlen = 0;
}
}
send(arr) {
this._sQ.set(arr, this._sQlen);
this._sQlen += arr.length;
this.flush();
}
sendString(str) {
this.send(str.split('').map(chr => chr.charCodeAt(0)));
_sQensureSpace(bytes) {
if (this._sQbufferSize - this._sQlen < bytes) {
this.flush();
}
}
// Event Handlers
@@ -283,17 +303,12 @@ export default class Websock {
}
// private methods
_encodeMessage() {
// Put in a binary arraybuffer
// according to the spec, you can send ArrayBufferViews with the send method
return new Uint8Array(this._sQ.buffer, 0, this._sQlen);
}
// We want to move all the unread data to the start of the queue,
// e.g. compacting.
// The function also expands the receive que if needed, and for
// performance reasons we combine these two actions to avoid
// unneccessary copying.
// unnecessary copying.
_expandCompactRQ(minFit) {
// if we're using less than 1/8th of the buffer even with the incoming bytes, compact in place
// instead of resizing
@@ -309,7 +324,7 @@ export default class Websock {
// we don't want to grow unboundedly
if (this._rQbufferSize > MAX_RQ_GROW_SIZE) {
this._rQbufferSize = MAX_RQ_GROW_SIZE;
if (this._rQbufferSize - this.rQlen < minFit) {
if (this._rQbufferSize - (this._rQlen - this._rQi) < minFit) {
throw new Error("Receive Queue buffer exceeded " + MAX_RQ_GROW_SIZE + " bytes, and the new message could not fit");
}
}
@@ -327,25 +342,22 @@ export default class Websock {
}
// push arraybuffer values onto the end of the receive que
_DecodeMessage(data) {
const u8 = new Uint8Array(data);
_recvMessage(e) {
if (this._rQlen == this._rQi) {
// All data has now been processed, this means we
// can reset the receive queue.
this._rQlen = 0;
this._rQi = 0;
}
const u8 = new Uint8Array(e.data);
if (u8.length > this._rQbufferSize - this._rQlen) {
this._expandCompactRQ(u8.length);
}
this._rQ.set(u8, this._rQlen);
this._rQlen += u8.length;
}
_recvMessage(e) {
this._DecodeMessage(e.data);
if (this.rQlen > 0) {
if (this._rQlen - this._rQi > 0) {
this._eventHandlers.message();
if (this._rQlen == this._rQi) {
// All data has now been processed, this means we
// can reset the receive queue.
this._rQlen = 0;
this._rQi = 0;
}
} else {
Log.Debug("Ignoring empty message");
}
-6
View File
@@ -81,9 +81,3 @@ None
| blitImage | (x, y, width, height, arr, offset, from_queue) | Blit pixels (of R,G,B,A) to the display
| drawImage | (img, x, y) | Draw image and track damage
| autoscale | (containerWidth, containerHeight) | Scale the display
### 2.2.3 Callbacks
| name | parameters | description
| ------- | ---------- | ------------
| onflush | () | A display flush has been requested and we are now ready to resume FBU processing
+366 -205
View File
@@ -16,60 +16,13 @@ protocol stream.
### Properties
`viewOnly`
- Is a `boolean` indicating if any events (e.g. key presses or mouse
movement) should be prevented from being sent to the server.
Disabled by default.
`focusOnClick`
- Is a `boolean` indicating if keyboard focus should automatically be
moved to the remote session when a `mousedown` or `touchstart`
event is received. Enabled by default.
`clipViewport`
- Is a `boolean` indicating if the remote session should be clipped
to its container. When disabled scrollbars will be shown to handle
the resulting overflow. Disabled by default.
`dragViewport`
- Is a `boolean` indicating if mouse events should control the
relative position of a clipped remote session. Only relevant if
`clipViewport` is enabled. Disabled by default.
`scaleViewport`
- Is a `boolean` indicating if the remote session should be scaled
locally so it fits its container. When disabled it will be centered
if the remote session is smaller than its container, or handled
according to `clipViewport` if it is larger. Disabled by default.
`resizeSession`
- Is a `boolean` indicating if a request to resize the remote session
should be sent whenever the container changes dimensions. Disabled
by default.
`showDotCursor`
- Is a `boolean` indicating whether a dot cursor should be shown
instead of a zero-sized or fully-transparent cursor if the server
sets such invisible cursor. Disabled by default.
`background`
- Is a valid CSS [background](https://developer.mozilla.org/en-US/docs/Web/CSS/background)
style value indicating which background style should be applied
to the element containing the remote session screen. The default value is `rgb(40, 40, 40)`
(solid gray color).
- Is a valid CSS [background][mdn-bg] style value indicating which
background style should be applied to the element containing the
remote session screen. The default value is `rgb(40, 40, 40)` (solid
gray color).
`qualityLevel`
- Is an `int` in range `[0-9]` controlling the desired JPEG quality.
Value `0` implies low quality and `9` implies high quality.
Default value is `6`.
`compressionLevel`
- Is an `int` in range `[0-9]` controlling the desired compression
level. Value `0` means no compression. Level 1 uses a minimum of CPU
resources and achieves weak compression ratios, while level 9 offers
best compression but is slow in terms of CPU consumption on the server
side. Use high levels with very slow network connections.
Default value is `2`.
[mdn-bg]: https://developer.mozilla.org/en-US/docs/Web/CSS/background
`capabilities` *Read only*
- Is an `Object` indicating which optional extensions are available
@@ -80,62 +33,122 @@ protocol stream.
| -------- | --------- | -----------
| `power` | `boolean` | Machine power control is available
`clippingViewport` *Read only*
- Is a `boolean` indicating if the remote session is currently being
clipped to its container. Only relevant if `clipViewport` is
enabled.
`clipViewport`
- Is a `boolean` indicating if the remote session should be clipped
to its container. When disabled scrollbars will be shown to handle
the resulting overflow. Disabled by default.
`compressionLevel`
- Is an `int` in range `[0-9]` controlling the desired compression
level. Value `0` means no compression. Level 1 uses a minimum of CPU
resources and achieves weak compression ratios, while level 9 offers
best compression but is slow in terms of CPU consumption on the server
side. Use high levels with very slow network connections.
Default value is `2`.
`dragViewport`
- Is a `boolean` indicating if mouse events should control the
relative position of a clipped remote session. Only relevant if
`clipViewport` is enabled. Disabled by default.
`focusOnClick`
- Is a `boolean` indicating if keyboard focus should automatically be
moved to the remote session when a `mousedown` or `touchstart`
event is received. Enabled by default.
`qualityLevel`
- Is an `int` in range `[0-9]` controlling the desired JPEG quality.
Value `0` implies low quality and `9` implies high quality.
Default value is `6`.
`resizeSession`
- Is a `boolean` indicating if a request to resize the remote session
should be sent whenever the container changes dimensions. Disabled
by default.
`scaleViewport`
- Is a `boolean` indicating if the remote session should be scaled
locally so it fits its container. When disabled it will be centered
if the remote session is smaller than its container, or handled
according to `clipViewport` if it is larger. Disabled by default.
`showDotCursor`
- Is a `boolean` indicating whether a dot cursor should be shown
instead of a zero-sized or fully-transparent cursor if the server
sets such invisible cursor. Disabled by default.
`viewOnly`
- Is a `boolean` indicating if any events (e.g. key presses or mouse
movement) should be prevented from being sent to the server.
Disabled by default.
### Events
[`connect`](#connect)
- The `connect` event is fired when the `RFB` object has completed
the connection and handshaking with the server.
[`disconnect`](#disconnected)
- The `disconnect` event is fired when the `RFB` object disconnects.
[`credentialsrequired`](#credentialsrequired)
- The `credentialsrequired` event is fired when more credentials must
be given to continue.
[`securityfailure`](#securityfailure)
- The `securityfailure` event is fired when the security negotiation
with the server fails.
[`clipboard`](#clipboard)
- The `clipboard` event is fired when clipboard data is received from
the server.
[`bell`](#bell)
- The `bell` event is fired when a audible bell request is received
from the server.
[`desktopname`](#desktopname)
- The `desktopname` event is fired when the remote desktop name
changes.
[`capabilities`](#capabilities)
- The `capabilities` event is fired when `RFB.capabilities` is
updated.
[`clipboard`](#clipboard)
- The `clipboard` event is fired when clipboard data is received from
the server.
[`clippingviewport`](#clippingviewport)
- The `clippingviewport` event is fired when `RFB.clippingViewport` is
updated.
[`connect`](#connect)
- The `connect` event is fired when the `RFB` object has completed
the connection and handshaking with the server.
[`credentialsrequired`](#credentialsrequired)
- The `credentialsrequired` event is fired when more credentials must
be given to continue.
[`desktopname`](#desktopname)
- The `desktopname` event is fired when the remote desktop name
changes.
[`disconnect`](#disconnect)
- The `disconnect` event is fired when the `RFB` object disconnects.
[`securityfailure`](#securityfailure)
- The `securityfailure` event is fired when the security negotiation
with the server fails.
[`serververification`](#serververification)
- The `serververification` event is fired when the server identity
must be confirmed by the user.
### Methods
[`RFB.disconnect()`](#rfbdisconnect)
- Disconnect from the server.
[`RFB.sendCredentials()`](#rfbsendcredentials)
- Send credentials to server. Should be called after the
[`credentialsrequired`](#credentialsrequired) event has fired.
[`RFB.sendKey()`](#rfbsendKey)
- Send a key event.
[`RFB.sendCtrlAltDel()`](#rfbsendctrlaltdel)
- Send Ctrl-Alt-Del key sequence.
[`RFB.focus()`](#rfbfocus)
- Move keyboard focus to the remote session.
[`RFB.approveServer()`](#rfbapproveserver)
- Proceed connecting to the server. Should be called after the
[`serververification`](#serververification) event has fired and the
user has verified the identity of the server.
[`RFB.blur()`](#rfbblur)
- Remove keyboard focus from the remote session.
[`RFB.machineShutdown()`](#rfbmachineshutdown)
- Request a shutdown of the remote machine.
[`RFB.clipboardPasteFrom()`](#rfbclipboardpastefrom)
- Send clipboard contents to server.
[`RFB.disconnect()`](#rfbdisconnect)
- Disconnect from the server.
[`RFB.focus()`](#rfbfocus)
- Move keyboard focus to the remote session.
[`RFB.getImageData()`](#rfbgetimagedata)
- Return the current content of the screen as an ImageData array.
[`RFB.machineReboot()`](#rfbmachinereboot)
- Request a reboot of the remote machine.
@@ -143,8 +156,24 @@ protocol stream.
[`RFB.machineReset()`](#rfbmachinereset)
- Request a reset of the remote machine.
[`RFB.clipboardPasteFrom()`](#rfbclipboardPasteFrom)
- Send clipboard contents to server.
[`RFB.machineShutdown()`](#rfbmachineshutdown)
- Request a shutdown of the remote machine.
[`RFB.sendCredentials()`](#rfbsendcredentials)
- Send credentials to server. Should be called after the
[`credentialsrequired`](#credentialsrequired) event has fired.
[`RFB.sendCtrlAltDel()`](#rfbsendctrlaltdel)
- Send Ctrl-Alt-Del key sequence.
[`RFB.sendKey()`](#rfbsendkey)
- Send a key event.
[`RFB.toBlob()`](#rfbtoblob)
- Return the current content of the screen as Blob encoded image file.
[`RFB.toDataURL()`](#rfbtodataurl)
- Return the current content of the screen as data-url encoded image file.
### Details
@@ -155,17 +184,22 @@ connection to a specified VNC server.
##### Syntax
let rfb = new RFB( target, url [, options] );
```js
new RFB(target, urlOrChannel);
new RFB(target, urlOrChannel, options);
```
###### Parameters
**`target`**
- A block [`HTMLElement`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement)
that specifies where the `RFB` object should attach itself. The
existing contents of the `HTMLElement` will be untouched, but new
elements will be added during the lifetime of the `RFB` object.
- A block [`HTMLElement`][mdn-elem] that specifies where the `RFB`
object should attach itself. The existing contents of the
`HTMLElement` will be untouched, but new elements will be added
during the lifetime of the `RFB` object.
**`urlOrDataChannel`**
[mdn-elem]: https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement
**`urlOrChannel`**
- A `DOMString` specifying the VNC server to connect to. This must be
a valid WebSocket URL. This can also be a `WebSocket` or `RTCDataChannel`.
@@ -198,12 +232,48 @@ connection to a specified VNC server.
- An `Array` of `DOMString`s specifying the sub-protocols to use
in the WebSocket connection. Empty by default.
#### bell
The `bell` event is fired when the server has requested an audible
bell.
#### capabilities
The `capabilities` event is fired whenever an entry is added or removed
from `RFB.capabilities`. The `detail` property is an `Object` with the
property `capabilities` containing the new value of `RFB.capabilities`.
#### clippingviewport
The `clippingviewport` event is fired whenever `RFB.clippingViewport`
changes between `true` and `false`. The `detail` property is a `boolean`
with the new value of `RFB.clippingViewport`.
#### clipboard
The `clipboard` event is fired when the server has sent clipboard data.
The `detail` property is an `Object` containing the property `text`
which is a `DOMString` with the clipboard data.
#### credentialsrequired
The `credentialsrequired` event is fired when the server requests more
credentials than were specified to [`RFB()`](#rfb-1). The `detail`
property is an `Object` containing the property `types` which is an
`Array` of `DOMString` listing the credentials that are required.
#### connect
The `connect` event is fired after all the handshaking with the server
is completed and the connection is fully established. After this event
the `RFB` object is ready to recieve graphics updates and to send input.
#### desktopname
The `desktopname` event is fired when the name of the remote desktop
changes. The `detail` property is an `Object` with the property `name`
which is a `DOMString` specifying the new name.
#### disconnect
The `disconnect` event is fired when the connection has been
@@ -212,13 +282,6 @@ property `clean`. `clean` is a `boolean` indicating if the termination
was clean or not. In the event of an unexpected termination or an error
`clean` will be set to false.
#### credentialsrequired
The `credentialsrequired` event is fired when the server requests more
credentials than were specified to [`RFB()`](#rfb-1). The `detail`
property is an `Object` containing the property `types` which is an
`Array` of `DOMString` listing the credentials that are required.
#### securityfailure
The `securityfailure` event is fired when the handshaking process with
@@ -230,8 +293,7 @@ property is an `Object` containing the following properties:
| `status` | `long` | The failure status code
| `reason` | `DOMString` | The **optional** reason for the failure
The property `status` corresponds to the
[SecurityResult](https://github.com/rfbproto/rfbproto/blob/master/rfbproto.rst#securityresult)
The property `status` corresponds to the [SecurityResult][rfb-secresult]
status code in cases of failure. A status of zero will not be sent in
this event since that indicates a successful security handshaking
process. The optional property `reason` is provided by the server and
@@ -239,96 +301,33 @@ thus the language of the string is not known. However most servers will
probably send English strings. The server can choose to not send a
reason and in these cases the `reason` property will be omitted.
#### clipboard
[rfb-secresult]: https://github.com/rfbproto/rfbproto/blob/master/rfbproto.rst#securityresult
The `clipboard` event is fired when the server has sent clipboard data.
The `detail` property is an `Object` containing the property `text`
which is a `DOMString` with the clipboard data.
#### serververification
#### bell
The `serververification` event is fired when the server provides
information that allows the user to verify that it is the correct server
and protect against a man-in-the-middle attack. The `detail` property is
an `Object` containing the property `type` which is a `DOMString`
specifying which type of information the server has provided. Other
properties are also available, depending on the value of `type`:
The `bell` event is fired when the server has requested an audible
bell.
`"RSA"`
- The server identity is verified using just a RSA key. The property
`publickey` is a `Uint8Array` containing the public key in a unsigned
big endian representation.
#### desktopname
#### RFB.approveServer()
The `desktopname` event is fired when the name of the remote desktop
changes. The `detail` property is an `Object` with the property `name`
which is a `DOMString` specifying the new name.
#### capabilities
The `capabilities` event is fired whenever an entry is added or removed
from `RFB.capabilities`. The `detail` property is an `Object` with the
property `capabilities` containing the new value of `RFB.capabilities`.
#### RFB.disconnect()
The `RFB.disconnect()` method is used to disconnect from the currently
connected server.
The `RFB.approveServer()` method is used to signal that the user has
verified the server identity provided in a `serververification` event
and that the connection can continue.
##### Syntax
RFB.disconnect( );
#### RFB.sendCredentials()
The `RFB.sendCredentials()` method is used to provide the missing
credentials after a `credentialsrequired` event has been fired.
##### Syntax
RFB.sendCredentials( credentials );
###### Parameters
**`credentials`**
- An `Object` specifying the credentials to provide to the server
when authenticating. See [`RFB()`](#rfb-1) for details.
#### RFB.sendKey()
The `RFB.sendKey()` method is used to send a key event to the server.
##### Syntax
RFB.sendKey( keysym, code [, down] );
###### Parameters
**`keysym`**
- A `long` specifying the RFB keysym to send. Can be `0` if a valid
**`code`** is specified.
**`code`**
- A `DOMString` specifying the physical key to send. Valid values are
those that can be specified to
[`KeyboardEvent.code`](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code).
If the physical key cannot be determined then `null` shall be
specified.
**`down`** *Optional*
- A `boolean` specifying if a press or a release event should be
sent. If omitted then both a press and release event are sent.
#### RFB.sendCtrlAltDel()
The `RFB.sendCtrlAltDel()` method is used to send the key sequence
*left Control*, *left Alt*, *Delete*. This is a convenience wrapper
around [`RFB.sendKey()`](#rfbsendkey).
##### Syntax
RFB.sendCtrlAltDel( );
#### RFB.focus()
The `RFB.focus()` method sets the keyboard focus on the remote session.
Keyboard events will be sent to the remote server after this point.
##### Syntax
RFB.focus( );
```js
RFB.approveServer();
```
#### RFB.blur()
@@ -338,17 +337,70 @@ point.
##### Syntax
RFB.blur( );
```js
RFB.blur();
```
#### RFB.machineShutdown()
#### RFB.clipboardPasteFrom()
The `RFB.machineShutdown()` method is used to request to shut down the
remote machine. The capability `power` must be set for this method to
have any effect.
The `RFB.clipboardPasteFrom()` method is used to send clipboard data
to the remote server.
##### Syntax
RFB.machineShutdown( );
```js
RFB.clipboardPasteFrom(text);
```
###### Parameters
**`text`**
- A `DOMString` specifying the clipboard data to send.
#### RFB.disconnect()
The `RFB.disconnect()` method is used to disconnect from the currently
connected server.
##### Syntax
```js
RFB.disconnect();
```
#### RFB.focus()
The `RFB.focus()` method sets the keyboard focus on the remote session.
Keyboard events will be sent to the remote server after this point.
##### Syntax
```js
RFB.focus();
RFB.focus(options);
```
###### Parameters
**`options`** *Optional*
- A `object` providing options to control how the focus will be
performed. Please see [`HTMLElement.focus()`][mdn-focus] for
available options.
[mdn-focus]: https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus
#### RFB.getImageData()
The `RFB.getImageData()` method is used to return the current content of
the screen encoded as [`ImageData`][mdn-imagedata].
[mdn-imagedata]: https://developer.mozilla.org/en-US/docs/Web/API/ImageData
##### Syntax
```js
RFB.getImageData();
```
#### RFB.machineReboot()
@@ -358,7 +410,9 @@ to have any effect.
##### Syntax
RFB.machineReboot( );
```js
RFB.machineReboot();
```
#### RFB.machineReset()
@@ -368,18 +422,125 @@ to have any effect.
##### Syntax
RFB.machineReset( );
```js
RFB.machineReset();
```
#### RFB.clipboardPasteFrom()
#### RFB.machineShutdown()
The `RFB.clipboardPasteFrom()` method is used to send clipboard data
to the remote server.
The `RFB.machineShutdown()` method is used to request to shut down the
remote machine. The capability `power` must be set for this method to
have any effect.
##### Syntax
RFB.clipboardPasteFrom( text );
```js
RFB.machineShutdown();
```
#### RFB.sendCredentials()
The `RFB.sendCredentials()` method is used to provide the missing
credentials after a `credentialsrequired` event has been fired.
##### Syntax
```js
RFB.sendCredentials(credentials);
```
###### Parameters
**`text`**
- A `DOMString` specifying the clipboard data to send.
**`credentials`**
- An `Object` specifying the credentials to provide to the server
when authenticating. See [`RFB()`](#rfb-1) for details.
#### RFB.sendCtrlAltDel()
The `RFB.sendCtrlAltDel()` method is used to send the key sequence
*left Control*, *left Alt*, *Delete*. This is a convenience wrapper
around [`RFB.sendKey()`](#rfbsendkey).
##### Syntax
```js
RFB.sendCtrlAltDel();
```
#### RFB.sendKey()
The `RFB.sendKey()` method is used to send a key event to the server.
##### Syntax
```js
RFB.sendKey(keysym, code);
RFB.sendKey(keysym, code, down);
```
###### Parameters
**`keysym`**
- A `long` specifying the RFB keysym to send. Can be `0` if a valid
**`code`** is specified.
**`code`**
- A `DOMString` specifying the physical key to send. Valid values are
those that can be specified to [`KeyboardEvent.code`][mdn-keycode].
If the physical key cannot be determined then `null` shall be
specified.
[mdn-keycode]: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code
**`down`** *Optional*
- A `boolean` specifying if a press or a release event should be
sent. If omitted then both a press and release event are sent.
#### RFB.toBlob()
The `RFB.toBlob()` method is used to return the current content of the
screen encoded as [`Blob`][mdn-blob].
[mdn-blob]: https://developer.mozilla.org/en-US/docs/Web/API/Blob
##### Syntax
```js
RFB.toBlob(callback);
RFB.toBlob(callback, type);
RFB.toBlob(callback, type, quality);
```
###### Parameters
**`callback`**
- A callback function which will receive the resulting
[`Blob`][mdn-blob] as the single argument
**`type`** *Optional*
- A string indicating the requested MIME type of the image
**`quality`** *Optional*
- A number between 0 and 1 indicating the image quality.
#### RFB.toDataURL()
The `RFB.toDataURL()` method is used to return the current content of the
screen encoded as a data URL that could for example be put in the `src` attribute
of an `img` tag.
##### Syntax
```js
RFB.toDataURL();
RFB.toDataURL(type);
RFB.toDataURL(type, encoderOptions);
```
###### Parameters
**`type`** *Optional*
- A string indicating the requested MIME type of the image
**`encoderOptions`** *Optional*
- A number between 0 and 1 indicating the image quality.
+3 -3
View File
@@ -3,13 +3,13 @@
.SH NAME
novnc_proxy - noVNC proxy server
.SH SYNOPSIS
.B novnc_proxy [--listen PORT] [--vnc VNC_HOST:PORT] [--cert CERT] [--ssl-only]
.B novnc_proxy [--listen [HOST:]PORT] [--vnc VNC_HOST:PORT] [--cert CERT] [--ssl-only]
Starts the WebSockets proxy and a mini-webserver and
provides a cut-and-paste URL to go to.
--listen PORT Port for proxy/webserver to listen on
Default: 6080
--listen [HOST:]PORT Port for proxy/webserver to listen on
Default: 6080 (on all interfaces)
--vnc VNC_HOST:PORT VNC server host:port proxy target
Default: localhost:5900
--cert CERT Path to combined cert/key file, or just
+26 -37
View File
@@ -1,6 +1,6 @@
{
"name": "@novnc/novnc",
"version": "1.3.0-beta",
"version": "1.4.0",
"description": "An HTML5 VNC client",
"browser": "lib/rfb",
"directories": {
@@ -21,7 +21,7 @@
"scripts": {
"lint": "eslint app core po/po2js po/xgettext-html tests utils",
"test": "karma start karma.conf.js",
"prepublish": "node ./utils/use_require.js --clean"
"prepublish": "node ./utils/convert.js --clean"
},
"repository": {
"type": "git",
@@ -29,8 +29,6 @@
},
"author": "Joel Martin <github@martintribe.org> (https://github.com/kanaka)",
"contributors": [
"Solly Ross <sross@redhat.com> (https://github.com/directxman12)",
"Peter Åstrand <astrand@cendio.se> (https://github.com/astrand)",
"Samuel Mannehed <samuel@cendio.se> (https://github.com/samhed)",
"Pierre Ossman <ossman@cendio.se> (https://github.com/CendioOssman)"
],
@@ -40,39 +38,30 @@
},
"homepage": "https://github.com/novnc/noVNC",
"devDependencies": {
"@babel/core": "*",
"@babel/plugin-syntax-dynamic-import": "*",
"@babel/plugin-transform-modules-commonjs": "*",
"@babel/preset-env": "*",
"@babel/cli": "*",
"babel-plugin-import-redirect": "*",
"browserify": "*",
"babelify": "*",
"core-js": "*",
"chai": "*",
"commander": "*",
"es-module-loader": "*",
"eslint": "*",
"fs-extra": "*",
"jsdom": "*",
"karma": "*",
"karma-mocha": "*",
"karma-chrome-launcher": "*",
"@chiragrupani/karma-chromium-edge-launcher": "*",
"karma-firefox-launcher": "*",
"karma-ie-launcher": "*",
"karma-mocha-reporter": "*",
"karma-safari-launcher": "*",
"karma-script-launcher": "*",
"karma-sinon-chai": "*",
"mocha": "*",
"node-getopt": "*",
"po2json": "*",
"requirejs": "*",
"rollup": "*",
"rollup-plugin-node-resolve": "*",
"sinon": "*",
"sinon-chai": "*"
"@babel/core": "latest",
"@babel/preset-env": "latest",
"babel-plugin-import-redirect": "latest",
"browserify": "latest",
"chai": "latest",
"commander": "latest",
"eslint": "latest",
"fs-extra": "latest",
"jsdom": "latest",
"karma": "latest",
"karma-mocha": "latest",
"karma-chrome-launcher": "latest",
"@chiragrupani/karma-chromium-edge-launcher": "latest",
"karma-firefox-launcher": "latest",
"karma-ie-launcher": "latest",
"karma-mocha-reporter": "latest",
"karma-safari-launcher": "latest",
"karma-script-launcher": "latest",
"karma-sinon-chai": "latest",
"mocha": "latest",
"node-getopt": "latest",
"po2json": "latest",
"sinon": "latest",
"sinon-chai": "latest"
},
"dependencies": {},
"keywords": [
+6 -5
View File
@@ -1,7 +1,8 @@
all:
.PHONY: update-po update-js update-pot
.PHONY: FORCE
LINGUAS := cs de el es fr ja ko nl pl pt_BR ru sv tr zh_CN zh_TW
LINGUAS := cs de el es fr it ja ko nl pl pt_BR ru sv tr zh_CN zh_TW
VERSION := $(shell grep '"version"' ../package.json | cut -d '"' -f 4)
@@ -11,10 +12,10 @@ JSONFILES := $(addprefix ../app/locale/,$(addsuffix .json,$(LINGUAS)))
update-po: $(POFILES)
update-js: $(JSONFILES)
%.po: noVNC.pot
msgmerge --update --lang=$* $@ $<
../app/locale/%.json: %.po
./po2js $< $@
%.po: FORCE
msgmerge --update --lang=$* $@ noVNC.pot
../app/locale/%.json: FORCE
./po2js $*.po $@
update-pot:
xgettext --output=noVNC.js.pot \
+22 -21
View File
@@ -3,14 +3,15 @@
# Copyright (C) 2021 The noVNC Authors
# This file is distributed under the same license as the noVNC package.
# Jose <jose.matsuda@canada.ca>, 2021.
# Lowxorx <lowxorx@lahan.fr>, 2022.
#
msgid ""
msgstr ""
"Project-Id-Version: noVNC 1.2.0\n"
"Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
"POT-Creation-Date: 2020-07-03 16:11+0200\n"
"PO-Revision-Date: 2021-05-05 20:19-0400\n"
"Last-Translator: Jose <jose.matsuda@canada.ca>\n"
"PO-Revision-Date: 2022-04-25 23:40+0200\n"
"Last-Translator: Lowxorx <lowxorx@lahan.fr>\n"
"Language-Team: French\n"
"Language: fr\n"
"MIME-Version: 1.0\n"
@@ -40,15 +41,15 @@ msgstr "Doit définir l'hôte"
#: ../app/ui.js:1090
msgid "Connected (encrypted) to "
msgstr "Connecté (crypté) à "
msgstr "Connecté (chiffré) à "
#: ../app/ui.js:1092
msgid "Connected (unencrypted) to "
msgstr "Connecté (non crypté) à "
msgstr "Connecté (non chiffré) à "
#: ../app/ui.js:1115
msgid "Something went wrong, connection is closed"
msgstr "Quelque chose est arrivé, la connexion est fermée"
msgstr "Quelque chose s'est mal passé, la connexion a été fermée"
#: ../app/ui.js:1118
msgid "Failed to connect to server"
@@ -60,7 +61,7 @@ msgstr "Déconnecté"
#: ../app/ui.js:1143
msgid "New connection has been rejected with reason: "
msgstr "Une nouvelle connexion a été rejetée avec raison: "
msgstr "Une nouvelle connexion a été rejetée avec motif : "
#: ../app/ui.js:1146
msgid "New connection has been rejected"
@@ -72,7 +73,7 @@ msgstr "Les identifiants sont requis"
#: ../vnc.html:74
msgid "noVNC encountered an error:"
msgstr "noVNC a rencontré une erreur:"
msgstr "noVNC a rencontré une erreur :"
#: ../vnc.html:84
msgid "Hide/Show the control bar"
@@ -84,7 +85,7 @@ msgstr "Faire glisser"
#: ../vnc.html:91
msgid "Move/Drag Viewport"
msgstr "Déplacer/faire glisser Viewport"
msgstr "Déplacer/faire glisser le Viewport"
#: ../vnc.html:97
msgid "Keyboard"
@@ -204,7 +205,7 @@ msgstr "Clip à fenêtre"
#: ../vnc.html:185
msgid "Scaling Mode:"
msgstr "Mode mise à l'échelle:"
msgstr "Mode mise à l'échelle :"
#: ../vnc.html:187
msgid "None"
@@ -224,15 +225,15 @@ msgstr "Avancé"
#: ../vnc.html:197
msgid "Quality:"
msgstr "Qualité:"
msgstr "Qualité :"
#: ../vnc.html:201
msgid "Compression level:"
msgstr "Niveau de compression:"
msgstr "Niveau de compression :"
#: ../vnc.html:206
msgid "Repeater ID:"
msgstr "ID Répéteur:"
msgstr "ID Répéteur :"
#: ../vnc.html:210
msgid "WebSocket"
@@ -240,19 +241,19 @@ msgstr "WebSocket"
#: ../vnc.html:213
msgid "Encrypt"
msgstr "Crypter"
msgstr "Chiffrer"
#: ../vnc.html:216
msgid "Host:"
msgstr "Hôte:"
msgstr "Hôte :"
#: ../vnc.html:220
msgid "Port:"
msgstr "Port:"
msgstr "Port :"
#: ../vnc.html:224
msgid "Path:"
msgstr "Chemin:"
msgstr "Chemin :"
#: ../vnc.html:231
msgid "Automatic Reconnect"
@@ -260,7 +261,7 @@ msgstr "Reconnecter automatiquemen"
#: ../vnc.html:234
msgid "Reconnect Delay (ms):"
msgstr "Délai de reconnexion (ms):"
msgstr "Délai de reconnexion (ms) :"
#: ../vnc.html:239
msgid "Show Dot when No Cursor"
@@ -268,11 +269,11 @@ msgstr "Afficher le point lorsqu'il n'y a pas de curseur"
#: ../vnc.html:244
msgid "Logging:"
msgstr "Se connecter:"
msgstr "Se connecter :"
#: ../vnc.html:253
msgid "Version:"
msgstr "Version:"
msgstr "Version :"
#: ../vnc.html:261
msgid "Disconnect"
@@ -284,11 +285,11 @@ msgstr "Connecter"
#: ../vnc.html:290
msgid "Username:"
msgstr "Nom d'utilisateur:"
msgstr "Nom d'utilisateur :"
#: ../vnc.html:294
msgid "Password:"
msgstr "Mot de passe:"
msgstr "Mot de passe :"
#: ../vnc.html:298
msgid "Send Credentials"
+300
View File
@@ -0,0 +1,300 @@
# Italian translations for noVNC
# Traduzione italiana di noVNC
# Copyright (C) 2022 The noVNC Authors
# This file is distributed under the same license as the noVNC package.
# Fabio Fantoni <fabio.fantoni@m2r.biz>, 2022.
#
msgid ""
msgstr ""
"Project-Id-Version: noVNC 1.3.0\n"
"Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
"POT-Creation-Date: 2021-08-27 16:03+0200\n"
"PO-Revision-Date: 2022-09-08 13:27+0200\n"
"Last-Translator: Fabio Fantoni <fabio.fantoni@m2r.biz>\n"
"Language-Team: Italian\n"
"Language: it\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 3.1.1\n"
#: ../app/ui.js:400
msgid "Connecting..."
msgstr "Connessione in corso..."
#: ../app/ui.js:407
msgid "Disconnecting..."
msgstr "Disconnessione..."
#: ../app/ui.js:413
msgid "Reconnecting..."
msgstr "Riconnessione..."
#: ../app/ui.js:418
msgid "Internal error"
msgstr "Errore interno"
#: ../app/ui.js:1009
msgid "Must set host"
msgstr "Devi impostare l'host"
#: ../app/ui.js:1091
msgid "Connected (encrypted) to "
msgstr "Connesso (crittografato) a "
#: ../app/ui.js:1093
msgid "Connected (unencrypted) to "
msgstr "Connesso (non crittografato) a"
#: ../app/ui.js:1116
msgid "Something went wrong, connection is closed"
msgstr "Qualcosa è andato storto, la connessione è stata chiusa"
#: ../app/ui.js:1119
msgid "Failed to connect to server"
msgstr "Impossibile connettersi al server"
#: ../app/ui.js:1129
msgid "Disconnected"
msgstr "Disconnesso"
#: ../app/ui.js:1144
msgid "New connection has been rejected with reason: "
msgstr "La nuova connessione è stata rifiutata con motivo: "
#: ../app/ui.js:1147
msgid "New connection has been rejected"
msgstr "La nuova connessione è stata rifiutata"
#: ../app/ui.js:1182
msgid "Credentials are required"
msgstr "Le credenziali sono obbligatorie"
#: ../vnc.html:61
msgid "noVNC encountered an error:"
msgstr "noVNC ha riscontrato un errore:"
#: ../vnc.html:71
msgid "Hide/Show the control bar"
msgstr "Nascondi/Mostra la barra di controllo"
#: ../vnc.html:78
msgid "Drag"
msgstr ""
#: ../vnc.html:78
msgid "Move/Drag Viewport"
msgstr ""
#: ../vnc.html:84
msgid "Keyboard"
msgstr "Tastiera"
#: ../vnc.html:84
msgid "Show Keyboard"
msgstr "Mostra tastiera"
#: ../vnc.html:89
msgid "Extra keys"
msgstr "Tasti Aggiuntivi"
#: ../vnc.html:89
msgid "Show Extra Keys"
msgstr "Mostra Tasti Aggiuntivi"
#: ../vnc.html:94
msgid "Ctrl"
msgstr "Ctrl"
#: ../vnc.html:94
msgid "Toggle Ctrl"
msgstr "Tieni premuto Ctrl"
#: ../vnc.html:97
msgid "Alt"
msgstr "Alt"
#: ../vnc.html:97
msgid "Toggle Alt"
msgstr "Tieni premuto Alt"
#: ../vnc.html:100
msgid "Toggle Windows"
msgstr "Tieni premuto Windows"
#: ../vnc.html:100
msgid "Windows"
msgstr "Windows"
#: ../vnc.html:103
msgid "Send Tab"
msgstr "Invia Tab"
#: ../vnc.html:103
msgid "Tab"
msgstr "Tab"
#: ../vnc.html:106
msgid "Esc"
msgstr "Esc"
#: ../vnc.html:106
msgid "Send Escape"
msgstr "Invia Esc"
#: ../vnc.html:109
msgid "Ctrl+Alt+Del"
msgstr "Ctrl+Alt+Canc"
#: ../vnc.html:109
msgid "Send Ctrl-Alt-Del"
msgstr "Invia Ctrl-Alt-Canc"
#: ../vnc.html:116
msgid "Shutdown/Reboot"
msgstr "Spegnimento/Riavvio"
#: ../vnc.html:116
msgid "Shutdown/Reboot..."
msgstr "Spegnimento/Riavvio..."
#: ../vnc.html:122
msgid "Power"
msgstr "Alimentazione"
#: ../vnc.html:124
msgid "Shutdown"
msgstr "Spegnimento"
#: ../vnc.html:125
msgid "Reboot"
msgstr "Riavvio"
#: ../vnc.html:126
msgid "Reset"
msgstr "Reset"
#: ../vnc.html:131 ../vnc.html:137
msgid "Clipboard"
msgstr "Clipboard"
#: ../vnc.html:141
msgid "Clear"
msgstr "Pulisci"
#: ../vnc.html:147
msgid "Fullscreen"
msgstr "Schermo intero"
#: ../vnc.html:152 ../vnc.html:159
msgid "Settings"
msgstr "Impostazioni"
#: ../vnc.html:162
msgid "Shared Mode"
msgstr "Modalità condivisa"
#: ../vnc.html:165
msgid "View Only"
msgstr "Sola Visualizzazione"
#: ../vnc.html:169
msgid "Clip to Window"
msgstr ""
#: ../vnc.html:172
msgid "Scaling Mode:"
msgstr "Modalità di ridimensionamento:"
#: ../vnc.html:174
msgid "None"
msgstr "Nessuna"
#: ../vnc.html:175
msgid "Local Scaling"
msgstr "Ridimensionamento Locale"
#: ../vnc.html:176
msgid "Remote Resizing"
msgstr "Ridimensionamento Remoto"
#: ../vnc.html:181
msgid "Advanced"
msgstr "Avanzate"
#: ../vnc.html:184
msgid "Quality:"
msgstr "Qualità:"
#: ../vnc.html:188
msgid "Compression level:"
msgstr "Livello Compressione:"
#: ../vnc.html:193
msgid "Repeater ID:"
msgstr "ID Ripetitore:"
#: ../vnc.html:197
msgid "WebSocket"
msgstr "WebSocket"
#: ../vnc.html:200
msgid "Encrypt"
msgstr "Crittografa"
#: ../vnc.html:203
msgid "Host:"
msgstr "Host:"
#: ../vnc.html:207
msgid "Port:"
msgstr "Porta:"
#: ../vnc.html:211
msgid "Path:"
msgstr "Percorso:"
#: ../vnc.html:218
msgid "Automatic Reconnect"
msgstr "Riconnessione Automatica"
#: ../vnc.html:221
msgid "Reconnect Delay (ms):"
msgstr "Ritardo Riconnessione (ms):"
#: ../vnc.html:226
msgid "Show Dot when No Cursor"
msgstr "Mostra Punto quando Nessun Cursore"
#: ../vnc.html:231
msgid "Logging:"
msgstr ""
#: ../vnc.html:240
msgid "Version:"
msgstr "Versione:"
#: ../vnc.html:248
msgid "Disconnect"
msgstr "Disconnetti"
#: ../vnc.html:267
msgid "Connect"
msgstr "Connetti"
#: ../vnc.html:277
msgid "Username:"
msgstr "Utente:"
#: ../vnc.html:281
msgid "Password:"
msgstr "Password:"
#: ../vnc.html:285
msgid "Send Credentials"
msgstr "Invia Credenziale"
#: ../vnc.html:295
msgid "Cancel"
msgstr "Annulla"
+119 -80
View File
@@ -8,8 +8,8 @@ msgid ""
msgstr ""
"Project-Id-Version: noVNC 1.1.0\n"
"Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
"POT-Creation-Date: 2020-07-03 16:11+0200\n"
"PO-Revision-Date: 2021-01-15 12:37+0900\n"
"POT-Creation-Date: 2022-12-27 15:24+0100\n"
"PO-Revision-Date: 2023-03-21 12:42+0900\n"
"Last-Translator: nnn1590 <nnn1590@nnn1590.org>\n"
"Language-Team: Japanese\n"
"Language: ja\n"
@@ -19,286 +19,325 @@ msgstr ""
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Poedit 2.3\n"
#: ../app/ui.js:394
#: ../app/ui.js:69
msgid "HTTPS is required for full functionality"
msgstr "すべての機能を使用するにはHTTPS接続が必要です"
#: ../app/ui.js:410
msgid "Connecting..."
msgstr "接続しています..."
#: ../app/ui.js:401
#: ../app/ui.js:417
msgid "Disconnecting..."
msgstr "切断しています..."
#: ../app/ui.js:407
#: ../app/ui.js:423
msgid "Reconnecting..."
msgstr "再接続しています..."
#: ../app/ui.js:412
#: ../app/ui.js:428
msgid "Internal error"
msgstr "内部エラー"
#: ../app/ui.js:1008
#: ../app/ui.js:1026
msgid "Must set host"
msgstr "ホストを設定する必要があります"
#: ../app/ui.js:1090
#: ../app/ui.js:1110
msgid "Connected (encrypted) to "
msgstr "接続しました (暗号化済み): "
#: ../app/ui.js:1092
#: ../app/ui.js:1112
msgid "Connected (unencrypted) to "
msgstr "接続しました (暗号化されていません): "
#: ../app/ui.js:1115
#: ../app/ui.js:1135
msgid "Something went wrong, connection is closed"
msgstr "何らかの問題で、接続が閉じられました"
#: ../app/ui.js:1118
#: ../app/ui.js:1138
msgid "Failed to connect to server"
msgstr "サーバーへの接続に失敗しました"
#: ../app/ui.js:1128
#: ../app/ui.js:1150
msgid "Disconnected"
msgstr "切断しました"
#: ../app/ui.js:1143
#: ../app/ui.js:1165
msgid "New connection has been rejected with reason: "
msgstr "新規接続は次の理由で拒否されました: "
#: ../app/ui.js:1146
#: ../app/ui.js:1168
msgid "New connection has been rejected"
msgstr "新規接続は拒否されました"
#: ../app/ui.js:1181
#: ../app/ui.js:1234
msgid "Credentials are required"
msgstr "資格情報が必要です"
#: ../vnc.html:74
#: ../vnc.html:57
msgid "noVNC encountered an error:"
msgstr "noVNC でエラーが発生しました:"
#: ../vnc.html:84
#: ../vnc.html:67
msgid "Hide/Show the control bar"
msgstr "コントロールバーを隠す/表示する"
#: ../vnc.html:91
#: ../vnc.html:76
msgid "Drag"
msgstr "ドラッグ"
#: ../vnc.html:91
#: ../vnc.html:76
msgid "Move/Drag Viewport"
msgstr "ビューポートを移動/ドラッグ"
#: ../vnc.html:97
#: ../vnc.html:82
msgid "Keyboard"
msgstr "キーボード"
#: ../vnc.html:97
#: ../vnc.html:82
msgid "Show Keyboard"
msgstr "キーボードを表示"
#: ../vnc.html:102
#: ../vnc.html:87
msgid "Extra keys"
msgstr "追加キー"
#: ../vnc.html:102
#: ../vnc.html:87
msgid "Show Extra Keys"
msgstr "追加キーを表示"
#: ../vnc.html:107
#: ../vnc.html:92
msgid "Ctrl"
msgstr "Ctrl"
#: ../vnc.html:107
#: ../vnc.html:92
msgid "Toggle Ctrl"
msgstr "Ctrl キーを切り替え"
msgstr "Ctrl キーをトグル"
#: ../vnc.html:110
#: ../vnc.html:95
msgid "Alt"
msgstr "Alt"
#: ../vnc.html:110
#: ../vnc.html:95
msgid "Toggle Alt"
msgstr "Alt キーを切り替え"
msgstr "Alt キーをトグル"
#: ../vnc.html:113
#: ../vnc.html:98
msgid "Toggle Windows"
msgstr "Windows キーを切り替え"
msgstr "Windows キーをトグル"
#: ../vnc.html:113
#: ../vnc.html:98
msgid "Windows"
msgstr "Windows"
#: ../vnc.html:116
#: ../vnc.html:101
msgid "Send Tab"
msgstr "Tab キーを送信"
#: ../vnc.html:116
#: ../vnc.html:101
msgid "Tab"
msgstr "Tab"
#: ../vnc.html:119
#: ../vnc.html:104
msgid "Esc"
msgstr "Esc"
#: ../vnc.html:119
#: ../vnc.html:104
msgid "Send Escape"
msgstr "Escape キーを送信"
#: ../vnc.html:122
#: ../vnc.html:107
msgid "Ctrl+Alt+Del"
msgstr "Ctrl+Alt+Del"
#: ../vnc.html:122
#: ../vnc.html:107
msgid "Send Ctrl-Alt-Del"
msgstr "Ctrl-Alt-Del を送信"
#: ../vnc.html:129
#: ../vnc.html:114
msgid "Shutdown/Reboot"
msgstr "シャットダウン/再起動"
#: ../vnc.html:129
#: ../vnc.html:114
msgid "Shutdown/Reboot..."
msgstr "シャットダウン/再起動..."
#: ../vnc.html:135
#: ../vnc.html:120
msgid "Power"
msgstr "電源"
#: ../vnc.html:137
#: ../vnc.html:122
msgid "Shutdown"
msgstr "シャットダウン"
#: ../vnc.html:138
#: ../vnc.html:123
msgid "Reboot"
msgstr "再起動"
#: ../vnc.html:139
#: ../vnc.html:124
msgid "Reset"
msgstr "リセット"
#: ../vnc.html:144 ../vnc.html:150
#: ../vnc.html:129 ../vnc.html:135
msgid "Clipboard"
msgstr "クリップボード"
#: ../vnc.html:154
msgid "Clear"
msgstr "クリア"
#: ../vnc.html:137
msgid "Edit clipboard content in the textarea below."
msgstr "以下の入力欄からクリップボードの内容を編集できます。"
#: ../vnc.html:160
msgid "Fullscreen"
#: ../vnc.html:145
msgid "Full Screen"
msgstr "全画面表示"
#: ../vnc.html:165 ../vnc.html:172
#: ../vnc.html:150 ../vnc.html:156
msgid "Settings"
msgstr "設定"
#: ../vnc.html:175
#: ../vnc.html:160
msgid "Shared Mode"
msgstr "共有モード"
#: ../vnc.html:178
#: ../vnc.html:163
msgid "View Only"
msgstr "表示のみ"
msgstr "表示専用"
#: ../vnc.html:182
#: ../vnc.html:167
msgid "Clip to Window"
msgstr "ウィンドウにクリップ"
#: ../vnc.html:185
#: ../vnc.html:170
msgid "Scaling Mode:"
msgstr "スケーリングモード:"
#: ../vnc.html:187
#: ../vnc.html:172
msgid "None"
msgstr "なし"
#: ../vnc.html:188
#: ../vnc.html:173
msgid "Local Scaling"
msgstr "ローカルスケーリング"
#: ../vnc.html:189
#: ../vnc.html:174
msgid "Remote Resizing"
msgstr "リモートでリサイズ"
#: ../vnc.html:194
#: ../vnc.html:179
msgid "Advanced"
msgstr "高度"
#: ../vnc.html:197
#: ../vnc.html:182
msgid "Quality:"
msgstr "品質:"
#: ../vnc.html:201
#: ../vnc.html:186
msgid "Compression level:"
msgstr "圧縮レベル:"
#: ../vnc.html:206
#: ../vnc.html:191
msgid "Repeater ID:"
msgstr "リピーター ID:"
#: ../vnc.html:210
#: ../vnc.html:195
msgid "WebSocket"
msgstr "WebSocket"
#: ../vnc.html:213
#: ../vnc.html:198
msgid "Encrypt"
msgstr "暗号化"
#: ../vnc.html:216
#: ../vnc.html:201
msgid "Host:"
msgstr "ホスト:"
#: ../vnc.html:220
#: ../vnc.html:205
msgid "Port:"
msgstr "ポート:"
#: ../vnc.html:224
#: ../vnc.html:209
msgid "Path:"
msgstr "パス:"
#: ../vnc.html:231
#: ../vnc.html:216
msgid "Automatic Reconnect"
msgstr "自動再接続"
#: ../vnc.html:234
#: ../vnc.html:219
msgid "Reconnect Delay (ms):"
msgstr "再接続する遅延 (ミリ秒):"
#: ../vnc.html:239
#: ../vnc.html:224
msgid "Show Dot when No Cursor"
msgstr "カーソルがないときにドットを表示"
msgstr "カーソルがないときにドットを表示する"
#: ../vnc.html:244
#: ../vnc.html:229
msgid "Logging:"
msgstr "ロギング:"
#: ../vnc.html:253
#: ../vnc.html:238
msgid "Version:"
msgstr "バージョン:"
#: ../vnc.html:261
#: ../vnc.html:246
msgid "Disconnect"
msgstr "切断"
#: ../vnc.html:280
#: ../vnc.html:269
msgid "Connect"
msgstr "接続"
#: ../vnc.html:290
#: ../vnc.html:278
msgid "Server identity"
msgstr "サーバーの識別情報"
#: ../vnc.html:281
msgid "The server has provided the following identifying information:"
msgstr "サーバーは以下の識別情報を提供しています:"
#: ../vnc.html:285
msgid "Fingerprint:"
msgstr "フィンガープリント:"
#: ../vnc.html:288
msgid ""
"Please verify that the information is correct and press \"Approve\". "
"Otherwise press \"Reject\"."
msgstr ""
"この情報が正しい場合は「承認」を、そうでない場合は「拒否」を押してく"
"ださい。"
#: ../vnc.html:293
msgid "Approve"
msgstr "承認"
#: ../vnc.html:294
msgid "Reject"
msgstr "拒否"
#: ../vnc.html:302
msgid "Credentials"
msgstr "資格情報"
#: ../vnc.html:306
msgid "Username:"
msgstr "ユーザー名:"
#: ../vnc.html:294
#: ../vnc.html:310
msgid "Password:"
msgstr "パスワード:"
#: ../vnc.html:298
#: ../vnc.html:314
msgid "Send Credentials"
msgstr "資格情報を送信"
#: ../vnc.html:308
#: ../vnc.html:323
msgid "Cancel"
msgstr "キャンセル"
#~ msgid "Clear"
#~ msgstr "クリア"
#~ msgid "Password is required"
#~ msgstr "パスワードが必要です"
+108 -74
View File
@@ -6,9 +6,9 @@
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: noVNC 1.3.0\n"
"Project-Id-Version: noVNC 1.4.0\n"
"Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
"POT-Creation-Date: 2021-08-27 16:03+0200\n"
"POT-Creation-Date: 2022-12-27 15:24+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -17,282 +17,316 @@ msgstr ""
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"
#: ../app/ui.js:400
#: ../app/ui.js:69
msgid "HTTPS is required for full functionality"
msgstr ""
#: ../app/ui.js:410
msgid "Connecting..."
msgstr ""
#: ../app/ui.js:407
#: ../app/ui.js:417
msgid "Disconnecting..."
msgstr ""
#: ../app/ui.js:413
#: ../app/ui.js:423
msgid "Reconnecting..."
msgstr ""
#: ../app/ui.js:418
#: ../app/ui.js:428
msgid "Internal error"
msgstr ""
#: ../app/ui.js:1009
#: ../app/ui.js:1026
msgid "Must set host"
msgstr ""
#: ../app/ui.js:1091
#: ../app/ui.js:1110
msgid "Connected (encrypted) to "
msgstr ""
#: ../app/ui.js:1093
#: ../app/ui.js:1112
msgid "Connected (unencrypted) to "
msgstr ""
#: ../app/ui.js:1116
#: ../app/ui.js:1135
msgid "Something went wrong, connection is closed"
msgstr ""
#: ../app/ui.js:1119
#: ../app/ui.js:1138
msgid "Failed to connect to server"
msgstr ""
#: ../app/ui.js:1129
#: ../app/ui.js:1150
msgid "Disconnected"
msgstr ""
#: ../app/ui.js:1144
#: ../app/ui.js:1165
msgid "New connection has been rejected with reason: "
msgstr ""
#: ../app/ui.js:1147
#: ../app/ui.js:1168
msgid "New connection has been rejected"
msgstr ""
#: ../app/ui.js:1182
#: ../app/ui.js:1234
msgid "Credentials are required"
msgstr ""
#: ../vnc.html:61
#: ../vnc.html:57
msgid "noVNC encountered an error:"
msgstr ""
#: ../vnc.html:71
#: ../vnc.html:67
msgid "Hide/Show the control bar"
msgstr ""
#: ../vnc.html:78
#: ../vnc.html:76
msgid "Drag"
msgstr ""
#: ../vnc.html:78
#: ../vnc.html:76
msgid "Move/Drag Viewport"
msgstr ""
#: ../vnc.html:84
#: ../vnc.html:82
msgid "Keyboard"
msgstr ""
#: ../vnc.html:84
#: ../vnc.html:82
msgid "Show Keyboard"
msgstr ""
#: ../vnc.html:89
#: ../vnc.html:87
msgid "Extra keys"
msgstr ""
#: ../vnc.html:89
#: ../vnc.html:87
msgid "Show Extra Keys"
msgstr ""
#: ../vnc.html:94
#: ../vnc.html:92
msgid "Ctrl"
msgstr ""
#: ../vnc.html:94
#: ../vnc.html:92
msgid "Toggle Ctrl"
msgstr ""
#: ../vnc.html:97
#: ../vnc.html:95
msgid "Alt"
msgstr ""
#: ../vnc.html:97
#: ../vnc.html:95
msgid "Toggle Alt"
msgstr ""
#: ../vnc.html:100
#: ../vnc.html:98
msgid "Toggle Windows"
msgstr ""
#: ../vnc.html:100
#: ../vnc.html:98
msgid "Windows"
msgstr ""
#: ../vnc.html:103
#: ../vnc.html:101
msgid "Send Tab"
msgstr ""
#: ../vnc.html:103
#: ../vnc.html:101
msgid "Tab"
msgstr ""
#: ../vnc.html:106
#: ../vnc.html:104
msgid "Esc"
msgstr ""
#: ../vnc.html:106
#: ../vnc.html:104
msgid "Send Escape"
msgstr ""
#: ../vnc.html:109
#: ../vnc.html:107
msgid "Ctrl+Alt+Del"
msgstr ""
#: ../vnc.html:109
#: ../vnc.html:107
msgid "Send Ctrl-Alt-Del"
msgstr ""
#: ../vnc.html:116
#: ../vnc.html:114
msgid "Shutdown/Reboot"
msgstr ""
#: ../vnc.html:116
#: ../vnc.html:114
msgid "Shutdown/Reboot..."
msgstr ""
#: ../vnc.html:122
#: ../vnc.html:120
msgid "Power"
msgstr ""
#: ../vnc.html:124
#: ../vnc.html:122
msgid "Shutdown"
msgstr ""
#: ../vnc.html:125
#: ../vnc.html:123
msgid "Reboot"
msgstr ""
#: ../vnc.html:126
#: ../vnc.html:124
msgid "Reset"
msgstr ""
#: ../vnc.html:131 ../vnc.html:137
#: ../vnc.html:129 ../vnc.html:135
msgid "Clipboard"
msgstr ""
#: ../vnc.html:141
msgid "Clear"
#: ../vnc.html:137
msgid "Edit clipboard content in the textarea below."
msgstr ""
#: ../vnc.html:147
msgid "Fullscreen"
#: ../vnc.html:145
msgid "Full Screen"
msgstr ""
#: ../vnc.html:152 ../vnc.html:159
#: ../vnc.html:150 ../vnc.html:156
msgid "Settings"
msgstr ""
#: ../vnc.html:162
#: ../vnc.html:160
msgid "Shared Mode"
msgstr ""
#: ../vnc.html:165
#: ../vnc.html:163
msgid "View Only"
msgstr ""
#: ../vnc.html:169
#: ../vnc.html:167
msgid "Clip to Window"
msgstr ""
#: ../vnc.html:172
#: ../vnc.html:170
msgid "Scaling Mode:"
msgstr ""
#: ../vnc.html:174
#: ../vnc.html:172
msgid "None"
msgstr ""
#: ../vnc.html:175
#: ../vnc.html:173
msgid "Local Scaling"
msgstr ""
#: ../vnc.html:176
#: ../vnc.html:174
msgid "Remote Resizing"
msgstr ""
#: ../vnc.html:181
#: ../vnc.html:179
msgid "Advanced"
msgstr ""
#: ../vnc.html:184
#: ../vnc.html:182
msgid "Quality:"
msgstr ""
#: ../vnc.html:188
#: ../vnc.html:186
msgid "Compression level:"
msgstr ""
#: ../vnc.html:193
#: ../vnc.html:191
msgid "Repeater ID:"
msgstr ""
#: ../vnc.html:197
#: ../vnc.html:195
msgid "WebSocket"
msgstr ""
#: ../vnc.html:200
#: ../vnc.html:198
msgid "Encrypt"
msgstr ""
#: ../vnc.html:203
#: ../vnc.html:201
msgid "Host:"
msgstr ""
#: ../vnc.html:207
#: ../vnc.html:205
msgid "Port:"
msgstr ""
#: ../vnc.html:211
#: ../vnc.html:209
msgid "Path:"
msgstr ""
#: ../vnc.html:218
#: ../vnc.html:216
msgid "Automatic Reconnect"
msgstr ""
#: ../vnc.html:221
#: ../vnc.html:219
msgid "Reconnect Delay (ms):"
msgstr ""
#: ../vnc.html:226
#: ../vnc.html:224
msgid "Show Dot when No Cursor"
msgstr ""
#: ../vnc.html:231
#: ../vnc.html:229
msgid "Logging:"
msgstr ""
#: ../vnc.html:240
#: ../vnc.html:238
msgid "Version:"
msgstr ""
#: ../vnc.html:248
#: ../vnc.html:246
msgid "Disconnect"
msgstr ""
#: ../vnc.html:267
#: ../vnc.html:269
msgid "Connect"
msgstr ""
#: ../vnc.html:277
msgid "Username:"
#: ../vnc.html:278
msgid "Server identity"
msgstr ""
#: ../vnc.html:281
msgid "Password:"
msgid "The server has provided the following identifying information:"
msgstr ""
#: ../vnc.html:285
msgid "Fingerprint:"
msgstr ""
#: ../vnc.html:288
msgid ""
"Please verify that the information is correct and press \"Approve\". "
"Otherwise press \"Reject\"."
msgstr ""
#: ../vnc.html:293
msgid "Approve"
msgstr ""
#: ../vnc.html:294
msgid "Reject"
msgstr ""
#: ../vnc.html:302
msgid "Credentials"
msgstr ""
#: ../vnc.html:306
msgid "Username:"
msgstr ""
#: ../vnc.html:310
msgid "Password:"
msgstr ""
#: ../vnc.html:314
msgid "Send Credentials"
msgstr ""
#: ../vnc.html:295
#: ../vnc.html:323
msgid "Cancel"
msgstr ""
+101 -105
View File
@@ -6,11 +6,11 @@
#
msgid ""
msgstr ""
"Project-Id-Version: noVNC 1.1.0\n"
"Project-Id-Version: noVNC 1.3.0\n"
"Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
"POT-Creation-Date: 2019-02-26 14:53+0400\n"
"PO-Revision-Date: 2019-02-17 17:29+0400\n"
"Last-Translator: Dmitriy Shweew <shweew@it-advisor.ru>\n"
"POT-Creation-Date: 2021-08-27 16:03+0200\n"
"PO-Revision-Date: 2021-09-09 10:29+0400\n"
"Last-Translator: Nia Remez <nia.remez@cendio.com>\n"
"Language-Team: Russian\n"
"Language: ru\n"
"MIME-Version: 1.0\n"
@@ -21,286 +21,282 @@ msgstr ""
"X-Generator: Poedit 2.2.1\n"
"X-Poedit-Flags-xgettext: --add-comments\n"
#: ../app/ui.js:387
#: ../app/ui.js:400
msgid "Connecting..."
msgstr "Подключение..."
#: ../app/ui.js:394
#: ../app/ui.js:407
msgid "Disconnecting..."
msgstr "Отключение..."
#: ../app/ui.js:400
#: ../app/ui.js:413
msgid "Reconnecting..."
msgstr "Переподключение..."
#: ../app/ui.js:405
#: ../app/ui.js:418
msgid "Internal error"
msgstr "Внутренняя ошибка"
#: ../app/ui.js:995
#: ../app/ui.js:1009
msgid "Must set host"
msgstr "Задайте имя сервера или IP"
#: ../app/ui.js:1077
#: ../app/ui.js:1091
msgid "Connected (encrypted) to "
msgstr "Подключено (с шифрованием) к "
#: ../app/ui.js:1079
#: ../app/ui.js:1093
msgid "Connected (unencrypted) to "
msgstr "Подключено (без шифрования) к "
#: ../app/ui.js:1102
#: ../app/ui.js:1116
msgid "Something went wrong, connection is closed"
msgstr "Что-то пошло не так, подключение разорвано"
#: ../app/ui.js:1105
#: ../app/ui.js:1119
msgid "Failed to connect to server"
msgstr "Ошибка подключения к серверу"
#: ../app/ui.js:1115
#: ../app/ui.js:1129
msgid "Disconnected"
msgstr "Отключено"
#: ../app/ui.js:1128
#: ../app/ui.js:1144
msgid "New connection has been rejected with reason: "
msgstr "Подключиться не удалось: "
msgstr "Новое соединение отклонено по причине: "
#: ../app/ui.js:1131
#: ../app/ui.js:1147
msgid "New connection has been rejected"
msgstr "Подключиться не удалось"
msgstr "Новое соединение отклонено"
#: ../app/ui.js:1151
msgid "Password is required"
msgstr "Требуется пароль"
#: ../app/ui.js:1182
msgid "Credentials are required"
msgstr "Требуются учетные данные"
#: ../vnc.html:84
#: ../vnc.html:61
msgid "noVNC encountered an error:"
msgstr "Ошибка noVNC: "
#: ../vnc.html:94
#: ../vnc.html:71
msgid "Hide/Show the control bar"
msgstr "Скрыть/Показать контрольную панель"
#: ../vnc.html:101
#: ../vnc.html:78
msgid "Drag"
msgstr "Переместить"
#: ../vnc.html:78
msgid "Move/Drag Viewport"
msgstr "Переместить окно"
#: ../vnc.html:101
msgid "viewport drag"
msgstr "Переместить окно"
#: ../vnc.html:107 ../vnc.html:110 ../vnc.html:113 ../vnc.html:116
msgid "Active Mouse Button"
msgstr "Активировать кнопки мыши"
#: ../vnc.html:107
msgid "No mousebutton"
msgstr "Отключить кнопки мыши"
#: ../vnc.html:110
msgid "Left mousebutton"
msgstr "Левая кнопка мыши"
#: ../vnc.html:113
msgid "Middle mousebutton"
msgstr "Средняя кнопка мыши"
#: ../vnc.html:116
msgid "Right mousebutton"
msgstr "Правая кнопка мыши"
#: ../vnc.html:119
#: ../vnc.html:84
msgid "Keyboard"
msgstr "Клавиатура"
#: ../vnc.html:119
#: ../vnc.html:84
msgid "Show Keyboard"
msgstr "Показать клавиатуру"
#: ../vnc.html:126
#: ../vnc.html:89
msgid "Extra keys"
msgstr "Доп. кнопки"
msgstr "Дополнительные Кнопки"
#: ../vnc.html:126
#: ../vnc.html:89
msgid "Show Extra Keys"
msgstr "Показать дополнительные кнопки"
msgstr "Показать Дополнительные Кнопки"
#: ../vnc.html:131
#: ../vnc.html:94
msgid "Ctrl"
msgstr "Ctrl"
#: ../vnc.html:131
#: ../vnc.html:94
msgid "Toggle Ctrl"
msgstr "Передать нажатие Ctrl"
msgstr "Переключение нажатия Ctrl"
#: ../vnc.html:134
#: ../vnc.html:97
msgid "Alt"
msgstr "Alt"
#: ../vnc.html:134
#: ../vnc.html:97
msgid "Toggle Alt"
msgstr "Передать нажатие Alt"
msgstr "Переключение нажатия Alt"
#: ../vnc.html:137
#: ../vnc.html:100
msgid "Toggle Windows"
msgstr "Переключение вкладок"
#: ../vnc.html:137
#: ../vnc.html:100
msgid "Windows"
msgstr "Вкладка"
#: ../vnc.html:140
#: ../vnc.html:103
msgid "Send Tab"
msgstr "Передать нажатие Tab"
#: ../vnc.html:140
#: ../vnc.html:103
msgid "Tab"
msgstr "Tab"
#: ../vnc.html:143
#: ../vnc.html:106
msgid "Esc"
msgstr "Esc"
#: ../vnc.html:143
#: ../vnc.html:106
msgid "Send Escape"
msgstr "Передать нажатие Escape"
#: ../vnc.html:146
#: ../vnc.html:109
msgid "Ctrl+Alt+Del"
msgstr "Ctrl+Alt+Del"
#: ../vnc.html:146
#: ../vnc.html:109
msgid "Send Ctrl-Alt-Del"
msgstr "Передать нажатие Ctrl-Alt-Del"
#: ../vnc.html:154
#: ../vnc.html:116
msgid "Shutdown/Reboot"
msgstr "Выключить/Перезагрузить"
#: ../vnc.html:154
#: ../vnc.html:116
msgid "Shutdown/Reboot..."
msgstr "Выключить/Перезагрузить..."
#: ../vnc.html:160
#: ../vnc.html:122
msgid "Power"
msgstr "Питание"
#: ../vnc.html:162
#: ../vnc.html:124
msgid "Shutdown"
msgstr "Выключить"
#: ../vnc.html:163
#: ../vnc.html:125
msgid "Reboot"
msgstr "Перезагрузить"
#: ../vnc.html:164
#: ../vnc.html:126
msgid "Reset"
msgstr "Сброс"
#: ../vnc.html:169 ../vnc.html:175
#: ../vnc.html:131 ../vnc.html:137
msgid "Clipboard"
msgstr "Буфер обмена"
#: ../vnc.html:179
#: ../vnc.html:141
msgid "Clear"
msgstr "Очистить"
#: ../vnc.html:185
#: ../vnc.html:147
msgid "Fullscreen"
msgstr "Во весь экран"
#: ../vnc.html:190 ../vnc.html:197
#: ../vnc.html:152 ../vnc.html:159
msgid "Settings"
msgstr "Настройки"
#: ../vnc.html:200
#: ../vnc.html:162
msgid "Shared Mode"
msgstr "Общий режим"
#: ../vnc.html:203
#: ../vnc.html:165
msgid "View Only"
msgstr "Просмотр"
msgstr "Только Просмотр"
#: ../vnc.html:207
#: ../vnc.html:169
msgid "Clip to Window"
msgstr "В окно"
#: ../vnc.html:210
#: ../vnc.html:172
msgid "Scaling Mode:"
msgstr "Масштаб:"
#: ../vnc.html:212
#: ../vnc.html:174
msgid "None"
msgstr "Нет"
#: ../vnc.html:213
#: ../vnc.html:175
msgid "Local Scaling"
msgstr "Локльный масштаб"
#: ../vnc.html:214
#: ../vnc.html:176
msgid "Remote Resizing"
msgstr "Удаленный масштаб"
msgstr "Удаленная перенастройка размера"
#: ../vnc.html:219
#: ../vnc.html:181
msgid "Advanced"
msgstr "Дополнительно"
#: ../vnc.html:222
#: ../vnc.html:184
msgid "Quality:"
msgstr "Качество"
#: ../vnc.html:188
msgid "Compression level:"
msgstr "Уровень Сжатия"
#: ../vnc.html:193
msgid "Repeater ID:"
msgstr "Идентификатор ID:"
#: ../vnc.html:226
#: ../vnc.html:197
msgid "WebSocket"
msgstr "WebSocket"
#: ../vnc.html:229
#: ../vnc.html:200
msgid "Encrypt"
msgstr "Шифрование"
#: ../vnc.html:232
#: ../vnc.html:203
msgid "Host:"
msgstr "Сервер:"
#: ../vnc.html:236
#: ../vnc.html:207
msgid "Port:"
msgstr "Порт:"
#: ../vnc.html:240
#: ../vnc.html:211
msgid "Path:"
msgstr "Путь:"
#: ../vnc.html:247
#: ../vnc.html:218
msgid "Automatic Reconnect"
msgstr "Автоматическое переподключение"
#: ../vnc.html:250
#: ../vnc.html:221
msgid "Reconnect Delay (ms):"
msgstr "Задержка переподключения (мс):"
#: ../vnc.html:255
#: ../vnc.html:226
msgid "Show Dot when No Cursor"
msgstr "Показать точку вместо курсора"
#: ../vnc.html:260
#: ../vnc.html:231
msgid "Logging:"
msgstr "Лог:"
#: ../vnc.html:272
#: ../vnc.html:240
msgid "Version:"
msgstr "Версия"
#: ../vnc.html:248
msgid "Disconnect"
msgstr "Отключение"
#: ../vnc.html:291
#: ../vnc.html:267
msgid "Connect"
msgstr "Подключение"
#: ../vnc.html:301
#: ../vnc.html:277
msgid "Username:"
msgstr "Имя Пользователя"
#: ../vnc.html:281
msgid "Password:"
msgstr "Пароль:"
#: ../vnc.html:305
msgid "Send Password"
msgstr "Пароль: "
#: ../vnc.html:285
msgid "Send Credentials"
msgstr "Передача Учетных Данных"
#: ../vnc.html:315
#: ../vnc.html:295
msgid "Cancel"
msgstr "Выход"
+114 -75
View File
@@ -8,8 +8,8 @@ msgid ""
msgstr ""
"Project-Id-Version: noVNC 1.3.0\n"
"Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
"POT-Creation-Date: 2021-08-27 16:03+0200\n"
"PO-Revision-Date: 2021-08-27 16:18+0200\n"
"POT-Creation-Date: 2023-01-20 12:54+0100\n"
"PO-Revision-Date: 2023-01-20 12:58+0100\n"
"Last-Translator: Samuel Mannehed <samuel@cendio.se>\n"
"Language-Team: none\n"
"Language: sv\n"
@@ -17,265 +17,269 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 2.0.3\n"
"X-Generator: Poedit 3.2.2\n"
#: ../app/ui.js:400
#: ../app/ui.js:69
msgid "HTTPS is required for full functionality"
msgstr "HTTPS krävs för full funktionalitet"
#: ../app/ui.js:410
msgid "Connecting..."
msgstr "Ansluter..."
#: ../app/ui.js:407
#: ../app/ui.js:417
msgid "Disconnecting..."
msgstr "Kopplar ner..."
#: ../app/ui.js:413
#: ../app/ui.js:423
msgid "Reconnecting..."
msgstr "Återansluter..."
#: ../app/ui.js:418
#: ../app/ui.js:428
msgid "Internal error"
msgstr "Internt fel"
#: ../app/ui.js:1009
#: ../app/ui.js:1026
msgid "Must set host"
msgstr "Du måste specifiera en värd"
#: ../app/ui.js:1091
#: ../app/ui.js:1110
msgid "Connected (encrypted) to "
msgstr "Ansluten (krypterat) till "
#: ../app/ui.js:1093
#: ../app/ui.js:1112
msgid "Connected (unencrypted) to "
msgstr "Ansluten (okrypterat) till "
#: ../app/ui.js:1116
#: ../app/ui.js:1135
msgid "Something went wrong, connection is closed"
msgstr "Något gick fel, anslutningen avslutades"
#: ../app/ui.js:1119
#: ../app/ui.js:1138
msgid "Failed to connect to server"
msgstr "Misslyckades att ansluta till servern"
#: ../app/ui.js:1129
#: ../app/ui.js:1150
msgid "Disconnected"
msgstr "Frånkopplad"
#: ../app/ui.js:1144
#: ../app/ui.js:1165
msgid "New connection has been rejected with reason: "
msgstr "Ny anslutning har blivit nekad med följande skäl: "
#: ../app/ui.js:1147
#: ../app/ui.js:1168
msgid "New connection has been rejected"
msgstr "Ny anslutning har blivit nekad"
#: ../app/ui.js:1182
#: ../app/ui.js:1234
msgid "Credentials are required"
msgstr "Användaruppgifter krävs"
#: ../vnc.html:61
#: ../vnc.html:55
msgid "noVNC encountered an error:"
msgstr "noVNC stötte på ett problem:"
#: ../vnc.html:71
#: ../vnc.html:65
msgid "Hide/Show the control bar"
msgstr "Göm/Visa kontrollbaren"
#: ../vnc.html:78
#: ../vnc.html:74
msgid "Drag"
msgstr "Dra"
#: ../vnc.html:78
#: ../vnc.html:74
msgid "Move/Drag Viewport"
msgstr "Flytta/Dra Vyn"
#: ../vnc.html:84
#: ../vnc.html:80
msgid "Keyboard"
msgstr "Tangentbord"
#: ../vnc.html:84
#: ../vnc.html:80
msgid "Show Keyboard"
msgstr "Visa Tangentbord"
#: ../vnc.html:89
#: ../vnc.html:85
msgid "Extra keys"
msgstr "Extraknappar"
#: ../vnc.html:89
#: ../vnc.html:85
msgid "Show Extra Keys"
msgstr "Visa Extraknappar"
#: ../vnc.html:94
#: ../vnc.html:90
msgid "Ctrl"
msgstr "Ctrl"
#: ../vnc.html:94
#: ../vnc.html:90
msgid "Toggle Ctrl"
msgstr "Växla Ctrl"
#: ../vnc.html:97
#: ../vnc.html:93
msgid "Alt"
msgstr "Alt"
#: ../vnc.html:97
#: ../vnc.html:93
msgid "Toggle Alt"
msgstr "Växla Alt"
#: ../vnc.html:100
#: ../vnc.html:96
msgid "Toggle Windows"
msgstr "Växla Windows"
#: ../vnc.html:100
#: ../vnc.html:96
msgid "Windows"
msgstr "Windows"
#: ../vnc.html:103
#: ../vnc.html:99
msgid "Send Tab"
msgstr "Skicka Tab"
#: ../vnc.html:103
#: ../vnc.html:99
msgid "Tab"
msgstr "Tab"
#: ../vnc.html:106
#: ../vnc.html:102
msgid "Esc"
msgstr "Esc"
#: ../vnc.html:106
#: ../vnc.html:102
msgid "Send Escape"
msgstr "Skicka Escape"
#: ../vnc.html:109
#: ../vnc.html:105
msgid "Ctrl+Alt+Del"
msgstr "Ctrl+Alt+Del"
#: ../vnc.html:109
#: ../vnc.html:105
msgid "Send Ctrl-Alt-Del"
msgstr "Skicka Ctrl-Alt-Del"
#: ../vnc.html:116
#: ../vnc.html:112
msgid "Shutdown/Reboot"
msgstr "Stäng av/Boota om"
#: ../vnc.html:116
#: ../vnc.html:112
msgid "Shutdown/Reboot..."
msgstr "Stäng av/Boota om..."
#: ../vnc.html:122
#: ../vnc.html:118
msgid "Power"
msgstr "Ström"
#: ../vnc.html:124
#: ../vnc.html:120
msgid "Shutdown"
msgstr "Stäng av"
#: ../vnc.html:125
#: ../vnc.html:121
msgid "Reboot"
msgstr "Boota om"
#: ../vnc.html:126
#: ../vnc.html:122
msgid "Reset"
msgstr "Återställ"
#: ../vnc.html:131 ../vnc.html:137
#: ../vnc.html:127 ../vnc.html:133
msgid "Clipboard"
msgstr "Urklipp"
#: ../vnc.html:141
msgid "Clear"
msgstr "Rensa"
#: ../vnc.html:135
msgid "Edit clipboard content in the textarea below."
msgstr "Redigera urklippets innehåll i fältet nedan."
#: ../vnc.html:147
msgid "Fullscreen"
#: ../vnc.html:143
msgid "Full Screen"
msgstr "Fullskärm"
#: ../vnc.html:152 ../vnc.html:159
#: ../vnc.html:148 ../vnc.html:154
msgid "Settings"
msgstr "Inställningar"
#: ../vnc.html:162
#: ../vnc.html:158
msgid "Shared Mode"
msgstr "Delat Läge"
#: ../vnc.html:165
#: ../vnc.html:161
msgid "View Only"
msgstr "Endast Visning"
#: ../vnc.html:169
#: ../vnc.html:165
msgid "Clip to Window"
msgstr "Begränsa till Fönster"
#: ../vnc.html:172
#: ../vnc.html:168
msgid "Scaling Mode:"
msgstr "Skalningsläge:"
#: ../vnc.html:174
#: ../vnc.html:170
msgid "None"
msgstr "Ingen"
#: ../vnc.html:175
#: ../vnc.html:171
msgid "Local Scaling"
msgstr "Lokal Skalning"
#: ../vnc.html:176
#: ../vnc.html:172
msgid "Remote Resizing"
msgstr "Ändra Storlek"
#: ../vnc.html:181
#: ../vnc.html:177
msgid "Advanced"
msgstr "Avancerat"
#: ../vnc.html:184
#: ../vnc.html:180
msgid "Quality:"
msgstr "Kvalitet:"
#: ../vnc.html:188
#: ../vnc.html:184
msgid "Compression level:"
msgstr "Kompressionsnivå:"
#: ../vnc.html:193
#: ../vnc.html:189
msgid "Repeater ID:"
msgstr "Repeater-ID:"
#: ../vnc.html:197
#: ../vnc.html:193
msgid "WebSocket"
msgstr "WebSocket"
#: ../vnc.html:200
#: ../vnc.html:196
msgid "Encrypt"
msgstr "Kryptera"
#: ../vnc.html:203
#: ../vnc.html:199
msgid "Host:"
msgstr "Värd:"
#: ../vnc.html:207
#: ../vnc.html:203
msgid "Port:"
msgstr "Port:"
#: ../vnc.html:211
#: ../vnc.html:207
msgid "Path:"
msgstr "Sökväg:"
#: ../vnc.html:218
#: ../vnc.html:214
msgid "Automatic Reconnect"
msgstr "Automatisk Återanslutning"
#: ../vnc.html:221
#: ../vnc.html:217
msgid "Reconnect Delay (ms):"
msgstr "Fördröjning (ms):"
#: ../vnc.html:226
#: ../vnc.html:222
msgid "Show Dot when No Cursor"
msgstr "Visa prick när ingen muspekare finns"
#: ../vnc.html:231
#: ../vnc.html:227
msgid "Logging:"
msgstr "Loggning:"
#: ../vnc.html:240
#: ../vnc.html:236
msgid "Version:"
msgstr "Version:"
#: ../vnc.html:248
#: ../vnc.html:244
msgid "Disconnect"
msgstr "Koppla från"
@@ -283,18 +287,53 @@ msgstr "Koppla från"
msgid "Connect"
msgstr "Anslut"
#: ../vnc.html:277
#: ../vnc.html:276
msgid "Server identity"
msgstr "Server-identitet"
#: ../vnc.html:279
msgid "The server has provided the following identifying information:"
msgstr "Servern har gett följande identifierande information:"
#: ../vnc.html:283
msgid "Fingerprint:"
msgstr "Fingeravtryck:"
#: ../vnc.html:286
msgid ""
"Please verify that the information is correct and press \"Approve\". "
"Otherwise press \"Reject\"."
msgstr ""
"Kontrollera att informationen är korrekt och tryck sedan "
"\"Godkänn\". Tryck annars \"Neka\"."
#: ../vnc.html:291
msgid "Approve"
msgstr "Godkänn"
#: ../vnc.html:292
msgid "Reject"
msgstr "Neka"
#: ../vnc.html:300
msgid "Credentials"
msgstr "Användaruppgifter"
#: ../vnc.html:304
msgid "Username:"
msgstr "Användarnamn:"
#: ../vnc.html:281
#: ../vnc.html:308
msgid "Password:"
msgstr "Lösenord:"
#: ../vnc.html:285
#: ../vnc.html:312
msgid "Send Credentials"
msgstr "Skicka Användaruppgifter"
#: ../vnc.html:295
#: ../vnc.html:321
msgid "Cancel"
msgstr "Avbryt"
#~ msgid "Clear"
#~ msgstr "Rensa"
+5 -1
View File
@@ -17,6 +17,10 @@ const opt = getopt.create([
const strings = {};
function addString(str, location) {
// We assume surrounding whitespace, and whitespace around line
// breaks, is just for source formatting
str = str.split("\n").map(s => s.trim()).join(" ").trim();
if (str.length == 0) {
return;
}
@@ -78,7 +82,7 @@ function process(elem, locator, enabled) {
if (node.nodeType === node.ELEMENT_NODE) {
process(node, locator, enabled);
} else if (node.nodeType === node.TEXT_NODE && enabled) {
addString(node.data.trim(), locator(node));
addString(node.data, locator(node));
}
}
}
+9 -3
View File
@@ -1,6 +1,6 @@
name: novnc
base: core18 # the base snap is the execution environment for this snap
version: '@VERSION@'
base: core22 # the base snap is the execution environment for this snap
version: git
summary: Open Source VNC client using HTML5 (WebSockets, Canvas)
description: |
Open Source VNC client using HTML5 (WebSockets, Canvas).
@@ -23,6 +23,9 @@ parts:
- core/**/*.js
- vendor/**/*.js
- novnc_proxy
novnc-deps:
plugin: nil
stage-packages:
- bash
@@ -31,12 +34,15 @@ parts:
plugin: dump
stage:
- svc_wrapper.sh
svc-script-deps:
plugin: nil
stage-packages:
- bash
- jq
websockify:
source: https://github.com/novnc/websockify/archive/v0.9.0.tar.gz
source: https://github.com/novnc/websockify/archive/v0.11.0.tar.gz
plugin: python
stage-packages:
- python3-numpy
-6
View File
@@ -29,12 +29,6 @@ chai.use(function (_chai, utils) {
_chai.Assertion.addMethod('sent', function (targetData) {
const obj = this._obj;
obj.inspect = () => {
const res = { _websocket: obj._websocket, rQi: obj._rQi, _rQ: new Uint8Array(obj._rQ.buffer, 0, obj._rQlen),
_sQ: new Uint8Array(obj._sQ.buffer, 0, obj._sQlen) };
res.prototype = obj;
return res;
};
const data = obj._websocket._getSentData();
let same = true;
if (data.length != targetData.length) {
+10 -6
View File
@@ -42,7 +42,7 @@ export default class FakeWebSocket {
}
_getSentData() {
const res = new Uint8Array(this._sendQueue.buffer, 0, this.bufferedAmount);
const res = this._sendQueue.slice(0, this.bufferedAmount);
this.bufferedAmount = 0;
return res;
}
@@ -55,11 +55,15 @@ export default class FakeWebSocket {
}
_receiveData(data) {
// Break apart the data to expose bugs where we assume data is
// neatly packaged
for (let i = 0;i < data.length;i++) {
let buf = data.subarray(i, i+1);
this.onmessage(new MessageEvent("message", { 'data': buf }));
if (data.length < 4096) {
// Break apart the data to expose bugs where we assume data is
// neatly packaged
for (let i = 0;i < data.length;i++) {
let buf = data.slice(i, i+1);
this.onmessage(new MessageEvent("message", { 'data': buf.buffer }));
}
} else {
this.onmessage(new MessageEvent("message", { 'data': data.buffer }));
}
}
}
+4 -1
View File
@@ -24,7 +24,7 @@ function loadFile() {
return new Promise((resolve, reject) => {
const script = document.createElement("script");
script.onload = resolve;
script.onerror = reject;
script.onerror = () => { reject("Failed to load " + fname); };
document.body.appendChild(script);
script.src = "../recordings/" + fname;
});
@@ -200,6 +200,9 @@ function start() {
player.onrfbdisconnected = (evt) => {
if (!evt.detail.clean) {
message(`noVNC sent disconnected during iteration ${evt.detail.iteration} frame ${evt.detail.frame}`);
document.getElementById('startButton').disabled = false;
document.getElementById('startButton').value = "Start";
}
};
player.onfinish = (evt) => {
+6 -13
View File
@@ -131,12 +131,10 @@ export default class RecordingPlayer {
_doPacket() {
// Avoid having excessive queue buildup in non-realtime mode
if (this._trafficManagement && this._rfb._flushing) {
const orig = this._rfb._display.onflush;
this._rfb._display.onflush = () => {
this._rfb._display.onflush = orig;
this._rfb._onFlush();
this._doPacket();
};
this._rfb.flush()
.then(() => {
this._doPacket();
});
return;
}
@@ -150,13 +148,8 @@ export default class RecordingPlayer {
_finish() {
if (this._rfb._display.pending()) {
this._rfb._display.onflush = () => {
if (this._rfb._flushing) {
this._rfb._onFlush();
}
this._finish();
};
this._rfb._display.flush();
this._rfb._display.flush()
.then(() => { this._finish(); });
} else {
this._running = false;
this._ws.onclose({code: 1000, reason: ""});
+244
View File
@@ -0,0 +1,244 @@
/* eslint-disable no-console */
const expect = chai.expect;
import { isMac, isWindows, isIOS, isAndroid, isChromeOS,
isSafari, isFirefox, isChrome, isChromium, isOpera, isEdge,
isGecko, isWebKit, isBlink } from '../core/util/browser.js';
describe('OS detection', function () {
let origNavigator;
beforeEach(function () {
// window.navigator is a protected read-only property in many
// environments, so we need to redefine it whilst running these
// tests.
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
Object.defineProperty(window, "navigator", {value: {}});
});
afterEach(function () {
Object.defineProperty(window, "navigator", origNavigator);
});
it('should handle macOS', function () {
const platforms = [
"MacIntel",
"MacPPC",
];
navigator.userAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 12_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.6 Safari/605.1.15";
platforms.forEach((platform) => {
navigator.platform = platform;
expect(isMac()).to.be.true;
expect(isWindows()).to.be.false;
expect(isIOS()).to.be.false;
expect(isAndroid()).to.be.false;
expect(isChromeOS()).to.be.false;
});
});
it('should handle Windows', function () {
const platforms = [
"Win32",
"Win64",
];
navigator.userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36";
platforms.forEach((platform) => {
navigator.platform = platform;
expect(isMac()).to.be.false;
expect(isWindows()).to.be.true;
expect(isIOS()).to.be.false;
expect(isAndroid()).to.be.false;
expect(isChromeOS()).to.be.false;
});
});
it('should handle iOS', function () {
const platforms = [
"iPhone",
"iPod",
"iPad",
];
navigator.userAgent = "Mozilla/5.0 (iPhone; CPU iPhone OS 16_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.1 Mobile/15E148 Safari/604.1";
platforms.forEach((platform) => {
navigator.platform = platform;
expect(isMac()).to.be.false;
expect(isWindows()).to.be.false;
expect(isIOS()).to.be.true;
expect(isAndroid()).to.be.false;
expect(isChromeOS()).to.be.false;
});
});
it('should handle Android', function () {
let userAgents = [
"Mozilla/5.0 (Linux; Android 13; SM-G960U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.5359.128 Mobile Safari/537.36",
"Mozilla/5.0 (Android 13; Mobile; LG-M255; rv:108.0) Gecko/108.0 Firefox/108.0",
];
navigator.platform = "Linux x86_64";
userAgents.forEach((ua) => {
navigator.userAgent = ua;
expect(isMac()).to.be.false;
expect(isWindows()).to.be.false;
expect(isIOS()).to.be.false;
expect(isAndroid()).to.be.true;
expect(isChromeOS()).to.be.false;
});
});
it('should handle ChromeOS', function () {
let userAgents = [
"Mozilla/5.0 (X11; CrOS x86_64 15183.59.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.5359.75 Safari/537.36",
"Mozilla/5.0 (X11; CrOS aarch64 15183.59.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.5359.75 Safari/537.36",
];
navigator.platform = "Linux x86_64";
userAgents.forEach((ua) => {
navigator.userAgent = ua;
expect(isMac()).to.be.false;
expect(isWindows()).to.be.false;
expect(isIOS()).to.be.false;
expect(isAndroid()).to.be.false;
expect(isChromeOS()).to.be.true;
});
});
});
describe('Browser detection', function () {
let origNavigator;
beforeEach(function () {
// window.navigator is a protected read-only property in many
// environments, so we need to redefine it whilst running these
// tests.
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
Object.defineProperty(window, "navigator", {value: {}});
});
afterEach(function () {
Object.defineProperty(window, "navigator", origNavigator);
});
it('should handle Chrome', function () {
navigator.userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36";
expect(isSafari()).to.be.false;
expect(isFirefox()).to.be.false;
expect(isChrome()).to.be.true;
expect(isChromium()).to.be.false;
expect(isOpera()).to.be.false;
expect(isEdge()).to.be.false;
expect(isGecko()).to.be.false;
expect(isWebKit()).to.be.false;
expect(isBlink()).to.be.true;
});
it('should handle Chromium', function () {
navigator.userAgent = "Mozilla/5.0 (X11; Linux armv7l) AppleWebKit/537.36 (KHTML, like Gecko) Raspbian Chromium/74.0.3729.157 Chrome/74.0.3729.157 Safari/537.36";
expect(isSafari()).to.be.false;
expect(isFirefox()).to.be.false;
expect(isChrome()).to.be.false;
expect(isChromium()).to.be.true;
expect(isOpera()).to.be.false;
expect(isEdge()).to.be.false;
expect(isGecko()).to.be.false;
expect(isWebKit()).to.be.false;
expect(isBlink()).to.be.true;
});
it('should handle Firefox', function () {
navigator.userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:105.0) Gecko/20100101 Firefox/105.0";
expect(isSafari()).to.be.false;
expect(isFirefox()).to.be.true;
expect(isChrome()).to.be.false;
expect(isChromium()).to.be.false;
expect(isOpera()).to.be.false;
expect(isEdge()).to.be.false;
expect(isGecko()).to.be.true;
expect(isWebKit()).to.be.false;
expect(isBlink()).to.be.false;
});
it('should handle Seamonkey', function () {
navigator.userAgent = "Mozilla/5.0 (Windows NT 6.1; rv:36.0) Gecko/20100101 Firefox/36.0 Seamonkey/2.33.1";
expect(isSafari()).to.be.false;
expect(isFirefox()).to.be.false;
expect(isChrome()).to.be.false;
expect(isChromium()).to.be.false;
expect(isOpera()).to.be.false;
expect(isEdge()).to.be.false;
expect(isGecko()).to.be.true;
expect(isWebKit()).to.be.false;
expect(isBlink()).to.be.false;
});
it('should handle Safari', function () {
navigator.userAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 12_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.6 Safari/605.1.15";
expect(isSafari()).to.be.true;
expect(isFirefox()).to.be.false;
expect(isChrome()).to.be.false;
expect(isChromium()).to.be.false;
expect(isOpera()).to.be.false;
expect(isEdge()).to.be.false;
expect(isGecko()).to.be.false;
expect(isWebKit()).to.be.true;
expect(isBlink()).to.be.false;
});
it('should handle Edge', function () {
navigator.userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36 Edg/106.0.1370.34";
expect(isSafari()).to.be.false;
expect(isFirefox()).to.be.false;
expect(isChrome()).to.be.false;
expect(isChromium()).to.be.false;
expect(isOpera()).to.be.false;
expect(isEdge()).to.be.true;
expect(isGecko()).to.be.false;
expect(isWebKit()).to.be.false;
expect(isBlink()).to.be.true;
});
it('should handle Opera', function () {
navigator.userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36 OPR/91.0.4516.20";
expect(isSafari()).to.be.false;
expect(isFirefox()).to.be.false;
expect(isChrome()).to.be.false;
expect(isChromium()).to.be.false;
expect(isOpera()).to.be.true;
expect(isEdge()).to.be.false;
expect(isGecko()).to.be.false;
expect(isWebKit()).to.be.false;
expect(isBlink()).to.be.true;
});
it('should handle Epiphany', function () {
navigator.userAgent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.0 Safari/605.1.15 Epiphany/605.1.15";
expect(isSafari()).to.be.false;
expect(isFirefox()).to.be.false;
expect(isChrome()).to.be.false;
expect(isChromium()).to.be.false;
expect(isOpera()).to.be.false;
expect(isEdge()).to.be.false;
expect(isGecko()).to.be.false;
expect(isWebKit()).to.be.true;
expect(isBlink()).to.be.false;
});
});
+18 -9
View File
@@ -9,23 +9,26 @@ import FakeWebSocket from './fake.websocket.js';
function testDecodeRect(decoder, x, y, width, height, data, display, depth) {
let sock;
let done = false;
sock = new Websock;
sock.open("ws://example.com");
sock.on('message', () => {
decoder.decodeRect(x, y, width, height, sock, display, depth);
done = decoder.decodeRect(x, y, width, height, sock, display, depth);
});
// Empty messages are filtered at multiple layers, so we need to
// do a direct call
if (data.length === 0) {
decoder.decodeRect(x, y, width, height, sock, display, depth);
done = decoder.decodeRect(x, y, width, height, sock, display, depth);
} else {
sock._websocket._receiveData(new Uint8Array(data));
}
display.flip();
return done;
}
describe('CopyRect Decoder', function () {
@@ -47,12 +50,15 @@ describe('CopyRect Decoder', function () {
display.fillRect(0, 0, 2, 2, [ 0x00, 0x00, 0xff ]);
display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);
testDecodeRect(decoder, 0, 2, 2, 2,
[0x00, 0x02, 0x00, 0x00],
display, 24);
testDecodeRect(decoder, 2, 2, 2, 2,
[0x00, 0x00, 0x00, 0x00],
display, 24);
let done;
done = testDecodeRect(decoder, 0, 2, 2, 2,
[0x00, 0x02, 0x00, 0x00],
display, 24);
expect(done).to.be.true;
done = testDecodeRect(decoder, 2, 2, 2, 2,
[0x00, 0x00, 0x00, 0x00],
display, 24);
expect(done).to.be.true;
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
@@ -69,7 +75,9 @@ describe('CopyRect Decoder', function () {
display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);
display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]);
testDecodeRect(decoder, 1, 2, 0, 0, [0x00, 0x00, 0x00, 0x00], display, 24);
let done = testDecodeRect(decoder, 1, 2, 0, 0,
[0x00, 0x00, 0x00, 0x00],
display, 24);
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
@@ -78,6 +86,7 @@ describe('CopyRect Decoder', function () {
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]);
expect(done).to.be.true;
expect(display).to.have.displayed(targetData);
});
});
+7 -11
View File
@@ -298,14 +298,11 @@ describe('Display/Canvas Helper', function () {
expect(display).to.have.displayed(checkedData);
});
it('should support drawing images via #imageRect', function (done) {
it('should support drawing images via #imageRect', async function () {
display.imageRect(0, 0, 4, 4, "image/png", makeImagePng(checkedData, 4, 4));
display.flip();
display.onflush = () => {
expect(display).to.have.displayed(checkedData);
done();
};
display.flush();
await display.flush();
expect(display).to.have.displayed(checkedData);
});
it('should support blit images with true color via #blitImage', function () {
@@ -360,12 +357,11 @@ describe('Display/Canvas Helper', function () {
expect(img.addEventListener).to.have.been.calledOnce;
});
it('should call callback when queue is flushed', function () {
display.onflush = sinon.spy();
it('should resolve promise when queue is flushed', async function () {
display.fillRect(0, 0, 4, 4, [0, 0xff, 0]);
expect(display.onflush).to.not.have.been.called;
display.flush();
expect(display.onflush).to.have.been.calledOnce;
let promise = display.flush();
expect(promise).to.be.an.instanceOf(Promise);
await promise;
});
it('should draw a blit image on type "blit"', function () {
+2 -18
View File
@@ -71,18 +71,10 @@ describe('Helpers', function () {
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
Object.defineProperty(window, "navigator", {value: {}});
if (window.navigator.platform !== undefined) {
// Object.defineProperty() doesn't work properly in old
// versions of Chrome
this.skip();
}
window.navigator.platform = "Mac x86_64";
});
afterEach(function () {
if (origNavigator !== undefined) {
Object.defineProperty(window, "navigator", origNavigator);
}
Object.defineProperty(window, "navigator", origNavigator);
});
it('should respect ContextMenu on modern browser', function () {
@@ -196,19 +188,11 @@ describe('Helpers', function () {
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
Object.defineProperty(window, "navigator", {value: {}});
if (window.navigator.platform !== undefined) {
// Object.defineProperty() doesn't work properly in old
// versions of Chrome
this.skip();
}
window.navigator.platform = "Windows";
});
afterEach(function () {
if (origNavigator !== undefined) {
Object.defineProperty(window, "navigator", origNavigator);
}
Object.defineProperty(window, "navigator", origNavigator);
});
const keys = { 'Zenkaku': 0xff2a, 'Hankaku': 0xff2a,
+19 -9
View File
@@ -9,23 +9,26 @@ import FakeWebSocket from './fake.websocket.js';
function testDecodeRect(decoder, x, y, width, height, data, display, depth) {
let sock;
let done = false;
sock = new Websock;
sock.open("ws://example.com");
sock.on('message', () => {
decoder.decodeRect(x, y, width, height, sock, display, depth);
done = decoder.decodeRect(x, y, width, height, sock, display, depth);
});
// Empty messages are filtered at multiple layers, so we need to
// do a direct call
if (data.length === 0) {
decoder.decodeRect(x, y, width, height, sock, display, depth);
done = decoder.decodeRect(x, y, width, height, sock, display, depth);
} else {
sock._websocket._receiveData(new Uint8Array(data));
}
display.flip();
return done;
}
function push32(arr, num) {
@@ -62,7 +65,7 @@ describe('Hextile Decoder', function () {
data.push(2 | (2 << 4)); // x: 2, y: 2
data.push(1 | (1 << 4)); // width: 2, height: 2
testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
let done = testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
@@ -71,6 +74,7 @@ describe('Hextile Decoder', function () {
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]);
expect(done).to.be.true;
expect(display).to.have.displayed(targetData);
});
@@ -92,8 +96,9 @@ describe('Hextile Decoder', function () {
data.push(0);
}
testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
let done = testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
expect(done).to.be.true;
expect(display).to.have.displayed(targetData);
});
@@ -102,13 +107,14 @@ describe('Hextile Decoder', function () {
data.push(0x02);
push32(data, 0x00ff0000); // becomes 00ff0000 --> #00FF00 bg color
testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
let done = testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
let expected = [];
for (let i = 0; i < 16; i++) {
push32(expected, 0x00ff00ff);
}
expect(done).to.be.true;
expect(display).to.have.displayed(new Uint8Array(expected));
});
@@ -125,7 +131,7 @@ describe('Hextile Decoder', function () {
// send an empty frame
data.push(0x00);
testDecodeRect(decoder, 0, 0, 32, 4, data, display, 24);
let done = testDecodeRect(decoder, 0, 0, 32, 4, data, display, 24);
let expected = [];
for (let i = 0; i < 16; i++) {
@@ -135,6 +141,7 @@ describe('Hextile Decoder', function () {
push32(expected, 0x00ff00ff); // rect 2: same bkground color
}
expect(done).to.be.true;
expect(display).to.have.displayed(new Uint8Array(expected));
});
@@ -156,7 +163,7 @@ describe('Hextile Decoder', function () {
data.push(2 | (2 << 4)); // x: 2, y: 2
data.push(1 | (1 << 4)); // width: 2, height: 2
testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
let done = testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
@@ -165,6 +172,7 @@ describe('Hextile Decoder', function () {
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]);
expect(done).to.be.true;
expect(display).to.have.displayed(targetData);
});
@@ -190,7 +198,7 @@ describe('Hextile Decoder', function () {
data.push(0); // x: 0, y: 0
data.push(1 | (1 << 4)); // width: 2, height: 2
testDecodeRect(decoder, 0, 0, 4, 17, data, display, 24);
let done = testDecodeRect(decoder, 0, 0, 4, 17, data, display, 24);
let targetData = [
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
@@ -205,6 +213,7 @@ describe('Hextile Decoder', function () {
}
expected = expected.concat(targetData.slice(0, 16));
expect(done).to.be.true;
expect(display).to.have.displayed(new Uint8Array(expected));
});
@@ -218,7 +227,7 @@ describe('Hextile Decoder', function () {
display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);
display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]);
testDecodeRect(decoder, 1, 2, 0, 0, [], display, 24);
let done = testDecodeRect(decoder, 1, 2, 0, 0, [], display, 24);
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
@@ -227,6 +236,7 @@ describe('Hextile Decoder', function () {
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]);
expect(done).to.be.true;
expect(display).to.have.displayed(targetData);
});
});
+113
View File
@@ -0,0 +1,113 @@
/* eslint-disable no-console */
const expect = chai.expect;
import { deflateInit, deflate, Z_FULL_FLUSH } from "../vendor/pako/lib/zlib/deflate.js";
import ZStream from "../vendor/pako/lib/zlib/zstream.js";
import Inflator from "../core/inflator.js";
function _deflator(data) {
let strm = new ZStream();
deflateInit(strm, 5);
/* eslint-disable camelcase */
strm.input = data;
strm.avail_in = strm.input.length;
strm.next_in = 0;
/* eslint-enable camelcase */
let chunks = [];
let totalLen = 0;
while (strm.avail_in > 0) {
/* eslint-disable camelcase */
strm.output = new Uint8Array(1024 * 10 * 10);
strm.avail_out = strm.output.length;
strm.next_out = 0;
/* eslint-enable camelcase */
let ret = deflate(strm, Z_FULL_FLUSH);
// Check that return code is not an error
expect(ret).to.be.greaterThan(-1);
let chunk = new Uint8Array(strm.output.buffer, 0, strm.next_out);
totalLen += chunk.length;
chunks.push(chunk);
}
// Combine chunks into a single data
let outData = new Uint8Array(totalLen);
let offset = 0;
for (let i = 0; i < chunks.length; i++) {
outData.set(chunks[i], offset);
offset += chunks[i].length;
}
return outData;
}
describe('Inflate data', function () {
it('should be able to inflate messages', function () {
let inflator = new Inflator();
let text = "123asdf";
let preText = new Uint8Array(text.length);
for (let i = 0; i < preText.length; i++) {
preText[i] = text.charCodeAt(i);
}
let compText = _deflator(preText);
inflator.setInput(compText);
let inflatedText = inflator.inflate(preText.length);
expect(inflatedText).to.array.equal(preText);
});
it('should be able to inflate large messages', function () {
let inflator = new Inflator();
/* Generate a big string with random characters. Used because
repetition of letters might be deflated more effectively than
random ones. */
let text = "";
let characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
for (let i = 0; i < 300000; i++) {
text += characters.charAt(Math.floor(Math.random() * characters.length));
}
let preText = new Uint8Array(text.length);
for (let i = 0; i < preText.length; i++) {
preText[i] = text.charCodeAt(i);
}
let compText = _deflator(preText);
//Check that the compressed size is expected size
expect(compText.length).to.be.greaterThan((1024 * 10 * 10) * 2);
inflator.setInput(compText);
let inflatedText = inflator.inflate(preText.length);
expect(inflatedText).to.array.equal(preText);
});
it('should throw an error on insufficient data', function () {
let inflator = new Inflator();
let text = "123asdf";
let preText = new Uint8Array(text.length);
for (let i = 0; i < preText.length; i++) {
preText[i] = text.charCodeAt(i);
}
let compText = _deflator(preText);
inflator.setInput(compText);
expect(() => inflator.inflate(preText.length * 2)).to.throw();
});
});
+292
View File
@@ -0,0 +1,292 @@
const expect = chai.expect;
import Websock from '../core/websock.js';
import Display from '../core/display.js';
import JPEGDecoder from '../core/decoders/jpeg.js';
import FakeWebSocket from './fake.websocket.js';
function testDecodeRect(decoder, x, y, width, height, data, display, depth) {
let sock;
let done = false;
sock = new Websock;
sock.open("ws://example.com");
sock.on('message', () => {
done = decoder.decodeRect(x, y, width, height, sock, display, depth);
});
// Empty messages are filtered at multiple layers, so we need to
// do a direct call
if (data.length === 0) {
done = decoder.decodeRect(x, y, width, height, sock, display, depth);
} else {
sock._websocket._receiveData(new Uint8Array(data));
}
display.flip();
return done;
}
describe('JPEG Decoder', function () {
let decoder;
let display;
before(FakeWebSocket.replace);
after(FakeWebSocket.restore);
beforeEach(function () {
decoder = new JPEGDecoder();
display = new Display(document.createElement('canvas'));
display.resize(4, 4);
});
it('should handle JPEG rects', async function () {
let data = [
// JPEG data
0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46,
0x49, 0x46, 0x00, 0x01, 0x01, 0x01, 0x01, 0x2c,
0x01, 0x2c, 0x00, 0x42, 0xff, 0xdb, 0x00, 0x43,
0x00, 0x03, 0x02, 0x02, 0x03, 0x02, 0x02, 0x03,
0x03, 0x03, 0x03, 0x04, 0x03, 0x03, 0x04, 0x05,
0x08, 0x05, 0x05, 0x04, 0x04, 0x05, 0x0a, 0x07,
0x07, 0x06, 0x08, 0x0c, 0x0a, 0x0c, 0x0c, 0x0b,
0x0a, 0x0b, 0x0b, 0x0d, 0x0e, 0x12, 0x10, 0x0d,
0x0e, 0x11, 0x0e, 0x0b, 0x0b, 0x10, 0x16, 0x10,
0x11, 0x13, 0x14, 0x15, 0x15, 0x15, 0x0c, 0x0f,
0x17, 0x18, 0x16, 0x14, 0x18, 0x12, 0x14, 0x15,
0x14, 0xff, 0xdb, 0x00, 0x43, 0x01, 0x03, 0x04,
0x04, 0x05, 0x04, 0x05, 0x09, 0x05, 0x05, 0x09,
0x14, 0x0d, 0x0b, 0x0d, 0x14, 0x14, 0x14, 0x14,
0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14,
0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14,
0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14,
0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14,
0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14,
0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0xff, 0xc0,
0x00, 0x11, 0x08, 0x00, 0x04, 0x00, 0x04, 0x03,
0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11,
0x01, 0xff, 0xc4, 0x00, 0x1f, 0x00, 0x00, 0x01,
0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
0x0a, 0x0b, 0xff, 0xc4, 0x00, 0xb5, 0x10, 0x00,
0x02, 0x01, 0x03, 0x03, 0x02, 0x04, 0x03, 0x05,
0x05, 0x04, 0x04, 0x00, 0x00, 0x01, 0x7d, 0x01,
0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21,
0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, 0x22,
0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, 0x23,
0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0, 0x24,
0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, 0x17,
0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28, 0x29,
0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a,
0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a,
0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a,
0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a,
0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a,
0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a,
0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99,
0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8,
0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6,
0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5,
0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2, 0xe3,
0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1,
0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9,
0xfa, 0xff, 0xc4, 0x00, 0x1f, 0x01, 0x00, 0x03,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
0x0a, 0x0b, 0xff, 0xc4, 0x00, 0xb5, 0x11, 0x00,
0x02, 0x01, 0x02, 0x04, 0x04, 0x03, 0x04, 0x07,
0x05, 0x04, 0x04, 0x00, 0x01, 0x02, 0x77, 0x00,
0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31,
0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, 0x13,
0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xa1,
0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15,
0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1,
0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26, 0x27,
0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38, 0x39,
0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49,
0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59,
0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79,
0x7a, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88,
0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,
0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6,
0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5,
0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4,
0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3,
0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe2,
0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea,
0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9,
0xfa, 0xff, 0xda, 0x00, 0x0c, 0x03, 0x01, 0x00,
0x02, 0x11, 0x03, 0x11, 0x00, 0x3f, 0x00, 0xf9,
0xf7, 0xfb, 0x67, 0x56, 0xff, 0x00, 0x9f, 0xf8,
0x3f, 0xf0, 0x51, 0xa7, 0xff, 0x00, 0xf2, 0x3d,
0x7e, 0x6f, 0xfd, 0xab, 0x94, 0x7f, 0xd0, 0x9a,
0x8f, 0xfe, 0x0d, 0xc7, 0x7f, 0xf3, 0x61, 0xfd,
0xa7, 0xff, 0x00, 0x10, 0x77, 0x0d, 0xff, 0x00,
0x43, 0xec, 0xcf, 0xff, 0x00, 0x0b, 0xab, 0x1f,
0xff, 0xd9,
];
let decodeDone = testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
expect(decodeDone).to.be.true;
let targetData = new Uint8Array([
0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255
]);
// Browsers have rounding errors, so we need an approximate
// comparing function
function almost(a, b) {
let diff = Math.abs(a - b);
return diff < 5;
}
await display.flush();
expect(display).to.have.displayed(targetData, almost);
});
it('should handle JPEG rects without Huffman and quantification tables', async function () {
let data1 = [
// JPEG data
0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46,
0x49, 0x46, 0x00, 0x01, 0x01, 0x01, 0x01, 0x2c,
0x01, 0x2c, 0x00, 0x42, 0xff, 0xdb, 0x00, 0x43,
0x00, 0x03, 0x02, 0x02, 0x03, 0x02, 0x02, 0x03,
0x03, 0x03, 0x03, 0x04, 0x03, 0x03, 0x04, 0x05,
0x08, 0x05, 0x05, 0x04, 0x04, 0x05, 0x0a, 0x07,
0x07, 0x06, 0x08, 0x0c, 0x0a, 0x0c, 0x0c, 0x0b,
0x0a, 0x0b, 0x0b, 0x0d, 0x0e, 0x12, 0x10, 0x0d,
0x0e, 0x11, 0x0e, 0x0b, 0x0b, 0x10, 0x16, 0x10,
0x11, 0x13, 0x14, 0x15, 0x15, 0x15, 0x0c, 0x0f,
0x17, 0x18, 0x16, 0x14, 0x18, 0x12, 0x14, 0x15,
0x14, 0xff, 0xdb, 0x00, 0x43, 0x01, 0x03, 0x04,
0x04, 0x05, 0x04, 0x05, 0x09, 0x05, 0x05, 0x09,
0x14, 0x0d, 0x0b, 0x0d, 0x14, 0x14, 0x14, 0x14,
0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14,
0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14,
0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14,
0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14,
0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14,
0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0xff, 0xc0,
0x00, 0x11, 0x08, 0x00, 0x04, 0x00, 0x04, 0x03,
0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11,
0x01, 0xff, 0xc4, 0x00, 0x1f, 0x00, 0x00, 0x01,
0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
0x0a, 0x0b, 0xff, 0xc4, 0x00, 0xb5, 0x10, 0x00,
0x02, 0x01, 0x03, 0x03, 0x02, 0x04, 0x03, 0x05,
0x05, 0x04, 0x04, 0x00, 0x00, 0x01, 0x7d, 0x01,
0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21,
0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, 0x22,
0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, 0x23,
0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0, 0x24,
0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, 0x17,
0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28, 0x29,
0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a,
0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a,
0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a,
0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a,
0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a,
0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a,
0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99,
0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8,
0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6,
0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5,
0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2, 0xe3,
0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1,
0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9,
0xfa, 0xff, 0xc4, 0x00, 0x1f, 0x01, 0x00, 0x03,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
0x0a, 0x0b, 0xff, 0xc4, 0x00, 0xb5, 0x11, 0x00,
0x02, 0x01, 0x02, 0x04, 0x04, 0x03, 0x04, 0x07,
0x05, 0x04, 0x04, 0x00, 0x01, 0x02, 0x77, 0x00,
0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31,
0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, 0x13,
0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xa1,
0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15,
0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1,
0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26, 0x27,
0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38, 0x39,
0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49,
0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59,
0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79,
0x7a, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88,
0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,
0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6,
0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5,
0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4,
0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3,
0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe2,
0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea,
0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9,
0xfa, 0xff, 0xda, 0x00, 0x0c, 0x03, 0x01, 0x00,
0x02, 0x11, 0x03, 0x11, 0x00, 0x3f, 0x00, 0xf9,
0xf7, 0xfb, 0x67, 0x56, 0xff, 0x00, 0x9f, 0xf8,
0x3f, 0xf0, 0x51, 0xa7, 0xff, 0x00, 0xf2, 0x3d,
0x7e, 0x6f, 0xfd, 0xab, 0x94, 0x7f, 0xd0, 0x9a,
0x8f, 0xfe, 0x0d, 0xc7, 0x7f, 0xf3, 0x61, 0xfd,
0xa7, 0xff, 0x00, 0x10, 0x77, 0x0d, 0xff, 0x00,
0x43, 0xec, 0xcf, 0xff, 0x00, 0x0b, 0xab, 0x1f,
0xff, 0xd9,
];
let decodeDone;
decodeDone = testDecodeRect(decoder, 0, 0, 4, 4, data1, display, 24);
expect(decodeDone).to.be.true;
display.fillRect(0, 0, 4, 4, [128, 128, 128, 255]);
let data2 = [
// JPEG data
0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46,
0x49, 0x46, 0x00, 0x01, 0x01, 0x01, 0x01, 0x2c,
0x01, 0x2c, 0x00, 0x73, 0xff, 0xc0, 0x00, 0x11,
0x08, 0x00, 0x04, 0x00, 0x04, 0x03, 0x01, 0x11,
0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xff,
0xda, 0x00, 0x0c, 0x03, 0x01, 0x00, 0x02, 0x11,
0x03, 0x11, 0x00, 0x3f, 0x00, 0xf9, 0xf7, 0xfb,
0x67, 0x56, 0xff, 0x00, 0x9f, 0xf8, 0x3f, 0xf0,
0x51, 0xa7, 0xff, 0x00, 0xf2, 0x3d, 0x7e, 0x6f,
0xfd, 0xab, 0x94, 0x7f, 0xd0, 0x9a, 0x8f, 0xfe,
0x0d, 0xc7, 0x7f, 0xf3, 0x61, 0xfd, 0xa7, 0xff,
0x00, 0x10, 0x77, 0x0d, 0xff, 0x00, 0x43, 0xec,
0xcf, 0xff, 0x00, 0x0b, 0xab, 0x1f, 0xff, 0xd9,
];
decodeDone = testDecodeRect(decoder, 0, 0, 4, 4, data2, display, 24);
expect(decodeDone).to.be.true;
let targetData = new Uint8Array([
0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255
]);
// Browsers have rounding errors, so we need an approximate
// comparing function
function almost(a, b) {
let diff = Math.abs(a - b);
return diff < 5;
}
await display.flush();
expect(display).to.have.displayed(targetData, almost);
});
});
+107 -37
View File
@@ -14,6 +14,10 @@ describe('Key Event Handling', function () {
}
e.stopPropagation = sinon.spy();
e.preventDefault = sinon.spy();
e.getModifierState = function (key) {
return e[key];
};
return e;
}
@@ -144,18 +148,10 @@ describe('Key Event Handling', function () {
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
Object.defineProperty(window, "navigator", {value: {}});
if (window.navigator.platform !== undefined) {
// Object.defineProperty() doesn't work properly in old
// versions of Chrome
this.skip();
}
window.navigator.platform = "Mac x86_64";
});
afterEach(function () {
if (origNavigator !== undefined) {
Object.defineProperty(window, "navigator", origNavigator);
}
Object.defineProperty(window, "navigator", origNavigator);
});
it('should change Alt to AltGraph', function () {
@@ -197,7 +193,7 @@ describe('Key Event Handling', function () {
});
});
describe('Caps Lock on iOS and macOS', function () {
describe('Meta key combination on iOS and macOS', function () {
let origNavigator;
beforeEach(function () {
// window.navigator is a protected read-only property in many
@@ -219,6 +215,60 @@ describe('Key Event Handling', function () {
}
});
it('should send keyup when meta key is pressed on iOS', function () {
window.navigator.platform = "iPad";
const kbd = new Keyboard(document);
kbd.onkeyevent = sinon.spy();
kbd._handleKeyDown(keyevent('keydown', {code: 'MetaRight', key: 'Meta', location: 2, metaKey: true}));
expect(kbd.onkeyevent).to.have.been.calledOnce;
kbd.onkeyevent.resetHistory();
kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a', metaKey: true}));
expect(kbd.onkeyevent).to.have.been.calledTwice;
expect(kbd.onkeyevent).to.have.been.calledWith(0x61, "KeyA", true);
expect(kbd.onkeyevent).to.have.been.calledWith(0x61, "KeyA", false);
kbd.onkeyevent.resetHistory();
kbd._handleKeyUp(keyevent('keyup', {code: 'MetaRight', key: 'Meta', location: 2, metaKey: true}));
expect(kbd.onkeyevent).to.have.been.calledOnce;
});
it('should send keyup when meta key is pressed on macOS', function () {
window.navigator.platform = "Mac";
const kbd = new Keyboard(document);
kbd.onkeyevent = sinon.spy();
kbd._handleKeyDown(keyevent('keydown', {code: 'MetaRight', key: 'Meta', location: 2, metaKey: true}));
expect(kbd.onkeyevent).to.have.been.calledOnce;
kbd.onkeyevent.resetHistory();
kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a', metaKey: true}));
expect(kbd.onkeyevent).to.have.been.calledTwice;
expect(kbd.onkeyevent).to.have.been.calledWith(0x61, "KeyA", true);
expect(kbd.onkeyevent).to.have.been.calledWith(0x61, "KeyA", false);
kbd.onkeyevent.resetHistory();
kbd._handleKeyUp(keyevent('keyup', {code: 'MetaRight', key: 'Meta', location: 2, metaKey: true}));
expect(kbd.onkeyevent).to.have.been.calledOnce;
});
});
describe('Caps Lock on iOS and macOS', function () {
let origNavigator;
beforeEach(function () {
// window.navigator is a protected read-only property in many
// environments, so we need to redefine it whilst running these
// tests.
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
Object.defineProperty(window, "navigator", {value: {}});
});
afterEach(function () {
Object.defineProperty(window, "navigator", origNavigator);
});
it('should toggle caps lock on key press on iOS', function () {
window.navigator.platform = "iPad";
const kbd = new Keyboard(document);
@@ -264,6 +314,50 @@ describe('Key Event Handling', function () {
});
});
describe('Modifier status info', function () {
let origNavigator;
beforeEach(function () {
// window.navigator is a protected read-only property in many
// environments, so we need to redefine it whilst running these
// tests.
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
Object.defineProperty(window, "navigator", {value: {}});
});
afterEach(function () {
Object.defineProperty(window, "navigator", origNavigator);
});
it('should provide caps lock state', function () {
const kbd = new Keyboard(document);
kbd.onkeyevent = sinon.spy();
kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'A', NumLock: false, CapsLock: true}));
expect(kbd.onkeyevent).to.have.been.calledOnce;
expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0x41, "KeyA", true, false, true);
});
it('should provide num lock state', function () {
const kbd = new Keyboard(document);
kbd.onkeyevent = sinon.spy();
kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'A', NumLock: true, CapsLock: false}));
expect(kbd.onkeyevent).to.have.been.calledOnce;
expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0x41, "KeyA", true, true, false);
});
it('should have no num lock state on mac', function () {
window.navigator.platform = "Mac";
const kbd = new Keyboard(document);
kbd.onkeyevent = sinon.spy();
kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'A', NumLock: false, CapsLock: true}));
expect(kbd.onkeyevent).to.have.been.calledOnce;
expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0x41, "KeyA", true, null, true);
});
});
describe('Japanese IM keys on Windows', function () {
let origNavigator;
beforeEach(function () {
@@ -273,19 +367,11 @@ describe('Key Event Handling', function () {
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
Object.defineProperty(window, "navigator", {value: {}});
if (window.navigator.platform !== undefined) {
// Object.defineProperty() doesn't work properly in old
// versions of Chrome
this.skip();
}
window.navigator.platform = "Windows";
});
afterEach(function () {
if (origNavigator !== undefined) {
Object.defineProperty(window, "navigator", origNavigator);
}
Object.defineProperty(window, "navigator", origNavigator);
});
const keys = { 'Zenkaku': 0xff2a, 'Hankaku': 0xff2a,
@@ -314,20 +400,12 @@ describe('Key Event Handling', function () {
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
Object.defineProperty(window, "navigator", {value: {}});
if (window.navigator.platform !== undefined) {
// Object.defineProperty() doesn't work properly in old
// versions of Chrome
this.skip();
}
window.navigator.platform = "Windows x86_64";
this.clock = sinon.useFakeTimers();
});
afterEach(function () {
if (origNavigator !== undefined) {
Object.defineProperty(window, "navigator", origNavigator);
}
Object.defineProperty(window, "navigator", origNavigator);
if (this.clock !== undefined) {
this.clock.restore();
}
@@ -459,20 +537,12 @@ describe('Key Event Handling', function () {
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
Object.defineProperty(window, "navigator", {value: {}});
if (window.navigator.platform !== undefined) {
// Object.defineProperty() doesn't work properly in old
// versions of Chrome
this.skip();
}
window.navigator.platform = "Windows x86_64";
this.clock = sinon.useFakeTimers();
});
afterEach(function () {
if (origNavigator !== undefined) {
Object.defineProperty(window, "navigator", origNavigator);
}
Object.defineProperty(window, "navigator", origNavigator);
if (this.clock !== undefined) {
this.clock.restore();
}
+122 -45
View File
@@ -1,69 +1,146 @@
const expect = chai.expect;
import { l10n } from '../app/localization.js';
import _, { Localizer, l10n } from '../app/localization.js';
describe('Localization', function () {
"use strict";
let origNavigator;
let fetch;
beforeEach(function () {
// window.navigator is a protected read-only property in many
// environments, so we need to redefine it whilst running these
// tests.
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
Object.defineProperty(window, "navigator", {value: {}});
window.navigator.languages = [];
fetch = sinon.stub(window, "fetch");
fetch.resolves(new Response("{}"));
});
afterEach(function () {
fetch.restore();
Object.defineProperty(window, "navigator", origNavigator);
});
describe('Singleton', function () {
it('should export a singleton object', function () {
expect(l10n).to.be.instanceOf(Localizer);
});
it('should export a singleton translation function', async function () {
// FIXME: Can we use some spy instead?
window.navigator.languages = ["de"];
fetch.resolves(new Response(JSON.stringify({ "Foobar": "gazonk" })));
await l10n.setup(["de"]);
expect(_("Foobar")).to.equal("gazonk");
});
});
describe('language selection', function () {
let origNavigator;
beforeEach(function () {
// window.navigator is a protected read-only property in many
// environments, so we need to redefine it whilst running these
// tests.
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
Object.defineProperty(window, "navigator", {value: {}});
if (window.navigator.languages !== undefined) {
// Object.defineProperty() doesn't work properly in old
// versions of Chrome
this.skip();
}
window.navigator.languages = [];
});
afterEach(function () {
if (origNavigator !== undefined) {
Object.defineProperty(window, "navigator", origNavigator);
}
});
it('should use English by default', function () {
expect(l10n.language).to.equal('en');
let lclz = new Localizer();
expect(lclz.language).to.equal('en');
});
it('should use English if no user language matches', function () {
it('should use English if no user language matches', async function () {
window.navigator.languages = ["nl", "de"];
l10n.setup(["es", "fr"]);
expect(l10n.language).to.equal('en');
let lclz = new Localizer();
await lclz.setup(["es", "fr"]);
expect(lclz.language).to.equal('en');
});
it('should use the most preferred user language', function () {
it('should fall back to generic English for other English', async function () {
window.navigator.languages = ["en-AU", "de"];
let lclz = new Localizer();
await lclz.setup(["de", "fr", "en-GB"]);
expect(lclz.language).to.equal('en');
});
it('should prefer specific English over generic', async function () {
window.navigator.languages = ["en-GB", "de"];
let lclz = new Localizer();
await lclz.setup(["de", "en-AU", "en-GB"]);
expect(lclz.language).to.equal('en-GB');
});
it('should use the most preferred user language', async function () {
window.navigator.languages = ["nl", "de", "fr"];
l10n.setup(["es", "fr", "de"]);
expect(l10n.language).to.equal('de');
let lclz = new Localizer();
await lclz.setup(["es", "fr", "de"]);
expect(lclz.language).to.equal('de');
});
it('should prefer sub-languages languages', function () {
it('should prefer sub-languages languages', async function () {
window.navigator.languages = ["pt-BR"];
l10n.setup(["pt", "pt-BR"]);
expect(l10n.language).to.equal('pt-BR');
let lclz = new Localizer();
await lclz.setup(["pt", "pt-BR"]);
expect(lclz.language).to.equal('pt-BR');
});
it('should fall back to language "parents"', function () {
it('should fall back to language "parents"', async function () {
window.navigator.languages = ["pt-BR"];
l10n.setup(["fr", "pt", "de"]);
expect(l10n.language).to.equal('pt');
let lclz = new Localizer();
await lclz.setup(["fr", "pt", "de"]);
expect(lclz.language).to.equal('pt');
});
it('should not use specific language when user asks for a generic language', function () {
it('should not use specific language when user asks for a generic language', async function () {
window.navigator.languages = ["pt", "de"];
l10n.setup(["fr", "pt-BR", "de"]);
expect(l10n.language).to.equal('de');
let lclz = new Localizer();
await lclz.setup(["fr", "pt-BR", "de"]);
expect(lclz.language).to.equal('de');
});
it('should handle underscore as a separator', function () {
it('should handle underscore as a separator', async function () {
window.navigator.languages = ["pt-BR"];
l10n.setup(["pt_BR"]);
expect(l10n.language).to.equal('pt_BR');
let lclz = new Localizer();
await lclz.setup(["pt_BR"]);
expect(lclz.language).to.equal('pt_BR');
});
it('should handle difference in case', function () {
it('should handle difference in case', async function () {
window.navigator.languages = ["pt-br"];
l10n.setup(["pt-BR"]);
expect(l10n.language).to.equal('pt-BR');
let lclz = new Localizer();
await lclz.setup(["pt-BR"]);
expect(lclz.language).to.equal('pt-BR');
});
});
describe('Translation loading', function () {
it('should not fetch a translation for English', async function () {
window.navigator.languages = [];
let lclz = new Localizer();
await lclz.setup([]);
expect(fetch).to.not.have.been.called;
});
it('should fetch dictionary relative base URL', async function () {
window.navigator.languages = ["de", "fr"];
fetch.resolves(new Response('{ "Foobar": "gazonk" }'));
let lclz = new Localizer();
await lclz.setup(["ru", "fr"], "/some/path/");
expect(fetch).to.have.been.calledOnceWith("/some/path/fr.json");
expect(lclz.get("Foobar")).to.equal("gazonk");
});
it('should handle base URL without trailing slash', async function () {
window.navigator.languages = ["de", "fr"];
fetch.resolves(new Response('{ "Foobar": "gazonk" }'));
let lclz = new Localizer();
await lclz.setup(["ru", "fr"], "/some/path");
expect(fetch).to.have.been.calledOnceWith("/some/path/fr.json");
expect(lclz.get("Foobar")).to.equal("gazonk");
});
it('should handle current base URL', async function () {
window.navigator.languages = ["de", "fr"];
fetch.resolves(new Response('{ "Foobar": "gazonk" }'));
let lclz = new Localizer();
await lclz.setup(["ru", "fr"]);
expect(fetch).to.have.been.calledOnceWith("fr.json");
expect(lclz.get("Foobar")).to.equal("gazonk");
});
it('should fail if dictionary cannot be found', async function () {
window.navigator.languages = ["de", "fr"];
fetch.resolves(new Response('{}', { status: 404 }));
let lclz = new Localizer();
let ok = false;
try {
await lclz.setup(["ru", "fr"], "/some/path/");
} catch (e) {
ok = true;
}
expect(ok).to.be.true;
});
});
});
+57 -32
View File
@@ -9,23 +9,26 @@ import FakeWebSocket from './fake.websocket.js';
function testDecodeRect(decoder, x, y, width, height, data, display, depth) {
let sock;
let done = false;
sock = new Websock;
sock.open("ws://example.com");
sock.on('message', () => {
decoder.decodeRect(x, y, width, height, sock, display, depth);
done = decoder.decodeRect(x, y, width, height, sock, display, depth);
});
// Empty messages are filtered at multiple layers, so we need to
// do a direct call
if (data.length === 0) {
decoder.decodeRect(x, y, width, height, sock, display, depth);
done = decoder.decodeRect(x, y, width, height, sock, display, depth);
} else {
sock._websocket._receiveData(new Uint8Array(data));
}
display.flip();
return done;
}
describe('Raw Decoder', function () {
@@ -42,22 +45,36 @@ describe('Raw Decoder', function () {
});
it('should handle the Raw encoding', function () {
testDecodeRect(decoder, 0, 0, 2, 2,
[0xff, 0x00, 0x00, 0, 0x00, 0xff, 0x00, 0,
0x00, 0xff, 0x00, 0, 0xff, 0x00, 0x00, 0],
display, 24);
testDecodeRect(decoder, 2, 0, 2, 2,
[0x00, 0x00, 0xff, 0, 0x00, 0x00, 0xff, 0,
0x00, 0x00, 0xff, 0, 0x00, 0x00, 0xff, 0],
display, 24);
testDecodeRect(decoder, 0, 2, 4, 1,
[0xee, 0x00, 0xff, 0, 0x00, 0xee, 0xff, 0,
0xaa, 0xee, 0xff, 0, 0xab, 0xee, 0xff, 0],
display, 24);
testDecodeRect(decoder, 0, 3, 4, 1,
[0xee, 0x00, 0xff, 0, 0x00, 0xee, 0xff, 0,
0xaa, 0xee, 0xff, 0, 0xab, 0xee, 0xff, 0],
display, 24);
let done;
done = testDecodeRect(decoder, 0, 0, 2, 2,
[0xff, 0x00, 0x00, 0,
0x00, 0xff, 0x00, 0,
0x00, 0xff, 0x00, 0,
0xff, 0x00, 0x00, 0],
display, 24);
expect(done).to.be.true;
done = testDecodeRect(decoder, 2, 0, 2, 2,
[0x00, 0x00, 0xff, 0,
0x00, 0x00, 0xff, 0,
0x00, 0x00, 0xff, 0,
0x00, 0x00, 0xff, 0],
display, 24);
expect(done).to.be.true;
done = testDecodeRect(decoder, 0, 2, 4, 1,
[0xee, 0x00, 0xff, 0,
0x00, 0xee, 0xff, 0,
0xaa, 0xee, 0xff, 0,
0xab, 0xee, 0xff, 0],
display, 24);
expect(done).to.be.true;
done = testDecodeRect(decoder, 0, 3, 4, 1,
[0xee, 0x00, 0xff, 0,
0x00, 0xee, 0xff, 0,
0xaa, 0xee, 0xff, 0,
0xab, 0xee, 0xff, 0],
display, 24);
expect(done).to.be.true;
let targetData = new Uint8Array([
0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
@@ -70,18 +87,24 @@ describe('Raw Decoder', function () {
});
it('should handle the Raw encoding in low colour mode', function () {
testDecodeRect(decoder, 0, 0, 2, 2,
[0x30, 0x30, 0x30, 0x30],
display, 8);
testDecodeRect(decoder, 2, 0, 2, 2,
[0x0c, 0x0c, 0x0c, 0x0c],
display, 8);
testDecodeRect(decoder, 0, 2, 4, 1,
[0x0c, 0x0c, 0x30, 0x30],
display, 8);
testDecodeRect(decoder, 0, 3, 4, 1,
[0x0c, 0x0c, 0x30, 0x30],
display, 8);
let done;
done = testDecodeRect(decoder, 0, 0, 2, 2,
[0x30, 0x30, 0x30, 0x30],
display, 8);
expect(done).to.be.true;
done = testDecodeRect(decoder, 2, 0, 2, 2,
[0x0c, 0x0c, 0x0c, 0x0c],
display, 8);
expect(done).to.be.true;
done = testDecodeRect(decoder, 0, 2, 4, 1,
[0x0c, 0x0c, 0x30, 0x30],
display, 8);
expect(done).to.be.true;
done = testDecodeRect(decoder, 0, 3, 4, 1,
[0x0c, 0x0c, 0x30, 0x30],
display, 8);
expect(done).to.be.true;
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
@@ -98,7 +121,7 @@ describe('Raw Decoder', function () {
display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);
display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]);
testDecodeRect(decoder, 1, 2, 0, 0, [], display, 24);
let done = testDecodeRect(decoder, 1, 2, 0, 0, [], display, 24);
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
@@ -107,6 +130,7 @@ describe('Raw Decoder', function () {
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]);
expect(done).to.be.true;
expect(display).to.have.displayed(targetData);
});
@@ -115,7 +139,7 @@ describe('Raw Decoder', function () {
display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);
display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]);
testDecodeRect(decoder, 1, 2, 0, 0, [], display, 8);
let done = testDecodeRect(decoder, 1, 2, 0, 0, [], display, 8);
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
@@ -124,6 +148,7 @@ describe('Raw Decoder', function () {
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]);
expect(done).to.be.true;
expect(display).to.have.displayed(targetData);
});
});
+1595 -419
View File
File diff suppressed because it is too large Load Diff
+12 -4
View File
@@ -9,23 +9,26 @@ import FakeWebSocket from './fake.websocket.js';
function testDecodeRect(decoder, x, y, width, height, data, display, depth) {
let sock;
let done = false;
sock = new Websock;
sock.open("ws://example.com");
sock.on('message', () => {
decoder.decodeRect(x, y, width, height, sock, display, depth);
done = decoder.decodeRect(x, y, width, height, sock, display, depth);
});
// Empty messages are filtered at multiple layers, so we need to
// do a direct call
if (data.length === 0) {
decoder.decodeRect(x, y, width, height, sock, display, depth);
done = decoder.decodeRect(x, y, width, height, sock, display, depth);
} else {
sock._websocket._receiveData(new Uint8Array(data));
}
display.flip();
return done;
}
function push16(arr, num) {
@@ -76,7 +79,7 @@ describe('RRE Decoder', function () {
push16(data, 2); // width: 2
push16(data, 2); // height: 2
testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
let done = testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
@@ -85,6 +88,7 @@ describe('RRE Decoder', function () {
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]);
expect(done).to.be.true;
expect(display).to.have.displayed(targetData);
});
@@ -93,7 +97,10 @@ describe('RRE Decoder', function () {
display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);
display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]);
testDecodeRect(decoder, 1, 2, 0, 0, [ 0x00, 0xff, 0xff, 0xff, 0xff ], display, 24);
let done = testDecodeRect(decoder, 1, 2, 0, 0,
[ 0x00, 0x00, 0x00, 0x00,
0xff, 0xff, 0xff, 0xff ],
display, 24);
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
@@ -102,6 +109,7 @@ describe('RRE Decoder', function () {
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]);
expect(done).to.be.true;
expect(display).to.have.displayed(targetData);
});
});
+55 -33
View File
@@ -9,23 +9,26 @@ import FakeWebSocket from './fake.websocket.js';
function testDecodeRect(decoder, x, y, width, height, data, display, depth) {
let sock;
let done = false;
sock = new Websock;
sock.open("ws://example.com");
sock.on('message', () => {
decoder.decodeRect(x, y, width, height, sock, display, depth);
done = decoder.decodeRect(x, y, width, height, sock, display, depth);
});
// Empty messages are filtered at multiple layers, so we need to
// do a direct call
if (data.length === 0) {
decoder.decodeRect(x, y, width, height, sock, display, depth);
done = decoder.decodeRect(x, y, width, height, sock, display, depth);
} else {
sock._websocket._receiveData(new Uint8Array(data));
}
display.flip();
return done;
}
describe('Tight Decoder', function () {
@@ -42,9 +45,9 @@ describe('Tight Decoder', function () {
});
it('should handle fill rects', function () {
testDecodeRect(decoder, 0, 0, 4, 4,
[0x80, 0xff, 0x88, 0x44],
display, 24);
let done = testDecodeRect(decoder, 0, 0, 4, 4,
[0x80, 0xff, 0x88, 0x44],
display, 24);
let targetData = new Uint8Array([
0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255,
@@ -53,21 +56,31 @@ describe('Tight Decoder', function () {
0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255,
]);
expect(done).to.be.true;
expect(display).to.have.displayed(targetData);
});
it('should handle uncompressed copy rects', function () {
let done;
let blueData = [ 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0xff ];
let greenData = [ 0x00, 0x00, 0xff, 0x00, 0x00, 0xff, 0x00 ];
testDecodeRect(decoder, 0, 0, 2, 1, blueData, display, 24);
testDecodeRect(decoder, 0, 1, 2, 1, blueData, display, 24);
testDecodeRect(decoder, 2, 0, 2, 1, greenData, display, 24);
testDecodeRect(decoder, 2, 1, 2, 1, greenData, display, 24);
testDecodeRect(decoder, 0, 2, 2, 1, greenData, display, 24);
testDecodeRect(decoder, 0, 3, 2, 1, greenData, display, 24);
testDecodeRect(decoder, 2, 2, 2, 1, blueData, display, 24);
testDecodeRect(decoder, 2, 3, 2, 1, blueData, display, 24);
done = testDecodeRect(decoder, 0, 0, 2, 1, blueData, display, 24);
expect(done).to.be.true;
done = testDecodeRect(decoder, 0, 1, 2, 1, blueData, display, 24);
expect(done).to.be.true;
done = testDecodeRect(decoder, 2, 0, 2, 1, greenData, display, 24);
expect(done).to.be.true;
done = testDecodeRect(decoder, 2, 1, 2, 1, greenData, display, 24);
expect(done).to.be.true;
done = testDecodeRect(decoder, 0, 2, 2, 1, greenData, display, 24);
expect(done).to.be.true;
done = testDecodeRect(decoder, 0, 3, 2, 1, greenData, display, 24);
expect(done).to.be.true;
done = testDecodeRect(decoder, 2, 2, 2, 1, blueData, display, 24);
expect(done).to.be.true;
done = testDecodeRect(decoder, 2, 3, 2, 1, blueData, display, 24);
expect(done).to.be.true;
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
@@ -89,7 +102,7 @@ describe('Tight Decoder', function () {
0x60, 0x82, 0x01, 0x99, 0x8d, 0x29, 0x02, 0xa6,
0x00, 0x7e, 0xbf, 0x0f, 0xf1 ];
testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
let done = testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
@@ -98,6 +111,7 @@ describe('Tight Decoder', function () {
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]);
expect(done).to.be.true;
expect(display).to.have.displayed(targetData);
});
@@ -110,7 +124,7 @@ describe('Tight Decoder', function () {
// Pixels
0x30, 0x30, 0xc0, 0xc0 ];
testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
let done = testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
@@ -119,6 +133,7 @@ describe('Tight Decoder', function () {
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]);
expect(done).to.be.true;
expect(display).to.have.displayed(targetData);
});
@@ -135,7 +150,7 @@ describe('Tight Decoder', function () {
0x78, 0x9c, 0x33, 0x30, 0x38, 0x70, 0xc0, 0x00,
0x8a, 0x01, 0x21, 0x3c, 0x05, 0xa1 ];
testDecodeRect(decoder, 0, 0, 4, 12, data, display, 24);
let done = testDecodeRect(decoder, 0, 0, 4, 12, data, display, 24);
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
@@ -152,10 +167,12 @@ describe('Tight Decoder', function () {
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]);
expect(done).to.be.true;
expect(display).to.have.displayed(targetData);
});
it('should handle uncompressed palette rects', function () {
let done;
let data1 = [
// Control bytes
0x40, 0x01,
@@ -171,8 +188,10 @@ describe('Tight Decoder', function () {
// Pixels
0x01, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00 ];
testDecodeRect(decoder, 0, 0, 4, 2, data1, display, 24);
testDecodeRect(decoder, 0, 2, 4, 2, data2, display, 24);
done = testDecodeRect(decoder, 0, 0, 4, 2, data1, display, 24);
expect(done).to.be.true;
done = testDecodeRect(decoder, 0, 2, 4, 2, data2, display, 24);
expect(done).to.be.true;
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
@@ -196,7 +215,7 @@ describe('Tight Decoder', function () {
0x62, 0x08, 0xc9, 0xc0, 0x00, 0x00, 0x00, 0x54,
0x00, 0x09 ];
testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
let done = testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
@@ -205,6 +224,7 @@ describe('Tight Decoder', function () {
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]);
expect(done).to.be.true;
expect(display).to.have.displayed(targetData);
});
@@ -221,7 +241,7 @@ describe('Tight Decoder', function () {
display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);
display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]);
testDecodeRect(decoder, 1, 2, 0, 0, [ 0x00 ], display, 24);
let done = testDecodeRect(decoder, 1, 2, 0, 0, [ 0x00 ], display, 24);
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
@@ -230,6 +250,7 @@ describe('Tight Decoder', function () {
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]);
expect(done).to.be.true;
expect(display).to.have.displayed(targetData);
});
@@ -238,10 +259,10 @@ describe('Tight Decoder', function () {
display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);
display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]);
testDecodeRect(decoder, 1, 2, 0, 0,
[ 0x40, 0x01, 0x01,
0xff, 0xff, 0xff,
0xff, 0xff, 0xff ], display, 24);
let done = testDecodeRect(decoder, 1, 2, 0, 0,
[ 0x40, 0x01, 0x01,
0xff, 0xff, 0xff,
0xff, 0xff, 0xff ], display, 24);
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
@@ -250,6 +271,7 @@ describe('Tight Decoder', function () {
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]);
expect(done).to.be.true;
expect(display).to.have.displayed(targetData);
});
@@ -258,8 +280,9 @@ describe('Tight Decoder', function () {
display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);
display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]);
testDecodeRect(decoder, 1, 2, 0, 0,
[ 0x80, 0xff, 0xff, 0xff ], display, 24);
let done = testDecodeRect(decoder, 1, 2, 0, 0,
[ 0x80, 0xff, 0xff, 0xff ],
display, 24);
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
@@ -268,10 +291,11 @@ describe('Tight Decoder', function () {
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]);
expect(done).to.be.true;
expect(display).to.have.displayed(targetData);
});
it('should handle JPEG rects', function (done) {
it('should handle JPEG rects', async function () {
let data = [
// Control bytes
0x90, 0xd6, 0x05,
@@ -369,7 +393,8 @@ describe('Tight Decoder', function () {
0x3f, 0xeb, 0xff, 0x00, 0xff, 0xd9,
];
testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
let decodeDone = testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
expect(decodeDone).to.be.true;
let targetData = new Uint8Array([
0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
@@ -385,10 +410,7 @@ describe('Tight Decoder', function () {
return diff < 5;
}
display.onflush = () => {
expect(display).to.have.displayed(targetData, almost);
done();
};
display.flush();
await display.flush();
expect(display).to.have.displayed(targetData, almost);
});
});
+10 -9
View File
@@ -9,23 +9,26 @@ import FakeWebSocket from './fake.websocket.js';
function testDecodeRect(decoder, x, y, width, height, data, display, depth) {
let sock;
let done = false;
sock = new Websock;
sock.open("ws://example.com");
sock.on('message', () => {
decoder.decodeRect(x, y, width, height, sock, display, depth);
done = decoder.decodeRect(x, y, width, height, sock, display, depth);
});
// Empty messages are filtered at multiple layers, so we need to
// do a direct call
if (data.length === 0) {
decoder.decodeRect(x, y, width, height, sock, display, depth);
done = decoder.decodeRect(x, y, width, height, sock, display, depth);
} else {
sock._websocket._receiveData(new Uint8Array(data));
}
display.flip();
return done;
}
describe('TightPng Decoder', function () {
@@ -41,7 +44,7 @@ describe('TightPng Decoder', function () {
display.resize(4, 4);
});
it('should handle the TightPng encoding', function (done) {
it('should handle the TightPng encoding', async function () {
let data = [
// Control bytes
0xa0, 0xb4, 0x04,
@@ -119,7 +122,8 @@ describe('TightPng Decoder', function () {
0xae, 0x42, 0x60, 0x82,
];
testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
let decodeDone = testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
expect(decodeDone).to.be.true;
let targetData = new Uint8Array([
0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
@@ -135,10 +139,7 @@ describe('TightPng Decoder', function () {
return diff < 30;
}
display.onflush = () => {
expect(display).to.have.displayed(targetData, almost);
done();
};
display.flush();
await display.flush();
expect(display).to.have.displayed(targetData, almost);
});
});
+261 -185
View File
@@ -6,92 +6,64 @@ import FakeWebSocket from './fake.websocket.js';
describe('Websock', function () {
"use strict";
describe('Queue methods', function () {
let sock;
const RQ_TEMPLATE = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7]);
describe('Receive queue methods', function () {
let sock, websock;
beforeEach(function () {
sock = new Websock();
// skip init
sock._allocateBuffers();
sock._rQ.set(RQ_TEMPLATE);
sock._rQlen = RQ_TEMPLATE.length;
});
describe('rQlen', function () {
it('should return the length of the receive queue', function () {
sock.rQi = 0;
expect(sock.rQlen).to.equal(RQ_TEMPLATE.length);
});
it("should return the proper length if we read some from the receive queue", function () {
sock.rQi = 1;
expect(sock.rQlen).to.equal(RQ_TEMPLATE.length - 1);
});
websock = new FakeWebSocket();
websock._open();
sock.attach(websock);
});
describe('rQpeek8', function () {
it('should peek at the next byte without poping it off the queue', function () {
const befLen = sock.rQlen;
const peek = sock.rQpeek8();
expect(sock.rQpeek8()).to.equal(peek);
expect(sock.rQlen).to.equal(befLen);
websock._receiveData(new Uint8Array([0xab, 0xcd]));
expect(sock.rQpeek8()).to.equal(0xab);
expect(sock.rQpeek8()).to.equal(0xab);
});
});
describe('rQshift8()', function () {
it('should pop a single byte from the receive queue', function () {
const peek = sock.rQpeek8();
const befLen = sock.rQlen;
expect(sock.rQshift8()).to.equal(peek);
expect(sock.rQlen).to.equal(befLen - 1);
websock._receiveData(new Uint8Array([0xab, 0xcd]));
expect(sock.rQshift8()).to.equal(0xab);
expect(sock.rQshift8()).to.equal(0xcd);
});
});
describe('rQshift16()', function () {
it('should pop two bytes from the receive queue and return a single number', function () {
const befLen = sock.rQlen;
const expected = (RQ_TEMPLATE[0] << 8) + RQ_TEMPLATE[1];
expect(sock.rQshift16()).to.equal(expected);
expect(sock.rQlen).to.equal(befLen - 2);
websock._receiveData(new Uint8Array([0xab, 0xcd, 0x12, 0x34]));
expect(sock.rQshift16()).to.equal(0xabcd);
expect(sock.rQshift16()).to.equal(0x1234);
});
});
describe('rQshift32()', function () {
it('should pop four bytes from the receive queue and return a single number', function () {
const befLen = sock.rQlen;
const expected = (RQ_TEMPLATE[0] << 24) +
(RQ_TEMPLATE[1] << 16) +
(RQ_TEMPLATE[2] << 8) +
RQ_TEMPLATE[3];
expect(sock.rQshift32()).to.equal(expected);
expect(sock.rQlen).to.equal(befLen - 4);
websock._receiveData(new Uint8Array([0xab, 0xcd, 0x12, 0x34,
0x88, 0xee, 0x11, 0x33]));
expect(sock.rQshift32()).to.equal(0xabcd1234);
expect(sock.rQshift32()).to.equal(0x88ee1133);
});
});
describe('rQshiftStr', function () {
it('should shift the given number of bytes off of the receive queue and return a string', function () {
const befLen = sock.rQlen;
const befRQi = sock.rQi;
const shifted = sock.rQshiftStr(3);
expect(shifted).to.be.a('string');
expect(shifted).to.equal(String.fromCharCode.apply(null, Array.prototype.slice.call(new Uint8Array(RQ_TEMPLATE.buffer, befRQi, 3))));
expect(sock.rQlen).to.equal(befLen - 3);
});
it('should shift the entire rest of the queue off if no length is given', function () {
sock.rQshiftStr();
expect(sock.rQlen).to.equal(0);
websock._receiveData(new Uint8Array([0xab, 0xcd, 0x12, 0x34,
0x88, 0xee, 0x11, 0x33]));
expect(sock.rQshiftStr(4)).to.equal('\xab\xcd\x12\x34');
expect(sock.rQshiftStr(4)).to.equal('\x88\xee\x11\x33');
});
it('should be able to handle very large strings', function () {
const BIG_LEN = 500000;
const RQ_BIG = new Uint8Array(BIG_LEN);
const incoming = new Uint8Array(BIG_LEN);
let expected = "";
let letterCode = 'a'.charCodeAt(0);
for (let i = 0; i < BIG_LEN; i++) {
RQ_BIG[i] = letterCode;
incoming[i] = letterCode;
expected += String.fromCharCode(letterCode);
if (letterCode < 'z'.charCodeAt(0)) {
@@ -100,146 +72,282 @@ describe('Websock', function () {
letterCode = 'a'.charCodeAt(0);
}
}
sock._rQ.set(RQ_BIG);
sock._rQlen = RQ_BIG.length;
websock._receiveData(incoming);
const shifted = sock.rQshiftStr();
const shifted = sock.rQshiftStr(BIG_LEN);
expect(shifted).to.be.equal(expected);
expect(sock.rQlen).to.equal(0);
});
});
describe('rQshiftBytes', function () {
it('should shift the given number of bytes of the receive queue and return an array', function () {
const befLen = sock.rQlen;
const befRQi = sock.rQi;
const shifted = sock.rQshiftBytes(3);
expect(shifted).to.be.an.instanceof(Uint8Array);
expect(shifted).to.array.equal(new Uint8Array(RQ_TEMPLATE.buffer, befRQi, 3));
expect(sock.rQlen).to.equal(befLen - 3);
websock._receiveData(new Uint8Array([0xab, 0xcd, 0x12, 0x34,
0x88, 0xee, 0x11, 0x33]));
expect(sock.rQshiftBytes(4)).to.array.equal(new Uint8Array([0xab, 0xcd, 0x12, 0x34]));
expect(sock.rQshiftBytes(4)).to.array.equal(new Uint8Array([0x88, 0xee, 0x11, 0x33]));
});
it('should shift the entire rest of the queue off if no length is given', function () {
sock.rQshiftBytes();
expect(sock.rQlen).to.equal(0);
it('should return a shared array if requested', function () {
websock._receiveData(new Uint8Array([0xab, 0xcd, 0x12, 0x34,
0x88, 0xee, 0x11, 0x33]));
const bytes = sock.rQshiftBytes(4, false);
expect(bytes).to.array.equal(new Uint8Array([0xab, 0xcd, 0x12, 0x34]));
expect(bytes.buffer.byteLength).to.not.equal(bytes.length);
});
});
describe('rQslice', function () {
beforeEach(function () {
sock.rQi = 0;
});
describe('rQpeekBytes', function () {
it('should not modify the receive queue', function () {
const befLen = sock.rQlen;
sock.rQslice(0, 2);
expect(sock.rQlen).to.equal(befLen);
websock._receiveData(new Uint8Array([0xab, 0xcd, 0x12, 0x34,
0x88, 0xee, 0x11, 0x33]));
expect(sock.rQpeekBytes(4)).to.array.equal(new Uint8Array([0xab, 0xcd, 0x12, 0x34]));
expect(sock.rQpeekBytes(4)).to.array.equal(new Uint8Array([0xab, 0xcd, 0x12, 0x34]));
});
it('should return an array containing the given slice of the receive queue', function () {
const sl = sock.rQslice(0, 2);
expect(sl).to.be.an.instanceof(Uint8Array);
expect(sl).to.array.equal(new Uint8Array(RQ_TEMPLATE.buffer, 0, 2));
});
it('should use the rest of the receive queue if no end is given', function () {
const sl = sock.rQslice(1);
expect(sl).to.have.length(RQ_TEMPLATE.length - 1);
expect(sl).to.array.equal(new Uint8Array(RQ_TEMPLATE.buffer, 1));
});
it('should take the current rQi in to account', function () {
sock.rQi = 1;
expect(sock.rQslice(0, 2)).to.array.equal(new Uint8Array(RQ_TEMPLATE.buffer, 1, 2));
it('should return a shared array if requested', function () {
websock._receiveData(new Uint8Array([0xab, 0xcd, 0x12, 0x34,
0x88, 0xee, 0x11, 0x33]));
const bytes = sock.rQpeekBytes(4, false);
expect(bytes).to.array.equal(new Uint8Array([0xab, 0xcd, 0x12, 0x34]));
expect(bytes.buffer.byteLength).to.not.equal(bytes.length);
});
});
describe('rQwait', function () {
beforeEach(function () {
sock.rQi = 0;
websock._receiveData(new Uint8Array([0xab, 0xcd, 0x12, 0x34,
0x88, 0xee, 0x11, 0x33]));
});
it('should return true if there are not enough bytes in the receive queue', function () {
expect(sock.rQwait('hi', RQ_TEMPLATE.length + 1)).to.be.true;
expect(sock.rQwait('hi', 9)).to.be.true;
});
it('should return false if there are enough bytes in the receive queue', function () {
expect(sock.rQwait('hi', RQ_TEMPLATE.length)).to.be.false;
expect(sock.rQwait('hi', 8)).to.be.false;
});
it('should return true and reduce rQi by "goback" if there are not enough bytes', function () {
sock.rQi = 5;
expect(sock.rQwait('hi', RQ_TEMPLATE.length, 4)).to.be.true;
expect(sock.rQi).to.equal(1);
expect(sock.rQshift32()).to.equal(0xabcd1234);
expect(sock.rQwait('hi', 8, 2)).to.be.true;
expect(sock.rQshift32()).to.equal(0x123488ee);
});
it('should raise an error if we try to go back more than possible', function () {
sock.rQi = 5;
expect(() => sock.rQwait('hi', RQ_TEMPLATE.length, 6)).to.throw(Error);
expect(sock.rQshift32()).to.equal(0xabcd1234);
expect(() => sock.rQwait('hi', 8, 6)).to.throw(Error);
});
it('should not reduce rQi if there are enough bytes', function () {
sock.rQi = 5;
sock.rQwait('hi', 1, 6);
expect(sock.rQi).to.equal(5);
expect(sock.rQshift32()).to.equal(0xabcd1234);
expect(sock.rQwait('hi', 4, 2)).to.be.false;
expect(sock.rQshift32()).to.equal(0x88ee1133);
});
});
});
describe('Send queue methods', function () {
let sock;
const bufferSize = 10 * 1024;
beforeEach(function () {
let websock = new FakeWebSocket();
websock._open();
sock = new Websock();
sock.attach(websock);
});
describe('sQpush8()', function () {
it('should send a single byte', function () {
sock.sQpush8(42);
sock.flush();
expect(sock).to.have.sent(new Uint8Array([42]));
});
it('should not send any data until flushing', function () {
sock.sQpush8(42);
expect(sock).to.have.sent(new Uint8Array([]));
});
it('should implicitly flush if the queue is full', function () {
for (let i = 0;i <= bufferSize;i++) {
sock.sQpush8(42);
}
let expected = [];
for (let i = 0;i < bufferSize;i++) {
expected.push(42);
}
expect(sock).to.have.sent(new Uint8Array(expected));
});
});
describe('sQpush16()', function () {
it('should send a number as two bytes', function () {
sock.sQpush16(420);
sock.flush();
expect(sock).to.have.sent(new Uint8Array([1, 164]));
});
it('should not send any data until flushing', function () {
sock.sQpush16(420);
expect(sock).to.have.sent(new Uint8Array([]));
});
it('should implicitly flush if the queue is full', function () {
for (let i = 0;i <= bufferSize/2;i++) {
sock.sQpush16(420);
}
let expected = [];
for (let i = 0;i < bufferSize/2;i++) {
expected.push(1);
expected.push(164);
}
expect(sock).to.have.sent(new Uint8Array(expected));
});
});
describe('sQpush32()', function () {
it('should send a number as two bytes', function () {
sock.sQpush32(420420);
sock.flush();
expect(sock).to.have.sent(new Uint8Array([0, 6, 106, 68]));
});
it('should not send any data until flushing', function () {
sock.sQpush32(420420);
expect(sock).to.have.sent(new Uint8Array([]));
});
it('should implicitly flush if the queue is full', function () {
for (let i = 0;i <= bufferSize/4;i++) {
sock.sQpush32(420420);
}
let expected = [];
for (let i = 0;i < bufferSize/4;i++) {
expected.push(0);
expected.push(6);
expected.push(106);
expected.push(68);
}
expect(sock).to.have.sent(new Uint8Array(expected));
});
});
describe('sQpushString()', function () {
it('should send a string buffer', function () {
sock.sQpushString('\x12\x34\x56\x78\x90');
sock.flush();
expect(sock).to.have.sent(new Uint8Array([0x12, 0x34, 0x56, 0x78, 0x90]));
});
it('should not send any data until flushing', function () {
sock.sQpushString('\x12\x34\x56\x78\x90');
expect(sock).to.have.sent(new Uint8Array([]));
});
it('should implicitly flush if the queue is full', function () {
for (let i = 0;i <= bufferSize/5;i++) {
sock.sQpushString('\x12\x34\x56\x78\x90');
}
let expected = [];
for (let i = 0;i < bufferSize/5;i++) {
expected.push(0x12);
expected.push(0x34);
expected.push(0x56);
expected.push(0x78);
expected.push(0x90);
}
expect(sock).to.have.sent(new Uint8Array(expected));
});
it('should implicitly split a large buffer', function () {
let str = '';
for (let i = 0;i <= bufferSize/5;i++) {
str += '\x12\x34\x56\x78\x90';
}
sock.sQpushString(str);
let expected = [];
for (let i = 0;i < bufferSize/5;i++) {
expected.push(0x12);
expected.push(0x34);
expected.push(0x56);
expected.push(0x78);
expected.push(0x90);
}
expect(sock).to.have.sent(new Uint8Array(expected));
});
});
describe('sQpushBytes()', function () {
it('should send a byte buffer', function () {
sock.sQpushBytes(new Uint8Array([0x12, 0x34, 0x56, 0x78, 0x90]));
sock.flush();
expect(sock).to.have.sent(new Uint8Array([0x12, 0x34, 0x56, 0x78, 0x90]));
});
it('should not send any data until flushing', function () {
sock.sQpushBytes(new Uint8Array([0x12, 0x34, 0x56, 0x78, 0x90]));
expect(sock).to.have.sent(new Uint8Array([]));
});
it('should implicitly flush if the queue is full', function () {
for (let i = 0;i <= bufferSize/5;i++) {
sock.sQpushBytes(new Uint8Array([0x12, 0x34, 0x56, 0x78, 0x90]));
}
let expected = [];
for (let i = 0;i < bufferSize/5;i++) {
expected.push(0x12);
expected.push(0x34);
expected.push(0x56);
expected.push(0x78);
expected.push(0x90);
}
expect(sock).to.have.sent(new Uint8Array(expected));
});
it('should implicitly split a large buffer', function () {
let buffer = [];
for (let i = 0;i <= bufferSize/5;i++) {
buffer.push(0x12);
buffer.push(0x34);
buffer.push(0x56);
buffer.push(0x78);
buffer.push(0x90);
}
sock.sQpushBytes(new Uint8Array(buffer));
let expected = [];
for (let i = 0;i < bufferSize/5;i++) {
expected.push(0x12);
expected.push(0x34);
expected.push(0x56);
expected.push(0x78);
expected.push(0x90);
}
expect(sock).to.have.sent(new Uint8Array(expected));
});
});
describe('flush', function () {
beforeEach(function () {
sock._websocket = {
send: sinon.spy()
};
});
it('should actually send on the websocket', function () {
sock._websocket.bufferedAmount = 8;
sock._websocket.readyState = WebSocket.OPEN;
sock._sQ = new Uint8Array([1, 2, 3]);
sock._sQlen = 3;
const encoded = sock._encodeMessage();
sock.flush();
expect(sock._websocket.send).to.have.been.calledOnce;
expect(sock._websocket.send).to.have.been.calledWith(encoded);
expect(sock).to.have.sent(new Uint8Array([1, 2, 3]));
});
it('should not call send if we do not have anything queued up', function () {
sock._sQlen = 0;
sock._websocket.bufferedAmount = 8;
sock.flush();
expect(sock._websocket.send).not.to.have.been.called;
});
});
describe('send', function () {
beforeEach(function () {
sock.flush = sinon.spy();
});
it('should add to the send queue', function () {
sock.send([1, 2, 3]);
const sq = sock.sQ;
expect(new Uint8Array(sq.buffer, sock._sQlen - 3, 3)).to.array.equal(new Uint8Array([1, 2, 3]));
});
it('should call flush', function () {
sock.send([1, 2, 3]);
expect(sock.flush).to.have.been.calledOnce;
});
});
describe('sendString', function () {
beforeEach(function () {
sock.send = sinon.spy();
});
it('should call send after converting the string to an array', function () {
sock.sendString("\x01\x02\x03");
expect(sock.send).to.have.been.calledWith([1, 2, 3]);
expect(sock).to.have.sent(new Uint8Array([]));
});
});
});
@@ -450,9 +558,8 @@ describe('Websock', function () {
sock._allocateBuffers();
});
it('should support adding binary Uint8Array data to the receive queue', function () {
it('should support adding data to the receive queue', function () {
const msg = { data: new Uint8Array([1, 2, 3]) };
sock._mode = 'binary';
sock._recvMessage(msg);
expect(sock.rQshiftStr(3)).to.equal('\x01\x02\x03');
});
@@ -473,80 +580,49 @@ describe('Websock', function () {
expect(sock._eventHandlers.message).not.to.have.been.called;
});
it('should compact the receive queue when a message handler empties it', function () {
sock._eventHandlers.message = () => { sock.rQi = sock._rQlen; };
it('should compact the receive queue when fully read', function () {
sock._rQ = new Uint8Array([0, 1, 2, 3, 4, 5, 0, 0, 0, 0]);
sock._rQlen = 6;
sock.rQi = 6;
sock._rQi = 6;
const msg = { data: new Uint8Array([1, 2, 3]).buffer };
sock._mode = 'binary';
sock._recvMessage(msg);
expect(sock._rQlen).to.equal(0);
expect(sock.rQi).to.equal(0);
expect(sock._rQlen).to.equal(3);
expect(sock._rQi).to.equal(0);
});
it('should compact the receive queue when we reach the end of the buffer', function () {
sock._rQ = new Uint8Array(20);
sock._rQbufferSize = 20;
sock._rQlen = 20;
sock.rQi = 10;
sock._rQi = 10;
const msg = { data: new Uint8Array([1, 2]).buffer };
sock._mode = 'binary';
sock._recvMessage(msg);
expect(sock._rQlen).to.equal(12);
expect(sock.rQi).to.equal(0);
expect(sock._rQi).to.equal(0);
});
it('should automatically resize the receive queue if the incoming message is larger than the buffer', function () {
sock._rQ = new Uint8Array(20);
sock._rQlen = 0;
sock.rQi = 0;
sock._rQi = 0;
sock._rQbufferSize = 20;
const msg = { data: new Uint8Array(30).buffer };
sock._mode = 'binary';
sock._recvMessage(msg);
expect(sock._rQlen).to.equal(30);
expect(sock.rQi).to.equal(0);
expect(sock._rQi).to.equal(0);
expect(sock._rQ.length).to.equal(240); // keep the invariant that rQbufferSize / 8 >= rQlen
});
it('should automatically resize the receive queue if the incoming message is larger than 1/8th of the buffer and we reach the end of the buffer', function () {
sock._rQ = new Uint8Array(20);
sock._rQlen = 16;
sock.rQi = 16;
sock._rQi = 15;
sock._rQbufferSize = 20;
const msg = { data: new Uint8Array(6).buffer };
sock._mode = 'binary';
sock._recvMessage(msg);
expect(sock._rQlen).to.equal(6);
expect(sock.rQi).to.equal(0);
expect(sock._rQ.length).to.equal(48);
});
});
describe('Data encoding', function () {
before(function () { FakeWebSocket.replace(); });
after(function () { FakeWebSocket.restore(); });
describe('as binary data', function () {
let sock;
beforeEach(function () {
sock = new Websock();
sock.open('ws://', 'binary');
sock._websocket._open();
});
it('should only send the send queue up to the send queue length', function () {
sock._sQ = new Uint8Array([1, 2, 3, 4, 5]);
sock._sQlen = 3;
const res = sock._encodeMessage();
expect(res).to.array.equal(new Uint8Array([1, 2, 3]));
});
it('should properly pass the encoded data off to the actual WebSocket', function () {
sock.send([1, 2, 3]);
expect(sock._websocket._getSentData()).to.array.equal(new Uint8Array([1, 2, 3]));
});
expect(sock._rQlen).to.equal(7);
expect(sock._rQi).to.equal(0);
expect(sock._rQ.length).to.equal(56);
});
});
});
+33 -16
View File
@@ -8,39 +8,48 @@ describe('WebUtil', function () {
"use strict";
describe('config variables', function () {
let origState, origHref;
beforeEach(function () {
origState = history.state;
origHref = location.href;
});
afterEach(function () {
history.replaceState(origState, '', origHref);
});
it('should parse query string variables', function () {
// history.pushState() will not cause the browser to attempt loading
// the URL, this is exactly what we want here for the tests.
history.pushState({}, '', "test?myvar=myval");
history.replaceState({}, '', "test?myvar=myval");
expect(WebUtil.getConfigVar("myvar")).to.be.equal("myval");
});
it('should return default value when no query match', function () {
history.pushState({}, '', "test?myvar=myval");
history.replaceState({}, '', "test?myvar=myval");
expect(WebUtil.getConfigVar("other", "def")).to.be.equal("def");
});
it('should handle no query match and no default value', function () {
history.pushState({}, '', "test?myvar=myval");
history.replaceState({}, '', "test?myvar=myval");
expect(WebUtil.getConfigVar("other")).to.be.equal(null);
});
it('should parse fragment variables', function () {
history.pushState({}, '', "test#myvar=myval");
history.replaceState({}, '', "test#myvar=myval");
expect(WebUtil.getConfigVar("myvar")).to.be.equal("myval");
});
it('should return default value when no fragment match', function () {
history.pushState({}, '', "test#myvar=myval");
history.replaceState({}, '', "test#myvar=myval");
expect(WebUtil.getConfigVar("other", "def")).to.be.equal("def");
});
it('should handle no fragment match and no default value', function () {
history.pushState({}, '', "test#myvar=myval");
history.replaceState({}, '', "test#myvar=myval");
expect(WebUtil.getConfigVar("other")).to.be.equal(null);
});
it('should handle both query and fragment', function () {
history.pushState({}, '', "test?myquery=1#myhash=2");
history.replaceState({}, '', "test?myquery=1#myhash=2");
expect(WebUtil.getConfigVar("myquery")).to.be.equal("1");
expect(WebUtil.getConfigVar("myhash")).to.be.equal("2");
});
it('should prioritize fragment if both provide same var', function () {
history.pushState({}, '', "test?myvar=1#myvar=2");
history.replaceState({}, '', "test?myvar=1#myvar=2");
expect(WebUtil.getConfigVar("myvar")).to.be.equal("2");
});
});
@@ -66,11 +75,6 @@ describe('WebUtil', function () {
origLocalStorage = Object.getOwnPropertyDescriptor(window, "localStorage");
Object.defineProperty(window, "localStorage", {value: {}});
if (window.localStorage.setItem !== undefined) {
// Object.defineProperty() doesn't work properly in old
// versions of Chrome
this.skip();
}
window.localStorage.setItem = sinon.stub();
window.localStorage.getItem = sinon.stub();
@@ -79,9 +83,7 @@ describe('WebUtil', function () {
return WebUtil.initSettings();
});
afterEach(function () {
if (origLocalStorage !== undefined) {
Object.defineProperty(window, "localStorage", origLocalStorage);
}
Object.defineProperty(window, "localStorage", origLocalStorage);
});
describe('writeSetting', function () {
@@ -90,6 +92,11 @@ describe('WebUtil', function () {
expect(window.localStorage.setItem).to.have.been.calledWithExactly('test', 'value');
expect(WebUtil.readSetting('test')).to.equal('value');
});
it('should not crash when local storage save fails', function () {
localStorage.setItem.throws(new DOMException());
expect(WebUtil.writeSetting('test', 'value')).to.not.throw;
});
});
describe('setSetting', function () {
@@ -135,6 +142,11 @@ describe('WebUtil', function () {
WebUtil.writeSetting('test', 'something else');
expect(WebUtil.readSetting('test')).to.equal('something else');
});
it('should not crash when local storage read fails', function () {
localStorage.getItem.throws(new DOMException());
expect(WebUtil.readSetting('test')).to.not.throw;
});
});
// this doesn't appear to be used anywhere
@@ -143,6 +155,11 @@ describe('WebUtil', function () {
WebUtil.eraseSetting('test');
expect(window.localStorage.removeItem).to.have.been.calledWithExactly('test');
});
it('should not crash when local storage remove fails', function () {
localStorage.removeItem.throws(new DOMException());
expect(WebUtil.eraseSetting('test')).to.not.throw;
});
});
});
+144
View File
@@ -0,0 +1,144 @@
const expect = chai.expect;
import Websock from '../core/websock.js';
import Display from '../core/display.js';
import ZRLEDecoder from '../core/decoders/zrle.js';
import FakeWebSocket from './fake.websocket.js';
function testDecodeRect(decoder, x, y, width, height, data, display, depth) {
let sock;
let done = false;
sock = new Websock;
sock.open("ws://example.com");
sock.on('message', () => {
done = decoder.decodeRect(x, y, width, height, sock, display, depth);
});
// Empty messages are filtered at multiple layers, so we need to
// do a direct call
if (data.length === 0) {
done = decoder.decodeRect(x, y, width, height, sock, display, depth);
} else {
sock._websocket._receiveData(new Uint8Array(data));
}
display.flip();
return done;
}
describe('ZRLE Decoder', function () {
let decoder;
let display;
before(FakeWebSocket.replace);
after(FakeWebSocket.restore);
beforeEach(function () {
decoder = new ZRLEDecoder();
display = new Display(document.createElement('canvas'));
display.resize(4, 4);
});
it('should handle the Raw subencoding', function () {
let done = testDecodeRect(decoder, 0, 0, 4, 4,
[0x00, 0x00, 0x00, 0x0e, 0x78, 0x5e,
0x62, 0x60, 0x60, 0xf8, 0x4f, 0x12,
0x02, 0x00, 0x00, 0x00, 0xff, 0xff],
display, 24);
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff
]);
expect(done).to.be.true;
expect(display).to.have.displayed(targetData);
});
it('should handle the Solid subencoding', function () {
let done = testDecodeRect(decoder, 0, 0, 4, 4,
[0x00, 0x00, 0x00, 0x0c, 0x78, 0x5e,
0x62, 0x64, 0x60, 0xf8, 0x0f, 0x00,
0x00, 0x00, 0xff, 0xff],
display, 24);
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff
]);
expect(done).to.be.true;
expect(display).to.have.displayed(targetData);
});
it('should handle the Palette Tile subencoding', function () {
let done = testDecodeRect(decoder, 0, 0, 4, 4,
[0x00, 0x00, 0x00, 0x12, 0x78, 0x5E,
0x62, 0x62, 0x60, 248, 0xff, 0x9F,
0x01, 0x08, 0x3E, 0x7C, 0x00, 0x00,
0x00, 0x00, 0xff, 0xff],
display, 24);
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff,
0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff
]);
expect(done).to.be.true;
expect(display).to.have.displayed(targetData);
});
it('should handle the RLE Tile subencoding', function () {
let done = testDecodeRect(decoder, 0, 0, 4, 4,
[0x00, 0x00, 0x00, 0x0d, 0x78, 0x5e,
0x6a, 0x60, 0x60, 0xf8, 0x2f, 0x00,
0x00, 0x00, 0x00, 0xff, 0xff],
display, 24);
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff
]);
expect(done).to.be.true;
expect(display).to.have.displayed(targetData);
});
it('should handle the RLE Palette Tile subencoding', function () {
let done = testDecodeRect(decoder, 0, 0, 4, 4,
[0x00, 0x00, 0x00, 0x11, 0x78, 0x5e,
0x6a, 0x62, 0x60, 0xf8, 0xff, 0x9f,
0x81, 0xa1, 0x81, 0x1f, 0x00, 0x00,
0x00, 0xff, 0xff],
display, 24);
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff
]);
expect(done).to.be.true;
expect(display).to.have.displayed(targetData);
});
it('should fail on an invalid subencoding', function () {
let data = [0x00, 0x00, 0x00, 0x0c, 0x78, 0x5e, 0x6a, 0x64, 0x60, 0xf8, 0x0f, 0x00, 0x00, 0x00, 0xff, 0xff];
expect(() => testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24)).to.throw();
});
});

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