305 Commits

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

This was broken by f796b05e42

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

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

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

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

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

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

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

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

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

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

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

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

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

Review cleanup for RTCDataChannel support (see below)

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

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

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

Reverts part of 95632e413d
2020-09-04 10:40:37 +02:00
Pierre Ossman 6a19390baa Switch to RGBx pixel format
This is what the browser wants so it avoids having to spend time
converting everything. Unfortunately it usually means the server instead
needs to convert it for us, but we assume it has more power than we do.
2020-06-08 07:57:17 +02:00
Pierre Ossman f5b5767c98 Standardise on a single blit function
Keep everything simpler by always blitting in the same pixel format.
It's up to the decoders to convert if they need to.
2020-06-08 07:53:41 +02:00
Pierre Ossman 34f52a8f41 Fix bad BasicCompression check in Tight decoder 2020-06-08 07:53:41 +02:00
Pierre Ossman 18a68dfac1 Test correct handling of alpha
The forth byte of a pixel is undefined in most encodings, so make sure
the decoders don't leak that through as an alpha channel.
2020-06-08 07:53:16 +02:00
Pierre Ossman 15cfa13563 Add tests for the Tight decoders 2020-06-08 07:48:20 +02:00
Pierre Ossman 111225fa41 Split decoder tests to separate files 2020-06-08 07:46:42 +02:00
Pierre Ossman 224f95f997 Move tile handling to Hextile decoder
It is only used there so no need for it to be in the general
Display class.
2020-06-06 13:23:05 +02:00
128 changed files with 8356 additions and 126944 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"],
+5
View File
@@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Question or discussion
url: https://groups.google.com/forum/?fromgroups#!forum/novnc
about: Ask a question or start a discussion
+69 -21
View File
@@ -1,6 +1,8 @@
name: Publish
on:
push:
pull_request:
release:
types: [published]
@@ -8,42 +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@v3
with:
name: npm
path: lib
- run: npm publish --access public
env:
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
if: ${{ !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.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
container: snapcore/snapcraft
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
- run: npm install
- run: ./utils/use_require.js --clean --as commonjs --with-app
- uses: actions/checkout@v3
- run: |
cp utils/launch.sh build/launch.sh
cp snap/local/svc_wrapper.sh build/svc_wrapper.sh
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
- run: snapcraft
- run: |
mkdir .snapcraft
echo ${SNAPCRAFT_LOGIN} | base64 --decode --ignore-garbage > .snapcraft/snapcraft.cfg
sed -i "s/^version:.*/version: '$VERSION'/" snap/snapcraft.yaml
- uses: snapcore/action-build@v1
id: snapcraft
- uses: actions/upload-artifact@v3
with:
name: snap
path: ${{ steps.snapcraft.outputs.snap }}
- uses: snapcore/action-publish@v1
with:
snap: ${{ steps.snapcraft.outputs.snap }}
release: stable
env:
SNAPCRAFT_LOGIN: ${{secrets.SNAPCRAFT_LOGIN}}
- run: snapcraft push --release=stable *.snap
if: ${{ !github.event.release.prerelease }}
- run: snapcraft push --release=beta *.snap
if: ${{ github.event.release.prerelease }}
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: beta
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 -5
View File
@@ -17,14 +17,12 @@ jobs:
browser: Safari
- os: windows-latest
browser: EdgeHeadless
- os: windows-latest
browser: IE
fail-fast: false
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
- run: npm install
- 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 -7
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
@@ -42,12 +42,6 @@ licenses (all MPL 2.0 compatible):
vendor/pako/ : MIT
vendor/browser-es-module-loader/src/ : MIT
vendor/browser-es-module-loader/dist/ : Various BSD style licenses
vendor/promise.js : MIT
Any other files not mentioned above are typically marked with
a copyright/license header at the top of the file. The default noVNC
license is MPL-2.0.
+26 -10
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
@@ -91,7 +95,7 @@ noVNC uses many modern web technologies so a formal requirement list is
not available. However these are the minimum versions we are currently
aware of:
* Chrome 49, Firefox 44, Safari 11, Opera 36, IE 11, Edge 12
* Chrome 64, Firefox 79, Safari 13.4, Opera 51, Edge 79
### Server Requirements
@@ -108,13 +112,18 @@ proxy.
### Quick Start
* Use the launch script to automatically download and start websockify, which
* Use the `novnc_proxy` script to automatically download and start websockify, which
includes a mini-webserver and the WebSockets proxy. The `--vnc` option is
used to specify the location of a running VNC server:
`./utils/launch.sh --vnc localhost:5901`
`./utils/novnc_proxy --vnc localhost:5901`
* 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 launch
* 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."
}
+72
View File
@@ -0,0 +1,72 @@
{
"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é (crypté) à ",
"Connected (unencrypted) to ": "Connecté (non crypté) à ",
"Something went wrong, connection is closed": "Quelque chose est arrivé, la connexion est 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 raison: ",
"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 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",
"Clear": "Effacer",
"Fullscreen": "Plein écran",
"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": "Crypter",
"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",
"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"
}
+8 -9
View File
@@ -6,21 +6,16 @@
"Must set host": "ホストを設定する必要があります",
"Connected (encrypted) to ": "接続しました (暗号化済み): ",
"Connected (unencrypted) to ": "接続しました (暗号化されていません): ",
"Something went wrong, connection is closed": "何かが問題で、接続が閉じられました",
"Something went wrong, connection is closed": "何らかの問題で、接続が閉じられました",
"Failed to connect to server": "サーバーへの接続に失敗しました",
"Disconnected": "切断しました",
"New connection has been rejected with reason: ": "新規接続は次の理由で拒否されました: ",
"New connection has been rejected": "新規接続は拒否されました",
"Password is required": "パスワードが必要です",
"Credentials are required": "資格情報が必要です",
"noVNC encountered an error:": "noVNC でエラーが発生しました:",
"Hide/Show the control bar": "コントロールバーを隠す/表示する",
"Drag": "ドラッグ",
"Move/Drag Viewport": "ビューポートを移動/ドラッグ",
"viewport drag": "ビューポートをドラッグ",
"Active Mouse Button": "アクティブなマウスボタン",
"No mousebutton": "マウスボタンなし",
"Left mousebutton": "左マウスボタン",
"Middle mousebutton": "中マウスボタン",
"Right mousebutton": "右マウスボタン",
"Keyboard": "キーボード",
"Show Keyboard": "キーボードを表示",
"Extra keys": "追加キー",
@@ -55,6 +50,8 @@
"Local Scaling": "ローカルスケーリング",
"Remote Resizing": "リモートでリサイズ",
"Advanced": "高度",
"Quality:": "品質:",
"Compression level:": "圧縮レベル:",
"Repeater ID:": "リピーター ID:",
"WebSocket": "WebSocket",
"Encrypt": "暗号化",
@@ -65,9 +62,11 @@
"Reconnect Delay (ms):": "再接続する遅延 (ミリ秒):",
"Show Dot when No Cursor": "カーソルがないときにドットを表示",
"Logging:": "ロギング:",
"Version:": "バージョン:",
"Disconnect": "切断",
"Connect": "接続",
"Username:": "ユーザー名:",
"Password:": "パスワード:",
"Send Password": "パスワードを送信",
"Send Credentials": "資格情報を送信",
"Cancel": "キャンセル"
}
+72
View File
@@ -0,0 +1,72 @@
{
"Connecting...": "Conectando...",
"Disconnecting...": "Desconectando...",
"Reconnecting...": "Reconectando...",
"Internal error": "Erro interno",
"Must set host": "É necessário definir o host",
"Connected (encrypted) to ": "Conectado (com criptografia) a ",
"Connected (unencrypted) to ": "Conectado (sem criptografia) a ",
"Something went wrong, connection is closed": "Algo deu errado. A conexão foi encerrada.",
"Failed to connect to server": "Falha ao conectar-se ao servidor",
"Disconnected": "Desconectado",
"New connection has been rejected with reason: ": "A nova conexão foi rejeitada pelo motivo: ",
"New connection has been rejected": "A nova conexão foi rejeitada",
"Credentials are required": "Credenciais são obrigatórias",
"noVNC encountered an error:": "O noVNC encontrou um erro:",
"Hide/Show the control bar": "Esconder/mostrar a barra de controles",
"Drag": "Arrastar",
"Move/Drag Viewport": "Mover/arrastar a janela",
"Keyboard": "Teclado",
"Show Keyboard": "Mostrar teclado",
"Extra keys": "Teclas adicionais",
"Show Extra Keys": "Mostar teclas adicionais",
"Ctrl": "Ctrl",
"Toggle Ctrl": "Pressionar/soltar Ctrl",
"Alt": "Alt",
"Toggle Alt": "Pressionar/soltar Alt",
"Toggle Windows": "Pressionar/soltar Windows",
"Windows": "Windows",
"Send Tab": "Enviar Tab",
"Tab": "Tab",
"Esc": "Esc",
"Send Escape": "Enviar Esc",
"Ctrl+Alt+Del": "Ctrl+Alt+Del",
"Send Ctrl-Alt-Del": "Enviar Ctrl-Alt-Del",
"Shutdown/Reboot": "Desligar/reiniciar",
"Shutdown/Reboot...": "Desligar/reiniciar...",
"Power": "Ligar",
"Shutdown": "Desligar",
"Reboot": "Reiniciar",
"Reset": "Reiniciar (forçado)",
"Clipboard": "Área de transferência",
"Clear": "Limpar",
"Fullscreen": "Tela cheia",
"Settings": "Configurações",
"Shared Mode": "Modo compartilhado",
"View Only": "Apenas visualizar",
"Clip to Window": "Recortar à janela",
"Scaling Mode:": "Modo de dimensionamento:",
"None": "Nenhum",
"Local Scaling": "Local",
"Remote Resizing": "Remoto",
"Advanced": "Avançado",
"Quality:": "Qualidade:",
"Compression level:": "Nível de compressão:",
"Repeater ID:": "ID do repetidor:",
"WebSocket": "WebSocket",
"Encrypt": "Criptografar",
"Host:": "Host:",
"Port:": "Porta:",
"Path:": "Caminho:",
"Automatic Reconnect": "Reconexão automática",
"Reconnect Delay (ms):": "Atraso da reconexão (ms)",
"Show Dot when No Cursor": "Mostrar ponto quando não há cursor",
"Logging:": "Registros:",
"Version:": "Versão:",
"Disconnect": "Desconectar",
"Connect": "Conectar",
"Username:": "Nome de usuário:",
"Password:": "Senha:",
"Send Credentials": "Enviar credenciais",
"Cancel": "Cancelar"
}
+15 -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": "Выход"
}
+9 -2
View File
@@ -103,13 +103,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;
}
+166 -214
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 {
@@ -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 {
+280
View File
@@ -0,0 +1,280 @@
/*
* 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] {
position: relative;
background-color: white;
background-image: unset;
border: 1px solid dimgrey;
border-radius: 3px;
width: 13px;
height: 13px;
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: "";
position: absolute;
top: 0;
left: 3px;
width: 3px;
height: 7px;
border: 1px solid white;
border-width: 0 2px 2px 0;
transform: rotate(40deg);
}
/*
* 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;
}
+97 -23
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,7 +62,21 @@ const UI = {
// Translate the DOM
l10n.translateDOM();
WebUtil.fetchJSON('./package.json')
// 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) {
throw Error("" + response.status + " " + response.statusText);
}
return response.json();
})
.then((packageInfo) => {
Array.from(document.getElementsByClassName('noVNC_version')).forEach(el => el.innerText = packageInfo.version);
})
@@ -74,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);
}
@@ -310,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);
},
@@ -319,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,
@@ -439,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');
},
@@ -571,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 {
@@ -755,11 +783,6 @@ const UI = {
}
}
} else {
/*Weird IE9 error leads to 'null' appearring
in textboxes instead of ''.*/
if (value === null) {
value = "";
}
ctrl.value = value;
}
},
@@ -953,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) + "...");
@@ -1029,8 +1047,10 @@ const UI = {
credentials: { password: password } });
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);
@@ -1117,7 +1137,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'));
@@ -1151,6 +1173,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------*/
@@ -1274,13 +1327,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 {
@@ -1311,7 +1376,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;
@@ -1328,6 +1394,8 @@ const UI = {
} else {
viewDragButton.classList.add("noVNC_hidden");
}
viewDragButton.disabled = !UI.rfb.clippingViewport;
},
/* ------^-------
@@ -1694,12 +1762,18 @@ const UI = {
};
// Set up translations
const LINGUAS = ["cs", "de", "el", "es", "ja", "ko", "nl", "pl", "ru", "sv", "tr", "zh_CN", "zh_TW"];
const LINGUAS = ["cs", "de", "el", "es", "fr", "it", "ja", "ko", "nl", "pl", "pt_BR", "ru", "sv", "tr", "zh_CN", "zh_TW"];
l10n.setup(LINGUAS);
if (l10n.language === "en" || l10n.dictionary !== undefined) {
UI.prime();
} else {
WebUtil.fetchJSON('app/locale/' + l10n.language + '.json')
fetch('app/locale/' + l10n.language + '.json')
.then((response) => {
if (!response.ok) {
throw Error("" + response.status + " " + response.statusText);
}
return response.json();
})
.then((translations) => { l10n.dictionary = translations; })
.catch(err => Log.Error("Failed to load translations: " + err))
.then(UI.prime);
+11 -64
View File
@@ -20,10 +20,19 @@ export function initLogging(level) {
}
// Read a query string variable
// A URL with a query parameter can look like this (But will most probably get logged on the http server):
// https://www.example.com?myqueryparam=myvalue
//
// For privacy (Using a hastag #, the parameters will not be sent to the server)
// the url can be requested in the following way:
// https://www.example.com#myqueryparam=myvalue&password=secreatvalue
//
// Even Mixing public and non public parameters will work:
// https://www.example.com?nonsecretparam=example.com#password=secreatvalue
export function getQueryVar(name, defVal) {
"use strict";
const re = new RegExp('.*[?&]' + name + '=([^&#]*)'),
match = document.location.href.match(re);
match = ''.concat(document.location.href, window.location.hash).match(re);
if (typeof defVal === 'undefined') { defVal = null; }
if (match) {
@@ -37,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) {
@@ -175,65 +184,3 @@ export function eraseSetting(name) {
localStorage.removeItem(name);
}
}
export function injectParamIfMissing(path, param, value) {
// force pretend that we're dealing with a relative path
// (assume that we wanted an extra if we pass one in)
path = "/" + path;
const elem = document.createElement('a');
elem.href = path;
const paramEq = encodeURIComponent(param) + "=";
let query;
if (elem.search) {
query = elem.search.slice(1).split('&');
} else {
query = [];
}
if (!query.some(v => v.startsWith(paramEq))) {
query.push(paramEq + encodeURIComponent(value));
elem.search = "?" + query.join("&");
}
// some browsers (e.g. IE11) may occasionally omit the leading slash
// in the elem.pathname string. Handle that case gracefully.
if (elem.pathname.charAt(0) == "/") {
return elem.pathname.slice(1) + elem.search + elem.hash;
}
return elem.pathname + elem.search + elem.hash;
}
// sadly, we can't use the Fetch API until we decide to drop
// IE11 support or polyfill promises and fetch in IE11.
// resolve will receive an object on success, while reject
// will receive either an event or an error on failure.
export function fetchJSON(path) {
return new Promise((resolve, reject) => {
// NB: IE11 doesn't support JSON as a responseType
const req = new XMLHttpRequest();
req.open('GET', path);
req.onload = () => {
if (req.status === 200) {
let resObj;
try {
resObj = JSON.parse(req.responseText);
} catch (err) {
reject(err);
}
resolve(resObj);
} else {
reject(new Error("XHR got non-200 status while trying to load '" + path + "': " + req.status));
}
};
req.onerror = evt => reject(new Error("XHR encountered an error while trying to load '" + path + "': " + evt.message));
req.ontimeout = evt => reject(new Error("XHR timed out while trying to load '" + path + "'"));
req.send();
});
}
+5
View File
@@ -15,6 +15,11 @@ export default class CopyRectDecoder {
let deltaX = sock.rQshift16();
let deltaY = sock.rQshift16();
if ((width === 0) || (height === 0)) {
return true;
}
display.copyImage(deltaX, deltaY, x, y, width, height);
return true;
+57 -3
View File
@@ -13,6 +13,7 @@ export default class HextileDecoder {
constructor() {
this._tiles = 0;
this._lastsubencoding = 0;
this._tileBuffer = new Uint8Array(16 * 16 * 4);
}
decodeRect(x, y, width, height, sock, display, depth) {
@@ -87,6 +88,11 @@ export default class HextileDecoder {
display.fillRect(tx, ty, tw, th, this._background);
}
} else if (subencoding & 0x01) { // Raw
let pixels = tw * th;
// Max sure the image is fully opaque
for (let i = 0;i < pixels;i++) {
rQ[rQi + i * 4 + 3] = 255;
}
display.blitImage(tx, ty, tw, th, rQ, rQi);
rQi += bytes - 1;
} else {
@@ -99,7 +105,7 @@ export default class HextileDecoder {
rQi += 4;
}
display.startTile(tx, ty, tw, th, this._background);
this._startTile(tx, ty, tw, th, this._background);
if (subencoding & 0x08) { // AnySubrects
let subrects = rQ[rQi];
rQi++;
@@ -122,10 +128,10 @@ export default class HextileDecoder {
const sw = (wh >> 4) + 1;
const sh = (wh & 0x0f) + 1;
display.subTile(sx, sy, sw, sh, color);
this._subTile(sx, sy, sw, sh, color);
}
}
display.finishTile();
this._finishTile(display);
}
sock.rQi = rQi;
this._lastsubencoding = subencoding;
@@ -134,4 +140,52 @@ export default class HextileDecoder {
return true;
}
// start updating a tile
_startTile(x, y, width, height, color) {
this._tileX = x;
this._tileY = y;
this._tileW = width;
this._tileH = height;
const red = color[0];
const green = color[1];
const blue = color[2];
const data = this._tileBuffer;
for (let i = 0; i < width * height * 4; i += 4) {
data[i] = red;
data[i + 1] = green;
data[i + 2] = blue;
data[i + 3] = 255;
}
}
// update sub-rectangle of the current tile
_subTile(x, y, w, h, color) {
const red = color[0];
const green = color[1];
const blue = color[2];
const xend = x + w;
const yend = y + h;
const data = this._tileBuffer;
const width = this._tileW;
for (let j = y; j < yend; j++) {
for (let i = x; i < xend; i++) {
const p = (i + (j * width)) * 4;
data[p] = red;
data[p + 1] = green;
data[p + 2] = blue;
data[p + 3] = 255;
}
}
}
// draw the current tile to the screen
_finishTile(display) {
display.blitImage(this._tileX, this._tileY,
this._tileW, this._tileH,
this._tileBuffer, 0);
}
}
+141
View File
@@ -0,0 +1,141 @@
/*
* 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._quantTables = [];
this._huffmanTables = [];
this._cachedQuantTables = [];
this._cachedHuffmanTables = [];
this._jpegLength = 0;
this._segments = [];
}
decodeRect(x, y, width, height, sock, display, depth) {
// A rect of JPEG encodings is simply a JPEG file
if (!this._parseJPEG(sock.rQslice(0))) {
return false;
}
const data = sock.rQshiftBytes(this._jpegLength);
if (this._quantTables.length != 0 && this._huffmanTables.length != 0) {
// If there are quantization tables and Huffman tables in the JPEG
// image, we can directly render it.
display.imageRect(x, y, width, height, "image/jpeg", data);
return true;
} else {
// Otherwise we need to insert cached tables.
const sofIndex = this._segments.findIndex(
x => x[1] == 0xC0 || x[1] == 0xC2
);
if (sofIndex == -1) {
throw new Error("Illegal JPEG image without SOF");
}
let segments = this._segments.slice(0, sofIndex);
segments = segments.concat(this._quantTables.length ?
this._quantTables :
this._cachedQuantTables);
segments.push(this._segments[sofIndex]);
segments = segments.concat(this._huffmanTables.length ?
this._huffmanTables :
this._cachedHuffmanTables,
this._segments.slice(sofIndex + 1));
let length = 0;
for (let i = 0; i < segments.length; i++) {
length += segments[i].length;
}
const data = new Uint8Array(length);
length = 0;
for (let i = 0; i < segments.length; i++) {
data.set(segments[i], length);
length += segments[i].length;
}
display.imageRect(x, y, width, height, "image/jpeg", data);
return true;
}
}
_parseJPEG(buffer) {
if (this._quantTables.length != 0) {
this._cachedQuantTables = this._quantTables;
}
if (this._huffmanTables.length != 0) {
this._cachedHuffmanTables = this._huffmanTables;
}
this._quantTables = [];
this._huffmanTables = [];
this._segments = [];
let i = 0;
let bufferLength = buffer.length;
while (true) {
let j = i;
if (j + 2 > bufferLength) {
return false;
}
if (buffer[j] != 0xFF) {
throw new Error("Illegal JPEG marker received (byte: " +
buffer[j] + ")");
}
const type = buffer[j+1];
j += 2;
if (type == 0xD9) {
this._jpegLength = j;
this._segments.push(buffer.slice(i, j));
return true;
} else if (type == 0xDA) {
// start of scan
let hasFoundEndOfScan = false;
for (let k = j + 3; k + 1 < bufferLength; k++) {
if (buffer[k] == 0xFF && buffer[k+1] != 0x00 &&
!(buffer[k+1] >= 0xD0 && buffer[k+1] <= 0xD7)) {
j = k;
hasFoundEndOfScan = true;
break;
}
}
if (!hasFoundEndOfScan) {
return false;
}
this._segments.push(buffer.slice(i, j));
i = j;
continue;
} else if (type >= 0xD0 && type < 0xD9 || type == 0x01) {
// No length after marker
this._segments.push(buffer.slice(i, j));
i = j;
continue;
}
if (j + 2 > bufferLength) {
return false;
}
const length = (buffer[j] << 8) + buffer[j+1] - 2;
if (length < 0) {
throw new Error("Illegal JPEG length received (length: " +
length + ")");
}
j += 2;
if (j + length > bufferLength) {
return false;
}
j += length;
const segment = buffer.slice(i, j);
if (type == 0xC4) {
// Huffman tables
this._huffmanTables.push(segment);
} else if (type == 0xDB) {
// Quantization tables
this._quantTables.push(segment);
}
this._segments.push(segment);
i = j;
}
}
}
+12 -2
View File
@@ -13,6 +13,10 @@ export default class RawDecoder {
}
decodeRect(x, y, width, height, sock, display, depth) {
if ((width === 0) || (height === 0)) {
return true;
}
if (this._lines === 0) {
this._lines = height;
}
@@ -27,23 +31,29 @@ export default class RawDecoder {
const curY = y + (height - this._lines);
const currHeight = Math.min(this._lines,
Math.floor(sock.rQlen / bytesPerLine));
const pixels = width * currHeight;
let data = sock.rQ;
let index = sock.rQi;
// Convert data if needed
if (depth == 8) {
const pixels = width * currHeight;
const newdata = new Uint8Array(pixels * 4);
for (let i = 0; i < pixels; i++) {
newdata[i * 4 + 0] = ((data[index + i] >> 0) & 0x3) * 255 / 3;
newdata[i * 4 + 1] = ((data[index + i] >> 2) & 0x3) * 255 / 3;
newdata[i * 4 + 2] = ((data[index + i] >> 4) & 0x3) * 255 / 3;
newdata[i * 4 + 4] = 0;
newdata[i * 4 + 3] = 255;
}
data = newdata;
index = 0;
}
// Max sure the image is fully opaque
for (let i = 0; i < pixels; i++) {
data[index + i * 4 + 3] = 255;
}
display.blitImage(x, curY, width, currHeight, data, index);
sock.rQskipBytes(currHeight * bytesPerLine);
this._lines -= currHeight;
+24 -8
View File
@@ -56,7 +56,7 @@ export default class TightDecoder {
} else if (this._ctl === 0x0A) {
ret = this._pngRect(x, y, width, height,
sock, display, depth);
} else if ((this._ctl & 0x80) == 0) {
} else if ((this._ctl & 0x08) == 0) {
ret = this._basicRect(this._ctl, x, y, width, height,
sock, display, depth);
} else {
@@ -80,7 +80,7 @@ export default class TightDecoder {
const rQ = sock.rQ;
display.fillRect(x, y, width, height,
[rQ[rQi + 2], rQ[rQi + 1], rQ[rQi]], false);
[rQ[rQi], rQ[rQi + 1], rQ[rQi + 2]], false);
sock.rQskipBytes(3);
return true;
@@ -148,6 +148,10 @@ export default class TightDecoder {
const uncompressedSize = width * height * 3;
let data;
if (uncompressedSize === 0) {
return true;
}
if (uncompressedSize < 12) {
if (sock.rQwait("TIGHT", uncompressedSize)) {
return false;
@@ -165,7 +169,15 @@ export default class TightDecoder {
this._zlibs[streamId].setInput(null);
}
display.blitRgbImage(x, y, width, height, data, 0, false);
let rgbx = new Uint8Array(width * height * 4);
for (let i = 0, j = 0; i < width * height * 4; i += 4, j += 3) {
rgbx[i] = data[j];
rgbx[i + 1] = data[j + 1];
rgbx[i + 2] = data[j + 2];
rgbx[i + 3] = 255; // Alpha
}
display.blitImage(x, y, width, height, rgbx, 0, false);
return true;
}
@@ -195,6 +207,10 @@ export default class TightDecoder {
let data;
if (uncompressedSize === 0) {
return true;
}
if (uncompressedSize < 12) {
if (sock.rQwait("TIGHT", uncompressedSize)) {
return false;
@@ -237,7 +253,7 @@ export default class TightDecoder {
for (let b = 7; b >= 0; b--) {
dp = (y * width + x * 8 + 7 - b) * 4;
sp = (data[y * w + x] >> b & 1) * 3;
dest[dp] = palette[sp];
dest[dp] = palette[sp];
dest[dp + 1] = palette[sp + 1];
dest[dp + 2] = palette[sp + 2];
dest[dp + 3] = 255;
@@ -247,14 +263,14 @@ export default class TightDecoder {
for (let b = 7; b >= 8 - width % 8; b--) {
dp = (y * width + x * 8 + 7 - b) * 4;
sp = (data[y * w + x] >> b & 1) * 3;
dest[dp] = palette[sp];
dest[dp] = palette[sp];
dest[dp + 1] = palette[sp + 1];
dest[dp + 2] = palette[sp + 2];
dest[dp + 3] = 255;
}
}
display.blitRgbxImage(x, y, width, height, dest, 0, false);
display.blitImage(x, y, width, height, dest, 0, false);
}
_paletteRect(x, y, width, height, data, palette, display) {
@@ -263,13 +279,13 @@ export default class TightDecoder {
const total = width * height * 4;
for (let i = 0, j = 0; i < total; i += 4, j++) {
const sp = data[j] * 3;
dest[i] = palette[sp];
dest[i] = palette[sp];
dest[i + 1] = palette[sp + 1];
dest[i + 2] = palette[sp + 2];
dest[i + 3] = 255;
}
display.blitRgbxImage(x, y, width, height, dest, 0, false);
display.blitImage(x, y, width, height, dest, 0, false);
}
_gradientFilter(streamId, x, y, width, height, sock, display, depth) {
+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);
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;
}
}
+1 -1
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;
+21 -151
View File
@@ -8,7 +8,6 @@
import * as Log from './util/logging.js';
import Base64 from "./base64.js";
import { supportsImageMetadata } from './util/browser.js';
import { toSigned32bit } from './util/int.js';
export default class Display {
@@ -23,10 +22,6 @@ export default class Display {
this._fbHeight = 0;
this._prevDrawStyle = "";
this._tile = null;
this._tile16x16 = null;
this._tileX = 0;
this._tileY = 0;
Log.Debug(">> Display.constructor");
@@ -60,12 +55,6 @@ export default class Display {
Log.Debug("User Agent: " + navigator.userAgent);
// Check canvas features
if (!('createImageData' in this._drawCtx)) {
throw new Error("Canvas does not support createImageData");
}
this._tile16x16 = this._drawCtx.createImageData(16, 16);
Log.Debug("<< Display.constructor");
// ===== PROPERTIES =====
@@ -235,6 +224,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) {
@@ -378,57 +379,6 @@ export default class Display {
});
}
// start updating a tile
startTile(x, y, width, height, color) {
this._tileX = x;
this._tileY = y;
if (width === 16 && height === 16) {
this._tile = this._tile16x16;
} else {
this._tile = this._drawCtx.createImageData(width, height);
}
const red = color[2];
const green = color[1];
const blue = color[0];
const data = this._tile.data;
for (let i = 0; i < width * height * 4; i += 4) {
data[i] = red;
data[i + 1] = green;
data[i + 2] = blue;
data[i + 3] = 255;
}
}
// update sub-rectangle of the current tile
subTile(x, y, w, h, color) {
const red = color[2];
const green = color[1];
const blue = color[0];
const xend = x + w;
const yend = y + h;
const data = this._tile.data;
const width = this._tile.width;
for (let j = y; j < yend; j++) {
for (let i = x; i < xend; i++) {
const p = (i + (j * width)) * 4;
data[p] = red;
data[p + 1] = green;
data[p + 2] = blue;
data[p + 3] = 255;
}
}
}
// draw the current tile to the screen
finishTile() {
this._drawCtx.putImageData(this._tile, this._tileX, this._tileY);
this._damage(this._tileX, this._tileY,
this._tile.width, this._tile.height);
}
blitImage(x, y, width, height, arr, offset, fromQueue) {
if (this._renderQ.length !== 0 && !fromQueue) {
// NB(directxman12): it's technically more performant here to use preallocated arrays,
@@ -445,47 +395,13 @@ export default class Display {
'height': height,
});
} else {
this._bgrxImageData(x, y, width, height, arr, offset);
}
}
blitRgbImage(x, y, width, height, arr, offset, fromQueue) {
if (this._renderQ.length !== 0 && !fromQueue) {
// NB(directxman12): it's technically more performant here to use preallocated arrays,
// but it's a lot of extra work for not a lot of payoff -- if we're using the render queue,
// this probably isn't getting called *nearly* as much
const newArr = new Uint8Array(width * height * 3);
newArr.set(new Uint8Array(arr.buffer, 0, newArr.length));
this._renderQPush({
'type': 'blitRgb',
'data': newArr,
'x': x,
'y': y,
'width': width,
'height': height,
});
} else {
this._rgbImageData(x, y, width, height, arr, offset);
}
}
blitRgbxImage(x, y, width, height, arr, offset, fromQueue) {
if (this._renderQ.length !== 0 && !fromQueue) {
// NB(directxman12): it's technically more performant here to use preallocated arrays,
// but it's a lot of extra work for not a lot of payoff -- if we're using the render queue,
// this probably isn't getting called *nearly* as much
const newArr = new Uint8Array(width * height * 4);
newArr.set(new Uint8Array(arr.buffer, 0, newArr.length));
this._renderQPush({
'type': 'blitRgbx',
'data': newArr,
'x': x,
'y': y,
'width': width,
'height': height,
});
} else {
this._rgbxImageData(x, y, width, height, arr, offset);
// NB(directxman12): arr must be an Type Array view
let data = new Uint8ClampedArray(arr.buffer,
arr.byteOffset + offset,
width * height * 4);
let img = new ImageData(data, width, height);
this._drawCtx.putImageData(img, x, y);
this._damage(x, y, width, height);
}
}
@@ -537,52 +453,13 @@ export default class Display {
}
_setFillColor(color) {
const newStyle = 'rgb(' + color[2] + ',' + color[1] + ',' + color[0] + ')';
const newStyle = 'rgb(' + color[0] + ',' + color[1] + ',' + color[2] + ')';
if (newStyle !== this._prevDrawStyle) {
this._drawCtx.fillStyle = newStyle;
this._prevDrawStyle = newStyle;
}
}
_rgbImageData(x, y, width, height, arr, offset) {
const img = this._drawCtx.createImageData(width, height);
const data = img.data;
for (let i = 0, j = offset; i < width * height * 4; i += 4, j += 3) {
data[i] = arr[j];
data[i + 1] = arr[j + 1];
data[i + 2] = arr[j + 2];
data[i + 3] = 255; // Alpha
}
this._drawCtx.putImageData(img, x, y);
this._damage(x, y, img.width, img.height);
}
_bgrxImageData(x, y, width, height, arr, offset) {
const img = this._drawCtx.createImageData(width, height);
const data = img.data;
for (let i = 0, j = offset; i < width * height * 4; i += 4, j += 4) {
data[i] = arr[j + 2];
data[i + 1] = arr[j + 1];
data[i + 2] = arr[j];
data[i + 3] = 255; // Alpha
}
this._drawCtx.putImageData(img, x, y);
this._damage(x, y, img.width, img.height);
}
_rgbxImageData(x, y, width, height, arr, offset) {
// NB(directxman12): arr must be an Type Array view
let img;
if (supportsImageMetadata) {
img = new ImageData(new Uint8ClampedArray(arr.buffer, arr.byteOffset, width * height * 4), width, height);
} else {
img = this._drawCtx.createImageData(width, height);
img.data.set(new Uint8ClampedArray(arr.buffer, arr.byteOffset, width * height * 4));
}
this._drawCtx.putImageData(img, x, y);
this._damage(x, y, img.width, img.height);
}
_renderQPush(action) {
this._renderQ.push(action);
if (this._renderQ.length === 1) {
@@ -616,15 +493,8 @@ export default class Display {
case 'blit':
this.blitImage(a.x, a.y, a.width, a.height, a.data, 0, true);
break;
case 'blitRgb':
this.blitRgbImage(a.x, a.y, a.width, a.height, a.data, 0, true);
break;
case 'blitRgbx':
this.blitRgbxImage(a.x, a.y, a.width, a.height, a.data, 0, true);
break;
case 'img':
/* IE tends to set "complete" prematurely, so check dimensions */
if (a.img.complete && (a.img.width !== 0) && (a.img.height !== 0)) {
if (a.img.complete) {
if (a.img.width !== a.width || a.img.height !== a.height) {
Log.Error("Decoded image has incorrect dimensions. Got " +
a.img.width + "x" + a.img.height + ". Expected " +
+4
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,
@@ -38,7 +40,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 + "]";
}
}
+25 -21
View File
@@ -35,7 +35,7 @@ function addNumpad(key, standard, numpad) {
DOMKeyTable[key] = [standard, standard, standard, numpad];
}
// 2.2. Modifier Keys
// 3.2. Modifier Keys
addLeftRight("Alt", KeyTable.XK_Alt_L, KeyTable.XK_Alt_R);
addStandard("AltGraph", KeyTable.XK_ISO_Level3_Shift);
@@ -49,25 +49,27 @@ addStandard("ScrollLock", KeyTable.XK_Scroll_Lock);
addLeftRight("Shift", KeyTable.XK_Shift_L, KeyTable.XK_Shift_R);
// - Symbol
// - SymbolLock
// - Hyper
// - Super
// 2.3. Whitespace Keys
// 3.3. Whitespace Keys
addNumpad("Enter", KeyTable.XK_Return, KeyTable.XK_KP_Enter);
addStandard("Tab", KeyTable.XK_Tab);
addNumpad(" ", KeyTable.XK_space, KeyTable.XK_KP_Space);
// 2.4. Navigation Keys
// 3.4. Navigation Keys
addNumpad("ArrowDown", KeyTable.XK_Down, KeyTable.XK_KP_Down);
addNumpad("ArrowUp", KeyTable.XK_Up, KeyTable.XK_KP_Up);
addNumpad("ArrowLeft", KeyTable.XK_Left, KeyTable.XK_KP_Left);
addNumpad("ArrowRight", KeyTable.XK_Right, KeyTable.XK_KP_Right);
addNumpad("ArrowUp", KeyTable.XK_Up, KeyTable.XK_KP_Up);
addNumpad("End", KeyTable.XK_End, KeyTable.XK_KP_End);
addNumpad("Home", KeyTable.XK_Home, KeyTable.XK_KP_Home);
addNumpad("PageDown", KeyTable.XK_Next, KeyTable.XK_KP_Next);
addNumpad("PageUp", KeyTable.XK_Prior, KeyTable.XK_KP_Prior);
// 2.5. Editing Keys
// 3.5. Editing Keys
addStandard("Backspace", KeyTable.XK_BackSpace);
// Browsers send "Clear" for the numpad 5 without NumLock because
@@ -85,7 +87,7 @@ addStandard("Paste", KeyTable.XF86XK_Paste);
addStandard("Redo", KeyTable.XK_Redo);
addStandard("Undo", KeyTable.XK_Undo);
// 2.6. UI Keys
// 3.6. UI Keys
// - Accept
// - Again (could just be XK_Redo)
@@ -103,7 +105,7 @@ addStandard("Select", KeyTable.XK_Select);
addStandard("ZoomIn", KeyTable.XF86XK_ZoomIn);
addStandard("ZoomOut", KeyTable.XF86XK_ZoomOut);
// 2.7. Device Keys
// 3.7. Device Keys
addStandard("BrightnessDown", KeyTable.XF86XK_MonBrightnessDown);
addStandard("BrightnessUp", KeyTable.XF86XK_MonBrightnessUp);
@@ -116,10 +118,10 @@ addStandard("Hibernate", KeyTable.XF86XK_Hibernate);
addStandard("Standby", KeyTable.XF86XK_Standby);
addStandard("WakeUp", KeyTable.XF86XK_WakeUp);
// 2.8. IME and Composition Keys
// 3.8. IME and Composition Keys
addStandard("AllCandidates", KeyTable.XK_MultipleCandidate);
addStandard("Alphanumeric", KeyTable.XK_Eisu_Shift); // could also be _Eisu_Toggle
addStandard("Alphanumeric", KeyTable.XK_Eisu_toggle);
addStandard("CodeInput", KeyTable.XK_Codeinput);
addStandard("Compose", KeyTable.XK_Multi_key);
addStandard("Convert", KeyTable.XK_Henkan);
@@ -137,7 +139,7 @@ addStandard("PreviousCandidate", KeyTable.XK_PreviousCandidate);
addStandard("SingleCandidate", KeyTable.XK_SingleCandidate);
addStandard("HangulMode", KeyTable.XK_Hangul);
addStandard("HanjaMode", KeyTable.XK_Hangul_Hanja);
addStandard("JunjuaMode", KeyTable.XK_Hangul_Jeonja);
addStandard("JunjaMode", KeyTable.XK_Hangul_Jeonja);
addStandard("Eisu", KeyTable.XK_Eisu_toggle);
addStandard("Hankaku", KeyTable.XK_Hankaku);
addStandard("Hiragana", KeyTable.XK_Hiragana);
@@ -147,9 +149,9 @@ addStandard("KanjiMode", KeyTable.XK_Kanji);
addStandard("Katakana", KeyTable.XK_Katakana);
addStandard("Romaji", KeyTable.XK_Romaji);
addStandard("Zenkaku", KeyTable.XK_Zenkaku);
addStandard("ZenkakuHanaku", KeyTable.XK_Zenkaku_Hankaku);
addStandard("ZenkakuHankaku", KeyTable.XK_Zenkaku_Hankaku);
// 2.9. General-Purpose Function Keys
// 3.9. General-Purpose Function Keys
addStandard("F1", KeyTable.XK_F1);
addStandard("F2", KeyTable.XK_F2);
@@ -188,7 +190,7 @@ addStandard("F34", KeyTable.XK_F34);
addStandard("F35", KeyTable.XK_F35);
// - Soft1...
// 2.10. Multimedia Keys
// 3.10. Multimedia Keys
// - ChannelDown
// - ChannelUp
@@ -200,6 +202,7 @@ addStandard("MailSend", KeyTable.XF86XK_Send);
addStandard("MediaFastForward", KeyTable.XF86XK_AudioForward);
addStandard("MediaPause", KeyTable.XF86XK_AudioPause);
addStandard("MediaPlay", KeyTable.XF86XK_AudioPlay);
// - MediaPlayPause
addStandard("MediaRecord", KeyTable.XF86XK_AudioRecord);
addStandard("MediaRewind", KeyTable.XF86XK_AudioRewind);
addStandard("MediaStop", KeyTable.XF86XK_AudioStop);
@@ -211,12 +214,12 @@ addStandard("Print", KeyTable.XK_Print);
addStandard("Save", KeyTable.XF86XK_Save);
addStandard("SpellCheck", KeyTable.XF86XK_Spell);
// 2.11. Multimedia Numpad Keys
// 3.11. Multimedia Numpad Keys
// - Key11
// - Key12
// 2.12. Audio Keys
// 3.12. Audio Keys
// - AudioBalanceLeft
// - AudioBalanceRight
@@ -236,16 +239,17 @@ addStandard("AudioVolumeMute", KeyTable.XF86XK_AudioMute);
// - MicrophoneVolumeUp
addStandard("MicrophoneVolumeMute", KeyTable.XF86XK_AudioMicMute);
// 2.13. Speech Keys
// 3.13. Speech Keys
// - SpeechCorrectionList
// - SpeechInputToggle
// 2.14. Application Keys
// 3.14. Application Keys
addStandard("LaunchApplication1", KeyTable.XF86XK_MyComputer);
addStandard("LaunchApplication2", KeyTable.XF86XK_Calculator);
addStandard("LaunchCalendar", KeyTable.XF86XK_Calendar);
// - LaunchContacts
addStandard("LaunchMail", KeyTable.XF86XK_Mail);
addStandard("LaunchMediaPlayer", KeyTable.XF86XK_AudioMedia);
addStandard("LaunchMusicPlayer", KeyTable.XF86XK_Music);
@@ -256,7 +260,7 @@ addStandard("LaunchWebBrowser", KeyTable.XF86XK_WWW);
addStandard("LaunchWebCam", KeyTable.XF86XK_WebCam);
addStandard("LaunchWordProcessor", KeyTable.XF86XK_Word);
// 2.15. Browser Keys
// 3.15. Browser Keys
addStandard("BrowserBack", KeyTable.XF86XK_Back);
addStandard("BrowserFavorites", KeyTable.XF86XK_Favorites);
@@ -266,15 +270,15 @@ addStandard("BrowserRefresh", KeyTable.XF86XK_Refresh);
addStandard("BrowserSearch", KeyTable.XF86XK_Search);
addStandard("BrowserStop", KeyTable.XF86XK_Stop);
// 2.16. Mobile Phone Keys
// 3.16. Mobile Phone Keys
// - A whole bunch...
// 2.17. TV Keys
// 3.17. TV Keys
// - A whole bunch...
// 2.18. Media Controller Keys
// 3.18. Media Controller Keys
// - A whole bunch...
addStandard("Dimmer", KeyTable.XF86XK_BrightnessAdjust);
+22 -127
View File
@@ -20,16 +20,13 @@ export default class Keyboard {
this._keyDownList = {}; // List of depressed keys
// (even if they are happy)
this._pendingKey = null; // Key waiting for keypress
this._altGrArmed = false; // Windows AltGr detection
// keep these here so we can refer to them later
this._eventHandlers = {
'keyup': this._handleKeyUp.bind(this),
'keydown': this._handleKeyDown.bind(this),
'keypress': this._handleKeyPress.bind(this),
'blur': this._allKeysUp.bind(this),
'checkalt': this._checkAlt.bind(this),
};
// ===== EVENT HANDLERS =====
@@ -62,9 +59,7 @@ export default class Keyboard {
}
// Unstable, but we don't have anything else to go on
// (don't use it for 'keypress' events thought since
// WebKit sets it to the same as charCode)
if (e.keyCode && (e.type !== 'keypress')) {
if (e.keyCode) {
// 229 is used for composition events
if (e.keyCode !== 229) {
return 'Platform' + e.keyCode;
@@ -158,6 +153,16 @@ 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);
this._sendKeyEvent(keysym, code, false);
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
@@ -169,20 +174,20 @@ export default class Keyboard {
return;
}
// If this is a legacy browser then we'll need to wait for
// a keypress event as well
// (IE and Edge has a broken KeyboardEvent.key, so we can't
// just check for the presence of that field)
if (!keysym && (!e.key || browser.isIE() || browser.isEdge())) {
this._pendingKey = code;
// However we might not get a keypress event if the key
// is non-printable, which needs some special fallback
// handling
setTimeout(this._handleKeyPressTimeout.bind(this), 10, e);
// Windows doesn't send proper key releases for a bunch of
// Japanese IM keys so we have to fake the release right away
const jpBadKeys = [ KeyTable.XK_Zenkaku_Hankaku,
KeyTable.XK_Eisu_toggle,
KeyTable.XK_Katakana,
KeyTable.XK_Hiragana,
KeyTable.XK_Romaji ];
if (browser.isWindows() && jpBadKeys.includes(keysym)) {
this._sendKeyEvent(keysym, code, true);
this._sendKeyEvent(keysym, code, false);
stopEvent(e);
return;
}
this._pendingKey = null;
stopEvent(e);
// Possible start of AltGr sequence? (see above)
@@ -197,69 +202,6 @@ export default class Keyboard {
this._sendKeyEvent(keysym, code, true);
}
// Legacy event for browsers without code/key
_handleKeyPress(e) {
stopEvent(e);
// Are we expecting a keypress?
if (this._pendingKey === null) {
return;
}
let code = this._getKeyCode(e);
const keysym = KeyboardUtil.getKeysym(e);
// The key we were waiting for?
if ((code !== 'Unidentified') && (code != this._pendingKey)) {
return;
}
code = this._pendingKey;
this._pendingKey = null;
if (!keysym) {
Log.Info('keypress with no keysym:', e);
return;
}
this._sendKeyEvent(keysym, code, true);
}
_handleKeyPressTimeout(e) {
// Did someone manage to sort out the key already?
if (this._pendingKey === null) {
return;
}
let keysym;
const code = this._pendingKey;
this._pendingKey = null;
// We have no way of knowing the proper keysym with the
// information given, but the following are true for most
// layouts
if ((e.keyCode >= 0x30) && (e.keyCode <= 0x39)) {
// Digit
keysym = e.keyCode;
} else if ((e.keyCode >= 0x41) && (e.keyCode <= 0x5a)) {
// Character (A-Z)
let char = String.fromCharCode(e.keyCode);
// A feeble attempt at the correct case
if (e.shiftKey) {
char = char.toUpperCase();
} else {
char = char.toLowerCase();
}
keysym = char.charCodeAt();
} else {
// Unknown, give up
keysym = 0;
}
this._sendKeyEvent(keysym, code, true);
}
_handleKeyUp(e) {
stopEvent(e);
@@ -312,30 +254,6 @@ export default class Keyboard {
Log.Debug("<< Keyboard.allKeysUp");
}
// Alt workaround for Firefox on Windows, see below
_checkAlt(e) {
if (e.skipCheckAlt) {
return;
}
if (e.altKey) {
return;
}
const target = this._target;
const downList = this._keyDownList;
['AltLeft', 'AltRight'].forEach((code) => {
if (!(code in downList)) {
return;
}
const event = new KeyboardEvent('keyup',
{ key: downList[code],
code: code });
event.skipCheckAlt = true;
target.dispatchEvent(event);
});
}
// ===== PUBLIC METHODS =====
grab() {
@@ -343,41 +261,18 @@ export default class Keyboard {
this._target.addEventListener('keydown', this._eventHandlers.keydown);
this._target.addEventListener('keyup', this._eventHandlers.keyup);
this._target.addEventListener('keypress', this._eventHandlers.keypress);
// Release (key up) if window loses focus
window.addEventListener('blur', this._eventHandlers.blur);
// Firefox on Windows has broken handling of Alt, so we need to
// poll as best we can for releases (still doesn't prevent the
// menu from popping up though as we can't call
// preventDefault())
if (browser.isWindows() && browser.isFirefox()) {
const handler = this._eventHandlers.checkalt;
['mousedown', 'mouseup', 'mousemove', 'wheel',
'touchstart', 'touchend', 'touchmove',
'keydown', 'keyup'].forEach(type =>
document.addEventListener(type, handler,
{ capture: true,
passive: true }));
}
//Log.Debug("<< Keyboard.grab");
}
ungrab() {
//Log.Debug(">> Keyboard.ungrab");
if (browser.isWindows() && browser.isFirefox()) {
const handler = this._eventHandlers.checkalt;
['mousedown', 'mouseup', 'mousemove', 'wheel',
'touchstart', 'touchend', 'touchmove',
'keydown', 'keyup'].forEach(type => document.removeEventListener(type, handler));
}
this._target.removeEventListener('keydown', this._eventHandlers.keydown);
this._target.removeEventListener('keyup', this._eventHandlers.keyup);
this._target.removeEventListener('keypress', this._eventHandlers.keypress);
window.removeEventListener('blur', this._eventHandlers.blur);
// Release (key up) all keys that are in a down state
+18 -35
View File
@@ -22,9 +22,8 @@ export function getKeycode(evt) {
}
// The de-facto standard is to use Windows Virtual-Key codes
// in the 'keyCode' field for non-printable characters. However
// Webkit sets it to the same as charCode in 'keypress' events.
if ((evt.type !== 'keypress') && (evt.keyCode in vkeys)) {
// in the 'keyCode' field for non-printable characters
if (evt.keyCode in vkeys) {
let code = vkeys[evt.keyCode];
// macOS has messed up this code for some reason
@@ -69,26 +68,6 @@ export function getKeycode(evt) {
export function getKey(evt) {
// Are we getting a proper key value?
if (evt.key !== undefined) {
// IE and Edge use some ancient version of the spec
// https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/8860571/
switch (evt.key) {
case 'Spacebar': return ' ';
case 'Esc': return 'Escape';
case 'Scroll': return 'ScrollLock';
case 'Win': return 'Meta';
case 'Apps': return 'ContextMenu';
case 'Up': return 'ArrowUp';
case 'Left': return 'ArrowLeft';
case 'Right': return 'ArrowRight';
case 'Down': return 'ArrowDown';
case 'Del': return 'Delete';
case 'Divide': return '/';
case 'Multiply': return '*';
case 'Subtract': return '-';
case 'Add': return '+';
case 'Decimal': return evt.char;
}
// Mozilla isn't fully in sync with the spec yet
switch (evt.key) {
case 'OS': return 'Meta';
@@ -110,18 +89,7 @@ export function getKey(evt) {
return 'Delete';
}
// IE and Edge need special handling, but for everyone else we
// can trust the value provided
if (!browser.isIE() && !browser.isEdge()) {
return evt.key;
}
// IE and Edge have broken handling of AltGraph so we can only
// trust them for non-printable characters (and unfortunately
// they also specify 'Unidentified' for some problem keys)
if ((evt.key.length !== 1) && (evt.key !== 'Unidentified')) {
return evt.key;
}
return evt.key;
}
// Try to deduce it based on the physical key
@@ -189,6 +157,21 @@ export function getKeysym(evt) {
}
}
// Windows sends alternating symbols for some keys when using a
// Japanese layout. We have no way of synchronising with the IM
// running on the remote system, so we send some combined keysym
// instead and hope for the best.
if (browser.isWindows()) {
switch (key) {
case 'Zenkaku':
case 'Hankaku':
return KeyTable.XK_Zenkaku_Hankaku;
case 'Romaji':
case 'KanaMode':
return KeyTable.XK_Romaji;
}
}
return DOMKeyTable[key][location];
}
-1
View File
@@ -13,7 +13,6 @@ export default {
0x08: 'Backspace',
0x09: 'Tab',
0x0a: 'NumpadClear',
0x0c: 'Numpad5', // IE11 sends evt.keyCode: 12 when numlock is off
0x0d: 'Enter',
0x10: 'ShiftLeft',
0x11: 'ControlLeft',
+5 -3
View File
@@ -1,8 +1,8 @@
/*
* This file is auto-generated from keymaps.csv on 2017-05-31 16:20
* Database checksum sha256(92fd165507f2a3b8c5b3fa56e425d45788dbcb98cf067a307527d91ce22cab94)
* This file is auto-generated from keymaps.csv
* Database checksum sha256(76d68c10e97d37fe2ea459e210125ae41796253fb217e900bf2983ade13a7920)
* To re-generate, run:
* keymap-gen --lang=js code-map keymaps.csv html atset1
* keymap-gen code-map --lang=js keymaps.csv html atset1
*/
export default {
"Again": 0xe005, /* html:Again (Again) -> linux:129 (KEY_AGAIN) -> atset1:57349 */
@@ -111,6 +111,8 @@ export default {
"KeyX": 0x2d, /* html:KeyX (KeyX) -> linux:45 (KEY_X) -> atset1:45 */
"KeyY": 0x15, /* html:KeyY (KeyY) -> linux:21 (KEY_Y) -> atset1:21 */
"KeyZ": 0x2c, /* html:KeyZ (KeyZ) -> linux:44 (KEY_Z) -> atset1:44 */
"Lang1": 0x72, /* html:Lang1 (Lang1) -> linux:122 (KEY_HANGEUL) -> atset1:114 */
"Lang2": 0x71, /* html:Lang2 (Lang2) -> linux:123 (KEY_HANJA) -> atset1:113 */
"Lang3": 0x78, /* html:Lang3 (Lang3) -> linux:90 (KEY_KATAKANA) -> atset1:120 */
"Lang4": 0x77, /* html:Lang4 (Lang4) -> linux:91 (KEY_HIRAGANA) -> atset1:119 */
"Lang5": 0x76, /* html:Lang5 (Lang5) -> linux:85 (KEY_ZENKAKUHANKAKU) -> atset1:118 */
+567
View File
@@ -0,0 +1,567 @@
import Base64 from './base64.js';
import { encodeUTF8 } from './util/strings.js';
import EventTargetMixin from './util/eventtarget.js';
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]);
}
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;
}
async setKey(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", "decrypt"]);
await this._initCMAC();
}
async encrypt(message, associatedData, nonce) {
const nCMAC = await this._computeCMAC(nonce, this._prefixBlock0);
const encrypted = await this._encryptCTR(message, nCMAC);
const adCMAC = await this._computeCMAC(associatedData, 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(encrypted, associatedData, nonce, mac) {
const nCMAC = await this._computeCMAC(nonce, this._prefixBlock0);
const adCMAC = await this._computeCMAC(associatedData, 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;
}
}
export class RA2Cipher {
constructor() {
this._cipher = new AESEAXCipher();
this._counter = new Uint8Array(16);
}
async setKey(key) {
await this._cipher.setKey(key);
}
async makeMessage(message) {
const ad = new Uint8Array([(message.length & 0xff00) >>> 8, message.length & 0xff]);
const encrypted = await this._cipher.encrypt(message, ad, this._counter);
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, mac) {
const ad = new Uint8Array([(length & 0xff00) >>> 8, length & 0xff]);
const res = await this._cipher.decrypt(encrypted, ad, this._counter, mac);
for (let i = 0; i < 16 && this._counter[i]++ === 255; i++);
return res;
}
}
export class RSACipher {
constructor(keyLength) {
this._key = null;
this._keyLength = keyLength;
this._keyBytes = Math.ceil(keyLength / 8);
this._n = null;
this._e = null;
this._d = null;
this._nBigInt = null;
this._eBigInt = null;
this._dBigInt = null;
}
_base64urlDecode(data) {
data = data.replace(/-/g, "+").replace(/_/g, "/");
data = data.padEnd(Math.ceil(data.length / 4) * 4, "=");
return Base64.decode(data);
}
_u8ArrayToBigInt(arr) {
let hex = '0x';
for (let i = 0; i < arr.length; i++) {
hex += arr[i].toString(16).padStart(2, '0');
}
return BigInt(hex);
}
_padArray(arr, length) {
const res = new Uint8Array(length);
res.set(arr, length - arr.length);
return res;
}
_bigIntToU8Array(bigint, padLength=0) {
let hex = bigint.toString(16);
if (padLength === 0) {
padLength = Math.ceil(hex.length / 2) * 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;
}
_modPow(b, e, m) {
if (m === 1n) {
return 0;
}
let r = 1n;
b = b % m;
while (e > 0) {
if (e % 2n === 1n) {
r = (r * b) % m;
}
e = e / 2n;
b = (b * b) % m;
}
return r;
}
async generateKey() {
this._key = await window.crypto.subtle.generateKey(
{
name: "RSA-OAEP",
modulusLength: this._keyLength,
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
hash: {name: "SHA-256"},
},
true, ["encrypt", "decrypt"]);
const privateKey = await window.crypto.subtle.exportKey("jwk", this._key.privateKey);
this._n = this._padArray(this._base64urlDecode(privateKey.n), this._keyBytes);
this._nBigInt = this._u8ArrayToBigInt(this._n);
this._e = this._padArray(this._base64urlDecode(privateKey.e), this._keyBytes);
this._eBigInt = this._u8ArrayToBigInt(this._e);
this._d = this._padArray(this._base64urlDecode(privateKey.d), this._keyBytes);
this._dBigInt = this._u8ArrayToBigInt(this._d);
}
setPublicKey(n, e) {
if (n.length !== this._keyBytes || e.length !== this._keyBytes) {
return;
}
this._n = new Uint8Array(this._keyBytes);
this._e = new Uint8Array(this._keyBytes);
this._n.set(n);
this._e.set(e);
this._nBigInt = this._u8ArrayToBigInt(this._n);
this._eBigInt = this._u8ArrayToBigInt(this._e);
}
encrypt(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 = this._u8ArrayToBigInt(em);
const c = this._modPow(emBigInt, this._eBigInt, this._nBigInt);
return this._bigIntToU8Array(c, this._keyBytes);
}
decrypt(message) {
if (message.length !== this._keyBytes) {
return null;
}
const msgBigInt = this._u8ArrayToBigInt(message);
const emBigInt = this._modPow(msgBigInt, this._dBigInt, this._nBigInt);
const em = this._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);
}
get keyLength() {
return this._keyLength;
}
get n() {
return this._n;
}
get e() {
return this._e;
}
get d() {
return this._d;
}
}
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.rQslice(0, 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 = new RSACipher(serverKeyLength);
serverRSACipher.setPublicKey(serverN, serverE);
const serverPublickey = new Uint8Array(4 + serverKeyBytes * 2);
serverPublickey.set(serverKeyLengthBuffer);
serverPublickey.set(serverN, 4);
serverPublickey.set(serverE, 4 + serverKeyBytes);
// verify server public key
this.dispatchEvent(new CustomEvent("serververification", {
detail: { type: "RSA", publickey: serverPublickey }
}));
await this._waitApproveKeyAsync();
// 2: Send client public key
const clientKeyLength = 2048;
const clientKeyBytes = Math.ceil(clientKeyLength / 8);
const clientRSACipher = new RSACipher(clientKeyLength);
await clientRSACipher.generateKey();
const clientN = clientRSACipher.n;
const clientE = clientRSACipher.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.send(clientPublicKey);
// 3: Send client random
const clientRandom = new Uint8Array(16);
window.crypto.getRandomValues(clientRandom);
const clientEncryptedRandom = serverRSACipher.encrypt(clientRandom);
const clientRandomMessage = new Uint8Array(2 + serverKeyBytes);
clientRandomMessage[0] = (serverKeyBytes & 0xff00) >>> 8;
clientRandomMessage[1] = serverKeyBytes & 0xff;
clientRandomMessage.set(clientEncryptedRandom, 2);
this._sock.send(clientRandomMessage);
// 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 = clientRSACipher.decrypt(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.send(await clientCipher.makeMessage(clientHash));
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), this._sock.rQshiftBytes(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), this._sock.rQshiftBytes(16)));
if (subtype === null) {
throw new Error("RA2: failed to authenticate the message");
}
subtype = subtype[0];
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 this._waitCredentialsAsync(subtype);
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.send(await clientCipher.makeMessage(credentials));
}
get hasStarted() {
return this._hasStarted;
}
set hasStarted(s) {
this._hasStarted = s;
}
}
+563 -171
View File
File diff suppressed because it is too large Load Diff
+56 -24
View File
@@ -45,15 +45,6 @@ try {
export const supportsCursorURIs = _supportsCursorURIs;
let _supportsImageMetadata = false;
try {
new ImageData(new Uint8ClampedArray(4), 1, 1);
_supportsImageMetadata = true;
} catch (ex) {
// ignore failure
}
export const supportsImageMetadata = _supportsImageMetadata;
let _hasScrollbarGutter = true;
try {
// Create invisible container
@@ -86,35 +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);
}
export function isIE() {
return navigator && !!(/trident/i).exec(navigator.userAgent);
}
export function isEdge() {
return navigator && !!(/edge/i).exec(navigator.userAgent);
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/...');
}
+5 -11
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';
}
@@ -43,9 +47,6 @@ export default class Cursor {
if (useFallback) {
document.body.appendChild(this._canvas);
// FIXME: These don't fire properly except for mouse
/// movement in IE. We want to also capture element
// movement, size changes, visibility, etc.
const options = { capture: true, passive: true };
this._target.addEventListener('mouseover', this._eventHandlers.mouseover, options);
this._target.addEventListener('mouseleave', this._eventHandlers.mouseleave, options);
@@ -90,14 +91,7 @@ export default class Cursor {
this._canvas.width = w;
this._canvas.height = h;
let img;
try {
// IE doesn't support this
img = new ImageData(new Uint8ClampedArray(rgba), w, h);
} catch (ex) {
img = ctx.createImageData(w, h);
img.data.set(new Uint8ClampedArray(rgba));
}
let img = new ImageData(new Uint8ClampedArray(rgba), w, h);
ctx.clearRect(0, 0, w, h);
ctx.putImageData(img, 0, 0);
-4
View File
@@ -65,10 +65,6 @@ export function setCapture(target) {
target.setCapture();
document.captureElement = target;
// IE releases capture on 'click' events which might not trigger
target.addEventListener('mouseup', releaseCapture);
} else {
// Release any existing capture in case this method is
// called multiple times without coordination
+79
View File
@@ -0,0 +1,79 @@
/*
* 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 a string of binary characters, returns an array of bytes
*/
export function MD5(d) {
let r = M(V(Y(X(d), 8 * d.length)));
return r;
}
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;
}
-61
View File
@@ -1,61 +0,0 @@
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2020 The noVNC Authors
* Licensed under MPL 2.0 or any later version (see LICENSE.txt)
*/
/* Polyfills to provide new APIs in old browsers */
/* Object.assign() (taken from MDN) */
if (typeof Object.assign != 'function') {
// Must be writable: true, enumerable: false, configurable: true
Object.defineProperty(Object, "assign", {
value: function assign(target, varArgs) { // .length of function is 2
'use strict';
if (target == null) { // TypeError if undefined or null
throw new TypeError('Cannot convert undefined or null to object');
}
const to = Object(target);
for (let index = 1; index < arguments.length; index++) {
const nextSource = arguments[index];
if (nextSource != null) { // Skip over if undefined or null
for (let nextKey in nextSource) {
// Avoid bugs when hasOwnProperty is shadowed
if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
to[nextKey] = nextSource[nextKey];
}
}
}
}
return to;
},
writable: true,
configurable: true
});
}
/* CustomEvent constructor (taken from MDN) */
(() => {
function CustomEvent(event, params) {
params = params || { bubbles: false, cancelable: false, detail: undefined };
const evt = document.createEvent( 'CustomEvent' );
evt.initCustomEvent( event, params.bubbles, params.cancelable, params.detail );
return evt;
}
CustomEvent.prototype = window.Event.prototype;
if (typeof window.CustomEvent !== "function") {
window.CustomEvent = CustomEvent;
}
})();
/* Number.isInteger() (taken from MDN) */
Number.isInteger = Number.isInteger || function isInteger(value) {
return typeof value === 'number' &&
isFinite(value) &&
Math.floor(value) === value;
};
+76 -17
View File
@@ -1,10 +1,10 @@
/*
* Websock: high-performance binary WebSockets
* Websock: high-performance buffering wrapper
* Copyright (C) 2019 The noVNC Authors
* Licensed under MPL 2.0 (see LICENSE.txt)
*
* Websock is similar to the standard WebSocket object but with extra
* buffer handling.
* Websock is similar to the standard WebSocket / RTCDataChannel object
* but with extra buffer handling.
*
* Websock has built-in receive queue buffering; the message event
* does not contain actual data but is simply a notification that
@@ -17,14 +17,39 @@ import * as Log from './util/logging.js';
// this has performance issues in some versions Chromium, and
// doesn't gain a tremendous amount of performance increase in Firefox
// at the moment. It may be valuable to turn it on in the future.
// Also copyWithin() for TypedArrays is not supported in IE 11 or
// Safari 13 (at the moment we want to support Safari 11).
const ENABLE_COPYWITHIN = false;
const MAX_RQ_GROW_SIZE = 40 * 1024 * 1024; // 40 MiB
// Constants pulled from RTCDataChannelState enum
// https://developer.mozilla.org/en-US/docs/Web/API/RTCDataChannel/readyState#RTCDataChannelState_enum
const DataChannel = {
CONNECTING: "connecting",
OPEN: "open",
CLOSING: "closing",
CLOSED: "closed"
};
const ReadyStates = {
CONNECTING: [WebSocket.CONNECTING, DataChannel.CONNECTING],
OPEN: [WebSocket.OPEN, DataChannel.OPEN],
CLOSING: [WebSocket.CLOSING, DataChannel.CLOSING],
CLOSED: [WebSocket.CLOSED, DataChannel.CLOSED],
};
// Properties a raw channel must have, WebSocket and RTCDataChannel are two examples
const rawChannelProps = [
"send",
"close",
"binaryType",
"onerror",
"onmessage",
"onopen",
"protocol",
"readyState",
];
export default class Websock {
constructor() {
this._websocket = null; // WebSocket object
this._websocket = null; // WebSocket or RTCDataChannel object
this._rQi = 0; // Receive queue index
this._rQlen = 0; // Next write position in the receive queue
@@ -46,6 +71,29 @@ export default class Websock {
}
// Getters and Setters
get readyState() {
let subState;
if (this._websocket === null) {
return "unused";
}
subState = this._websocket.readyState;
if (ReadyStates.CONNECTING.includes(subState)) {
return "connecting";
} else if (ReadyStates.OPEN.includes(subState)) {
return "open";
} else if (ReadyStates.CLOSING.includes(subState)) {
return "closing";
} else if (ReadyStates.CLOSED.includes(subState)) {
return "closed";
}
return "unknown";
}
get sQ() {
return this._sQ;
}
@@ -143,7 +191,7 @@ export default class Websock {
// Send Queue
flush() {
if (this._sQlen > 0 && this._websocket.readyState === WebSocket.OPEN) {
if (this._sQlen > 0 && this.readyState === 'open') {
this._websocket.send(this._encodeMessage());
this._sQlen = 0;
}
@@ -180,12 +228,25 @@ export default class Websock {
}
open(uri, protocols) {
this.attach(new WebSocket(uri, protocols));
}
attach(rawChannel) {
this.init();
this._websocket = new WebSocket(uri, protocols);
this._websocket.binaryType = 'arraybuffer';
// Must get object and class methods to be compatible with the tests.
const channelProps = [...Object.keys(rawChannel), ...Object.getOwnPropertyNames(Object.getPrototypeOf(rawChannel))];
for (let i = 0; i < rawChannelProps.length; i++) {
const prop = rawChannelProps[i];
if (channelProps.indexOf(prop) < 0) {
throw new Error('Raw channel missing property: ' + prop);
}
}
this._websocket = rawChannel;
this._websocket.binaryType = "arraybuffer";
this._websocket.onmessage = this._recvMessage.bind(this);
this._websocket.onopen = () => {
Log.Debug('>> WebSock.onopen');
if (this._websocket.protocol) {
@@ -195,11 +256,13 @@ export default class Websock {
this._eventHandlers.open();
Log.Debug("<< WebSock.onopen");
};
this._websocket.onclose = (e) => {
Log.Debug(">> WebSock.onclose");
this._eventHandlers.close(e);
Log.Debug("<< WebSock.onclose");
};
this._websocket.onerror = (e) => {
Log.Debug(">> WebSock.onerror: " + e);
this._eventHandlers.error(e);
@@ -209,8 +272,8 @@ export default class Websock {
close() {
if (this._websocket) {
if ((this._websocket.readyState === WebSocket.OPEN) ||
(this._websocket.readyState === WebSocket.CONNECTING)) {
if (this.readyState === 'connecting' ||
this.readyState === 'open') {
Log.Info("Closing WebSocket connection");
this._websocket.close();
}
@@ -256,11 +319,7 @@ export default class Websock {
this._rQ = new Uint8Array(this._rQbufferSize);
this._rQ.set(new Uint8Array(oldRQbuffer, this._rQi, this._rQlen - this._rQi));
} else {
if (ENABLE_COPYWITHIN) {
this._rQ.copyWithin(0, this._rQi, this._rQlen);
} else {
this._rQ.set(new Uint8Array(this._rQ.buffer, this._rQi, this._rQlen - this._rQi));
}
this._rQ.copyWithin(0, this._rQi, this._rQlen);
}
this._rQlen = this._rQlen - this._rQi;
-5
View File
@@ -78,12 +78,7 @@ None
| fillRect | (x, y, width, height, color, from_queue) | Draw a filled in rectangle
| copyImage | (old_x, old_y, new_x, new_y, width, height, from_queue) | Copy a rectangular area
| imageRect | (x, y, width, height, mime, arr) | Draw a rectangle with an image
| startTile | (x, y, width, height, color) | Begin updating a tile
| subTile | (tile, x, y, w, h, color) | Update a sub-rectangle within the given tile
| finishTile | () | Draw the current tile to the display
| blitImage | (x, y, width, height, arr, offset, from_queue) | Blit pixels (of R,G,B,A) to the display
| blitRgbImage | (x, y, width, height, arr, offset, from_queue) | Blit RGB encoded image to display
| blitRgbxImage | (x, y, width, height, arr, offset, from_queue) | Blit RGBX encoded image to display
| drawImage | (img, x, y) | Draw image and track damage
| autoscale | (containerWidth, containerHeight) | Scale the display
+367 -206
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,19 +184,24 @@ 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.
**`url`**
[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.
a valid WebSocket URL. This can also be a `WebSocket` or `RTCDataChannel`.
**`options`** *Optional*
- An `Object` specifying extra details about how the connection
@@ -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.
-18
View File
@@ -71,24 +71,6 @@ query string. Currently the following options are available:
* `logging` - The console log level. Can be one of `error`, `warn`, `info` or
`debug`.
## Pre-conversion of Modules
noVNC is written using ECMAScript 6 modules. Many of the major browsers support
these modules natively, but not all. By default the noVNC application includes
a script that can convert these modules to an older format as they are being
loaded. However this process can be slow and severely increases the load time
for the application.
It is possible to perform this conversion ahead of time, avoiding the extra
load times. To do this please follow these steps:
1. Install Node.js
2. Run `npm install` in the noVNC directory
3. Run `./utils/use_require.js --with-app --as commonjs`
This will produce a `build/` directory that includes everything needed to run
the noVNC application.
## HTTP Serving Considerations
### Browser Cache Issue
+3 -7
View File
@@ -18,18 +18,14 @@ do things.
## Conversion of Modules
noVNC is written using ECMAScript 6 modules. Many of the major browsers support
these modules natively, but not all. They are also not supported by Node.js. To
use noVNC in these places the library must first be converted.
noVNC is written using ECMAScript 6 modules. This is not supported by older
versions of Node.js. To use noVNC with those older versions of Node.js the
library must first be converted.
Fortunately noVNC includes a script to handle this conversion. Please follow
the following steps:
1. Install Node.js
2. Run `npm install` in the noVNC directory
3. Run `./utils/use_require.js --as <module format>`
Several module formats are available. Please run
`./utils/use_require.js --help` to see them all.
The result of the conversion is available in the `lib/` directory.
+37
View File
@@ -0,0 +1,37 @@
.TH novnc_proxy 1 "June 25, 2020" "version 1.2.0" "USER COMMANDS"
.SH NAME
novnc_proxy - noVNC proxy server
.SH SYNOPSIS
.B novnc_proxy [--listen PORT] [--vnc VNC_HOST:PORT] [--cert CERT] [--ssl-only]
Starts the WebSockets proxy and a mini-webserver and
provides a cut-and-paste URL to go to.
--listen PORT Port for proxy/webserver to listen on
Default: 6080
--vnc VNC_HOST:PORT VNC server host:port proxy target
Default: localhost:5900
--cert CERT Path to combined cert/key file, or just
the cert file if used with --key
Default: self.pem
--key KEY Path to key file, when not combined with cert
--web WEB Path to web files (e.g. vnc.html)
Default: ./
--ssl-only Disable non-https connections.
--record FILE Record traffic to FILE.session.js
--syslog SERVER Can be local socket such as /dev/log, or a UDP host:port pair.
--heartbeat SEC send a ping to the client every SEC seconds
--timeout SEC after SEC seconds exit when not connected
--idle-timeout SEC server exits after SEC seconds if there are no
active connections
.SH AUTHOR
The noVNC Authors
https://github.com/novnc/noVNC
.SH SEE ALSO
websockify(1), nova-novncproxy(1)
+7 -10
View File
@@ -31,16 +31,13 @@ module.exports = (config) => {
// list of files / patterns to load in the browser (loaded in order)
files: [
{ pattern: 'app/localization.js', included: false },
{ pattern: 'app/webutil.js', included: false },
{ pattern: 'core/**/*.js', included: false },
{ pattern: 'vendor/pako/**/*.js', included: false },
{ pattern: 'vendor/browser-es-module-loader/dist/*.js*', included: false },
{ pattern: 'tests/test.*.js', included: false },
{ pattern: 'tests/fake.*.js', included: false },
{ pattern: 'tests/assertions.js', included: false },
'vendor/promise.js',
'tests/karma-test-main.js',
{ pattern: 'app/localization.js', included: false, type: 'module' },
{ pattern: 'app/webutil.js', included: false, type: 'module' },
{ pattern: 'core/**/*.js', included: false, type: 'module' },
{ pattern: 'vendor/pako/**/*.js', included: false, type: 'module' },
{ pattern: 'tests/test.*.js', type: 'module' },
{ pattern: 'tests/fake.*.js', included: false, type: 'module' },
{ pattern: 'tests/assertions.js', type: 'module' },
],
client: {
+32 -40
View File
@@ -1,6 +1,6 @@
{
"name": "@novnc/novnc",
"version": "1.2.0",
"version": "1.4.0-beta",
"description": "An HTML5 VNC client",
"browser": "lib/rfb",
"directories": {
@@ -21,7 +21,7 @@
"scripts": {
"lint": "eslint app core po/po2js po/xgettext-html tests utils",
"test": "karma start karma.conf.js",
"prepublish": "node ./utils/use_require.js --as commonjs --clean"
"prepublish": "node ./utils/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,42 +38,36 @@
},
"homepage": "https://github.com/novnc/noVNC",
"devDependencies": {
"@babel/core": "*",
"@babel/plugin-syntax-dynamic-import": "*",
"@babel/plugin-transform-modules-amd": "*",
"@babel/plugin-transform-modules-commonjs": "*",
"@babel/plugin-transform-modules-systemjs": "*",
"@babel/plugin-transform-modules-umd": "*",
"@babel/preset-env": "*",
"@babel/cli": "*",
"babel-plugin-import-redirect": "*",
"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/plugin-syntax-dynamic-import": "latest",
"@babel/plugin-transform-modules-commonjs": "latest",
"@babel/preset-env": "latest",
"@babel/cli": "latest",
"babel-plugin-import-redirect": "latest",
"browserify": "latest",
"babelify": "latest",
"core-js": "latest",
"chai": "latest",
"commander": "latest",
"es-module-loader": "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 ja ko nl pl 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 \
+17 -16
View File
@@ -3,14 +3,15 @@
# Copyright (C) 2018 The noVNC Authors
# This file is distributed under the same license as the noVNC package.
# Juanjo Diaz <juanjo.diazmo@gmail.com>, 2018.
# Adrian Scillato <ascillato@gmail.com>, 2021.
#
msgid ""
msgstr ""
"Project-Id-Version: noVNC 1.0.0-testing.2\n"
"Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
"POT-Creation-Date: 2017-10-06 10:07+0200\n"
"PO-Revision-Date: 2018-01-30 19:14-0800\n"
"Last-Translator: Juanjo Diaz <juanjo.diazmo@gmail.com>\n"
"PO-Revision-Date: 2021-04-23 12:00-0300\n"
"Last-Translator: Adrian Scillato <ascillato@gmail.com>\n"
"Language-Team: Spanish\n"
"Language: es\n"
"MIME-Version: 1.0\n"
@@ -40,7 +41,7 @@ msgstr "Desconectado"
#: ../app/ui.js:1052 ../core/rfb.js:248
msgid "Must set host"
msgstr "Debes configurar el host"
msgstr "Se debe configurar el host"
#: ../app/ui.js:1101
msgid "Reconnecting..."
@@ -48,7 +49,7 @@ msgstr "Reconectando..."
#: ../app/ui.js:1140
msgid "Password is required"
msgstr "Contraseña es obligatoria"
msgstr "La contraseña es obligatoria"
#: ../core/rfb.js:548
msgid "Disconnect timeout"
@@ -186,6 +187,10 @@ msgstr "Pantalla Completa"
msgid "Settings"
msgstr "Configuraciones"
#: ../vnc.html:200
msgid "Encrypt"
msgstr "Encriptar"
#: ../vnc.html:202
msgid "Shared Mode"
msgstr "Modo Compartido"
@@ -228,27 +233,23 @@ msgstr "Cursor Local"
#: ../vnc.html:229
msgid "Repeater ID:"
msgstr "ID del Repetidor"
msgstr "ID del Repetidor:"
#: ../vnc.html:233
msgid "WebSocket"
msgstr "WebSocket"
#: ../vnc.html:236
msgid "Encrypt"
msgstr ""
#: ../vnc.html:239
msgid "Host:"
msgstr "Host"
msgstr "Host:"
#: ../vnc.html:243
msgid "Port:"
msgstr "Puesto"
msgstr "Puerto:"
#: ../vnc.html:247
msgid "Path:"
msgstr "Ruta"
msgstr "Ruta:"
#: ../vnc.html:254
msgid "Automatic Reconnect"
@@ -256,11 +257,11 @@ msgstr "Reconexión automática"
#: ../vnc.html:257
msgid "Reconnect Delay (ms):"
msgstr "Retraso en la reconexión (ms)"
msgstr "Retraso en la reconexión (ms):"
#: ../vnc.html:263
msgid "Logging:"
msgstr "Logging"
msgstr "Registrando:"
#: ../vnc.html:275
msgid "Disconnect"
@@ -272,7 +273,7 @@ msgstr "Conectar"
#: ../vnc.html:304
msgid "Password:"
msgstr "Contraseña"
msgstr "Contraseña:"
#: ../vnc.html:318
msgid "Cancel"
@@ -280,4 +281,4 @@ msgstr "Cancelar"
#: ../vnc.html:334
msgid "Canvas not supported."
msgstr "Canvas no está soportado"
msgstr "Canvas no soportado."
+300
View File
@@ -0,0 +1,300 @@
# French translations for noVNC package
# Traductions françaises du paquet noVNC.
# Copyright (C) 2021 The noVNC Authors
# This file is distributed under the same license as the noVNC package.
# Jose <jose.matsuda@canada.ca>, 2021.
# 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: 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"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#: ../app/ui.js:394
msgid "Connecting..."
msgstr "En cours de connexion..."
#: ../app/ui.js:401
msgid "Disconnecting..."
msgstr "Déconnexion en cours..."
#: ../app/ui.js:407
msgid "Reconnecting..."
msgstr "Reconnexion en cours..."
#: ../app/ui.js:412
msgid "Internal error"
msgstr "Erreur interne"
#: ../app/ui.js:1008
msgid "Must set host"
msgstr "Doit définir l'hôte"
#: ../app/ui.js:1090
msgid "Connected (encrypted) to "
msgstr "Connecté (chiffré) à "
#: ../app/ui.js:1092
msgid "Connected (unencrypted) to "
msgstr "Connecté (non chiffré) à "
#: ../app/ui.js:1115
msgid "Something went wrong, connection is closed"
msgstr "Quelque chose s'est mal passé, la connexion a été fermée"
#: ../app/ui.js:1118
msgid "Failed to connect to server"
msgstr "Échec de connexion au serveur"
#: ../app/ui.js:1128
msgid "Disconnected"
msgstr "Déconnecté"
#: ../app/ui.js:1143
msgid "New connection has been rejected with reason: "
msgstr "Une nouvelle connexion a été rejetée avec motif : "
#: ../app/ui.js:1146
msgid "New connection has been rejected"
msgstr "Une nouvelle connexion a été rejetée"
#: ../app/ui.js:1181
msgid "Credentials are required"
msgstr "Les identifiants sont requis"
#: ../vnc.html:74
msgid "noVNC encountered an error:"
msgstr "noVNC a rencontré une erreur :"
#: ../vnc.html:84
msgid "Hide/Show the control bar"
msgstr "Masquer/Afficher la barre de contrôle"
#: ../vnc.html:91
msgid "Drag"
msgstr "Faire glisser"
#: ../vnc.html:91
msgid "Move/Drag Viewport"
msgstr "Déplacer/faire glisser le Viewport"
#: ../vnc.html:97
msgid "Keyboard"
msgstr "Clavier"
#: ../vnc.html:97
msgid "Show Keyboard"
msgstr "Afficher le clavier"
#: ../vnc.html:102
msgid "Extra keys"
msgstr "Touches supplémentaires"
#: ../vnc.html:102
msgid "Show Extra Keys"
msgstr "Afficher les touches supplémentaires"
#: ../vnc.html:107
msgid "Ctrl"
msgstr "Ctrl"
#: ../vnc.html:107
msgid "Toggle Ctrl"
msgstr "Basculer Ctrl"
#: ../vnc.html:110
msgid "Alt"
msgstr "Alt"
#: ../vnc.html:110
msgid "Toggle Alt"
msgstr "Basculer Alt"
#: ../vnc.html:113
msgid "Toggle Windows"
msgstr "Basculer Windows"
#: ../vnc.html:113
msgid "Windows"
msgstr "Windows"
#: ../vnc.html:116
msgid "Send Tab"
msgstr "Envoyer l'onglet"
#: ../vnc.html:116
msgid "Tab"
msgstr "l'onglet"
#: ../vnc.html:119
msgid "Esc"
msgstr "Esc"
#: ../vnc.html:119
msgid "Send Escape"
msgstr "Envoyer Escape"
#: ../vnc.html:122
msgid "Ctrl+Alt+Del"
msgstr "Ctrl+Alt+Del"
#: ../vnc.html:122
msgid "Send Ctrl-Alt-Del"
msgstr "Envoyer Ctrl-Alt-Del"
#: ../vnc.html:129
msgid "Shutdown/Reboot"
msgstr "Arrêter/Redémarrer"
#: ../vnc.html:129
msgid "Shutdown/Reboot..."
msgstr "Arrêter/Redémarrer..."
#: ../vnc.html:135
msgid "Power"
msgstr "Alimentation"
#: ../vnc.html:137
msgid "Shutdown"
msgstr "Arrêter"
#: ../vnc.html:138
msgid "Reboot"
msgstr "Redémarrer"
#: ../vnc.html:139
msgid "Reset"
msgstr "Réinitialiser"
#: ../vnc.html:144 ../vnc.html:150
msgid "Clipboard"
msgstr "Presse-papiers"
#: ../vnc.html:154
msgid "Clear"
msgstr "Effacer"
#: ../vnc.html:160
msgid "Fullscreen"
msgstr "Plein écran"
#: ../vnc.html:165 ../vnc.html:172
msgid "Settings"
msgstr "Paramètres"
#: ../vnc.html:175
msgid "Shared Mode"
msgstr "Mode partagé"
#: ../vnc.html:178
msgid "View Only"
msgstr "Afficher uniquement"
#: ../vnc.html:182
msgid "Clip to Window"
msgstr "Clip à fenêtre"
#: ../vnc.html:185
msgid "Scaling Mode:"
msgstr "Mode mise à l'échelle :"
#: ../vnc.html:187
msgid "None"
msgstr "Aucun"
#: ../vnc.html:188
msgid "Local Scaling"
msgstr "Mise à l'échelle locale"
#: ../vnc.html:189
msgid "Remote Resizing"
msgstr "Redimensionnement à distance"
#: ../vnc.html:194
msgid "Advanced"
msgstr "Avancé"
#: ../vnc.html:197
msgid "Quality:"
msgstr "Qualité :"
#: ../vnc.html:201
msgid "Compression level:"
msgstr "Niveau de compression :"
#: ../vnc.html:206
msgid "Repeater ID:"
msgstr "ID Répéteur :"
#: ../vnc.html:210
msgid "WebSocket"
msgstr "WebSocket"
#: ../vnc.html:213
msgid "Encrypt"
msgstr "Chiffrer"
#: ../vnc.html:216
msgid "Host:"
msgstr "Hôte :"
#: ../vnc.html:220
msgid "Port:"
msgstr "Port :"
#: ../vnc.html:224
msgid "Path:"
msgstr "Chemin :"
#: ../vnc.html:231
msgid "Automatic Reconnect"
msgstr "Reconnecter automatiquemen"
#: ../vnc.html:234
msgid "Reconnect Delay (ms):"
msgstr "Délai de reconnexion (ms) :"
#: ../vnc.html:239
msgid "Show Dot when No Cursor"
msgstr "Afficher le point lorsqu'il n'y a pas de curseur"
#: ../vnc.html:244
msgid "Logging:"
msgstr "Se connecter :"
#: ../vnc.html:253
msgid "Version:"
msgstr "Version :"
#: ../vnc.html:261
msgid "Disconnect"
msgstr "Déconnecter"
#: ../vnc.html:280
msgid "Connect"
msgstr "Connecter"
#: ../vnc.html:290
msgid "Username:"
msgstr "Nom d'utilisateur :"
#: ../vnc.html:294
msgid "Password:"
msgstr "Mot de passe :"
#: ../vnc.html:298
msgid "Send Credentials"
msgstr "Envoyer les identifiants"
#: ../vnc.html:308
msgid "Cancel"
msgstr "Annuler"
+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"
+117 -96
View File
@@ -8,8 +8,8 @@ msgid ""
msgstr ""
"Project-Id-Version: noVNC 1.1.0\n"
"Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
"POT-Creation-Date: 2019-01-16 11:06+0100\n"
"PO-Revision-Date: 2020-06-15 17:44+0900\n"
"POT-Creation-Date: 2020-07-03 16:11+0200\n"
"PO-Revision-Date: 2021-01-15 12:37+0900\n"
"Last-Translator: nnn1590 <nnn1590@nnn1590.org>\n"
"Language-Team: Japanese\n"
"Language: ja\n"
@@ -17,287 +17,308 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Poedit 2.3\n"
#: ../app/ui.js:387
#: ../app/ui.js:394
msgid "Connecting..."
msgstr "接続しています..."
#: ../app/ui.js:394
#: ../app/ui.js:401
msgid "Disconnecting..."
msgstr "切断しています..."
#: ../app/ui.js:400
#: ../app/ui.js:407
msgid "Reconnecting..."
msgstr "再接続しています..."
#: ../app/ui.js:405
#: ../app/ui.js:412
msgid "Internal error"
msgstr "内部エラー"
#: ../app/ui.js:995
#: ../app/ui.js:1008
msgid "Must set host"
msgstr "ホストを設定する必要があります"
#: ../app/ui.js:1077
#: ../app/ui.js:1090
msgid "Connected (encrypted) to "
msgstr "接続しました (暗号化済み): "
#: ../app/ui.js:1079
#: ../app/ui.js:1092
msgid "Connected (unencrypted) to "
msgstr "接続しました (暗号化されていません): "
#: ../app/ui.js:1102
#: ../app/ui.js:1115
msgid "Something went wrong, connection is closed"
msgstr "何かが問題で、接続が閉じられました"
msgstr "何らかの問題で、接続が閉じられました"
#: ../app/ui.js:1105
#: ../app/ui.js:1118
msgid "Failed to connect to server"
msgstr "サーバーへの接続に失敗しました"
#: ../app/ui.js:1115
#: ../app/ui.js:1128
msgid "Disconnected"
msgstr "切断しました"
#: ../app/ui.js:1128
#: ../app/ui.js:1143
msgid "New connection has been rejected with reason: "
msgstr "新規接続は次の理由で拒否されました: "
#: ../app/ui.js:1131
#: ../app/ui.js:1146
msgid "New connection has been rejected"
msgstr "新規接続は拒否されました"
#: ../app/ui.js:1151
msgid "Password is required"
msgstr "パスワードが必要です"
#: ../app/ui.js:1181
msgid "Credentials are required"
msgstr "資格情報が必要です"
#: ../vnc.html:84
#: ../vnc.html:74
msgid "noVNC encountered an error:"
msgstr "noVNC でエラーが発生しました:"
#: ../vnc.html:94
#: ../vnc.html:84
msgid "Hide/Show the control bar"
msgstr "コントロールバーを隠す/表示する"
#: ../vnc.html:101
#: ../vnc.html:91
msgid "Drag"
msgstr "ドラッグ"
#: ../vnc.html:91
msgid "Move/Drag Viewport"
msgstr "ビューポートを移動/ドラッグ"
#: ../vnc.html:101
msgid "viewport drag"
msgstr "ビューポートをドラッグ"
#: ../vnc.html:107 ../vnc.html:110 ../vnc.html:113 ../vnc.html:116
msgid "Active Mouse Button"
msgstr "アクティブなマウスボタン"
#: ../vnc.html:107
msgid "No mousebutton"
msgstr "マウスボタンなし"
#: ../vnc.html:110
msgid "Left mousebutton"
msgstr "左マウスボタン"
#: ../vnc.html:113
msgid "Middle mousebutton"
msgstr "中マウスボタン"
#: ../vnc.html:116
msgid "Right mousebutton"
msgstr "右マウスボタン"
#: ../vnc.html:119
#: ../vnc.html:97
msgid "Keyboard"
msgstr "キーボード"
#: ../vnc.html:119
#: ../vnc.html:97
msgid "Show Keyboard"
msgstr "キーボードを表示"
#: ../vnc.html:126
#: ../vnc.html:102
msgid "Extra keys"
msgstr "追加キー"
#: ../vnc.html:126
#: ../vnc.html:102
msgid "Show Extra Keys"
msgstr "追加キーを表示"
#: ../vnc.html:131
#: ../vnc.html:107
msgid "Ctrl"
msgstr "Ctrl"
#: ../vnc.html:131
#: ../vnc.html:107
msgid "Toggle Ctrl"
msgstr "Ctrl キーを切り替え"
#: ../vnc.html:134
#: ../vnc.html:110
msgid "Alt"
msgstr "Alt"
#: ../vnc.html:134
#: ../vnc.html:110
msgid "Toggle Alt"
msgstr "Alt キーを切り替え"
#: ../vnc.html:137
#: ../vnc.html:113
msgid "Toggle Windows"
msgstr "Windows キーを切り替え"
#: ../vnc.html:137
#: ../vnc.html:113
msgid "Windows"
msgstr "Windows"
#: ../vnc.html:140
#: ../vnc.html:116
msgid "Send Tab"
msgstr "Tab キーを送信"
#: ../vnc.html:140
#: ../vnc.html:116
msgid "Tab"
msgstr "Tab"
#: ../vnc.html:143
#: ../vnc.html:119
msgid "Esc"
msgstr "Esc"
#: ../vnc.html:143
#: ../vnc.html:119
msgid "Send Escape"
msgstr "Escape キーを送信"
#: ../vnc.html:146
#: ../vnc.html:122
msgid "Ctrl+Alt+Del"
msgstr "Ctrl+Alt+Del"
#: ../vnc.html:146
#: ../vnc.html:122
msgid "Send Ctrl-Alt-Del"
msgstr "Ctrl-Alt-Del を送信"
#: ../vnc.html:154
#: ../vnc.html:129
msgid "Shutdown/Reboot"
msgstr "シャットダウン/再起動"
#: ../vnc.html:154
#: ../vnc.html:129
msgid "Shutdown/Reboot..."
msgstr "シャットダウン/再起動..."
#: ../vnc.html:160
#: ../vnc.html:135
msgid "Power"
msgstr "電源"
#: ../vnc.html:162
#: ../vnc.html:137
msgid "Shutdown"
msgstr "シャットダウン"
#: ../vnc.html:163
#: ../vnc.html:138
msgid "Reboot"
msgstr "再起動"
#: ../vnc.html:164
#: ../vnc.html:139
msgid "Reset"
msgstr "リセット"
#: ../vnc.html:169 ../vnc.html:175
#: ../vnc.html:144 ../vnc.html:150
msgid "Clipboard"
msgstr "クリップボード"
#: ../vnc.html:179
#: ../vnc.html:154
msgid "Clear"
msgstr "クリア"
#: ../vnc.html:185
#: ../vnc.html:160
msgid "Fullscreen"
msgstr "全画面表示"
#: ../vnc.html:190 ../vnc.html:197
#: ../vnc.html:165 ../vnc.html:172
msgid "Settings"
msgstr "設定"
#: ../vnc.html:200
#: ../vnc.html:175
msgid "Shared Mode"
msgstr "共有モード"
#: ../vnc.html:203
#: ../vnc.html:178
msgid "View Only"
msgstr "表示のみ"
#: ../vnc.html:207
#: ../vnc.html:182
msgid "Clip to Window"
msgstr "ウィンドウにクリップ"
#: ../vnc.html:210
#: ../vnc.html:185
msgid "Scaling Mode:"
msgstr "スケーリングモード:"
#: ../vnc.html:212
#: ../vnc.html:187
msgid "None"
msgstr "なし"
#: ../vnc.html:213
#: ../vnc.html:188
msgid "Local Scaling"
msgstr "ローカルスケーリング"
#: ../vnc.html:214
#: ../vnc.html:189
msgid "Remote Resizing"
msgstr "リモートでリサイズ"
#: ../vnc.html:219
#: ../vnc.html:194
msgid "Advanced"
msgstr "高度"
#: ../vnc.html:222
#: ../vnc.html:197
msgid "Quality:"
msgstr "品質:"
#: ../vnc.html:201
msgid "Compression level:"
msgstr "圧縮レベル:"
#: ../vnc.html:206
msgid "Repeater ID:"
msgstr "リピーター ID:"
#: ../vnc.html:226
#: ../vnc.html:210
msgid "WebSocket"
msgstr "WebSocket"
#: ../vnc.html:229
#: ../vnc.html:213
msgid "Encrypt"
msgstr "暗号化"
#: ../vnc.html:232
#: ../vnc.html:216
msgid "Host:"
msgstr "ホスト:"
#: ../vnc.html:236
#: ../vnc.html:220
msgid "Port:"
msgstr "ポート:"
#: ../vnc.html:240
#: ../vnc.html:224
msgid "Path:"
msgstr "パス:"
#: ../vnc.html:247
#: ../vnc.html:231
msgid "Automatic Reconnect"
msgstr "自動再接続"
#: ../vnc.html:250
#: ../vnc.html:234
msgid "Reconnect Delay (ms):"
msgstr "再接続する遅延 (ミリ秒):"
#: ../vnc.html:255
#: ../vnc.html:239
msgid "Show Dot when No Cursor"
msgstr "カーソルがないときにドットを表示"
#: ../vnc.html:260
#: ../vnc.html:244
msgid "Logging:"
msgstr "ロギング:"
#: ../vnc.html:272
#: ../vnc.html:253
msgid "Version:"
msgstr "バージョン:"
#: ../vnc.html:261
msgid "Disconnect"
msgstr "切断"
#: ../vnc.html:291
#: ../vnc.html:280
msgid "Connect"
msgstr "接続"
#: ../vnc.html:301
#: ../vnc.html:290
msgid "Username:"
msgstr "ユーザー名:"
#: ../vnc.html:294
msgid "Password:"
msgstr "パスワード:"
#: ../vnc.html:305
msgid "Send Password"
msgstr "パスワードを送信"
#: ../vnc.html:298
msgid "Send Credentials"
msgstr "資格情報を送信"
#: ../vnc.html:315
#: ../vnc.html:308
msgid "Cancel"
msgstr "キャンセル"
#~ msgid "Password is required"
#~ msgstr "パスワードが必要です"
#~ msgid "viewport drag"
#~ msgstr "ビューポートをドラッグ"
#~ msgid "Active Mouse Button"
#~ msgstr "アクティブなマウスボタン"
#~ msgid "No mousebutton"
#~ msgstr "マウスボタンなし"
#~ msgid "Left mousebutton"
#~ msgstr "左マウスボタン"
#~ msgid "Middle mousebutton"
#~ msgstr "中マウスボタン"
#~ msgid "Right mousebutton"
#~ msgstr "右マウスボタン"
#~ msgid "Send Password"
#~ msgstr "パスワードを送信"
+108 -74
View File
@@ -6,9 +6,9 @@
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: noVNC 1.2.0\n"
"Project-Id-Version: noVNC 1.4.0\n"
"Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
"POT-Creation-Date: 2020-07-03 16:11+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:394
#: ../app/ui.js:69
msgid "HTTPS is required for full functionality"
msgstr ""
#: ../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 ""
#: ../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 ""
#: ../vnc.html:107
#: ../vnc.html:92
msgid "Toggle Ctrl"
msgstr ""
#: ../vnc.html:110
#: ../vnc.html:95
msgid "Alt"
msgstr ""
#: ../vnc.html:110
#: ../vnc.html:95
msgid "Toggle Alt"
msgstr ""
#: ../vnc.html:113
#: ../vnc.html:98
msgid "Toggle Windows"
msgstr ""
#: ../vnc.html:113
#: ../vnc.html:98
msgid "Windows"
msgstr ""
#: ../vnc.html:116
#: ../vnc.html:101
msgid "Send Tab"
msgstr ""
#: ../vnc.html:116
#: ../vnc.html:101
msgid "Tab"
msgstr ""
#: ../vnc.html:119
#: ../vnc.html:104
msgid "Esc"
msgstr ""
#: ../vnc.html:119
#: ../vnc.html:104
msgid "Send Escape"
msgstr ""
#: ../vnc.html:122
#: ../vnc.html:107
msgid "Ctrl+Alt+Del"
msgstr ""
#: ../vnc.html:122
#: ../vnc.html:107
msgid "Send Ctrl-Alt-Del"
msgstr ""
#: ../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"
#: ../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 ""
#: ../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 ""
#: ../vnc.html:210
#: ../vnc.html:195
msgid "WebSocket"
msgstr ""
#: ../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 ""
#: ../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
msgid "Username:"
#: ../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:310
msgid "Password:"
msgstr ""
#: ../vnc.html:298
#: ../vnc.html:314
msgid "Send Credentials"
msgstr ""
#: ../vnc.html:308
#: ../vnc.html:323
msgid "Cancel"
msgstr ""
+299
View File
@@ -0,0 +1,299 @@
# Portuguese translations for noVNC package.
# Copyright (C) 2021 The noVNC Authors
# This file is distributed under the same license as the noVNC package.
# <liddack@outlook.com>, 2021.
#
msgid ""
msgstr ""
"Project-Id-Version: noVNC 1.2.0\n"
"Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
"POT-Creation-Date: 2021-03-15 21:55-0300\n"
"PO-Revision-Date: 2021-03-15 22:09-0300\n"
"Last-Translator: <liddack@outlook.com>\n"
"Language-Team: Brazilian Portuguese\n"
"Language: pt_BR\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"X-Generator: Poedit 2.4.1\n"
#: ../app/ui.js:400
msgid "Connecting..."
msgstr "Conectando..."
#: ../app/ui.js:407
msgid "Disconnecting..."
msgstr "Desconectando..."
#: ../app/ui.js:413
msgid "Reconnecting..."
msgstr "Reconectando..."
#: ../app/ui.js:418
msgid "Internal error"
msgstr "Erro interno"
#: ../app/ui.js:1009
msgid "Must set host"
msgstr "É necessário definir o host"
#: ../app/ui.js:1091
msgid "Connected (encrypted) to "
msgstr "Conectado (com criptografia) a "
#: ../app/ui.js:1093
msgid "Connected (unencrypted) to "
msgstr "Conectado (sem criptografia) a "
#: ../app/ui.js:1116
msgid "Something went wrong, connection is closed"
msgstr "Algo deu errado. A conexão foi encerrada."
#: ../app/ui.js:1119
msgid "Failed to connect to server"
msgstr "Falha ao conectar-se ao servidor"
#: ../app/ui.js:1129
msgid "Disconnected"
msgstr "Desconectado"
#: ../app/ui.js:1144
msgid "New connection has been rejected with reason: "
msgstr "A nova conexão foi rejeitada pelo motivo: "
#: ../app/ui.js:1147
msgid "New connection has been rejected"
msgstr "A nova conexão foi rejeitada"
#: ../app/ui.js:1182
msgid "Credentials are required"
msgstr "Credenciais são obrigatórias"
#: ../vnc.html:61
msgid "noVNC encountered an error:"
msgstr "O noVNC encontrou um erro:"
#: ../vnc.html:71
msgid "Hide/Show the control bar"
msgstr "Esconder/mostrar a barra de controles"
#: ../vnc.html:78
msgid "Drag"
msgstr "Arrastar"
#: ../vnc.html:78
msgid "Move/Drag Viewport"
msgstr "Mover/arrastar a janela"
#: ../vnc.html:84
msgid "Keyboard"
msgstr "Teclado"
#: ../vnc.html:84
msgid "Show Keyboard"
msgstr "Mostrar teclado"
#: ../vnc.html:89
msgid "Extra keys"
msgstr "Teclas adicionais"
#: ../vnc.html:89
msgid "Show Extra Keys"
msgstr "Mostar teclas adicionais"
#: ../vnc.html:94
msgid "Ctrl"
msgstr "Ctrl"
#: ../vnc.html:94
msgid "Toggle Ctrl"
msgstr "Pressionar/soltar Ctrl"
#: ../vnc.html:97
msgid "Alt"
msgstr "Alt"
#: ../vnc.html:97
msgid "Toggle Alt"
msgstr "Pressionar/soltar Alt"
#: ../vnc.html:100
msgid "Toggle Windows"
msgstr "Pressionar/soltar Windows"
#: ../vnc.html:100
msgid "Windows"
msgstr "Windows"
#: ../vnc.html:103
msgid "Send Tab"
msgstr "Enviar Tab"
#: ../vnc.html:103
msgid "Tab"
msgstr "Tab"
#: ../vnc.html:106
msgid "Esc"
msgstr "Esc"
#: ../vnc.html:106
msgid "Send Escape"
msgstr "Enviar Esc"
#: ../vnc.html:109
msgid "Ctrl+Alt+Del"
msgstr "Ctrl+Alt+Del"
#: ../vnc.html:109
msgid "Send Ctrl-Alt-Del"
msgstr "Enviar Ctrl-Alt-Del"
#: ../vnc.html:116
msgid "Shutdown/Reboot"
msgstr "Desligar/reiniciar"
#: ../vnc.html:116
msgid "Shutdown/Reboot..."
msgstr "Desligar/reiniciar..."
#: ../vnc.html:122
msgid "Power"
msgstr "Ligar"
#: ../vnc.html:124
msgid "Shutdown"
msgstr "Desligar"
#: ../vnc.html:125
msgid "Reboot"
msgstr "Reiniciar"
#: ../vnc.html:126
msgid "Reset"
msgstr "Reiniciar (forçado)"
#: ../vnc.html:131 ../vnc.html:137
msgid "Clipboard"
msgstr "Área de transferência"
#: ../vnc.html:141
msgid "Clear"
msgstr "Limpar"
#: ../vnc.html:147
msgid "Fullscreen"
msgstr "Tela cheia"
#: ../vnc.html:152 ../vnc.html:159
msgid "Settings"
msgstr "Configurações"
#: ../vnc.html:162
msgid "Shared Mode"
msgstr "Modo compartilhado"
#: ../vnc.html:165
msgid "View Only"
msgstr "Apenas visualizar"
#: ../vnc.html:169
msgid "Clip to Window"
msgstr "Recortar à janela"
#: ../vnc.html:172
msgid "Scaling Mode:"
msgstr "Modo de dimensionamento:"
#: ../vnc.html:174
msgid "None"
msgstr "Nenhum"
#: ../vnc.html:175
msgid "Local Scaling"
msgstr "Local"
#: ../vnc.html:176
msgid "Remote Resizing"
msgstr "Remoto"
#: ../vnc.html:181
msgid "Advanced"
msgstr "Avançado"
#: ../vnc.html:184
msgid "Quality:"
msgstr "Qualidade:"
#: ../vnc.html:188
msgid "Compression level:"
msgstr "Nível de compressão:"
#: ../vnc.html:193
msgid "Repeater ID:"
msgstr "ID do repetidor:"
#: ../vnc.html:197
msgid "WebSocket"
msgstr "WebSocket"
#: ../vnc.html:200
msgid "Encrypt"
msgstr "Criptografar"
#: ../vnc.html:203
msgid "Host:"
msgstr "Host:"
#: ../vnc.html:207
msgid "Port:"
msgstr "Porta:"
#: ../vnc.html:211
msgid "Path:"
msgstr "Caminho:"
#: ../vnc.html:218
msgid "Automatic Reconnect"
msgstr "Reconexão automática"
#: ../vnc.html:221
msgid "Reconnect Delay (ms):"
msgstr "Atraso da reconexão (ms)"
#: ../vnc.html:226
msgid "Show Dot when No Cursor"
msgstr "Mostrar ponto quando não há cursor"
#: ../vnc.html:231
msgid "Logging:"
msgstr "Registros:"
#: ../vnc.html:240
msgid "Version:"
msgstr "Versão:"
#: ../vnc.html:248
msgid "Disconnect"
msgstr "Desconectar"
#: ../vnc.html:267
msgid "Connect"
msgstr "Conectar"
#: ../vnc.html:277
msgid "Username:"
msgstr "Nome de usuário:"
#: ../vnc.html:281
msgid "Password:"
msgstr "Senha:"
#: ../vnc.html:285
msgid "Send Credentials"
msgstr "Enviar credenciais"
#: ../vnc.html:295
msgid "Cancel"
msgstr "Cancelar"
+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 "Выход"
+74 -74
View File
@@ -6,10 +6,10 @@
#
msgid ""
msgstr ""
"Project-Id-Version: noVNC 1.2.0\n"
"Project-Id-Version: noVNC 1.3.0\n"
"Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
"POT-Creation-Date: 2020-07-03 16:11+0200\n"
"PO-Revision-Date: 2020-07-08 23:18+0200\n"
"POT-Creation-Date: 2021-08-27 16:03+0200\n"
"PO-Revision-Date: 2021-08-27 16:18+0200\n"
"Last-Translator: Samuel Mannehed <samuel@cendio.se>\n"
"Language-Team: none\n"
"Language: sv\n"
@@ -19,282 +19,282 @@ msgstr ""
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 2.0.3\n"
#: ../app/ui.js:394
#: ../app/ui.js:400
msgid "Connecting..."
msgstr "Ansluter..."
#: ../app/ui.js:401
#: ../app/ui.js:407
msgid "Disconnecting..."
msgstr "Kopplar ner..."
#: ../app/ui.js:407
#: ../app/ui.js:413
msgid "Reconnecting..."
msgstr "Återansluter..."
#: ../app/ui.js:412
#: ../app/ui.js:418
msgid "Internal error"
msgstr "Internt fel"
#: ../app/ui.js:1008
#: ../app/ui.js:1009
msgid "Must set host"
msgstr "Du måste specifiera en värd"
#: ../app/ui.js:1090
#: ../app/ui.js:1091
msgid "Connected (encrypted) to "
msgstr "Ansluten (krypterat) till "
#: ../app/ui.js:1092
#: ../app/ui.js:1093
msgid "Connected (unencrypted) to "
msgstr "Ansluten (okrypterat) till "
#: ../app/ui.js:1115
#: ../app/ui.js:1116
msgid "Something went wrong, connection is closed"
msgstr "Något gick fel, anslutningen avslutades"
#: ../app/ui.js:1118
#: ../app/ui.js:1119
msgid "Failed to connect to server"
msgstr "Misslyckades att ansluta till servern"
#: ../app/ui.js:1128
#: ../app/ui.js:1129
msgid "Disconnected"
msgstr "Frånkopplad"
#: ../app/ui.js:1143
#: ../app/ui.js:1144
msgid "New connection has been rejected with reason: "
msgstr "Ny anslutning har blivit nekad med följande skäl: "
#: ../app/ui.js:1146
#: ../app/ui.js:1147
msgid "New connection has been rejected"
msgstr "Ny anslutning har blivit nekad"
#: ../app/ui.js:1181
#: ../app/ui.js:1182
msgid "Credentials are required"
msgstr "Användaruppgifter krävs"
#: ../vnc.html:74
#: ../vnc.html:61
msgid "noVNC encountered an error:"
msgstr "noVNC stötte på ett problem:"
#: ../vnc.html:84
#: ../vnc.html:71
msgid "Hide/Show the control bar"
msgstr "Göm/Visa kontrollbaren"
#: ../vnc.html:91
#: ../vnc.html:78
msgid "Drag"
msgstr "Dra"
#: ../vnc.html:91
#: ../vnc.html:78
msgid "Move/Drag Viewport"
msgstr "Flytta/Dra Vyn"
#: ../vnc.html:97
#: ../vnc.html:84
msgid "Keyboard"
msgstr "Tangentbord"
#: ../vnc.html:97
#: ../vnc.html:84
msgid "Show Keyboard"
msgstr "Visa Tangentbord"
#: ../vnc.html:102
#: ../vnc.html:89
msgid "Extra keys"
msgstr "Extraknappar"
#: ../vnc.html:102
#: ../vnc.html:89
msgid "Show Extra Keys"
msgstr "Visa Extraknappar"
#: ../vnc.html:107
#: ../vnc.html:94
msgid "Ctrl"
msgstr "Ctrl"
#: ../vnc.html:107
#: ../vnc.html:94
msgid "Toggle Ctrl"
msgstr "Växla Ctrl"
#: ../vnc.html:110
#: ../vnc.html:97
msgid "Alt"
msgstr "Alt"
#: ../vnc.html:110
#: ../vnc.html:97
msgid "Toggle Alt"
msgstr "Växla Alt"
#: ../vnc.html:113
#: ../vnc.html:100
msgid "Toggle Windows"
msgstr "Växla Windows"
#: ../vnc.html:113
#: ../vnc.html:100
msgid "Windows"
msgstr "Windows"
#: ../vnc.html:116
#: ../vnc.html:103
msgid "Send Tab"
msgstr "Skicka Tab"
#: ../vnc.html:116
#: ../vnc.html:103
msgid "Tab"
msgstr "Tab"
#: ../vnc.html:119
#: ../vnc.html:106
msgid "Esc"
msgstr "Esc"
#: ../vnc.html:119
#: ../vnc.html:106
msgid "Send Escape"
msgstr "Skicka Escape"
#: ../vnc.html:122
#: ../vnc.html:109
msgid "Ctrl+Alt+Del"
msgstr "Ctrl+Alt+Del"
#: ../vnc.html:122
#: ../vnc.html:109
msgid "Send Ctrl-Alt-Del"
msgstr "Skicka Ctrl-Alt-Del"
#: ../vnc.html:129
#: ../vnc.html:116
msgid "Shutdown/Reboot"
msgstr "Stäng av/Boota om"
#: ../vnc.html:129
#: ../vnc.html:116
msgid "Shutdown/Reboot..."
msgstr "Stäng av/Boota om..."
#: ../vnc.html:135
#: ../vnc.html:122
msgid "Power"
msgstr "Ström"
#: ../vnc.html:137
#: ../vnc.html:124
msgid "Shutdown"
msgstr "Stäng av"
#: ../vnc.html:138
#: ../vnc.html:125
msgid "Reboot"
msgstr "Boota om"
#: ../vnc.html:139
#: ../vnc.html:126
msgid "Reset"
msgstr "Återställ"
#: ../vnc.html:144 ../vnc.html:150
#: ../vnc.html:131 ../vnc.html:137
msgid "Clipboard"
msgstr "Urklipp"
#: ../vnc.html:154
#: ../vnc.html:141
msgid "Clear"
msgstr "Rensa"
#: ../vnc.html:160
#: ../vnc.html:147
msgid "Fullscreen"
msgstr "Fullskärm"
#: ../vnc.html:165 ../vnc.html:172
#: ../vnc.html:152 ../vnc.html:159
msgid "Settings"
msgstr "Inställningar"
#: ../vnc.html:175
#: ../vnc.html:162
msgid "Shared Mode"
msgstr "Delat Läge"
#: ../vnc.html:178
#: ../vnc.html:165
msgid "View Only"
msgstr "Endast Visning"
#: ../vnc.html:182
#: ../vnc.html:169
msgid "Clip to Window"
msgstr "Begränsa till Fönster"
#: ../vnc.html:185
#: ../vnc.html:172
msgid "Scaling Mode:"
msgstr "Skalningsläge:"
#: ../vnc.html:187
#: ../vnc.html:174
msgid "None"
msgstr "Ingen"
#: ../vnc.html:188
#: ../vnc.html:175
msgid "Local Scaling"
msgstr "Lokal Skalning"
#: ../vnc.html:189
#: ../vnc.html:176
msgid "Remote Resizing"
msgstr "Ändra Storlek"
#: ../vnc.html:194
#: ../vnc.html:181
msgid "Advanced"
msgstr "Avancerat"
#: ../vnc.html:197
#: ../vnc.html:184
msgid "Quality:"
msgstr "Kvalitet:"
#: ../vnc.html:201
#: ../vnc.html:188
msgid "Compression level:"
msgstr "Kompressionsnivå:"
#: ../vnc.html:206
#: ../vnc.html:193
msgid "Repeater ID:"
msgstr "Repeater-ID:"
#: ../vnc.html:210
#: ../vnc.html:197
msgid "WebSocket"
msgstr "WebSocket"
#: ../vnc.html:213
#: ../vnc.html:200
msgid "Encrypt"
msgstr "Kryptera"
#: ../vnc.html:216
#: ../vnc.html:203
msgid "Host:"
msgstr "Värd:"
#: ../vnc.html:220
#: ../vnc.html:207
msgid "Port:"
msgstr "Port:"
#: ../vnc.html:224
#: ../vnc.html:211
msgid "Path:"
msgstr "Sökväg:"
#: ../vnc.html:231
#: ../vnc.html:218
msgid "Automatic Reconnect"
msgstr "Automatisk Återanslutning"
#: ../vnc.html:234
#: ../vnc.html:221
msgid "Reconnect Delay (ms):"
msgstr "Fördröjning (ms):"
#: ../vnc.html:239
#: ../vnc.html:226
msgid "Show Dot when No Cursor"
msgstr "Visa prick när ingen muspekare finns"
#: ../vnc.html:244
#: ../vnc.html:231
msgid "Logging:"
msgstr "Loggning:"
#: ../vnc.html:253
#: ../vnc.html:240
msgid "Version:"
msgstr "Version:"
#: ../vnc.html:261
#: ../vnc.html:248
msgid "Disconnect"
msgstr "Koppla från"
#: ../vnc.html:280
#: ../vnc.html:267
msgid "Connect"
msgstr "Anslut"
#: ../vnc.html:290
#: ../vnc.html:277
msgid "Username:"
msgstr "Användarnamn:"
#: ../vnc.html:294
#: ../vnc.html:281
msgid "Password:"
msgstr "Lösenord:"
#: ../vnc.html:298
#: ../vnc.html:285
msgid "Send Credentials"
msgstr "Skicka Användaruppgifter"
#: ../vnc.html:308
#: ../vnc.html:295
msgid "Cancel"
msgstr "Avbryt"
msgstr "Avbryt"
+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));
}
}
}
+1 -1
View File
@@ -24,6 +24,6 @@ snapctl get services | jq -c '.[]' | while read service; do # for each service t
echo "novnc: not starting service ${service} with listen_port ${listen_port} and vnc_host_port ${vnc_host_port}"
else
# start (and fork with '&') the service using the specified listen port and VNC host:port
$SNAP/launch.sh --listen $listen_port --vnc $vnc_host_port &
$SNAP/novnc_proxy --listen $listen_port --vnc $vnc_host_port &
fi
done
+30 -5
View File
@@ -1,6 +1,6 @@
name: novnc
base: core18 # the base snap is the execution environment for this snap
version: '@VERSION@'
version: git
summary: Open Source VNC client using HTML5 (WebSockets, Canvas)
description: |
Open Source VNC client using HTML5 (WebSockets, Canvas).
@@ -13,13 +13,38 @@ confinement: strict
parts:
novnc:
source: build/
source: .
plugin: dump
organize:
utils/novnc_proxy: /
stage:
- vnc.html
- app
- core/**/*.js
- vendor/**/*.js
- novnc_proxy
novnc-deps:
plugin: nil
stage-packages:
- bash
svc-script:
source: snap/local
plugin: dump
stage:
- svc_wrapper.sh
svc-script-deps:
plugin: nil
stage-packages:
- websockify
- bash
- jq
- python-numpy
websockify:
source: https://github.com/novnc/websockify/archive/v0.9.0.tar.gz
plugin: python
stage-packages:
- python3-numpy
hooks:
@@ -28,7 +53,7 @@ hooks:
apps:
novnc:
command: ./launch.sh
command: ./novnc_proxy
plugs: [network, network-bind]
novncsvc:
command: ./svc_wrapper.sh
+7 -12
View File
@@ -1,16 +1,17 @@
// noVNC specific assertions
chai.use(function (_chai, utils) {
_chai.Assertion.addMethod('displayed', function (targetData) {
function _equal(a, b) {
return a === b;
}
_chai.Assertion.addMethod('displayed', function (targetData, cmp=_equal) {
const obj = this._obj;
const ctx = obj._target.getContext('2d');
const dataCl = ctx.getImageData(0, 0, obj._target.width, obj._target.height).data;
// NB(directxman12): PhantomJS 1.x doesn't implement Uint8ClampedArray, so work around that
const data = new Uint8Array(dataCl);
const len = dataCl.length;
const data = ctx.getImageData(0, 0, obj._target.width, obj._target.height).data;
const len = data.length;
new chai.Assertion(len).to.be.equal(targetData.length, "unexpected display size");
let same = true;
for (let i = 0; i < len; i++) {
if (data[i] != targetData[i]) {
if (!cmp(data[i], targetData[i])) {
same = false;
break;
}
@@ -28,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) {
+7 -15
View File
@@ -1,23 +1,15 @@
import Base64 from '../core/base64.js';
// PhantomJS can't create Event objects directly, so we need to use this
function makeEvent(name, props) {
const evt = document.createEvent('Event');
evt.initEvent(name, true, true);
if (props) {
for (let prop in props) {
evt[prop] = props[prop];
}
}
return evt;
}
export default class FakeWebSocket {
constructor(uri, protocols) {
this.url = uri;
this.binaryType = "arraybuffer";
this.extensions = "";
this.onerror = null;
this.onmessage = null;
this.onopen = null;
if (!protocols || typeof protocols === 'string') {
this.protocol = protocols;
} else {
@@ -35,7 +27,7 @@ export default class FakeWebSocket {
close(code, reason) {
this.readyState = FakeWebSocket.CLOSED;
if (this.onclose) {
this.onclose(makeEvent("close", { 'code': code, 'reason': reason, 'wasClean': true }));
this.onclose(new CloseEvent("close", { 'code': code, 'reason': reason, 'wasClean': true }));
}
}
@@ -58,7 +50,7 @@ export default class FakeWebSocket {
_open() {
this.readyState = FakeWebSocket.OPEN;
if (this.onopen) {
this.onopen(makeEvent('open'));
this.onopen(new Event('open'));
}
}
@@ -67,7 +59,7 @@ export default class FakeWebSocket {
// neatly packaged
for (let i = 0;i < data.length;i++) {
let buf = data.subarray(i, i+1);
this.onmessage(makeEvent("message", { 'data': buf }));
this.onmessage(new MessageEvent("message", { 'data': buf }));
}
}
}
-48
View File
@@ -1,48 +0,0 @@
const TEST_REGEXP = /test\..*\.js/;
const allTestFiles = [];
const extraFiles = ['/base/tests/assertions.js'];
Object.keys(window.__karma__.files).forEach(function (file) {
if (TEST_REGEXP.test(file)) {
// TODO: normalize?
allTestFiles.push(file);
}
});
// Stub out mocha's start function so we can run it once we're done loading
mocha.origRun = mocha.run;
mocha.run = function () {};
let script;
// Script to import all our tests
script = document.createElement("script");
script.type = "module";
script.text = "";
let allModules = allTestFiles.concat(extraFiles);
allModules.forEach(function (file) {
script.text += "import \"" + file + "\";\n";
});
script.text += "\nmocha.origRun();\n";
document.body.appendChild(script);
// Fallback code for browsers that don't support modules (IE)
script = document.createElement("script");
script.type = "module";
script.text = "window._noVNC_has_module_support = true;\n";
document.body.appendChild(script);
function fallback() {
if (!window._noVNC_has_module_support) {
/* eslint-disable no-console */
if (console) {
console.log("No module support detected. Loading fallback...");
}
/* eslint-enable no-console */
let loader = document.createElement("script");
loader.src = "base/vendor/browser-es-module-loader/dist/browser-es-module-loader.js";
document.body.appendChild(loader);
}
}
setTimeout(fallback, 500);
+11 -6
View File
@@ -1,4 +1,4 @@
/* global vncFrameData, vncFrameEncoding */
/* global VNC_frame_data, VNC_frame_encoding */
import * as WebUtil from '../app/webutil.js';
import RecordingPlayer from './playback.js';
@@ -41,7 +41,8 @@ function enableUI() {
document.getElementById('mode1').checked = true;
}
message("Loaded " + vncFrameData.length + " frames");
/* eslint-disable-next-line camelcase */
message("Loaded " + VNC_frame_data.length + " frames");
const startButton = document.getElementById('startButton');
startButton.disabled = false;
@@ -49,12 +50,16 @@ function enableUI() {
message("Converting...");
frames = vncFrameData;
/* eslint-disable-next-line camelcase */
frames = VNC_frame_data;
let encoding;
// Only present in older recordings
if (window.vncFrameEncoding) {
encoding = vncFrameEncoding;
/* eslint-disable camelcase */
if (window.VNC_frame_encoding) {
// Only present in older recordings
encoding = VNC_frame_encoding;
/* eslint-enable camelcase */
} else {
let frame = frames[0];
let start = frame.indexOf('{', 1) + 1;
+23 -17
View File
@@ -42,6 +42,24 @@ if (window.setImmediate === undefined) {
});
}
class FakeWebSocket {
constructor() {
this.binaryType = "arraybuffer";
this.protocol = "";
this.readyState = "open";
this.onerror = () => {};
this.onmessage = () => {};
this.onopen = () => {};
}
send() {
}
close() {
}
}
export default class RecordingPlayer {
constructor(frames, disconnected) {
this._frames = frames;
@@ -63,13 +81,13 @@ export default class RecordingPlayer {
run(realtime, trafficManagement) {
// initialize a new RFB
this._rfb = new RFB(document.getElementById('VNC_screen'), 'wss://test');
this._ws = new FakeWebSocket();
this._rfb = new RFB(document.getElementById('VNC_screen'), this._ws);
this._rfb.viewOnly = true;
this._rfb.addEventListener("disconnect",
this._handleDisconnect.bind(this));
this._rfb.addEventListener("credentialsrequired",
this._handleCredentials.bind(this));
this._enablePlaybackMode();
// reset the frame index and timer
this._frameIndex = 0;
@@ -79,19 +97,7 @@ export default class RecordingPlayer {
this._trafficManagement = (trafficManagement === undefined) ? !realtime : trafficManagement;
this._running = true;
}
// _enablePlaybackMode mocks out things not required for running playback
_enablePlaybackMode() {
const self = this;
this._rfb._sock.send = () => {};
this._rfb._sock.close = () => {};
this._rfb._sock.flush = () => {};
this._rfb._sock.open = function () {
this.init();
this._eventHandlers.open();
self._queueNextPacket();
};
this._queueNextPacket();
}
_queueNextPacket() {
@@ -136,7 +142,7 @@ export default class RecordingPlayer {
const frame = this._frames[this._frameIndex];
this._rfb._sock._recv_message({'data': frame.data});
this._ws.onmessage({'data': frame.data});
this._frameIndex++;
this._queueNextPacket();
@@ -153,7 +159,7 @@ export default class RecordingPlayer {
this._rfb._display.flush();
} else {
this._running = false;
this._rfb._sock._eventHandlers.close({code: 1000, reason: ""});
this._ws.onclose({code: 1000, reason: ""});
delete this._rfb;
this.onfinish((new Date()).getTime() - this._startTime);
}
+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;
});
});
+83
View File
@@ -0,0 +1,83 @@
const expect = chai.expect;
import Websock from '../core/websock.js';
import Display from '../core/display.js';
import CopyRectDecoder from '../core/decoders/copyrect.js';
import FakeWebSocket from './fake.websocket.js';
function testDecodeRect(decoder, x, y, width, height, data, display, depth) {
let sock;
sock = new Websock;
sock.open("ws://example.com");
sock.on('message', () => {
decoder.decodeRect(x, y, width, height, sock, display, depth);
});
// Empty messages are filtered at multiple layers, so we need to
// do a direct call
if (data.length === 0) {
decoder.decodeRect(x, y, width, height, sock, display, depth);
} else {
sock._websocket._receiveData(new Uint8Array(data));
}
display.flip();
}
describe('CopyRect Decoder', function () {
let decoder;
let display;
before(FakeWebSocket.replace);
after(FakeWebSocket.restore);
beforeEach(function () {
decoder = new CopyRectDecoder();
display = new Display(document.createElement('canvas'));
display.resize(4, 4);
});
it('should handle the CopyRect encoding', function () {
// seed some initial data to copy
display.fillRect(0, 0, 4, 4, [ 0x11, 0x22, 0x33 ]);
display.fillRect(0, 0, 2, 2, [ 0x00, 0x00, 0xff ]);
display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);
testDecodeRect(decoder, 0, 2, 2, 2,
[0x00, 0x02, 0x00, 0x00],
display, 24);
testDecodeRect(decoder, 2, 2, 2, 2,
[0x00, 0x00, 0x00, 0x00],
display, 24);
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]);
expect(display).to.have.displayed(targetData);
});
it('should handle empty rects', function () {
display.fillRect(0, 0, 4, 4, [ 0x00, 0x00, 0xff ]);
display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);
display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]);
testDecodeRect(decoder, 1, 2, 0, 0, [0x00, 0x00, 0x00, 0x00], display, 24);
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]);
expect(display).to.have.displayed(targetData);
});
});
+18 -101
View File
@@ -4,28 +4,27 @@ import Base64 from '../core/base64.js';
import Display from '../core/display.js';
describe('Display/Canvas Helper', function () {
const checkedData = new Uint8Array([
const checkedData = new Uint8ClampedArray([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]);
const basicData = new Uint8Array([0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0xff, 0xff, 0xff, 255]);
const basicData = new Uint8ClampedArray([0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0xff, 0xff, 0xff, 255]);
function makeImageCanvas(inputData) {
function makeImageCanvas(inputData, width, height) {
const canvas = document.createElement('canvas');
canvas.width = 4;
canvas.height = 4;
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
const data = ctx.createImageData(4, 4);
for (let i = 0; i < checkedData.length; i++) { data.data[i] = inputData[i]; }
const data = new ImageData(inputData, width, height);
ctx.putImageData(data, 0, 0);
return canvas;
}
function makeImagePng(inputData) {
const canvas = makeImageCanvas(inputData);
function makeImagePng(inputData, width, height) {
const canvas = makeImageCanvas(inputData, width, height);
const url = canvas.toDataURL();
const data = url.split(",")[1];
return Base64.decode(data);
@@ -44,7 +43,7 @@ describe('Display/Canvas Helper', function () {
it('should take viewport location into consideration when drawing images', function () {
display.resize(4, 4);
display.viewportChangeSize(2, 2);
display.drawImage(makeImageCanvas(basicData), 1, 1);
display.drawImage(makeImageCanvas(basicData, 4, 1), 1, 1);
display.flip();
const expected = new Uint8Array(16);
@@ -128,7 +127,7 @@ describe('Display/Canvas Helper', function () {
});
it('should keep the framebuffer data', function () {
display.fillRect(0, 0, 4, 4, [0, 0, 0xff]);
display.fillRect(0, 0, 4, 4, [0xff, 0, 0]);
display.resize(2, 2);
display.flip();
const expected = [];
@@ -271,7 +270,7 @@ describe('Display/Canvas Helper', function () {
});
it('should not draw directly on the target canvas', function () {
display.fillRect(0, 0, 4, 4, [0, 0, 0xff]);
display.fillRect(0, 0, 4, 4, [0xff, 0, 0]);
display.flip();
display.fillRect(0, 0, 4, 4, [0, 0xff, 0]);
const expected = [];
@@ -285,22 +284,22 @@ describe('Display/Canvas Helper', function () {
it('should support filling a rectangle with particular color via #fillRect', function () {
display.fillRect(0, 0, 4, 4, [0, 0xff, 0]);
display.fillRect(0, 0, 2, 2, [0xff, 0, 0]);
display.fillRect(2, 2, 2, 2, [0xff, 0, 0]);
display.fillRect(0, 0, 2, 2, [0, 0, 0xff]);
display.fillRect(2, 2, 2, 2, [0, 0, 0xff]);
display.flip();
expect(display).to.have.displayed(checkedData);
});
it('should support copying an portion of the canvas via #copyImage', function () {
display.fillRect(0, 0, 4, 4, [0, 0xff, 0]);
display.fillRect(0, 0, 2, 2, [0xff, 0, 0x00]);
display.fillRect(0, 0, 2, 2, [0, 0, 0xff]);
display.copyImage(0, 0, 2, 2, 2, 2);
display.flip();
expect(display).to.have.displayed(checkedData);
});
it('should support drawing images via #imageRect', function (done) {
display.imageRect(0, 0, 4, 4, "image/png", makeImagePng(checkedData));
display.imageRect(0, 0, 4, 4, "image/png", makeImagePng(checkedData, 4, 4));
display.flip();
display.onflush = () => {
expect(display).to.have.displayed(checkedData);
@@ -309,68 +308,14 @@ describe('Display/Canvas Helper', function () {
display.flush();
});
it('should support drawing tile data with a background color and sub tiles', function () {
display.startTile(0, 0, 4, 4, [0, 0xff, 0]);
display.subTile(0, 0, 2, 2, [0xff, 0, 0]);
display.subTile(2, 2, 2, 2, [0xff, 0, 0]);
display.finishTile();
display.flip();
expect(display).to.have.displayed(checkedData);
});
// We have a special cache for 16x16 tiles that we need to test
it('should support drawing a 16x16 tile', function () {
const largeCheckedData = new Uint8Array(16*16*4);
display.resize(16, 16);
for (let y = 0;y < 16;y++) {
for (let x = 0;x < 16;x++) {
let pixel;
if ((x < 4) && (y < 4)) {
// NB: of course IE11 doesn't support #slice on ArrayBufferViews...
pixel = Array.prototype.slice.call(checkedData, (y*4+x)*4, (y*4+x+1)*4);
} else {
pixel = [0, 0xff, 0, 255];
}
largeCheckedData.set(pixel, (y*16+x)*4);
}
}
display.startTile(0, 0, 16, 16, [0, 0xff, 0]);
display.subTile(0, 0, 2, 2, [0xff, 0, 0]);
display.subTile(2, 2, 2, 2, [0xff, 0, 0]);
display.finishTile();
display.flip();
expect(display).to.have.displayed(largeCheckedData);
});
it('should support drawing BGRX blit images with true color via #blitImage', function () {
const data = [];
for (let i = 0; i < 16; i++) {
data[i * 4] = checkedData[i * 4 + 2];
data[i * 4 + 1] = checkedData[i * 4 + 1];
data[i * 4 + 2] = checkedData[i * 4];
data[i * 4 + 3] = checkedData[i * 4 + 3];
}
display.blitImage(0, 0, 4, 4, data, 0);
display.flip();
expect(display).to.have.displayed(checkedData);
});
it('should support drawing RGB blit images with true color via #blitRgbImage', function () {
const data = [];
for (let i = 0; i < 16; i++) {
data[i * 3] = checkedData[i * 4];
data[i * 3 + 1] = checkedData[i * 4 + 1];
data[i * 3 + 2] = checkedData[i * 4 + 2];
}
display.blitRgbImage(0, 0, 4, 4, data, 0);
it('should support blit images with true color via #blitImage', function () {
display.blitImage(0, 0, 4, 4, checkedData, 0);
display.flip();
expect(display).to.have.displayed(checkedData);
});
it('should support drawing an image object via #drawImage', function () {
const img = makeImageCanvas(checkedData);
const img = makeImageCanvas(checkedData, 4, 4);
display.drawImage(img, 0, 0);
display.flip();
expect(display).to.have.displayed(checkedData);
@@ -415,27 +360,6 @@ describe('Display/Canvas Helper', function () {
expect(img.addEventListener).to.have.been.calledOnce;
});
it('should wait if an image is incorrectly loaded', function () {
const img = { complete: true, width: 0, height: 0, addEventListener: sinon.spy() };
display._renderQ = [{ type: 'img', x: 3, y: 4, width: 4, height: 4, img: img },
{ type: 'fill', x: 1, y: 2, width: 3, height: 4, color: 5 }];
display.drawImage = sinon.spy();
display.fillRect = sinon.spy();
display._scanRenderQ();
expect(display.drawImage).to.not.have.been.called;
expect(display.fillRect).to.not.have.been.called;
expect(img.addEventListener).to.have.been.calledOnce;
display._renderQ[0].img.complete = true;
display._renderQ[0].img.width = 4;
display._renderQ[0].img.height = 4;
display._scanRenderQ();
expect(display.drawImage).to.have.been.calledOnce;
expect(display.fillRect).to.have.been.calledOnce;
expect(img.addEventListener).to.have.been.calledOnce;
});
it('should call callback when queue is flushed', function () {
display.onflush = sinon.spy();
display.fillRect(0, 0, 4, 4, [0, 0xff, 0]);
@@ -451,13 +375,6 @@ describe('Display/Canvas Helper', function () {
expect(display.blitImage).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9], 0);
});
it('should draw a blit RGB image on type "blitRgb"', function () {
display.blitRgbImage = sinon.spy();
display._renderQPush({ type: 'blitRgb', x: 3, y: 4, width: 5, height: 6, data: [7, 8, 9] });
expect(display.blitRgbImage).to.have.been.calledOnce;
expect(display.blitRgbImage).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9], 0);
});
it('should copy a region on type "copy"', function () {
display.copyImage = sinon.spy();
display._renderQPush({ type: 'copy', x: 3, y: 4, width: 5, height: 6, oldX: 7, oldY: 8 });
-7
View File
@@ -3,7 +3,6 @@ const expect = chai.expect;
import EventTargetMixin from '../core/util/eventtarget.js';
import GestureHandler from '../core/input/gesturehandler.js';
import * as browser from '../core/util/browser.js';
class DummyTarget extends EventTargetMixin {
}
@@ -23,12 +22,6 @@ describe('Gesture handler', function () {
});
beforeEach(function () {
// Touch events and gestures are not supported on IE
if (browser.isIE()) {
this.skip();
return;
}
target = new DummyTarget();
gestures = sinon.spy();
target.addEventListener('gesturestart', gestures);
+26 -75
View File
@@ -2,7 +2,6 @@
import keysyms from '../core/input/keysymdef.js';
import * as KeyboardUtil from "../core/input/util.js";
import * as browser from '../core/util/browser.js';
describe('Helpers', function () {
"use strict";
@@ -70,25 +69,12 @@ describe('Helpers', function () {
// environments, so we need to redefine it whilst running these
// tests.
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
if (origNavigator === undefined) {
// Object.getOwnPropertyDescriptor() doesn't work
// properly in any version of IE
this.skip();
}
Object.defineProperty(window, "navigator", {value: {}});
if (window.navigator.platform !== undefined) {
// 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 () {
@@ -102,14 +88,10 @@ describe('Helpers', function () {
describe('getKey', function () {
it('should prefer key', function () {
if (browser.isIE() || browser.isEdge()) this.skip();
expect(KeyboardUtil.getKey({key: 'a', charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43})).to.be.equal('a');
});
it('should map legacy values', function () {
expect(KeyboardUtil.getKey({key: 'Spacebar'})).to.be.equal(' ');
expect(KeyboardUtil.getKey({key: 'Left'})).to.be.equal('ArrowLeft');
expect(KeyboardUtil.getKey({key: 'OS'})).to.be.equal('Meta');
expect(KeyboardUtil.getKey({key: 'Win'})).to.be.equal('Meta');
expect(KeyboardUtil.getKey({key: 'UIKeyInputLeftArrow'})).to.be.equal('ArrowLeft');
});
it('should handle broken Delete', function () {
@@ -130,60 +112,6 @@ describe('Helpers', function () {
it('should return Unidentified when it cannot map the key', function () {
expect(KeyboardUtil.getKey({keycode: 0x42})).to.be.equal('Unidentified');
});
describe('Broken key AltGraph on IE/Edge', function () {
let origNavigator;
beforeEach(function () {
// window.navigator is a protected read-only property in many
// environments, so we need to redefine it whilst running these
// tests.
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
if (origNavigator === undefined) {
// Object.getOwnPropertyDescriptor() doesn't work
// properly in any version of IE
this.skip();
}
Object.defineProperty(window, "navigator", {value: {}});
if (window.navigator.platform !== undefined) {
// Object.defineProperty() doesn't work properly in old
// versions of Chrome
this.skip();
}
});
afterEach(function () {
if (origNavigator !== undefined) {
Object.defineProperty(window, "navigator", origNavigator);
}
});
it('should ignore printable character key on IE', function () {
window.navigator.userAgent = "Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko";
expect(KeyboardUtil.getKey({key: 'a'})).to.be.equal('Unidentified');
});
it('should ignore printable character key on Edge', function () {
window.navigator.userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36 Edge/14.14393";
expect(KeyboardUtil.getKey({key: 'a'})).to.be.equal('Unidentified');
});
it('should allow non-printable character key on IE', function () {
window.navigator.userAgent = "Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko";
expect(KeyboardUtil.getKey({key: 'Shift'})).to.be.equal('Shift');
});
it('should allow non-printable character key on Edge', function () {
window.navigator.userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36 Edge/14.14393";
expect(KeyboardUtil.getKey({key: 'Shift'})).to.be.equal('Shift');
});
it('should allow printable character key with charCode on IE', function () {
window.navigator.userAgent = "Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko";
expect(KeyboardUtil.getKey({key: 'a', charCode: 0x61})).to.be.equal('a');
expect(KeyboardUtil.getKey({key: 'Unidentified', charCode: 0x61})).to.be.equal('a');
});
it('should allow printable character key with charCode on Edge', function () {
window.navigator.userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36 Edge/14.14393";
expect(KeyboardUtil.getKey({key: 'a', charCode: 0x61})).to.be.equal('a');
expect(KeyboardUtil.getKey({key: 'Unidentified', charCode: 0x61})).to.be.equal('a');
});
});
});
describe('getKeysym', function () {
@@ -236,7 +164,6 @@ describe('Helpers', function () {
describe('Numpad', function () {
it('should handle Numpad numbers', function () {
if (browser.isIE() || browser.isEdge()) this.skip();
expect(KeyboardUtil.getKeysym({code: 'Digit5', key: '5', location: 0})).to.be.equal(0x0035);
expect(KeyboardUtil.getKeysym({code: 'Numpad5', key: '5', location: 3})).to.be.equal(0xFFB5);
});
@@ -247,10 +174,34 @@ describe('Helpers', function () {
expect(KeyboardUtil.getKeysym({code: 'NumpadDecimal', key: 'Delete', location: 3})).to.be.equal(0xFF9F);
});
it('should handle Numpad Decimal key', function () {
if (browser.isIE() || browser.isEdge()) this.skip();
expect(KeyboardUtil.getKeysym({code: 'NumpadDecimal', key: '.', location: 3})).to.be.equal(0xFFAE);
expect(KeyboardUtil.getKeysym({code: 'NumpadDecimal', key: ',', location: 3})).to.be.equal(0xFFAC);
});
});
describe('Japanese IM keys on Windows', function () {
let origNavigator;
beforeEach(function () {
// window.navigator is a protected read-only property in many
// environments, so we need to redefine it whilst running these
// tests.
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
Object.defineProperty(window, "navigator", {value: {}});
window.navigator.platform = "Windows";
});
afterEach(function () {
Object.defineProperty(window, "navigator", origNavigator);
});
const keys = { 'Zenkaku': 0xff2a, 'Hankaku': 0xff2a,
'Romaji': 0xff24, 'KanaMode': 0xff24 };
for (let [key, keysym] of Object.entries(keys)) {
it(`should fake combined key for ${key} on Windows`, function () {
expect(KeyboardUtil.getKeysym({code: 'FakeIM', key: key})).to.be.equal(keysym);
});
}
});
});
});
+232
View File
@@ -0,0 +1,232 @@
const expect = chai.expect;
import Websock from '../core/websock.js';
import Display from '../core/display.js';
import HextileDecoder from '../core/decoders/hextile.js';
import FakeWebSocket from './fake.websocket.js';
function testDecodeRect(decoder, x, y, width, height, data, display, depth) {
let sock;
sock = new Websock;
sock.open("ws://example.com");
sock.on('message', () => {
decoder.decodeRect(x, y, width, height, sock, display, depth);
});
// Empty messages are filtered at multiple layers, so we need to
// do a direct call
if (data.length === 0) {
decoder.decodeRect(x, y, width, height, sock, display, depth);
} else {
sock._websocket._receiveData(new Uint8Array(data));
}
display.flip();
}
function push32(arr, num) {
arr.push((num >> 24) & 0xFF,
(num >> 16) & 0xFF,
(num >> 8) & 0xFF,
num & 0xFF);
}
describe('Hextile Decoder', function () {
let decoder;
let display;
before(FakeWebSocket.replace);
after(FakeWebSocket.restore);
beforeEach(function () {
decoder = new HextileDecoder();
display = new Display(document.createElement('canvas'));
display.resize(4, 4);
});
it('should handle a tile with fg, bg specified, normal subrects', function () {
let data = [];
data.push(0x02 | 0x04 | 0x08); // bg spec, fg spec, anysubrects
push32(data, 0x00ff0000); // becomes 00ff0000 --> #00FF00 bg color
data.push(0x00); // becomes 0000ff00 --> #0000FF fg color
data.push(0x00);
data.push(0xff);
data.push(0x00);
data.push(2); // 2 subrects
data.push(0); // x: 0, y: 0
data.push(1 | (1 << 4)); // width: 2, height: 2
data.push(2 | (2 << 4)); // x: 2, y: 2
data.push(1 | (1 << 4)); // width: 2, height: 2
testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]);
expect(display).to.have.displayed(targetData);
});
it('should handle a raw tile', function () {
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]);
let data = [];
data.push(0x01); // raw
for (let i = 0; i < targetData.length; i += 4) {
data.push(targetData[i]);
data.push(targetData[i + 1]);
data.push(targetData[i + 2]);
// Last byte zero to test correct alpha handling
data.push(0);
}
testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
expect(display).to.have.displayed(targetData);
});
it('should handle a tile with only bg specified (solid bg)', function () {
let data = [];
data.push(0x02);
push32(data, 0x00ff0000); // becomes 00ff0000 --> #00FF00 bg color
testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
let expected = [];
for (let i = 0; i < 16; i++) {
push32(expected, 0x00ff00ff);
}
expect(display).to.have.displayed(new Uint8Array(expected));
});
it('should handle a tile with only bg specified and an empty frame afterwards', function () {
// set the width so we can have two tiles
display.resize(8, 4);
let data = [];
// send a bg frame
data.push(0x02);
push32(data, 0x00ff0000); // becomes 00ff0000 --> #00FF00 bg color
// send an empty frame
data.push(0x00);
testDecodeRect(decoder, 0, 0, 32, 4, data, display, 24);
let expected = [];
for (let i = 0; i < 16; i++) {
push32(expected, 0x00ff00ff); // rect 1: solid
}
for (let i = 0; i < 16; i++) {
push32(expected, 0x00ff00ff); // rect 2: same bkground color
}
expect(display).to.have.displayed(new Uint8Array(expected));
});
it('should handle a tile with bg and coloured subrects', function () {
let data = [];
data.push(0x02 | 0x08 | 0x10); // bg spec, anysubrects, colouredsubrects
push32(data, 0x00ff0000); // becomes 00ff0000 --> #00FF00 bg color
data.push(2); // 2 subrects
data.push(0x00); // becomes 0000ff00 --> #0000FF fg color
data.push(0x00);
data.push(0xff);
data.push(0x00);
data.push(0); // x: 0, y: 0
data.push(1 | (1 << 4)); // width: 2, height: 2
data.push(0x00); // becomes 0000ff00 --> #0000FF fg color
data.push(0x00);
data.push(0xff);
data.push(0x00);
data.push(2 | (2 << 4)); // x: 2, y: 2
data.push(1 | (1 << 4)); // width: 2, height: 2
testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]);
expect(display).to.have.displayed(targetData);
});
it('should carry over fg and bg colors from the previous tile if not specified', function () {
display.resize(4, 17);
let data = [];
data.push(0x02 | 0x04 | 0x08); // bg spec, fg spec, anysubrects
push32(data, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
data.push(0x00); // becomes 0000ffff --> #0000FF fg color
data.push(0x00);
data.push(0xff);
data.push(0xff);
data.push(8); // 8 subrects
for (let i = 0; i < 4; i++) {
data.push((0 << 4) | (i * 4)); // x: 0, y: i*4
data.push(1 | (1 << 4)); // width: 2, height: 2
data.push((2 << 4) | (i * 4 + 2)); // x: 2, y: i * 4 + 2
data.push(1 | (1 << 4)); // width: 2, height: 2
}
data.push(0x08); // anysubrects
data.push(1); // 1 subrect
data.push(0); // x: 0, y: 0
data.push(1 | (1 << 4)); // width: 2, height: 2
testDecodeRect(decoder, 0, 0, 4, 17, data, display, 24);
let targetData = [
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
];
let expected = [];
for (let i = 0; i < 4; i++) {
expected = expected.concat(targetData);
}
expected = expected.concat(targetData.slice(0, 16));
expect(display).to.have.displayed(new Uint8Array(expected));
});
it('should fail on an invalid subencoding', function () {
let data = [45]; // an invalid subencoding
expect(() => testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24)).to.throw();
});
it('should handle empty rects', function () {
display.fillRect(0, 0, 4, 4, [ 0x00, 0x00, 0xff ]);
display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);
display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]);
testDecodeRect(decoder, 1, 2, 0, 0, [], display, 24);
let targetData = new Uint8Array([
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
]);
expect(display).to.have.displayed(targetData);
});
});
+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();
});
});
+288
View File
@@ -0,0 +1,288 @@
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;
sock = new Websock;
sock.open("ws://example.com");
sock.on('message', () => {
decoder.decodeRect(x, y, width, height, sock, display, depth);
});
// Empty messages are filtered at multiple layers, so we need to
// do a direct call
if (data.length === 0) {
decoder.decodeRect(x, y, width, height, sock, display, depth);
} else {
sock._websocket._receiveData(new Uint8Array(data));
}
display.flip();
}
describe('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', function (done) {
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,
];
testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
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;
}
display.onflush = () => {
expect(display).to.have.displayed(targetData, almost);
done();
};
display.flush();
});
it('should handle JPEG rects without Huffman and quantification tables', function (done) {
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,
];
testDecodeRect(decoder, 0, 0, 4, 4, data1, display, 24);
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,
];
testDecodeRect(decoder, 0, 0, 4, 4, data2, display, 24);
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;
}
display.onflush = () => {
expect(display).to.have.displayed(targetData, almost);
done();
};
display.flush();
});
});

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