Compare commits

...

226 Commits

Author SHA1 Message Date
Alexander Zeijlon
a8dfd6a3ea noVNC 1.6.0
Some checks failed
Publish / npm (push) Has been cancelled
Publish / snap (push) Has been cancelled
Lint / eslint (push) Has been cancelled
Lint / html (push) Has been cancelled
Test / test (ChromeHeadless, ubuntu-latest) (push) Has been cancelled
Test / test (ChromeHeadless, windows-latest) (push) Has been cancelled
Test / test (EdgeHeadless, windows-latest) (push) Has been cancelled
Test / test (FirefoxHeadless, ubuntu-latest) (push) Has been cancelled
Test / test (FirefoxHeadless, windows-latest) (push) Has been cancelled
Test / test (Safari, macos-latest) (push) Has been cancelled
Translate / translate (push) Has been cancelled
2025-03-12 20:25:30 +01:00
Alexander Zeijlon
3947b4ca84 Update generated json files for new translations 2025-03-12 20:16:15 +01:00
Harold Horsman
09440da5c1 Update Dutch translation 2025-03-12 17:35:59 +01:00
Martine & Philippe
6002d57a88 Update French translation 2025-03-12 17:35:54 +01:00
Alexander Zeijlon
59b674d374 Merge branch 'fix-sub-url-query' 2025-03-12 14:54:07 +01:00
Alexander Zeijlon
045a0ba158 Let browser handle parsing of URLs before relaying
We don't want to assign a path directly to url.pathname that contains a
search query, since this causes '?' at the beginning of the query to be
URL-encoded to '%3F'. Instead use URL() to parse the path for us.
2025-03-12 14:30:29 +01:00
Alexander Zeijlon
b25675e052 Upgrade to websockify 0.13.0 in snap package 2025-02-28 14:42:52 +01:00
Alexander Zeijlon
b45f35c6d7 noVNC 1.6.0 beta 2025-02-14 11:23:23 +01:00
Alexander Zeijlon
c3b8cbd3d2 Update README.md with H.264 encoding support 2025-02-14 11:23:23 +01:00
Alexander Zeijlon
09fc4f7fb9 Update Swedish translations 2025-02-14 10:42:16 +01:00
Alexander Zeijlon
e8030a9fb1 Update translation template file 2025-02-14 10:16:08 +01:00
Zeijlon (ThinLinc Team)
d5315ebb8b
Merge pull request #1934 from tianzedavid/master
chore: fix some typos
2025-02-14 10:05:47 +01:00
tianzedavid
7ee7922766 chore: fix some typos
Signed-off-by: tianzedavid <cuitianze@aliyun.com>
2025-02-13 01:00:46 +08:00
Pierre Ossman
bb797dcb2a Merge branch 'resize' of https://github.com/CendioOssman/noVNC 2025-02-06 14:26:25 +01:00
Pierre Ossman
0b5e968e14 Better resize rate limiting
Be more aggressive with resizing, limiting it to once ever 100 ms
instead of after a 500 ms idle period. This gives a more responsive user
experience.
2025-02-05 16:59:22 +01:00
Pierre Ossman
c82178348a Include SetDesktopSize responses in tests
There might be subtle changes in behaviour, so we should mimic what a
real server does.
2025-02-05 16:53:12 +01:00
Pierre Ossman
70446bf742 Make resizeSession setting test more realistic
We shouldn't expect a resize request to be sent if the container didn't
change size first.
2025-02-05 16:53:12 +01:00
Pierre Ossman
bbbef2d9fa Add helper for ExtendedDesktopSize in tests 2025-02-05 16:53:12 +01:00
Samuel Mannehed (ThinLinc team)
4e410a0619
Use less technical phrasing in README 2025-02-04 16:14:31 +01:00
Alexander Zeijlon
b9f172dcdb Update README.md with ExtendedMouseButtons feature 2025-01-28 11:01:53 +01:00
Alexander Zeijlon
9f727b7db8 Merge branch 'ui_refresh' 2025-01-28 09:42:00 +01:00
Samuel Mannehed
24835bdda4 Make the background of expanded settings lighter
A very slight change to the background color, to make the contrast
better with the light-grey input elements.
2025-01-23 15:51:11 +01:00
Samuel Mannehed
237a34dfb3 Add exceptions for CSS validator false positives
Some new CSS incorrectly give errors from validator.w3.org. Issues were
opened in that repo, so hopefully we can remove these exceptions soon.

I searched for alternative validators, but couldn't find a different one
that had a simple API like this one.

In order to reliably detect & handle these exceptions we unfortunately
need to make the validator output parsing quite a bit more complicated.
2025-01-23 15:51:11 +01:00
Samuel Mannehed
14f9ea5880 Fix settings panel layout on small screens
Both labels and inputs protruded outside the panel on for example a
phone in portrait mode. This commit fixes that by allowing wrapping and
setting a max-width.

Since the --input-xpadding variable is now used in two different CSS
files, it was moved to constants.css.
2025-01-23 15:51:11 +01:00
Samuel Mannehed
6db9dbcf90 Tweak design of noVNC connect button
Make the color contrast with the background and the button more rounded.
The goal is to make the button stand out.
2025-01-23 15:51:11 +01:00
Samuel Mannehed
b5675bb5f6 Fix spacing between elements in dialogs
Dialogs have had text inputs and buttons cramped together without space
between, this fixes that.
2025-01-23 15:51:11 +01:00
Samuel Mannehed
4ab4286b25 Make text in panel headings bold
Makes the heading pop a bit more in the new airier layout.
2025-01-23 15:51:11 +01:00
Samuel Mannehed
88009230b6 Only color the left part of the range track
Makes it easier to envision the value is selected.
2025-01-23 15:51:11 +01:00
Samuel Mannehed
abe3c7bce9 Make range slider thumbs circular
Fits better with the new slightly rounded and spacious style. The track
was made slightly thicker to ensure proper centering of the new thumb.
2025-01-23 15:40:52 +01:00
Samuel Mannehed
20611b677f Add styling for multi-select lists
This is a type of select box that doesn't appear like a button, but more
like a textarea that lists options. It is not currently used, but added
for completeness.
2025-01-23 15:40:52 +01:00
Samuel Mannehed
3a5dd22603 Add styling for checked options in select boxes 2025-01-23 15:40:52 +01:00
Samuel Mannehed
6c1e7bc507 Utilize toggle switch in settings
These settings are well suited to use toggle switches. This makes these
settings more approachable and user-friendly.
2025-01-23 15:40:52 +01:00
Samuel Mannehed
24f99e548d Add styling for toggle switches
These are a type of checkbox that is suitable for ON/OFF-type switches.
2025-01-23 15:40:52 +01:00
Samuel Mannehed
331ad34d90 Make interface airier by increasing line-height
Modern interfaces are less cramped, this makes noVNC feel more up to
date.

Note that this required some adjustments on noVNC_headings and
noVNC_connect_button since the text now takes up more height than the
images.
2025-01-23 15:40:52 +01:00
Samuel Mannehed
2bc505741f Add styling for color pickers
Note that no color picker elements are currently in use, this is for
completeness.
2025-01-23 15:40:52 +01:00
Samuel Mannehed
54e76817df Pointer cursor on buttons & grab on sliders
This makes buttons and slider stand out more.
2025-01-23 15:40:52 +01:00
Samuel Mannehed
7b58cb96bc Add minimum width to buttons
This ensures they aren't too small, even if the text label is short.
2025-01-23 15:40:52 +01:00
Samuel Mannehed
a2352b99c1 Standardize on ellipsis-type text-overflow 2025-01-23 15:40:52 +01:00
Samuel Mannehed
28e1717cf9 Remove number picker's increase/decrease buttons
We can't style them, and they don't fit the noVNC CSS style - we are
better off without them.
2025-01-23 15:40:52 +01:00
Samuel Mannehed
deb76f97cd Add some sane default CSS for textareas 2025-01-23 15:40:52 +01:00
Samuel Mannehed
f0ec3d62b5 Avoid 2 borders when focus-visible on text inputs
By having the focus-visible outline overlapping the regular border
things look a bit more sane on text input elements.
2025-01-23 15:40:52 +01:00
Samuel Mannehed
91cd920266 Keep CSS for different input elements together
By moving the CSS for select buttons to the bottom, we keep a more
logical ordering of the elements.
2025-01-23 15:40:52 +01:00
Samuel Mannehed
1c45fd8547 Capitalize CSS section headings
Makes them easier to distinguish from regular comments.
2025-01-23 15:40:52 +01:00
Samuel Mannehed
d8199859d3 Support 'disabled' attribute on labels
Note that the :disabled selector only works on inputs, buttons and the
like.

The current method of applying .noVNC_disabled to the settings
labels is still used. This support is added mostly for completeness.

Note that when a label wraps an input, only the label should have the
disabled attribute. Otherwise the effect applies twice to the input.
2025-01-23 15:40:52 +01:00
Samuel Mannehed
e092f06d01 Move general :disabled rules to common top section
Start with general stuff, followed by specific things.
2025-01-23 15:40:52 +01:00
Samuel Mannehed
ee08032fe7 Put specific :disabled rules with its element
It makes more sense to group rules per element type.
2025-01-23 15:40:52 +01:00
Samuel Mannehed
30d46a00fa Fix :disabled styling of file-selector-button
By applying the rule to the button within the input, we effectively
applied the opacity twice - making the button almost disappear. Applying
the opacity to the input element is enough.
2025-01-23 15:40:52 +01:00
Samuel Mannehed
33a2548fcb Make buttons flat by removing borders
Gives a more clean look that fits well with the new checkboxes and
radios. The old border was mostly used to contribute to a 3d-effect,
that was used for :active. That :active-styling has been replaced by
activation levels.
2025-01-23 15:40:40 +01:00
Samuel Mannehed
017888c9a8 Rework how buttons react to :hover and :active
Instead of having two different types of effects (hover had a different
color, and active had a 3d-effect simulating a pressed button), we now
have an increasing activation-level. That means the button goes a bit
dark for hover, and then even darker when pressed.

There is also a variant that goes lighter for each activation level,
that can be used when the initial color is dark.

With this change, we can get rid of special :hover and :active styling
for the connect button and the control bar buttons. We can use the same
activation level principle for all buttons.
2025-01-23 15:39:19 +01:00
Samuel Mannehed
7fdcc66d2c Add indeterminate styling to checkboxes and radios
This is used when the control is neither checked or unchecked.
2025-01-23 15:39:19 +01:00
Samuel Mannehed
633b4c266d Redesign checkboxes and radiobuttons
Makes them bigger and gets rid of their borders. The change also allowed
for some shared styling between them.
2025-01-23 15:39:19 +01:00
Samuel Mannehed
e1208b0939 Redesign select dropdown arrow
Makes it more of a "V"-shape rather than a triangle, suits better in the
new spacier select-buttons.
2025-01-23 15:39:19 +01:00
Samuel Mannehed
3f29c9d993 Differentiate buttons from text inputs
By making buttons grey with bold text, they are easy to distinguish from
text inputs.
2025-01-23 15:39:19 +01:00
Samuel Mannehed
e9b48ae409 Get rid of gradients on buttons and inputs
Lets make things more flat and modern.
2025-01-23 15:39:19 +01:00
Samuel Mannehed
ca270efcc3 Standardize on 6px or 12px border-radius
This results in a few things becoming slighly more rounded, for example
the controlbar, the settings panel and buttons/inputs. Increased
rounding gives a more friendly feel.
2025-01-23 15:38:57 +01:00
Alexander Zeijlon
4f284c2f15 Remove bold styling tags in vnc.html
We aren't emphasizing important information with bold tags anywhere
else, so we shouldn't do it here either.
2025-01-17 11:29:07 +01:00
Pierre Ossman
83a5e9e9db Also test Ctrl+AltGr, as that is what browsers use
Modern browsers now send the odd sequence of Ctrl+AltGr, rather than the
raw Ctrl+Alt, or the fully adjusted just AltGr.

Make sure we have a test for this scenario and don't break it.
2025-01-15 12:43:44 +01:00
Pierre Ossman
c7bd247daa Merge branch 'extra-mouse-buttons' of https://github.com/CendioHalim/noVNC 2025-01-15 10:10:15 +01:00
Adam Halim
e081d1415a Add support for forward and back mouse buttons
This commit implements the extendedMouseButtons pseudo-encoding, which
makes it possible to use the forward and back mouse buttons.
2025-01-15 09:11:48 +01:00
Adam Halim
e8602f23ab Move sendFbuMsg() to broader scope
This is needed if we want to use this function elsewhere in our tests.
2025-01-14 16:27:14 +01:00
Pierre Ossman
9cdbd28761 Merge branch 'mouse-buttonstate-remove' of https://github.com/CendioHalim/noVNC 2025-01-14 12:56:19 +01:00
Adam Halim
6383fa6384 Flush mouseMove when initiating viewport dragging
We want to flush pending mouse moves before we initiate viewport
dragging.

Before this commit, there were scenarios where the _mouseButtonMask
would track a released button as being down.
2025-01-14 12:32:51 +01:00
Adam Halim
d1548c12ec Don't send mouse events when dragging viewport
We don't want to send any mouse events to the server when dragging the
viewport. Instead, we treat them as a client-only operation.
2025-01-14 12:14:59 +01:00
Adam Halim
b9230cf23e Use MouseEvent.buttons for button state tracking
Instead of keeping track of button states ourselves by looking at
MouseEvent.button, we can use the MouseEvent.buttons which already
contains the state of all buttons.
2025-01-14 12:14:59 +01:00
Adam Halim
f9eb476f6d Add tests for dragging with gestures
There were no test for viewport dragging using gesture previously, so
let's add some.

Note that there currently are some viewport dragging behaviours that we
don't want to have, so some tests have commented out what our desired
behaviour should be.
2025-01-14 12:14:59 +01:00
Adam Halim
ea057d0793 Move gesture event help functions to broader scope
This is needed if we want to test gestures with dragging.
2025-01-14 12:14:59 +01:00
Adam Halim
31d6a77af6 Check for correct button events in dragging tests
Previously, these unit tests did not check which events were sent to the
server, only how many events were sent. This commit adds checks to see
that the expected button events are sent.
2025-01-14 12:14:59 +01:00
Adam Halim
dce8ab395b Dispatch mouse events in dragging unit tests
This makes our tests reflect the real world better, as we now send real
mouse events instead of calling private methods directly.
2025-01-14 12:14:59 +01:00
Adam Halim
c3934e0938 Move mouse move flushing to separate function 2025-01-14 12:14:34 +01:00
Adam Halim
db22ec6ee6 Split button click with dragging test 2025-01-14 10:38:39 +01:00
Adam Halim
de9d6888db Add unit test for wheel + buttons pressed 2025-01-14 10:38:39 +01:00
Adam Halim
7a4d1a8274 Move mouse event help functions to broader scope
These functions can be used elsewhere in the tests. We want to use these
in the dragging tests in the future instead of directly calling private
methods.
2025-01-14 10:38:39 +01:00
Samuel Mannehed
7603ced54e Create CSS variables for common noVNC-colors 2025-01-13 08:32:19 +01:00
Samuel Mannehed
72cac2ef6a Add margin between label and input in noVNC_panel
To make stuff feel less cramped, lets add some margin here.

As of comitting this, it only affects the logging-level select dropdown
in the settings, but this is a general rule of thumb. It doesn't apply
to checkboxes or radios since they have a margin by default, and their
label to the left.
2025-01-11 23:20:33 +01:00
Samuel Mannehed
bf245da7b7 Increase padding of buttons and inputs
Gives them a more modern and spacious look.
2025-01-11 23:02:37 +01:00
Samuel Mannehed
4bbed1dc12 Standardize on 4 space-indentation in CSS files
This is what we use in every other file in noVNC. It also much more
common for a CSS file in general. By standardizing on 4 spaces we can
avoid indentation mistakes.
2025-01-11 17:51:02 +01:00
Samuel Mannehed
3193f808b5 Comment different resize functions in rfb.js 2024-12-27 14:48:20 +01:00
Samuel Mannehed
e6e03a226f Fix resizes back to initial remote session size
Since the expected client size wasn't updated when the browser window
resized, noVNC didn't resize the canvas properly when going back to
the exact same dimensions.

Fixes issue #1903
2024-12-27 14:48:11 +01:00
Pierre Ossman
673cb349fd Replace po2json with pofile
The former doesn't seem to be properly maintained and nodejs gives
deprecation warnings.
2024-12-17 17:15:14 +01:00
Pierre Ossman
3e2e04bea1 Replace node-getopt with commander for args
node-getopt isn't maintained and nodejs has started complaining about
deprecated features in it.
2024-12-17 16:44:21 +01:00
NNN1590
52392ec150
Update Japanese translation 2024-12-14 15:59:01 +09:00
Pierre Ossman
52ddb20d25 Merge branch 'master' of https://github.com/wxtewx/noVNC 2024-11-27 16:12:05 +01:00
Pierre Ossman
7335bb440d Also adjust to "sentence case" in translations
This would have resolved itself automatically on the next translation
update, but this commit will reduce unnecessary noise in that change.
2024-11-27 16:04:18 +01:00
Pierre Ossman
7f5b51acf3 Consistently use "sentence case" style
Try to be more consistent in how we capitalize things. Both the "Title
Case" and "Sentence case" styles are popular, so either would work.
Google and Mozilla both prefer "Sentence case", so let's follow them.
2024-11-27 14:40:40 +01:00
wxtewx
90a6c7bbb6
Update zh_CN.po 2024-11-23 15:36:12 +08:00
Pierre Ossman
2463ccd08f Detect broken Firefox H.264 decoder
The Firefox H.264 decoder on Windows might simply just refuse to deliver
any finished frames. It also doesn't deliver any errors.

Detect this early by expecting a frame after flush() has completed.
2024-11-21 13:33:52 +01:00
Pierre Ossman
a89dfd6141 Handle exceptions from VideoDecoder.flush()
These are not supposed to happen according to the specification, but
Firefox has some bug and throws them anyway.
2024-11-21 13:33:32 +01:00
Pierre Ossman
3677afe305 Do a real H.264 test decode to determine support
Firefox is buggy and reports support for H.264 but then throws errors
once we actually try to decode things. Detect this early by doing a
quick test decode of a single frame.
2024-11-21 13:33:31 +01:00
Pierre Ossman
69750c74a6 Raise JavaScript version requirement
So that we can use await at module top level.
2024-11-21 13:33:10 +01:00
Pierre Ossman
89e0591aab Use common H.264 check in tests
Avoid duplicating this logic in multiple places.
2024-11-21 13:19:44 +01:00
Pierre Ossman
43326eb67b Fix handling of VideoDecoder.isConfigSupported()
It returns an object with details, not just a simple boolean.
2024-11-20 10:46:52 +01:00
dim5x
88a5a59629 Fix typos in Russian translation 2024-11-02 04:38:57 +03:00
Pierre Ossman
ed1fef4fc3 Merge branch 'ui_init' of github.com:CendioOssman/noVNC 2024-10-09 13:12:33 +02:00
Pierre Ossman
28d4020302 Load settings from web server
Make it even easier to customize things by loading the settings from
separate configuration files.
2024-10-03 16:08:53 +02:00
Pierre Ossman
438e5b3608 Make it easier for downstream to modify settings
Expose a simple and stable API to override default settings, and force
settings that users shouldn't be able to change.
2024-10-03 16:08:53 +02:00
Pierre Ossman
047531e886 Merge branch 'webcodec-h264' of https://github.com/any1/noVNC 2024-08-29 16:59:25 +02:00
Pierre Ossman
50e4685bff Fix tests for large WebSocket sends
These failed to test that the data was correctly split as they only
checked the first chunk transmitted.

Use random values to avoid the risk of aligning our test data with the
split boundaries and hence allowing false positives.
2024-08-29 16:51:51 +02:00
Pierre Ossman
ffb4c0bf56 Let fake WebSocket handle large sends
Dynamically grow the recorded send buffer if the test needs to send a
lot of data.
2024-08-29 16:51:16 +02:00
Tomasz Kalisiak
a4465516df
Fix sQpushBytes sending the beginning of the array multiple times 2024-08-23 13:14:36 +02:00
Andri Yngvason
c1bba972f4 Add unit tests for H.264 decoder 2024-08-19 22:08:32 +00:00
Pierre Ossman
bbb6a5b938 Fix host and port via query string
We need to call initSetting() even if we don't have any interesting
default to set, as that is what checks if values have been provided as a
query string.

Fixes 96c76f7.
2024-08-19 14:01:00 +02:00
Andri Yngvason
d106b7a6bb Add H.264 decoder
This adds an H.264 decoder based on WebCodecs.
2024-08-18 14:06:25 +00:00
Mark Peek
c6c8e5e513 Add Zlib encoding 2024-08-16 09:12:43 -07:00
Pierre Ossman
84897fd110 Handle disabling settings without label 2024-08-08 15:59:59 +02:00
Pierre Ossman
c6606a5caf Merge UI startup in to a single routine
Makes it easier to see how things are connected.
2024-08-08 15:59:58 +02:00
Pierre Ossman
9334c68241 Handle all settings via UI.getSetting()
Makes sure everything behaves the same way, even if there is no visible
UI for a settings.
2024-08-08 15:00:48 +02:00
Pierre Ossman
96c76f7709 Allow relative WebSocket URLs
This can be very useful if you have multiple instances of noVNC, and you
want to redirect them to different VNC servers.

The new default settings will have the same behaviour as before for
systems where noVNC is deployed in the root web folder.
2024-08-08 14:53:42 +02:00
Pierre Ossman
074fa1a40f Let browser construct URL string for us
Likely a lot safer for corner cases than us trying to figure this out
ourselves.
2024-08-08 14:40:04 +02:00
Pierre Ossman
06f14a5cd3 Add test for AltGr abort on blur 2024-08-05 16:31:59 +02:00
Pierre Ossman
a020ef0f44 Merge branch 'altgr-seq-interrupt' of https://github.com/leedagee/noVNC 2024-08-05 16:30:47 +02:00
Pierre Ossman
1b2fe3321b Manually load sinon and chai
karma-sinon-chai is not compatible with Chai 5+, and Karma is no longer
being updated.

Load sinon and chai manually instead, until we can have a long term plan
in place.
2024-08-05 15:49:39 +02:00
Pierre Ossman
bc31e4e8a2 Stop creating sinon sandbox early
sinon might not be loaded at this point, which can cause tests to fail.

We could create the sandbox in one of the hooks instead, but let's
remove the sandbox completely to stay consistent with our other tests.
2024-08-05 15:45:41 +02:00
leedagee
6c07136169 Interrupt AltGr sequence detection on focus lost, fixes #1880 2024-08-01 01:31:47 +08:00
Samuel Mannehed
1230a4ce73 Use theme-color to color address bar in browsers
This makes the address bar on mobile browsers match the background. Note
that it requires a valid certificate and a non-dark mode set on the
device. Not supported on desktop browsers.
2024-07-23 00:19:26 +02:00
Pierre Ossman
7fcf9dcfe0 noVNC 1.5.0 2024-06-18 14:05:35 +02:00
Pierre Ossman
aaadec4f13 Update json files for new translations 2024-06-18 14:03:30 +02:00
Pierre Ossman
7f364a173d Update Swedish translation 2024-06-18 14:02:34 +02:00
Pierre Ossman
1a62eb7d3e Don't include missing translation in .js
It just adds size and confusion. Instead, omit any lines where no
translation is available.
2024-06-18 14:01:40 +02:00
Pierre Ossman
fb1817c99f Remove Chrome timeout workaround
This is a revert of fca48df85d. The issue
seems to be fixed in the current version of Chrome, so let's keep things
simple again.
2024-06-13 09:01:07 +02:00
Pierre Ossman
aead0b2f89 noVNC 1.5.0 beta 2024-06-03 14:46:13 +02:00
Pierre Ossman
68e09ee8b3 Upgrade to websockify 0.12.0 in snap package 2024-06-03 14:45:39 +02:00
Pierre Ossman
f28e9daec3 Update translation template file 2024-06-03 14:10:47 +02:00
Pierre Ossman
fc11b9d2b0 Remove Twitter links
These are not updated anymore as they are not under the control of the
current team.
2024-06-03 14:09:00 +02:00
Pierre Ossman
d80e3bfa2f Add unit tests for Tight gradient filter 2024-05-16 16:53:49 +02:00
Jiang XueQian
c187b2e5e0 Implement gradient filter of tight decoder, fixing issue #1767
This commit is a basic implementation of the gradient filter required by
qemu `lossy` option.
2024-05-02 20:41:38 +08:00
Samuel Mannehed
10ee10ce56 Cleanup "no-console" eslint rules
Removes unexpected exceptions and clarifies where we want to avoid
console calls.
2024-04-30 15:26:50 +02:00
Samuel Mannehed
8d1b665808 Migrate deprecated eslint config to to new format
The .eslintrc and .eslintignore formats are deprecated. The new format
uses a single eslint.config.js (or .mjs) file at the top.
2024-04-30 15:26:50 +02:00
Samuel Mannehed (ThinLinc team)
c998c723ad
Merge pull request #1853 from kosmasgiannis/gr20240424
Updated greek translations
2024-04-26 09:41:25 +02:00
Giannis Kosmas
9d293f1aba Updated el.po 2024-04-24 19:53:49 +03:00
Bubble
92c8a91964
Update zh_CN.po (#1851)
Update Chinese translation
2024-04-24 16:54:24 +02:00
Kostiantyn Syrykh
9a1b1f0d06 Clipboard: handle multiple CR+LF 2024-03-25 17:35:28 +02:00
Samuel Mannehed (ThinLinc team)
786aba602f
Merge pull request #1834 from sbungartz/avoid-exception-when-disconnecting-after-dom-morph
Avoid exception when cursor was removed from DOM already
2024-02-22 16:26:51 +01:00
Pierre Ossman
65e9ecd5af Merge branch 'actions' of github.com:CendioOssman/noVNC 2024-02-05 16:58:00 +01:00
Pierre Ossman
cd927723bc Fix import of "commander"
The default import was deprecated ages ago, and in v12 it has now
finally been changed in a breaking way.

Change the code to import things the proper way.
2024-02-05 16:53:20 +01:00
Pierre Ossman
60643fe695 Update github actions to latest versions
Primarily to avoid the versions that are now deprecated, but also update
actions/upload-artifact to keep us up to date.
2024-02-05 16:34:47 +01:00
Samuel Mannehed
e75938bebc Make non-HTTPS message more harsh
As browsers are placing more and more new functionality as
secure-context only, we need to prepare users for more problems. I find
it likely that we will disable non-HTTPS connections in the future.
2024-02-02 16:51:21 +01:00
Simon Bungartz
bd32922ff8 Avoid exception when cursor was removed from DOM already 2024-01-31 16:16:01 +00:00
Pierre Ossman
48c8e41877 Fix key event debug output
Fix for a0b7c0dac5.
2024-01-23 12:54:18 +01:00
Pierre Ossman
ab2fd41693 Handle broken Oculus browser keyboard events
It sets KeyboardEvent.key to "Unidentified" for all non-character keys,
which means we must ignore it and use the legacy handling to figure out
the key pressed.
2024-01-23 12:51:35 +01:00
Pierre Ossman
fca48df85d Increase test timeout for Chrome on Windows
There is some bug in Chrome 119+ on some systems, where it takes
forever for the first readback from a canvas, timing out the first
test that does this.

Work around the issue by increasing the timeout on that platform until
Chrome manages to resolve the issue.
2024-01-19 16:19:41 +01:00
Pierre Ossman
b35cf6dd12 Don't include ES6 module versions in npm package
The npm package is supposed to be for CommonJS usage, so only package
that to avoid confusion. This has become an issue now that nodejs
supports ES6 modules, where users are accidentally trying to import the
wrong files and get errors.
2024-01-17 16:19:16 +01:00
Pierre Ossman
d3aaf4d5b3 Upgrade base snap to Ubuntu 22.04
Ubuntu 18.04 base snap is no longer supported, so switch to the
currently newest one.
2024-01-10 14:44:44 +01:00
Pierre Ossman
796e924e47 Remove unused npm dependencies
These should have been removed as part of 890cff9.
2024-01-10 14:41:52 +01:00
Pierre Ossman
829725b30e Handle relative paths in novnc_proxy
websockify changes the working directory before it starts looking for
files, so we must give it relative paths for things to work reliably.
2023-12-05 11:33:15 +01:00
Pierre Ossman
9ac632deee Handle immediate connection errors
The browser might throw an exception right away if there is something it
doesn't like with our connect attempt. E.g. using a non-TLS WebSocket
from a TLS web page.
2023-12-05 11:30:30 +01:00
Pierre Ossman
7d2dad0f9e Merge branch 'listen-host' of https://github.com/afbjorklund/noVNC 2023-10-27 12:57:09 +02:00
Anders F Björklund
5ebc297164 Remove comment about websockify command arguments 2023-10-14 09:55:25 +02:00
Anders F Björklund
a792b7f39e Document default port applies to all interfaces 2023-10-14 09:55:25 +02:00
Anders F Björklund
f1174023c1 Add the possibility to listen on a specific host
For instance, for listening only on "localhost"

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

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

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

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

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

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

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

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

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

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

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

By instead using a unitless number, we get the behavior we want. Note
that this bug has no effects right now since no children to any of the
related elements have different font-sizes.
2023-03-23 11:06:03 +01:00
Samuel Mannehed
4558104196 Properly center the checkbox checkmark
Using a flexbox we can easily center the checkmark without using hard
coded positions.
2023-03-23 11:06:03 +01:00
NNN1590
6751cc1236
Update Japanese translation 2023-03-21 13:15:26 +09:00
Pierre Ossman
9985950bfa Upgrade to latest websockify in snap package 2023-01-26 10:45:26 +01:00
Pierre Ossman
a0e6e7b1d8 Merge branch 'crypto-cleanup-fallback' of https://github.com/pdlan/noVNC 2023-01-20 16:52:32 +01:00
pdlan
f974b73137 Cleanup for the cryptographic algorithms that are not supported by SubtleCrypto 2023-01-20 05:54:00 -05:00
140 changed files with 8306 additions and 4775 deletions

View File

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

View File

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

View File

@ -7,7 +7,7 @@ about: Create a report to help us improve
**Describe the bug** **Describe the bug**
A clear and concise description of what the bug is. A clear and concise description of what the bug is.
**To Reproduce** **To reproduce**
Steps to reproduce the behavior: Steps to reproduce the behavior:
1. Go to '...' 1. Go to '...'
2. Click on '....' 2. Click on '....'

View File

@ -10,18 +10,18 @@ jobs:
npm: npm:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- run: | - run: |
GITREV=$(git rev-parse --short HEAD) GITREV=$(git rev-parse --short HEAD)
echo $GITREV echo $GITREV
sed -i "s/^\(.*\"version\".*\)\"\([^\"]\+\)\"\(.*\)\$/\1\"\2-g$GITREV\"\3/" package.json sed -i "s/^\(.*\"version\".*\)\"\([^\"]\+\)\"\(.*\)\$/\1\"\2-g$GITREV\"\3/" package.json
if: github.event_name != 'release' if: github.event_name != 'release'
- uses: actions/setup-node@v3 - uses: actions/setup-node@v4
with: with:
# Needs to be explicitly specified for auth to work # Needs to be explicitly specified for auth to work
registry-url: 'https://registry.npmjs.org' registry-url: 'https://registry.npmjs.org'
- run: npm install - run: npm install
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v4
with: with:
name: npm name: npm
path: lib path: lib
@ -49,7 +49,7 @@ jobs:
snap: snap:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- run: | - run: |
GITREV=$(git rev-parse --short HEAD) GITREV=$(git rev-parse --short HEAD)
echo $GITREV echo $GITREV
@ -61,7 +61,7 @@ jobs:
sed -i "s/^version:.*/version: '$VERSION'/" snap/snapcraft.yaml sed -i "s/^version:.*/version: '$VERSION'/" snap/snapcraft.yaml
- uses: snapcore/action-build@v1 - uses: snapcore/action-build@v1
id: snapcraft id: snapcraft
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v4
with: with:
name: snap name: snap
path: ${{ steps.snapcraft.outputs.snap }} path: ${{ steps.snapcraft.outputs.snap }}

View File

@ -6,14 +6,14 @@ jobs:
eslint: eslint:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: actions/setup-node@v3 - uses: actions/setup-node@v4
- run: npm update - run: npm update
- run: npm run lint - run: npm run lint
html: html:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: actions/setup-node@v3 - uses: actions/setup-node@v4
- run: npm update - run: npm update
- run: git ls-tree --name-only -r HEAD | grep -E "[.](html|css)$" | xargs ./utils/validate - run: git ls-tree --name-only -r HEAD | grep -E "[.](html|css)$" | xargs ./utils/validate

View File

@ -20,8 +20,8 @@ jobs:
fail-fast: false fail-fast: false
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: actions/setup-node@v3 - uses: actions/setup-node@v4
- run: npm update - run: npm update
- run: npm run test - run: npm run test
env: env:

View File

@ -6,8 +6,8 @@ jobs:
translate: translate:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: actions/setup-node@v3 - uses: actions/setup-node@v4
- run: npm update - run: npm update
- run: sudo apt-get install gettext - run: sudo apt-get install gettext
- run: make -C po update-pot - run: make -C po update-pot

View File

@ -1,4 +1,4 @@
noVNC is Copyright (C) 2022 The noVNC Authors noVNC is Copyright (C) 2022 The noVNC authors
(./AUTHORS) (./AUTHORS)
The noVNC core library files are licensed under the MPL 2.0 (Mozilla The noVNC core library files are licensed under the MPL 2.0 (Mozilla

View File

@ -1,4 +1,4 @@
## noVNC: HTML VNC Client Library and Application ## noVNC: HTML VNC client library and application
[![Test Status](https://github.com/novnc/noVNC/workflows/Test/badge.svg)](https://github.com/novnc/noVNC/actions?query=workflow%3ATest) [![Test Status](https://github.com/novnc/noVNC/workflows/Test/badge.svg)](https://github.com/novnc/noVNC/actions?query=workflow%3ATest)
[![Lint Status](https://github.com/novnc/noVNC/workflows/Lint/badge.svg)](https://github.com/novnc/noVNC/actions?query=workflow%3ALint) [![Lint Status](https://github.com/novnc/noVNC/workflows/Lint/badge.svg)](https://github.com/novnc/noVNC/actions?query=workflow%3ALint)
@ -14,26 +14,24 @@ Many companies, projects and products have integrated noVNC including
[OpenNebula](http://opennebula.org/), [OpenNebula](http://opennebula.org/),
[LibVNCServer](http://libvncserver.sourceforge.net), and [LibVNCServer](http://libvncserver.sourceforge.net), and
[ThinLinc](https://cendio.com/thinlinc). See [ThinLinc](https://cendio.com/thinlinc). See
[the Projects and Companies wiki page](https://github.com/novnc/noVNC/wiki/Projects-and-companies-using-noVNC) [the Projects and companies wiki page](https://github.com/novnc/noVNC/wiki/Projects-and-companies-using-noVNC)
for a more complete list with additional info and links. for a more complete list with additional info and links.
### Table of Contents ### Table of contents
- [News/help/contact](#newshelpcontact) - [News/help/contact](#newshelpcontact)
- [Features](#features) - [Features](#features)
- [Screenshots](#screenshots) - [Screenshots](#screenshots)
- [Browser Requirements](#browser-requirements) - [Browser requirements](#browser-requirements)
- [Server Requirements](#server-requirements) - [Server requirements](#server-requirements)
- [Quick Start](#quick-start) - [Quick start](#quick-start)
- [Installation from Snap Package](#installation-from-snap-package) - [Installation from snap package](#installation-from-snap-package)
- [Integration and Deployment](#integration-and-deployment) - [Integration and deployment](#integration-and-deployment)
- [Authors/Contributors](#authorscontributors) - [Authors/Contributors](#authorscontributors)
### News/help/contact ### News/help/contact
The project website is found at [novnc.com](http://novnc.com). The project website is found at [novnc.com](http://novnc.com).
Notable commits, announcements and news are posted to
[@noVNC](http://www.twitter.com/noVNC).
If you are a noVNC developer/integrator/user (or want to be) please join the If you are a noVNC developer/integrator/user (or want to be) please join the
[noVNC discussion group](https://groups.google.com/forum/?fromgroups#!forum/novnc). [noVNC discussion group](https://groups.google.com/forum/?fromgroups#!forum/novnc).
@ -59,7 +57,6 @@ profits such as:
[Electronic Frontier Foundation](https://www.eff.org/), [Electronic Frontier Foundation](https://www.eff.org/),
[Against Malaria Foundation](http://www.againstmalaria.com/), [Against Malaria Foundation](http://www.againstmalaria.com/),
[Nothing But Nets](http://www.nothingbutnets.net/), etc. [Nothing But Nets](http://www.nothingbutnets.net/), etc.
Please tweet [@noVNC](http://www.twitter.com/noVNC) if you do.
### Features ### Features
@ -69,8 +66,9 @@ Please tweet [@noVNC](http://www.twitter.com/noVNC) if you do.
RSA-AES, Tight, VeNCrypt Plain, XVP, Apple's Diffie-Hellman, RSA-AES, Tight, VeNCrypt Plain, XVP, Apple's Diffie-Hellman,
UltraVNC's MSLogonII UltraVNC's MSLogonII
* Supported VNC encodings: raw, copyrect, rre, hextile, tight, tightPNG, * Supported VNC encodings: raw, copyrect, rre, hextile, tight, tightPNG,
ZRLE, JPEG ZRLE, JPEG, Zlib, H.264
* Supports scaling, clipping and resizing the desktop * Supports scaling, clipping and resizing the desktop
* Supports back & forward mouse buttons
* Local cursor rendering * Local cursor rendering
* Clipboard copy/paste with full Unicode support * Clipboard copy/paste with full Unicode support
* Translations * Translations
@ -89,16 +87,16 @@ See more screenshots
[here](http://novnc.com/screenshots.html). [here](http://novnc.com/screenshots.html).
### Browser Requirements ### Browser requirements
noVNC uses many modern web technologies so a formal requirement list is noVNC uses many modern web technologies so a formal requirement list is
not available. However these are the minimum versions we are currently not available. However these are the minimum versions we are currently
aware of: aware of:
* Chrome 64, Firefox 79, Safari 13.4, Opera 51, Edge 79 * Chrome 89, Firefox 89, Safari 15, Opera 75, Edge 89
### Server Requirements ### Server requirements
noVNC follows the standard VNC protocol, but unlike other VNC clients it does noVNC follows the standard VNC protocol, but unlike other VNC clients it does
require WebSockets support. Many servers include support (e.g. require WebSockets support. Many servers include support (e.g.
@ -110,7 +108,7 @@ use a WebSockets to TCP socket proxy. noVNC has a sister project
proxy. proxy.
### Quick Start ### Quick start
* Use the `novnc_proxy` 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 includes a mini-webserver and the WebSockets proxy. The `--vnc` option is
@ -127,25 +125,25 @@ proxy.
script. Hit the Connect button, enter a password if the VNC server has one script. Hit the Connect button, enter a password if the VNC server has one
configured, and enjoy! configured, and enjoy!
### Installation from Snap Package ### Installation from snap package
Running the command below will install the latest release of noVNC from Snap: Running the command below will install the latest release of noVNC from snap:
`sudo snap install novnc` `sudo snap install novnc`
#### Running noVNC from Snap Directly #### Running noVNC from snap directly
You can run the Snap-package installed novnc directly with, for example: 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` `novnc --listen 6081 --vnc localhost:5901 # /snap/bin/novnc if /snap/bin is not in your PATH`
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: 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` `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) #### Running noVNC from snap as a service (daemon)
The Snap package also has the capability to run a 'novnc' service which can be 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 configured to listen on multiple ports connecting to multiple VNC servers
(effectively a service runing multiple instances of novnc). (effectively a service running multiple instances of novnc).
Instructions (with example values): Instructions (with example values):
List current services (out-of-box this will be blank): List current services (out-of-box this will be blank):
@ -175,7 +173,7 @@ services.n6082.listen 6082
services.n6082.vnc localhost:5902 services.n6082.vnc localhost:5902
``` ```
Disable a service (note that because of a limitation in Snap it's currently not Disable a service (note that because of a limitation in snap it's currently not
possible to unset config variables, setting them to blank values is the way possible to unset config variables, setting them to blank values is the way
to disable a service): to disable a service):
@ -192,7 +190,7 @@ services.n6082.listen
services.n6082.vnc services.n6082.vnc
``` ```
### Integration and Deployment ### Integration and deployment
Please see our other documents for how to integrate noVNC in your own software, Please see our other documents for how to integrate noVNC in your own software,
or deploying the noVNC application in production environments: or deploying the noVNC application in production environments:
@ -215,8 +213,8 @@ that list and you think you should be, feel free to send a PR to fix that.
* [Solly Ross](https://github.com/DirectXMan12) (Red Hat / OpenStack) * [Solly Ross](https://github.com/DirectXMan12) (Red Hat / OpenStack)
* Notable contributions: * Notable contributions:
* UI and Icons : Pierre Ossman, Chris Gordon * UI and icons : Pierre Ossman, Chris Gordon
* Original Logo : Michael Sersen * Original logo : Michael Sersen
* tight encoding : Michael Tinglof (Mercuri.ca) * tight encoding : Michael Tinglof (Mercuri.ca)
* RealVNC RSA AES authentication : USTC Vlab Team * RealVNC RSA AES authentication : USTC Vlab Team

View File

@ -1,6 +1,6 @@
/* /*
* noVNC: HTML5 VNC client * noVNC: HTML5 VNC client
* Copyright (C) 2019 The noVNC Authors * Copyright (C) 2019 The noVNC authors
* Licensed under MPL 2.0 (see LICENSE.txt) * Licensed under MPL 2.0 (see LICENSE.txt)
* *
* See README.md for usage and integration instructions. * See README.md for usage and integration instructions.

View File

@ -14,7 +14,7 @@
"Password is required": "Je vyžadováno heslo", "Password is required": "Je vyžadováno heslo",
"noVNC encountered an error:": "noVNC narazilo na chybu:", "noVNC encountered an error:": "noVNC narazilo na chybu:",
"Hide/Show the control bar": "Skrýt/zobrazit ovládací panel", "Hide/Show the control bar": "Skrýt/zobrazit ovládací panel",
"Move/Drag Viewport": "Přesunout/přetáhnout výřez", "Move/Drag viewport": "Přesunout/přetáhnout výřez",
"viewport drag": "přesun výřezu", "viewport drag": "přesun výřezu",
"Active Mouse Button": "Aktivní tlačítka myši", "Active Mouse Button": "Aktivní tlačítka myši",
"No mousebutton": "Žádné", "No mousebutton": "Žádné",
@ -22,9 +22,9 @@
"Middle mousebutton": "Prostřední tlačítko myši", "Middle mousebutton": "Prostřední tlačítko myši",
"Right mousebutton": "Pravé tlačítko myši", "Right mousebutton": "Pravé tlačítko myši",
"Keyboard": "Klávesnice", "Keyboard": "Klávesnice",
"Show Keyboard": "Zobrazit klávesnici", "Show keyboard": "Zobrazit klávesnici",
"Extra keys": "Extra klávesy", "Extra keys": "Extra klávesy",
"Show Extra Keys": "Zobrazit extra klávesy", "Show extra keys": "Zobrazit extra klávesy",
"Ctrl": "Ctrl", "Ctrl": "Ctrl",
"Toggle Ctrl": "Přepnout Ctrl", "Toggle Ctrl": "Přepnout Ctrl",
"Alt": "Alt", "Alt": "Alt",
@ -45,13 +45,13 @@
"Clear": "Vymazat", "Clear": "Vymazat",
"Fullscreen": "Celá obrazovka", "Fullscreen": "Celá obrazovka",
"Settings": "Nastavení", "Settings": "Nastavení",
"Shared Mode": "Sdílený režim", "Shared mode": "Sdílený režim",
"View Only": "Pouze prohlížení", "View only": "Pouze prohlížení",
"Clip to Window": "Přizpůsobit oknu", "Clip to window": "Přizpůsobit oknu",
"Scaling Mode:": "Přizpůsobení velikosti", "Scaling mode:": "Přizpůsobení velikosti",
"None": "Žádné", "None": "Žádné",
"Local Scaling": "Místní", "Local scaling": "Místní",
"Remote Resizing": "Vzdálené", "Remote resizing": "Vzdálené",
"Advanced": "Pokročilé", "Advanced": "Pokročilé",
"Repeater ID:": "ID opakovače", "Repeater ID:": "ID opakovače",
"WebSocket": "WebSocket", "WebSocket": "WebSocket",
@ -59,9 +59,9 @@
"Host:": "Hostitel:", "Host:": "Hostitel:",
"Port:": "Port:", "Port:": "Port:",
"Path:": "Cesta", "Path:": "Cesta",
"Automatic Reconnect": "Automatická obnova připojení", "Automatic reconnect": "Automatická obnova připojení",
"Reconnect Delay (ms):": "Zpoždění připojení (ms)", "Reconnect delay (ms):": "Zpoždění připojení (ms)",
"Show Dot when No Cursor": "Tečka místo chybějícího kurzoru myši", "Show dot when no cursor": "Tečka místo chybějícího kurzoru myši",
"Logging:": "Logování:", "Logging:": "Logování:",
"Disconnect": "Odpojit", "Disconnect": "Odpojit",
"Connect": "Připojit", "Connect": "Připojit",

View File

@ -13,7 +13,7 @@
"Password is required": "Passwort ist erforderlich", "Password is required": "Passwort ist erforderlich",
"noVNC encountered an error:": "Ein Fehler ist aufgetreten:", "noVNC encountered an error:": "Ein Fehler ist aufgetreten:",
"Hide/Show the control bar": "Kontrollleiste verstecken/anzeigen", "Hide/Show the control bar": "Kontrollleiste verstecken/anzeigen",
"Move/Drag Viewport": "Ansichtsfenster verschieben/ziehen", "Move/Drag viewport": "Ansichtsfenster verschieben/ziehen",
"viewport drag": "Ansichtsfenster ziehen", "viewport drag": "Ansichtsfenster ziehen",
"Active Mouse Button": "Aktive Maustaste", "Active Mouse Button": "Aktive Maustaste",
"No mousebutton": "Keine Maustaste", "No mousebutton": "Keine Maustaste",
@ -21,9 +21,9 @@
"Middle mousebutton": "Mittlere Maustaste", "Middle mousebutton": "Mittlere Maustaste",
"Right mousebutton": "Rechte Maustaste", "Right mousebutton": "Rechte Maustaste",
"Keyboard": "Tastatur", "Keyboard": "Tastatur",
"Show Keyboard": "Tastatur anzeigen", "Show keyboard": "Tastatur anzeigen",
"Extra keys": "Zusatztasten", "Extra keys": "Zusatztasten",
"Show Extra Keys": "Zusatztasten anzeigen", "Show extra keys": "Zusatztasten anzeigen",
"Ctrl": "Strg", "Ctrl": "Strg",
"Toggle Ctrl": "Strg umschalten", "Toggle Ctrl": "Strg umschalten",
"Alt": "Alt", "Alt": "Alt",
@ -44,13 +44,13 @@
"Clear": "Löschen", "Clear": "Löschen",
"Fullscreen": "Vollbild", "Fullscreen": "Vollbild",
"Settings": "Einstellungen", "Settings": "Einstellungen",
"Shared Mode": "Geteilter Modus", "Shared mode": "Geteilter Modus",
"View Only": "Nur betrachten", "View only": "Nur betrachten",
"Clip to Window": "Auf Fenster begrenzen", "Clip to window": "Auf Fenster begrenzen",
"Scaling Mode:": "Skalierungsmodus:", "Scaling mode:": "Skalierungsmodus:",
"None": "Keiner", "None": "Keiner",
"Local Scaling": "Lokales skalieren", "Local scaling": "Lokales skalieren",
"Remote Resizing": "Serverseitiges skalieren", "Remote resizing": "Serverseitiges skalieren",
"Advanced": "Erweitert", "Advanced": "Erweitert",
"Repeater ID:": "Repeater ID:", "Repeater ID:": "Repeater ID:",
"WebSocket": "WebSocket", "WebSocket": "WebSocket",
@ -58,12 +58,17 @@
"Host:": "Server:", "Host:": "Server:",
"Port:": "Port:", "Port:": "Port:",
"Path:": "Pfad:", "Path:": "Pfad:",
"Automatic Reconnect": "Automatisch wiederverbinden", "Automatic reconnect": "Automatisch wiederverbinden",
"Reconnect Delay (ms):": "Wiederverbindungsverzögerung (ms):", "Reconnect delay (ms):": "Wiederverbindungsverzögerung (ms):",
"Logging:": "Protokollierung:", "Logging:": "Protokollierung:",
"Disconnect": "Verbindung trennen", "Disconnect": "Verbindung trennen",
"Connect": "Verbinden", "Connect": "Verbinden",
"Password:": "Passwort:", "Password:": "Passwort:",
"Cancel": "Abbrechen", "Cancel": "Abbrechen",
"Canvas not supported.": "Canvas nicht unterstützt." "Canvas not supported.": "Canvas nicht unterstützt.",
"Disconnect timeout": "Zeitüberschreitung beim Trennen",
"Local Downscaling": "Lokales herunterskalieren",
"Local Cursor": "Lokaler Mauszeiger",
"Forcing clipping mode since scrollbars aren't supported by IE in fullscreen": "'Clipping-Modus' aktiviert, Scrollbalken in 'IE-Vollbildmodus' werden nicht unterstützt",
"True Color": "True Color"
} }

View File

@ -1,4 +1,5 @@
{ {
"HTTPS is required for full functionality": "Το HTTPS είναι απαιτούμενο για πλήρη λειτουργικότητα",
"Connecting...": "Συνδέεται...", "Connecting...": "Συνδέεται...",
"Disconnecting...": "Aποσυνδέεται...", "Disconnecting...": "Aποσυνδέεται...",
"Reconnecting...": "Επανασυνδέεται...", "Reconnecting...": "Επανασυνδέεται...",
@ -7,19 +8,15 @@
"Connected (encrypted) to ": "Συνδέθηκε (κρυπτογραφημένα) με το ", "Connected (encrypted) to ": "Συνδέθηκε (κρυπτογραφημένα) με το ",
"Connected (unencrypted) to ": "Συνδέθηκε (μη κρυπτογραφημένα) με το ", "Connected (unencrypted) to ": "Συνδέθηκε (μη κρυπτογραφημένα) με το ",
"Something went wrong, connection is closed": "Κάτι πήγε στραβά, η σύνδεση διακόπηκε", "Something went wrong, connection is closed": "Κάτι πήγε στραβά, η σύνδεση διακόπηκε",
"Failed to connect to server": "Αποτυχία στη σύνδεση με το διακομιστή",
"Disconnected": "Αποσυνδέθηκε", "Disconnected": "Αποσυνδέθηκε",
"New connection has been rejected with reason: ": "Η νέα σύνδεση απορρίφθηκε διότι: ", "New connection has been rejected with reason: ": "Η νέα σύνδεση απορρίφθηκε διότι: ",
"New connection has been rejected": "Η νέα σύνδεση απορρίφθηκε ", "New connection has been rejected": "Η νέα σύνδεση απορρίφθηκε ",
"Password is required": "Απαιτείται ο κωδικός πρόσβασης", "Credentials are required": "Απαιτούνται διαπιστευτήρια",
"noVNC encountered an error:": "το noVNC αντιμετώπισε ένα σφάλμα:", "noVNC encountered an error:": "το noVNC αντιμετώπισε ένα σφάλμα:",
"Hide/Show the control bar": "Απόκρυψη/Εμφάνιση γραμμής ελέγχου", "Hide/Show the control bar": "Απόκρυψη/Εμφάνιση γραμμής ελέγχου",
"Drag": "Σύρσιμο",
"Move/Drag Viewport": "Μετακίνηση/Σύρσιμο Θεατού πεδίου", "Move/Drag Viewport": "Μετακίνηση/Σύρσιμο Θεατού πεδίου",
"viewport drag": "σύρσιμο θεατού πεδίου",
"Active Mouse Button": "Ενεργό Πλήκτρο Ποντικιού",
"No mousebutton": "Χωρίς Πλήκτρο Ποντικιού",
"Left mousebutton": "Αριστερό Πλήκτρο Ποντικιού",
"Middle mousebutton": "Μεσαίο Πλήκτρο Ποντικιού",
"Right mousebutton": "Δεξί Πλήκτρο Ποντικιού",
"Keyboard": "Πληκτρολόγιο", "Keyboard": "Πληκτρολόγιο",
"Show Keyboard": "Εμφάνιση Πληκτρολογίου", "Show Keyboard": "Εμφάνιση Πληκτρολογίου",
"Extra keys": "Επιπλέον πλήκτρα", "Extra keys": "Επιπλέον πλήκτρα",
@ -28,6 +25,8 @@
"Toggle Ctrl": "Εναλλαγή Ctrl", "Toggle Ctrl": "Εναλλαγή Ctrl",
"Alt": "Alt", "Alt": "Alt",
"Toggle Alt": "Εναλλαγή Alt", "Toggle Alt": "Εναλλαγή Alt",
"Toggle Windows": "Εναλλαγή Παράθυρων",
"Windows": "Παράθυρα",
"Send Tab": "Αποστολή Tab", "Send Tab": "Αποστολή Tab",
"Tab": "Tab", "Tab": "Tab",
"Esc": "Esc", "Esc": "Esc",
@ -41,8 +40,8 @@
"Reboot": "Επανεκκίνηση", "Reboot": "Επανεκκίνηση",
"Reset": "Επαναφορά", "Reset": "Επαναφορά",
"Clipboard": "Πρόχειρο", "Clipboard": "Πρόχειρο",
"Clear": "Καθάρισμα", "Edit clipboard content in the textarea below.": "Επεξεργαστείτε το περιεχόμενο του πρόχειρου στην περιοχή κειμένου παρακάτω.",
"Fullscreen": "Πλήρης Οθόνη", "Full Screen": "Πλήρης Οθόνη",
"Settings": "Ρυθμίσεις", "Settings": "Ρυθμίσεις",
"Shared Mode": "Κοινόχρηστη Λειτουργία", "Shared Mode": "Κοινόχρηστη Λειτουργία",
"View Only": "Μόνο Θέαση", "View Only": "Μόνο Θέαση",
@ -52,6 +51,8 @@
"Local Scaling": "Τοπική Κλιμάκωση", "Local Scaling": "Τοπική Κλιμάκωση",
"Remote Resizing": "Απομακρυσμένη Αλλαγή μεγέθους", "Remote Resizing": "Απομακρυσμένη Αλλαγή μεγέθους",
"Advanced": "Για προχωρημένους", "Advanced": "Για προχωρημένους",
"Quality:": "Ποιότητα:",
"Compression level:": "Επίπεδο συμπίεσης:",
"Repeater ID:": "Repeater ID:", "Repeater ID:": "Repeater ID:",
"WebSocket": "WebSocket", "WebSocket": "WebSocket",
"Encrypt": "Κρυπτογράφηση", "Encrypt": "Κρυπτογράφηση",
@ -60,10 +61,40 @@
"Path:": "Διαδρομή:", "Path:": "Διαδρομή:",
"Automatic Reconnect": "Αυτόματη επανασύνδεση", "Automatic Reconnect": "Αυτόματη επανασύνδεση",
"Reconnect Delay (ms):": "Καθυστέρηση επανασύνδεσης (ms):", "Reconnect Delay (ms):": "Καθυστέρηση επανασύνδεσης (ms):",
"Show Dot when No Cursor": "Εμφάνιση Τελείας όταν δεν υπάρχει Δρομέας",
"Logging:": "Καταγραφή:", "Logging:": "Καταγραφή:",
"Version:": "Έκδοση:",
"Disconnect": "Αποσύνδεση", "Disconnect": "Αποσύνδεση",
"Connect": "Σύνδεση", "Connect": "Σύνδεση",
"Server identity": "Ταυτότητα Διακομιστή",
"The server has provided the following identifying information:": "Ο διακομιστής παρείχε την ακόλουθη πληροφορία ταυτοποίησης:",
"Fingerprint:": "Δακτυλικό αποτύπωμα:",
"Please verify that the information is correct and press \"Approve\". Otherwise press \"Reject\".": "Παρακαλώ επαληθεύσετε ότι η πληροφορία είναι σωστή και πιέστε \"Αποδοχή\". Αλλιώς πιέστε \"Απόρριψη\".",
"Approve": "Αποδοχή",
"Reject": "Απόρριψη",
"Credentials": "Διαπιστευτήρια",
"Username:": "Κωδικός Χρήστη:",
"Password:": "Κωδικός Πρόσβασης:", "Password:": "Κωδικός Πρόσβασης:",
"Send Credentials": "Αποστολή Διαπιστευτηρίων",
"Cancel": "Ακύρωση", "Cancel": "Ακύρωση",
"Canvas not supported.": "Δεν υποστηρίζεται το στοιχείο Canvas" "Password is required": "Απαιτείται ο κωδικός πρόσβασης",
"viewport drag": "σύρσιμο θεατού πεδίου",
"Active Mouse Button": "Ενεργό Πλήκτρο Ποντικιού",
"No mousebutton": "Χωρίς Πλήκτρο Ποντικιού",
"Left mousebutton": "Αριστερό Πλήκτρο Ποντικιού",
"Middle mousebutton": "Μεσαίο Πλήκτρο Ποντικιού",
"Right mousebutton": "Δεξί Πλήκτρο Ποντικιού",
"Clear": "Καθάρισμα",
"Canvas not supported.": "Δεν υποστηρίζεται το στοιχείο Canvas",
"Disconnect timeout": "Παρέλευση χρονικού ορίου αποσύνδεσης",
"Local Downscaling": "Τοπική Συρρίκνωση",
"Local Cursor": "Τοπικός Δρομέας",
"Forcing clipping mode since scrollbars aren't supported by IE in fullscreen": "Εφαρμογή λειτουργίας αποκοπής αφού δεν υποστηρίζονται οι λωρίδες κύλισης σε πλήρη οθόνη στον IE",
"True Color": "Πραγματικά Χρώματα",
"Style:": "Στυλ:",
"default": "προεπιλεγμένο",
"Apply": "Εφαρμογή",
"Connection": "Σύνδεση",
"Token:": "Διακριτικό:",
"Send Password": "Αποστολή Κωδικού Πρόσβασης"
} }

View File

@ -10,7 +10,7 @@
"Disconnect timeout": "Tiempo de desconexión agotado", "Disconnect timeout": "Tiempo de desconexión agotado",
"noVNC encountered an error:": "noVNC ha encontrado un error:", "noVNC encountered an error:": "noVNC ha encontrado un error:",
"Hide/Show the control bar": "Ocultar/Mostrar la barra de control", "Hide/Show the control bar": "Ocultar/Mostrar la barra de control",
"Move/Drag Viewport": "Mover/Arrastrar la ventana", "Move/Drag viewport": "Mover/Arrastrar la ventana",
"viewport drag": "Arrastrar la ventana", "viewport drag": "Arrastrar la ventana",
"Active Mouse Button": "Botón activo del ratón", "Active Mouse Button": "Botón activo del ratón",
"No mousebutton": "Ningún botón del ratón", "No mousebutton": "Ningún botón del ratón",
@ -18,7 +18,7 @@
"Middle mousebutton": "Botón central del ratón", "Middle mousebutton": "Botón central del ratón",
"Right mousebutton": "Botón derecho del ratón", "Right mousebutton": "Botón derecho del ratón",
"Keyboard": "Teclado", "Keyboard": "Teclado",
"Show Keyboard": "Mostrar teclado", "Show keyboard": "Mostrar teclado",
"Extra keys": "Teclas adicionales", "Extra keys": "Teclas adicionales",
"Show Extra Keys": "Mostrar Teclas Adicionales", "Show Extra Keys": "Mostrar Teclas Adicionales",
"Ctrl": "Ctrl", "Ctrl": "Ctrl",
@ -43,13 +43,13 @@
"Settings": "Configuraciones", "Settings": "Configuraciones",
"Encrypt": "Encriptar", "Encrypt": "Encriptar",
"Shared Mode": "Modo Compartido", "Shared Mode": "Modo Compartido",
"View Only": "Solo visualización", "View only": "Solo visualización",
"Clip to Window": "Recortar al tamaño de la ventana", "Clip to window": "Recortar al tamaño de la ventana",
"Scaling Mode:": "Modo de escalado:", "Scaling mode:": "Modo de escalado:",
"None": "Ninguno", "None": "Ninguno",
"Local Scaling": "Escalado Local", "Local Scaling": "Escalado Local",
"Local Downscaling": "Reducción de escala local", "Local Downscaling": "Reducción de escala local",
"Remote Resizing": "Cambio de tamaño remoto", "Remote resizing": "Cambio de tamaño remoto",
"Advanced": "Avanzado", "Advanced": "Avanzado",
"Local Cursor": "Cursor Local", "Local Cursor": "Cursor Local",
"Repeater ID:": "ID del Repetidor:", "Repeater ID:": "ID del Repetidor:",
@ -57,8 +57,8 @@
"Host:": "Host:", "Host:": "Host:",
"Port:": "Puerto:", "Port:": "Puerto:",
"Path:": "Ruta:", "Path:": "Ruta:",
"Automatic Reconnect": "Reconexión automática", "Automatic reconnect": "Reconexión automática",
"Reconnect Delay (ms):": "Retraso en la reconexión (ms):", "Reconnect delay (ms):": "Retraso en la reconexión (ms):",
"Logging:": "Registrando:", "Logging:": "Registrando:",
"Disconnect": "Desconectar", "Disconnect": "Desconectar",
"Connect": "Conectar", "Connect": "Conectar",

View File

@ -1,10 +1,10 @@
{ {
"HTTPS is required for full functionality": "", "Running without HTTPS is not recommended, crashes or other issues are likely.": "Lancer sans HTTPS n'est pas recommandé, crashs ou autres problèmes en vue.",
"Connecting...": "En cours de connexion...", "Connecting...": "En cours de connexion...",
"Disconnecting...": "Déconnexion en cours...", "Disconnecting...": "Déconnexion en cours...",
"Reconnecting...": "Reconnexion en cours...", "Reconnecting...": "Reconnexion en cours...",
"Internal error": "Erreur interne", "Internal error": "Erreur interne",
"Must set host": "Doit définir l'hôte", "Failed to connect to server: ": "Échec de connexion au serveur ",
"Connected (encrypted) to ": "Connecté (chiffré) à ", "Connected (encrypted) to ": "Connecté (chiffré) à ",
"Connected (unencrypted) to ": "Connecté (non chiffré) à ", "Connected (unencrypted) to ": "Connecté (non chiffré) à ",
"Something went wrong, connection is closed": "Quelque chose s'est mal passé, la connexion a été fermée", "Something went wrong, connection is closed": "Quelque chose s'est mal passé, la connexion a été fermée",
@ -16,19 +16,19 @@
"noVNC encountered an error:": "noVNC a rencontré une erreur :", "noVNC encountered an error:": "noVNC a rencontré une erreur :",
"Hide/Show the control bar": "Masquer/Afficher la barre de contrôle", "Hide/Show the control bar": "Masquer/Afficher la barre de contrôle",
"Drag": "Faire glisser", "Drag": "Faire glisser",
"Move/Drag Viewport": "Déplacer/faire glisser le Viewport", "Move/Drag viewport": "Déplacer la fenêtre de visualisation",
"Keyboard": "Clavier", "Keyboard": "Clavier",
"Show Keyboard": "Afficher le clavier", "Show keyboard": "Afficher le clavier",
"Extra keys": "Touches supplémentaires", "Extra keys": "Touches supplémentaires",
"Show Extra Keys": "Afficher les touches supplémentaires", "Show extra keys": "Afficher les touches supplémentaires",
"Ctrl": "Ctrl", "Ctrl": "Ctrl",
"Toggle Ctrl": "Basculer Ctrl", "Toggle Ctrl": "Basculer Ctrl",
"Alt": "Alt", "Alt": "Alt",
"Toggle Alt": "Basculer Alt", "Toggle Alt": "Basculer Alt",
"Toggle Windows": "Basculer Windows", "Toggle Windows": "Basculer Windows",
"Windows": "Windows", "Windows": "Fenêtre",
"Send Tab": "Envoyer l'onglet", "Send Tab": "Envoyer Tab",
"Tab": "l'onglet", "Tab": "Tabulation",
"Esc": "Esc", "Esc": "Esc",
"Send Escape": "Envoyer Escape", "Send Escape": "Envoyer Escape",
"Ctrl+Alt+Del": "Ctrl+Alt+Del", "Ctrl+Alt+Del": "Ctrl+Alt+Del",
@ -40,15 +40,16 @@
"Reboot": "Redémarrer", "Reboot": "Redémarrer",
"Reset": "Réinitialiser", "Reset": "Réinitialiser",
"Clipboard": "Presse-papiers", "Clipboard": "Presse-papiers",
"Edit clipboard content in the textarea below.": "", "Edit clipboard content in the textarea below.": "Editer le contenu du presse-papier dans la zone ci-dessous.",
"Full screen": "Plein écran",
"Settings": "Paramètres", "Settings": "Paramètres",
"Shared Mode": "Mode partagé", "Shared mode": "Mode partagé",
"View Only": "Afficher uniquement", "View only": "Afficher uniquement",
"Clip to Window": "Clip à fenêtre", "Clip to window": "Ajuster à la fenêtre",
"Scaling Mode:": "Mode mise à l'échelle :", "Scaling mode:": "Mode mise à l'échelle :",
"None": "Aucun", "None": "Aucun",
"Local Scaling": "Mise à l'échelle locale", "Local scaling": "Mise à l'échelle locale",
"Remote Resizing": "Redimensionnement à distance", "Remote resizing": "Redimensionnement à distance",
"Advanced": "Avancé", "Advanced": "Avancé",
"Quality:": "Qualité :", "Quality:": "Qualité :",
"Compression level:": "Niveau de compression :", "Compression level:": "Niveau de compression :",
@ -58,21 +59,24 @@
"Host:": "Hôte :", "Host:": "Hôte :",
"Port:": "Port :", "Port:": "Port :",
"Path:": "Chemin :", "Path:": "Chemin :",
"Automatic Reconnect": "Reconnecter automatiquemen", "Automatic reconnect": "Reconnecter automatiquement",
"Reconnect Delay (ms):": "Délai de reconnexion (ms) :", "Reconnect delay (ms):": "Délai de reconnexion (ms) :",
"Show Dot when No Cursor": "Afficher le point lorsqu'il n'y a pas de curseur", "Show dot when no cursor": "Afficher le point lorsqu'il n'y a pas de curseur",
"Logging:": "Se connecter :", "Logging:": "Se connecter :",
"Version:": "Version :", "Version:": "Version :",
"Disconnect": "Déconnecter", "Disconnect": "Déconnecter",
"Connect": "Connecter", "Connect": "Connecter",
"Server identity": "", "Server identity": "Identité du serveur",
"The server has provided the following identifying information:": "", "The server has provided the following identifying information:": "Le serveur a fourni l'identification suivante :",
"Fingerprint:": "", "Fingerprint:": "Empreinte digitale :",
"Please verify that the information is correct and press \"Approve\". Otherwise press \"Reject\".": "", "Please verify that the information is correct and press \"Approve\". Otherwise press \"Reject\".": "SVP, verifiez que l'information est correcte et pressez \"Accepter\". Sinon pressez \"Refuser\".",
"Approve": "", "Approve": "Accepter",
"Reject": "", "Reject": "Refuser",
"Credentials": "Envoyer les identifiants",
"Username:": "Nom d'utilisateur :", "Username:": "Nom d'utilisateur :",
"Password:": "Mot de passe :", "Password:": "Mot de passe :",
"Send Credentials": "Envoyer les identifiants", "Send credentials": "Envoyer les identifiants",
"Cancel": "Annuler" "Cancel": "Annuler",
"Must set host": "Doit définir l'hôte",
"Clear": "Effacer"
} }

View File

@ -14,10 +14,8 @@
"Credentials are required": "Le credenziali sono obbligatorie", "Credentials are required": "Le credenziali sono obbligatorie",
"noVNC encountered an error:": "noVNC ha riscontrato un errore:", "noVNC encountered an error:": "noVNC ha riscontrato un errore:",
"Hide/Show the control bar": "Nascondi/Mostra la barra di controllo", "Hide/Show the control bar": "Nascondi/Mostra la barra di controllo",
"Drag": "",
"Move/Drag Viewport": "",
"Keyboard": "Tastiera", "Keyboard": "Tastiera",
"Show Keyboard": "Mostra tastiera", "Show keyboard": "Mostra tastiera",
"Extra keys": "Tasti Aggiuntivi", "Extra keys": "Tasti Aggiuntivi",
"Show Extra Keys": "Mostra Tasti Aggiuntivi", "Show Extra Keys": "Mostra Tasti Aggiuntivi",
"Ctrl": "Ctrl", "Ctrl": "Ctrl",
@ -42,10 +40,9 @@
"Clear": "Pulisci", "Clear": "Pulisci",
"Fullscreen": "Schermo intero", "Fullscreen": "Schermo intero",
"Settings": "Impostazioni", "Settings": "Impostazioni",
"Shared Mode": "Modalità condivisa", "Shared mode": "Modalità condivisa",
"View Only": "Sola Visualizzazione", "View Only": "Sola Visualizzazione",
"Clip to Window": "", "Scaling mode:": "Modalità di ridimensionamento:",
"Scaling Mode:": "Modalità di ridimensionamento:",
"None": "Nessuna", "None": "Nessuna",
"Local Scaling": "Ridimensionamento Locale", "Local Scaling": "Ridimensionamento Locale",
"Remote Resizing": "Ridimensionamento Remoto", "Remote Resizing": "Ridimensionamento Remoto",
@ -61,7 +58,6 @@
"Automatic Reconnect": "Riconnessione Automatica", "Automatic Reconnect": "Riconnessione Automatica",
"Reconnect Delay (ms):": "Ritardo Riconnessione (ms):", "Reconnect Delay (ms):": "Ritardo Riconnessione (ms):",
"Show Dot when No Cursor": "Mostra Punto quando Nessun Cursore", "Show Dot when No Cursor": "Mostra Punto quando Nessun Cursore",
"Logging:": "",
"Version:": "Versione:", "Version:": "Versione:",
"Disconnect": "Disconnetti", "Disconnect": "Disconnetti",
"Connect": "Connetti", "Connect": "Connetti",

View File

@ -1,12 +1,14 @@
{ {
"Running without HTTPS is not recommended, crashes or other issues are likely.": "HTTPS接続なしで実行することは推奨されません。クラッシュしたりその他の問題が発生したりする可能性があります。",
"Connecting...": "接続しています...", "Connecting...": "接続しています...",
"Disconnecting...": "切断しています...", "Disconnecting...": "切断しています...",
"Reconnecting...": "再接続しています...", "Reconnecting...": "再接続しています...",
"Internal error": "内部エラー", "Internal error": "内部エラー",
"Must set host": "ホストを設定する必要があります", "Must set host": "ホストを設定する必要があります",
"Failed to connect to server: ": "サーバーへの接続に失敗しました: ",
"Connected (encrypted) to ": "接続しました (暗号化済み): ", "Connected (encrypted) to ": "接続しました (暗号化済み): ",
"Connected (unencrypted) to ": "接続しました (暗号化されていません): ", "Connected (unencrypted) to ": "接続しました (暗号化されていません): ",
"Something went wrong, connection is closed": "何らかの問題で、接続が閉じられました", "Something went wrong, connection is closed": "問題が発生したため、接続が閉じられました",
"Failed to connect to server": "サーバーへの接続に失敗しました", "Failed to connect to server": "サーバーへの接続に失敗しました",
"Disconnected": "切断しました", "Disconnected": "切断しました",
"New connection has been rejected with reason: ": "新規接続は次の理由で拒否されました: ", "New connection has been rejected with reason: ": "新規接続は次の理由で拒否されました: ",
@ -15,16 +17,16 @@
"noVNC encountered an error:": "noVNC でエラーが発生しました:", "noVNC encountered an error:": "noVNC でエラーが発生しました:",
"Hide/Show the control bar": "コントロールバーを隠す/表示する", "Hide/Show the control bar": "コントロールバーを隠す/表示する",
"Drag": "ドラッグ", "Drag": "ドラッグ",
"Move/Drag Viewport": "ビューポートを移動/ドラッグ", "Move/Drag viewport": "ビューポートを移動/ドラッグ",
"Keyboard": "キーボード", "Keyboard": "キーボード",
"Show Keyboard": "キーボードを表示", "Show keyboard": "キーボードを表示",
"Extra keys": "追加キー", "Extra keys": "追加キー",
"Show Extra Keys": "追加キーを表示", "Show extra keys": "追加キーを表示",
"Ctrl": "Ctrl", "Ctrl": "Ctrl",
"Toggle Ctrl": "Ctrl キーを切り替え", "Toggle Ctrl": "Ctrl キーをトグル",
"Alt": "Alt", "Alt": "Alt",
"Toggle Alt": "Alt キーを切り替え", "Toggle Alt": "Alt キーをトグル",
"Toggle Windows": "Windows キーを切り替え", "Toggle Windows": "Windows キーをトグル",
"Windows": "Windows", "Windows": "Windows",
"Send Tab": "Tab キーを送信", "Send Tab": "Tab キーを送信",
"Tab": "Tab", "Tab": "Tab",
@ -39,16 +41,16 @@
"Reboot": "再起動", "Reboot": "再起動",
"Reset": "リセット", "Reset": "リセット",
"Clipboard": "クリップボード", "Clipboard": "クリップボード",
"Clear": "クリア", "Edit clipboard content in the textarea below.": "以下の入力欄からクリップボードの内容を編集できます。",
"Fullscreen": "全画面表示", "Full screen": "全画面表示",
"Settings": "設定", "Settings": "設定",
"Shared Mode": "共有モード", "Shared mode": "共有モード",
"View Only": "表示のみ", "View only": "表示専用",
"Clip to Window": "ウィンドウにクリップ", "Clip to window": "ウィンドウにクリップ",
"Scaling Mode:": "スケーリングモード:", "Scaling mode:": "スケーリングモード:",
"None": "なし", "None": "なし",
"Local Scaling": "ローカルスケーリング", "Local scaling": "ローカルでスケーリング",
"Remote Resizing": "リモートでリサイズ", "Remote resizing": "リモートでリサイズ",
"Advanced": "高度", "Advanced": "高度",
"Quality:": "品質:", "Quality:": "品質:",
"Compression level:": "圧縮レベル:", "Compression level:": "圧縮レベル:",
@ -58,15 +60,22 @@
"Host:": "ホスト:", "Host:": "ホスト:",
"Port:": "ポート:", "Port:": "ポート:",
"Path:": "パス:", "Path:": "パス:",
"Automatic Reconnect": "自動再接続", "Automatic reconnect": "自動再接続",
"Reconnect Delay (ms):": "再接続する遅延 (ミリ秒):", "Reconnect delay (ms):": "再接続する遅延 (ミリ秒):",
"Show Dot when No Cursor": "カーソルがないときにドットを表示", "Show dot when no cursor": "カーソルがないときにドットを表示する",
"Logging:": "ロギング:", "Logging:": "ロギング:",
"Version:": "バージョン:", "Version:": "バージョン:",
"Disconnect": "切断", "Disconnect": "切断",
"Connect": "接続", "Connect": "接続",
"Server identity": "サーバーの識別情報",
"The server has provided the following identifying information:": "サーバーは以下の識別情報を提供しています:",
"Fingerprint:": "フィンガープリント:",
"Please verify that the information is correct and press \"Approve\". Otherwise press \"Reject\".": "この情報が正しい場合は「承認」を、そうでない場合は「拒否」を押してください。",
"Approve": "承認",
"Reject": "拒否",
"Credentials": "資格情報",
"Username:": "ユーザー名:", "Username:": "ユーザー名:",
"Password:": "パスワード:", "Password:": "パスワード:",
"Send Credentials": "資格情報を送信", "Send credentials": "資格情報を送信",
"Cancel": "キャンセル" "Cancel": "キャンセル"
} }

View File

@ -14,7 +14,7 @@
"Password is required": "비밀번호가 필요합니다.", "Password is required": "비밀번호가 필요합니다.",
"noVNC encountered an error:": "noVNC에 오류가 발생했습니다:", "noVNC encountered an error:": "noVNC에 오류가 발생했습니다:",
"Hide/Show the control bar": "컨트롤 바 숨기기/보이기", "Hide/Show the control bar": "컨트롤 바 숨기기/보이기",
"Move/Drag Viewport": "움직이기/드래그 뷰포트", "Move/Drag viewport": "움직이기/드래그 뷰포트",
"viewport drag": "뷰포트 드래그", "viewport drag": "뷰포트 드래그",
"Active Mouse Button": "마우스 버튼 활성화", "Active Mouse Button": "마우스 버튼 활성화",
"No mousebutton": "마우스 버튼 없음", "No mousebutton": "마우스 버튼 없음",
@ -22,9 +22,9 @@
"Middle mousebutton": "중간 마우스 버튼", "Middle mousebutton": "중간 마우스 버튼",
"Right mousebutton": "오른쪽 마우스 버튼", "Right mousebutton": "오른쪽 마우스 버튼",
"Keyboard": "키보드", "Keyboard": "키보드",
"Show Keyboard": "키보드 보이기", "Show keyboard": "키보드 보이기",
"Extra keys": "기타 키들", "Extra keys": "기타 키들",
"Show Extra Keys": "기타 키들 보이기", "Show extra keys": "기타 키들 보이기",
"Ctrl": "Ctrl", "Ctrl": "Ctrl",
"Toggle Ctrl": "Ctrl 켜기/끄기", "Toggle Ctrl": "Ctrl 켜기/끄기",
"Alt": "Alt", "Alt": "Alt",
@ -45,13 +45,13 @@
"Clear": "지우기", "Clear": "지우기",
"Fullscreen": "전체화면", "Fullscreen": "전체화면",
"Settings": "설정", "Settings": "설정",
"Shared Mode": "공유 모드", "Shared mode": "공유 모드",
"View Only": "보기 전용", "View only": "보기 전용",
"Clip to Window": "창에 클립", "Clip to window": "창에 클립",
"Scaling Mode:": "스케일링 모드:", "Scaling mode:": "스케일링 모드:",
"None": "없음", "None": "없음",
"Local Scaling": "로컬 스케일링", "Local scaling": "로컬 스케일링",
"Remote Resizing": "원격 크기 조절", "Remote resizing": "원격 크기 조절",
"Advanced": "고급", "Advanced": "고급",
"Repeater ID:": "중계 ID", "Repeater ID:": "중계 ID",
"WebSocket": "웹소켓", "WebSocket": "웹소켓",
@ -59,8 +59,8 @@
"Host:": "호스트:", "Host:": "호스트:",
"Port:": "포트:", "Port:": "포트:",
"Path:": "위치:", "Path:": "위치:",
"Automatic Reconnect": "자동 재연결", "Automatic reconnect": "자동 재연결",
"Reconnect Delay (ms):": "재연결 지연 시간 (ms)", "Reconnect delay (ms):": "재연결 지연 시간 (ms)",
"Logging:": "로깅", "Logging:": "로깅",
"Disconnect": "연결 해제", "Disconnect": "연결 해제",
"Connect": "연결", "Connect": "연결",

View File

@ -1,36 +1,32 @@
{ {
"Connecting...": "Verbinden...", "Running without HTTPS is not recommended, crashes or other issues are likely.": "Het is niet aan te raden om zonder HTTPS te werken, crashes of andere problemen zijn dan waarschijnlijk.",
"Disconnecting...": "Verbinding verbreken...", "Connecting...": "Aan het verbinden…",
"Disconnecting...": "Bezig om verbinding te verbreken...",
"Reconnecting...": "Opnieuw verbinding maken...", "Reconnecting...": "Opnieuw verbinding maken...",
"Internal error": "Interne fout", "Internal error": "Interne fout",
"Must set host": "Host moeten worden ingesteld", "Failed to connect to server: ": "Verbinding maken met server is mislukt",
"Connected (encrypted) to ": "Verbonden (versleuteld) met ", "Connected (encrypted) to ": "Verbonden (versleuteld) met ",
"Connected (unencrypted) to ": "Verbonden (onversleuteld) met ", "Connected (unencrypted) to ": "Verbonden (onversleuteld) met ",
"Something went wrong, connection is closed": "Er iets fout gelopen, verbinding werd verbroken", "Something went wrong, connection is closed": "Er iets fout gelopen, verbinding werd verbroken",
"Failed to connect to server": "Verbinding maken met server is mislukt", "Failed to connect to server": "Verbinding maken met server is mislukt",
"Disconnected": "Verbinding verbroken", "Disconnected": "Verbinding verbroken",
"New connection has been rejected with reason: ": "Nieuwe verbinding is geweigerd omwille van de volgende reden: ", "New connection has been rejected with reason: ": "Nieuwe verbinding is geweigerd met de volgende reden: ",
"New connection has been rejected": "Nieuwe verbinding is geweigerd", "New connection has been rejected": "Nieuwe verbinding is geweigerd",
"Password is required": "Wachtwoord is vereist", "Credentials are required": "Inloggegevens zijn nodig",
"noVNC encountered an error:": "noVNC heeft een fout bemerkt:", "noVNC encountered an error:": "noVNC heeft een fout bemerkt:",
"Hide/Show the control bar": "Verberg/Toon de bedieningsbalk", "Hide/Show the control bar": "Verberg/Toon de bedieningsbalk",
"Move/Drag Viewport": "Verplaats/Versleep Kijkvenster", "Drag": "Sleep",
"viewport drag": "kijkvenster slepen", "Move/Drag viewport": "Verplaats/Versleep Kijkvenster",
"Active Mouse Button": "Actieve Muisknop",
"No mousebutton": "Geen muisknop",
"Left mousebutton": "Linker muisknop",
"Middle mousebutton": "Middelste muisknop",
"Right mousebutton": "Rechter muisknop",
"Keyboard": "Toetsenbord", "Keyboard": "Toetsenbord",
"Show Keyboard": "Toon Toetsenbord", "Show keyboard": "Toon Toetsenbord",
"Extra keys": "Extra toetsen", "Extra keys": "Extra toetsen",
"Show Extra Keys": "Toon Extra Toetsen", "Show extra keys": "Toon Extra Toetsen",
"Ctrl": "Ctrl", "Ctrl": "Ctrl",
"Toggle Ctrl": "Ctrl omschakelen", "Toggle Ctrl": "Ctrl omschakelen",
"Alt": "Alt", "Alt": "Alt",
"Toggle Alt": "Alt omschakelen", "Toggle Alt": "Alt omschakelen",
"Toggle Windows": "Windows omschakelen", "Toggle Windows": "Vensters omschakelen",
"Windows": "Windows", "Windows": "Vensters",
"Send Tab": "Tab Sturen", "Send Tab": "Tab Sturen",
"Tab": "Tab", "Tab": "Tab",
"Esc": "Esc", "Esc": "Esc",
@ -44,30 +40,56 @@
"Reboot": "Herstarten", "Reboot": "Herstarten",
"Reset": "Resetten", "Reset": "Resetten",
"Clipboard": "Klembord", "Clipboard": "Klembord",
"Clear": "Wissen", "Edit clipboard content in the textarea below.": "Edit de inhoud van het klembord in het tekstveld hieronder",
"Fullscreen": "Volledig Scherm", "Full screen": "Volledig Scherm",
"Settings": "Instellingen", "Settings": "Instellingen",
"Shared Mode": "Gedeelde Modus", "Shared mode": "Gedeelde Modus",
"View Only": "Alleen Kijken", "View only": "Alleen Kijken",
"Clip to Window": "Randen buiten venster afsnijden", "Clip to window": "Randen buiten venster afsnijden",
"Scaling Mode:": "Schaalmodus:", "Scaling mode:": "Schaalmodus:",
"None": "Geen", "None": "Geen",
"Local Scaling": "Lokaal Schalen", "Local scaling": "Lokaal Schalen",
"Remote Resizing": "Op Afstand Formaat Wijzigen", "Remote resizing": "Op Afstand Formaat Wijzigen",
"Advanced": "Geavanceerd", "Advanced": "Geavanceerd",
"Quality:": "Kwaliteit:",
"Compression level:": "Compressieniveau:",
"Repeater ID:": "Repeater ID:", "Repeater ID:": "Repeater ID:",
"WebSocket": "WebSocket", "WebSocket": "WebSocket",
"Encrypt": "Versleutelen", "Encrypt": "Versleutelen",
"Host:": "Host:", "Host:": "Host:",
"Port:": "Poort:", "Port:": "Poort:",
"Path:": "Pad:", "Path:": "Pad:",
"Automatic Reconnect": "Automatisch Opnieuw Verbinden", "Automatic reconnect": "Automatisch Opnieuw Verbinden",
"Reconnect Delay (ms):": "Vertraging voor Opnieuw Verbinden (ms):", "Reconnect delay (ms):": "Vertraging voor Opnieuw Verbinden (ms):",
"Show Dot when No Cursor": "Geef stip weer indien geen cursor", "Show dot when no cursor": "Geef stip weer indien geen cursor",
"Logging:": "Logmeldingen:", "Logging:": "Logmeldingen:",
"Version:": "Versie:",
"Disconnect": "Verbinding verbreken", "Disconnect": "Verbinding verbreken",
"Connect": "Verbinden", "Connect": "Verbinden",
"Server identity": "Serveridentiteit",
"The server has provided the following identifying information:": "De server geeft de volgende identificerende informatie:",
"Fingerprint:": "Vingerafdruk:",
"Please verify that the information is correct and press \"Approve\". Otherwise press \"Reject\".": "Verifieer dat de informatie is correct en druk “OK”. Druk anders op “Afwijzen”.",
"Approve": "OK",
"Reject": "Afwijzen",
"Credentials": "Inloggegevens",
"Username:": "Gebruikersnaam:",
"Password:": "Wachtwoord:", "Password:": "Wachtwoord:",
"Send credentials": "Stuur inloggegevens",
"Cancel": "Annuleren",
"Must set host": "Host moeten worden ingesteld",
"Password is required": "Wachtwoord is vereist",
"viewport drag": "kijkvenster slepen",
"Active Mouse Button": "Actieve Muisknop",
"No mousebutton": "Geen muisknop",
"Left mousebutton": "Linker muisknop",
"Middle mousebutton": "Middelste muisknop",
"Right mousebutton": "Rechter muisknop",
"Clear": "Wissen",
"Send Password": "Verzend Wachtwoord:", "Send Password": "Verzend Wachtwoord:",
"Cancel": "Annuleren" "Disconnect timeout": "Timeout tijdens verbreken van verbinding",
"Local Downscaling": "Lokaal Neerschalen",
"Local Cursor": "Lokale Cursor",
"Canvas not supported.": "Canvas wordt niet ondersteund.",
"Forcing clipping mode since scrollbars aren't supported by IE in fullscreen": "''Clipping mode' ingeschakeld, omdat schuifbalken in volledige-scherm-modus in IE niet worden ondersteund"
} }

View File

@ -21,9 +21,9 @@
"Middle mousebutton": "Środkowy przycisk myszy", "Middle mousebutton": "Środkowy przycisk myszy",
"Right mousebutton": "Prawy przycisk myszy", "Right mousebutton": "Prawy przycisk myszy",
"Keyboard": "Klawiatura", "Keyboard": "Klawiatura",
"Show Keyboard": "Pokaż klawiaturę", "Show keyboard": "Pokaż klawiaturę",
"Extra keys": "Przyciski dodatkowe", "Extra keys": "Przyciski dodatkowe",
"Show Extra Keys": "Pokaż przyciski dodatkowe", "Show extra keys": "Pokaż przyciski dodatkowe",
"Ctrl": "Ctrl", "Ctrl": "Ctrl",
"Toggle Ctrl": "Przełącz Ctrl", "Toggle Ctrl": "Przełącz Ctrl",
"Alt": "Alt", "Alt": "Alt",
@ -49,8 +49,8 @@
"Clip to Window": "Przytnij do Okna", "Clip to Window": "Przytnij do Okna",
"Scaling Mode:": "Tryb Skalowania:", "Scaling Mode:": "Tryb Skalowania:",
"None": "Brak", "None": "Brak",
"Local Scaling": "Skalowanie lokalne", "Local scaling": "Skalowanie lokalne",
"Remote Resizing": "Skalowanie zdalne", "Remote resizing": "Skalowanie zdalne",
"Advanced": "Zaawansowane", "Advanced": "Zaawansowane",
"Repeater ID:": "ID Repeatera:", "Repeater ID:": "ID Repeatera:",
"WebSocket": "WebSocket", "WebSocket": "WebSocket",
@ -58,12 +58,23 @@
"Host:": "Host:", "Host:": "Host:",
"Port:": "Port:", "Port:": "Port:",
"Path:": "Ścieżka:", "Path:": "Ścieżka:",
"Automatic Reconnect": "Automatycznie wznawiaj połączenie", "Automatic reconnect": "Automatycznie wznawiaj połączenie",
"Reconnect Delay (ms):": "Opóźnienie wznawiania (ms):", "Reconnect delay (ms):": "Opóźnienie wznawiania (ms):",
"Logging:": "Poziom logowania:", "Logging:": "Poziom logowania:",
"Disconnect": "Rozłącz", "Disconnect": "Rozłącz",
"Connect": "Połącz", "Connect": "Połącz",
"Password:": "Hasło:", "Password:": "Hasło:",
"Cancel": "Anuluj", "Cancel": "Anuluj",
"Canvas not supported.": "Element Canvas nie jest wspierany." "Canvas not supported.": "Element Canvas nie jest wspierany.",
"Disconnect timeout": "Timeout rozłączenia",
"Local Downscaling": "Downscaling lokalny",
"Local Cursor": "Lokalny kursor",
"Forcing clipping mode since scrollbars aren't supported by IE in fullscreen": "Wymuszam clipping mode ponieważ paski przewijania nie są wspierane przez IE w trybie pełnoekranowym",
"True Color": "True Color",
"Style:": "Styl:",
"default": "domyślny",
"Apply": "Zapisz",
"Connection": "Połączenie",
"Token:": "Token:",
"Send Password": "Wyślij Hasło"
} }

View File

@ -15,11 +15,11 @@
"noVNC encountered an error:": "O noVNC encontrou um erro:", "noVNC encountered an error:": "O noVNC encontrou um erro:",
"Hide/Show the control bar": "Esconder/mostrar a barra de controles", "Hide/Show the control bar": "Esconder/mostrar a barra de controles",
"Drag": "Arrastar", "Drag": "Arrastar",
"Move/Drag Viewport": "Mover/arrastar a janela", "Move/Drag viewport": "Mover/arrastar a janela",
"Keyboard": "Teclado", "Keyboard": "Teclado",
"Show Keyboard": "Mostrar teclado", "Show keyboard": "Mostrar teclado",
"Extra keys": "Teclas adicionais", "Extra keys": "Teclas adicionais",
"Show Extra Keys": "Mostar teclas adicionais", "Show extra keys": "Mostar teclas adicionais",
"Ctrl": "Ctrl", "Ctrl": "Ctrl",
"Toggle Ctrl": "Pressionar/soltar Ctrl", "Toggle Ctrl": "Pressionar/soltar Ctrl",
"Alt": "Alt", "Alt": "Alt",
@ -42,13 +42,13 @@
"Clear": "Limpar", "Clear": "Limpar",
"Fullscreen": "Tela cheia", "Fullscreen": "Tela cheia",
"Settings": "Configurações", "Settings": "Configurações",
"Shared Mode": "Modo compartilhado", "Shared mode": "Modo compartilhado",
"View Only": "Apenas visualizar", "View only": "Apenas visualizar",
"Clip to Window": "Recortar à janela", "Clip to window": "Recortar à janela",
"Scaling Mode:": "Modo de dimensionamento:", "Scaling mode:": "Modo de dimensionamento:",
"None": "Nenhum", "None": "Nenhum",
"Local Scaling": "Local", "Local scaling": "Local",
"Remote Resizing": "Remoto", "Remote resizing": "Remoto",
"Advanced": "Avançado", "Advanced": "Avançado",
"Quality:": "Qualidade:", "Quality:": "Qualidade:",
"Compression level:": "Nível de compressão:", "Compression level:": "Nível de compressão:",
@ -58,15 +58,15 @@
"Host:": "Host:", "Host:": "Host:",
"Port:": "Porta:", "Port:": "Porta:",
"Path:": "Caminho:", "Path:": "Caminho:",
"Automatic Reconnect": "Reconexão automática", "Automatic reconnect": "Reconexão automática",
"Reconnect Delay (ms):": "Atraso da reconexão (ms)", "Reconnect delay (ms):": "Atraso da reconexão (ms)",
"Show Dot when No Cursor": "Mostrar ponto quando não há cursor", "Show dot when no cursor": "Mostrar ponto quando não há cursor",
"Logging:": "Registros:", "Logging:": "Registros:",
"Version:": "Versão:", "Version:": "Versão:",
"Disconnect": "Desconectar", "Disconnect": "Desconectar",
"Connect": "Conectar", "Connect": "Conectar",
"Username:": "Nome de usuário:", "Username:": "Nome de usuário:",
"Password:": "Senha:", "Password:": "Senha:",
"Send Credentials": "Enviar credenciais", "Send credentials": "Enviar credenciais",
"Cancel": "Cancelar" "Cancel": "Cancelar"
} }

View File

@ -15,16 +15,16 @@
"noVNC encountered an error:": "Ошибка noVNC: ", "noVNC encountered an error:": "Ошибка noVNC: ",
"Hide/Show the control bar": "Скрыть/Показать контрольную панель", "Hide/Show the control bar": "Скрыть/Показать контрольную панель",
"Drag": "Переместить", "Drag": "Переместить",
"Move/Drag Viewport": "Переместить окно", "Move/Drag viewport": "Переместить окно",
"Keyboard": "Клавиатура", "Keyboard": "Клавиатура",
"Show Keyboard": "Показать клавиатуру", "Show keyboard": "Показать клавиатуру",
"Extra keys": "Дополнительные Кнопки", "Extra keys": "Дополнительные Кнопки",
"Show Extra Keys": "Показать Дополнительные Кнопки", "Show Extra Keys": "Показать Дополнительные Кнопки",
"Ctrl": "Ctrl", "Ctrl": "Ctrl",
"Toggle Ctrl": "Переключение нажатия Ctrl", "Toggle Ctrl": "Зажать Ctrl",
"Alt": "Alt", "Alt": "Alt",
"Toggle Alt": "Переключение нажатия Alt", "Toggle Alt": "Зажать Alt",
"Toggle Windows": "Переключение вкладок", "Toggle Windows": "Зажать Windows",
"Windows": "Вкладка", "Windows": "Вкладка",
"Send Tab": "Передать нажатие Tab", "Send Tab": "Передать нажатие Tab",
"Tab": "Tab", "Tab": "Tab",
@ -42,13 +42,13 @@
"Clear": "Очистить", "Clear": "Очистить",
"Fullscreen": "Во весь экран", "Fullscreen": "Во весь экран",
"Settings": "Настройки", "Settings": "Настройки",
"Shared Mode": "Общий режим", "Shared mode": "Общий режим",
"View Only": "Только Просмотр", "View Only": "Только Просмотр",
"Clip to Window": "В окно", "Clip to window": "В окно",
"Scaling Mode:": "Масштаб:", "Scaling mode:": "Масштаб:",
"None": "Нет", "None": "Нет",
"Local Scaling": "Локльный масштаб", "Local scaling": "Локальный масштаб",
"Remote Resizing": "Удаленная перенастройка размера", "Remote resizing": "Удаленная перенастройка размера",
"Advanced": "Дополнительно", "Advanced": "Дополнительно",
"Quality:": "Качество", "Quality:": "Качество",
"Compression level:": "Уровень Сжатия", "Compression level:": "Уровень Сжатия",
@ -58,9 +58,9 @@
"Host:": "Сервер:", "Host:": "Сервер:",
"Port:": "Порт:", "Port:": "Порт:",
"Path:": "Путь:", "Path:": "Путь:",
"Automatic Reconnect": "Автоматическое переподключение", "Automatic reconnect": "Автоматическое переподключение",
"Reconnect Delay (ms):": "Задержка переподключения (мс):", "Reconnect delay (ms):": "Задержка переподключения (мс):",
"Show Dot when No Cursor": "Показать точку вместо курсора", "Show dot when no cursor": "Показать точку вместо курсора",
"Logging:": "Лог:", "Logging:": "Лог:",
"Version:": "Версия", "Version:": "Версия",
"Disconnect": "Отключение", "Disconnect": "Отключение",

View File

@ -1,10 +1,10 @@
{ {
"HTTPS is required for full functionality": "HTTPS krävs för full funktionalitet", "Running without HTTPS is not recommended, crashes or other issues are likely.": "Det är ej rekommenderat att köra utan HTTPS, krascher och andra problem är troliga.",
"Connecting...": "Ansluter...", "Connecting...": "Ansluter...",
"Disconnecting...": "Kopplar ner...", "Disconnecting...": "Kopplar ner...",
"Reconnecting...": "Återansluter...", "Reconnecting...": "Återansluter...",
"Internal error": "Internt fel", "Internal error": "Internt fel",
"Must set host": "Du måste specifiera en värd", "Failed to connect to server: ": "Misslyckades att ansluta till servern: ",
"Connected (encrypted) to ": "Ansluten (krypterat) till ", "Connected (encrypted) to ": "Ansluten (krypterat) till ",
"Connected (unencrypted) to ": "Ansluten (okrypterat) till ", "Connected (unencrypted) to ": "Ansluten (okrypterat) till ",
"Something went wrong, connection is closed": "Något gick fel, anslutningen avslutades", "Something went wrong, connection is closed": "Något gick fel, anslutningen avslutades",
@ -16,11 +16,11 @@
"noVNC encountered an error:": "noVNC stötte på ett problem:", "noVNC encountered an error:": "noVNC stötte på ett problem:",
"Hide/Show the control bar": "Göm/Visa kontrollbaren", "Hide/Show the control bar": "Göm/Visa kontrollbaren",
"Drag": "Dra", "Drag": "Dra",
"Move/Drag Viewport": "Flytta/Dra Vyn", "Move/Drag viewport": "Flytta/Dra vyn",
"Keyboard": "Tangentbord", "Keyboard": "Tangentbord",
"Show Keyboard": "Visa Tangentbord", "Show keyboard": "Visa tangentbord",
"Extra keys": "Extraknappar", "Extra keys": "Extraknappar",
"Show Extra Keys": "Visa Extraknappar", "Show extra keys": "Visa extraknappar",
"Ctrl": "Ctrl", "Ctrl": "Ctrl",
"Toggle Ctrl": "Växla Ctrl", "Toggle Ctrl": "Växla Ctrl",
"Alt": "Alt", "Alt": "Alt",
@ -41,15 +41,15 @@
"Reset": "Återställ", "Reset": "Återställ",
"Clipboard": "Urklipp", "Clipboard": "Urklipp",
"Edit clipboard content in the textarea below.": "Redigera urklippets innehåll i fältet nedan.", "Edit clipboard content in the textarea below.": "Redigera urklippets innehåll i fältet nedan.",
"Full Screen": "Fullskärm", "Full screen": "Fullskärm",
"Settings": "Inställningar", "Settings": "Inställningar",
"Shared Mode": "Delat Läge", "Shared mode": "Delat läge",
"View Only": "Endast Visning", "View only": "Endast visning",
"Clip to Window": "Begränsa till Fönster", "Clip to window": "Begränsa till fönster",
"Scaling Mode:": "Skalningsläge:", "Scaling mode:": "Skalningsläge:",
"None": "Ingen", "None": "Ingen",
"Local Scaling": "Lokal Skalning", "Local scaling": "Lokal skalning",
"Remote Resizing": "Ändra Storlek", "Remote resizing": "Ändra storlek",
"Advanced": "Avancerat", "Advanced": "Avancerat",
"Quality:": "Kvalitet:", "Quality:": "Kvalitet:",
"Compression level:": "Kompressionsnivå:", "Compression level:": "Kompressionsnivå:",
@ -59,9 +59,9 @@
"Host:": "Värd:", "Host:": "Värd:",
"Port:": "Port:", "Port:": "Port:",
"Path:": "Sökväg:", "Path:": "Sökväg:",
"Automatic Reconnect": "Automatisk Återanslutning", "Automatic reconnect": "Automatisk återanslutning",
"Reconnect Delay (ms):": "Fördröjning (ms):", "Reconnect delay (ms):": "Fördröjning (ms):",
"Show Dot when No Cursor": "Visa prick när ingen muspekare finns", "Show dot when no cursor": "Visa prick när ingen muspekare finns",
"Logging:": "Loggning:", "Logging:": "Loggning:",
"Version:": "Version:", "Version:": "Version:",
"Disconnect": "Koppla från", "Disconnect": "Koppla från",
@ -75,6 +75,9 @@
"Credentials": "Användaruppgifter", "Credentials": "Användaruppgifter",
"Username:": "Användarnamn:", "Username:": "Användarnamn:",
"Password:": "Lösenord:", "Password:": "Lösenord:",
"Send Credentials": "Skicka Användaruppgifter", "Send credentials": "Skicka användaruppgifter",
"Cancel": "Avbryt" "Cancel": "Avbryt",
"Must set host": "Du måste specifiera en värd",
"HTTPS is required for full functionality": "HTTPS krävs för full funktionalitet",
"Clear": "Rensa"
} }

View File

@ -23,7 +23,7 @@
"Keyboard": "Klavye", "Keyboard": "Klavye",
"Show Keyboard": "Klavye Düzenini Göster", "Show Keyboard": "Klavye Düzenini Göster",
"Extra keys": "Ekstra tuşlar", "Extra keys": "Ekstra tuşlar",
"Show Extra Keys": "Ekstra tuşları göster", "Show extra keys": "Ekstra tuşları göster",
"Ctrl": "Ctrl", "Ctrl": "Ctrl",
"Toggle Ctrl": "Ctrl Değiştir ", "Toggle Ctrl": "Ctrl Değiştir ",
"Alt": "Alt", "Alt": "Alt",

View File

@ -1,69 +1,93 @@
{ {
"Running without HTTPS is not recommended, crashes or other issues are likely.": "不建议在没有 HTTPS 的情况下运行,可能会出现崩溃或出现其他问题。",
"Connecting...": "连接中...", "Connecting...": "连接中...",
"Disconnecting...": "正在断开连接...", "Disconnecting...": "正在断开连接...",
"Reconnecting...": "重新连接中...", "Reconnecting...": "重新连接中...",
"Internal error": "内部错误", "Internal error": "内部错误",
"Must set host": "请提供主机名", "Must set host": "必须设置主机",
"Connected (encrypted) to ": "已连接到(加密)", "Failed to connect to server: ": "无法连接到服务器:",
"Connected (unencrypted) to ": "已连接到(未加密)", "Connected (encrypted) to ": "已连接(已加密)到",
"Something went wrong, connection is closed": "发生错误,连接已关闭", "Connected (unencrypted) to ": "已连接(未加密)到",
"Something went wrong, connection is closed": "出了点问题,连接已关闭",
"Failed to connect to server": "无法连接到服务器", "Failed to connect to server": "无法连接到服务器",
"Disconnected": "已断开连接", "Disconnected": "已断开连接",
"New connection has been rejected with reason: ": "连接被拒绝,原因:", "New connection has been rejected with reason: ": "连接被拒绝,原因如下",
"New connection has been rejected": "连接被拒绝", "New connection has been rejected": "连接被拒绝",
"Password is required": "请提供密码", "Credentials are required": "需要凭证",
"noVNC encountered an error:": "noVNC 遇到一个错误:", "noVNC encountered an error:": "noVNC 遇到一个错误:",
"Hide/Show the control bar": "显示/隐藏控制栏", "Hide/Show the control bar": "显示/隐藏控制栏",
"Move/Drag Viewport": "拖放显示范围", "Drag": "拖动",
"viewport drag": "显示范围拖放", "Move/Drag viewport": "移动/拖动窗口",
"Active Mouse Button": "启动鼠标按鍵",
"No mousebutton": "禁用鼠标按鍵",
"Left mousebutton": "鼠标左鍵",
"Middle mousebutton": "鼠标中鍵",
"Right mousebutton": "鼠标右鍵",
"Keyboard": "键盘", "Keyboard": "键盘",
"Show Keyboard": "显示键盘", "Show keyboard": "显示键盘",
"Extra keys": "额外按键", "Extra keys": "额外按键",
"Show Extra Keys": "显示额外按键", "Show extra keys": "显示额外按键",
"Ctrl": "Ctrl", "Ctrl": "Ctrl",
"Toggle Ctrl": "切换 Ctrl", "Toggle Ctrl": "切换 Ctrl",
"Alt": "Alt", "Alt": "Alt",
"Toggle Alt": "切换 Alt", "Toggle Alt": "切换 Alt",
"Toggle Windows": "切换窗口",
"Windows": "窗口",
"Send Tab": "发送 Tab 键", "Send Tab": "发送 Tab 键",
"Tab": "Tab", "Tab": "Tab",
"Esc": "Esc", "Esc": "Esc",
"Send Escape": "发送 Escape 键", "Send Escape": "发送 Escape 键",
"Ctrl+Alt+Del": "Ctrl-Alt-Del", "Ctrl+Alt+Del": "Ctrl+Alt+Del",
"Send Ctrl-Alt-Del": "发送 Ctrl-Alt-Del 键", "Send Ctrl-Alt-Del": "发送 Ctrl+Alt+Del 键",
"Shutdown/Reboot": "关机/重", "Shutdown/Reboot": "关机/重启",
"Shutdown/Reboot...": "关机/重...", "Shutdown/Reboot...": "关机/重启...",
"Power": "电源", "Power": "电源",
"Shutdown": "关机", "Shutdown": "关机",
"Reboot": "重", "Reboot": "重启",
"Reset": "重置", "Reset": "重置",
"Clipboard": "剪贴板", "Clipboard": "剪贴板",
"Clear": "清除", "Edit clipboard content in the textarea below.": "在下面的文本区域中编辑剪贴板内容。",
"Fullscreen": "全屏", "Full screen": "全屏",
"Settings": "设置", "Settings": "设置",
"Shared Mode": "分享模式", "Shared mode": "分享模式",
"View Only": "仅查看", "View only": "仅查看",
"Clip to Window": "限制/裁切窗口大小", "Clip to window": "限制/裁切窗口大小",
"Scaling Mode:": "缩放模式:", "Scaling mode:": "缩放模式:",
"None": "无", "None": "无",
"Local Scaling": "本地缩放", "Local scaling": "本地缩放",
"Remote Resizing": "远程调整大小", "Remote resizing": "远程调整大小",
"Advanced": "高级", "Advanced": "高级",
"Quality:": "品质:",
"Compression level:": "压缩级别:",
"Repeater ID:": "中继站 ID", "Repeater ID:": "中继站 ID",
"WebSocket": "WebSocket", "WebSocket": "WebSocket",
"Encrypt": "加密", "Encrypt": "加密",
"Host:": "主机:", "Host:": "主机:",
"Port:": "端口:", "Port:": "端口:",
"Path:": "路径:", "Path:": "路径:",
"Automatic Reconnect": "自动重新连接", "Automatic reconnect": "自动重新连接",
"Reconnect Delay (ms):": "重新连接间隔 (ms)", "Reconnect delay (ms):": "重新连接间隔 (ms)",
"Show dot when no cursor": "无光标时显示点",
"Logging:": "日志级别:", "Logging:": "日志级别:",
"Disconnect": "中断连接", "Version:": "版本:",
"Disconnect": "断开连接",
"Connect": "连接", "Connect": "连接",
"Server identity": "服务器身份",
"The server has provided the following identifying information:": "服务器提供了以下识别信息:",
"Fingerprint:": "指纹:",
"Please verify that the information is correct and press \"Approve\". Otherwise press \"Reject\".": "请核实信息是否正确,并按 “同意”,否则按 “拒绝”。",
"Approve": "同意",
"Reject": "拒绝",
"Credentials": "凭证",
"Username:": "用户名:",
"Password:": "密码:", "Password:": "密码:",
"Cancel": "取消" "Send credentials": "发送凭证",
"Cancel": "取消",
"Password is required": "请提供密码",
"Disconnect timeout": "超时断开",
"viewport drag": "窗口拖动",
"Active Mouse Button": "启动鼠标按键",
"No mousebutton": "禁用鼠标按键",
"Left mousebutton": "鼠标左键",
"Middle mousebutton": "鼠标中键",
"Right mousebutton": "鼠标右键",
"Clear": "清除",
"Local Downscaling": "降低本地尺寸",
"Local Cursor": "本地光标",
"Canvas not supported.": "不支持 Canvas。"
} }

View File

@ -14,7 +14,7 @@
"Password is required": "請提供密碼", "Password is required": "請提供密碼",
"noVNC encountered an error:": "noVNC 遇到一個錯誤:", "noVNC encountered an error:": "noVNC 遇到一個錯誤:",
"Hide/Show the control bar": "顯示/隱藏控制列", "Hide/Show the control bar": "顯示/隱藏控制列",
"Move/Drag Viewport": "拖放顯示範圍", "Move/Drag viewport": "拖放顯示範圍",
"viewport drag": "顯示範圍拖放", "viewport drag": "顯示範圍拖放",
"Active Mouse Button": "啟用滑鼠按鍵", "Active Mouse Button": "啟用滑鼠按鍵",
"No mousebutton": "無滑鼠按鍵", "No mousebutton": "無滑鼠按鍵",
@ -22,9 +22,9 @@
"Middle mousebutton": "滑鼠中鍵", "Middle mousebutton": "滑鼠中鍵",
"Right mousebutton": "滑鼠右鍵", "Right mousebutton": "滑鼠右鍵",
"Keyboard": "鍵盤", "Keyboard": "鍵盤",
"Show Keyboard": "顯示鍵盤", "Show keyboard": "顯示鍵盤",
"Extra keys": "額外按鍵", "Extra keys": "額外按鍵",
"Show Extra Keys": "顯示額外按鍵", "Show extra keys": "顯示額外按鍵",
"Ctrl": "Ctrl", "Ctrl": "Ctrl",
"Toggle Ctrl": "切換 Ctrl", "Toggle Ctrl": "切換 Ctrl",
"Alt": "Alt", "Alt": "Alt",
@ -45,13 +45,13 @@
"Clear": "清除", "Clear": "清除",
"Fullscreen": "全螢幕", "Fullscreen": "全螢幕",
"Settings": "設定", "Settings": "設定",
"Shared Mode": "分享模式", "Shared mode": "分享模式",
"View Only": "僅檢視", "View only": "僅檢視",
"Clip to Window": "限制/裁切視窗大小", "Clip to window": "限制/裁切視窗大小",
"Scaling Mode:": "縮放模式:", "Scaling mode:": "縮放模式:",
"None": "無", "None": "無",
"Local Scaling": "本機縮放", "Local scaling": "本機縮放",
"Remote Resizing": "遠端調整大小", "Remote resizing": "遠端調整大小",
"Advanced": "進階", "Advanced": "進階",
"Repeater ID:": "中繼站 ID", "Repeater ID:": "中繼站 ID",
"WebSocket": "WebSocket", "WebSocket": "WebSocket",
@ -59,8 +59,8 @@
"Host:": "主機:", "Host:": "主機:",
"Port:": "連接埠:", "Port:": "連接埠:",
"Path:": "路徑:", "Path:": "路徑:",
"Automatic Reconnect": "自動重新連線", "Automatic reconnect": "自動重新連線",
"Reconnect Delay (ms):": "重新連線間隔 (ms)", "Reconnect delay (ms):": "重新連線間隔 (ms)",
"Logging:": "日誌級別:", "Logging:": "日誌級別:",
"Disconnect": "中斷連線", "Disconnect": "中斷連線",
"Connect": "連線", "Connect": "連線",

View File

@ -1,13 +1,13 @@
/* /*
* noVNC: HTML5 VNC client * noVNC: HTML5 VNC client
* Copyright (C) 2018 The noVNC Authors * Copyright (C) 2018 The noVNC authors
* Licensed under MPL 2.0 (see LICENSE.txt) * Licensed under MPL 2.0 (see LICENSE.txt)
* *
* See README.md for usage and integration instructions. * See README.md for usage and integration instructions.
*/ */
/* /*
* Localization Utilities * Localization utilities
*/ */
export class Localizer { export class Localizer {
@ -16,13 +16,19 @@ export class Localizer {
this.language = 'en'; this.language = 'en';
// Current dictionary of translations // Current dictionary of translations
this.dictionary = undefined; this._dictionary = undefined;
} }
// Configure suitable language based on user preferences // Configure suitable language based on user preferences
setup(supportedLanguages) { async setup(supportedLanguages, baseURL) {
this.language = 'en'; // Default: US English this.language = 'en'; // Default: US English
this._dictionary = undefined;
this._setupLanguage(supportedLanguages);
await this._setupDictionary(baseURL);
}
_setupLanguage(supportedLanguages) {
/* /*
* Navigator.languages only available in Chrome (32+) and FireFox (32+) * Navigator.languages only available in Chrome (32+) and FireFox (32+)
* Fall back to navigator.language for other browsers * Fall back to navigator.language for other browsers
@ -40,12 +46,6 @@ export class Localizer {
.replace("_", "-") .replace("_", "-")
.split("-"); .split("-");
// Built-in default?
if ((userLang[0] === 'en') &&
((userLang[1] === undefined) || (userLang[1] === 'us'))) {
return;
}
// First pass: perfect match // First pass: perfect match
for (let j = 0; j < supportedLanguages.length; j++) { for (let j = 0; j < supportedLanguages.length; j++) {
const supLang = supportedLanguages[j] const supLang = supportedLanguages[j]
@ -64,7 +64,12 @@ export class Localizer {
return; return;
} }
// Second pass: fallback // Second pass: English fallback
if (userLang[0] === 'en') {
return;
}
// Third pass pass: other fallback
for (let j = 0;j < supportedLanguages.length;j++) { for (let j = 0;j < supportedLanguages.length;j++) {
const supLang = supportedLanguages[j] const supLang = supportedLanguages[j]
.toLowerCase() .toLowerCase()
@ -84,10 +89,32 @@ export class Localizer {
} }
} }
async _setupDictionary(baseURL) {
if (baseURL) {
if (!baseURL.endsWith("/")) {
baseURL = baseURL + "/";
}
} else {
baseURL = "";
}
if (this.language === "en") {
return;
}
let response = await fetch(baseURL + this.language + ".json");
if (!response.ok) {
throw Error("" + response.status + " " + response.statusText);
}
this._dictionary = await response.json();
}
// Retrieve localised text // Retrieve localised text
get(id) { get(id) {
if (typeof this.dictionary !== 'undefined' && this.dictionary[id]) { if (typeof this._dictionary !== 'undefined' &&
return this.dictionary[id]; this._dictionary[id]) {
return this._dictionary[id];
} else { } else {
return id; return id;
} }

File diff suppressed because it is too large Load Diff

30
app/styles/constants.css Normal file
View File

@ -0,0 +1,30 @@
/*
* noVNC general CSS constant variables
* Copyright (C) 2025 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).
*/
/* ---------- COLORS ----------- */
:root {
--novnc-grey: rgb(128, 128, 128);
--novnc-lightgrey: rgb(192, 192, 192);
--novnc-darkgrey: rgb(92, 92, 92);
/* Transparent to make button colors adapt to the background */
--novnc-buttongrey: rgba(192, 192, 192, 0.5);
--novnc-blue: rgb(110, 132, 163);
--novnc-lightblue: rgb(74, 144, 217);
--novnc-darkblue: rgb(83, 99, 122);
--novnc-green: rgb(0, 128, 0);
--novnc-yellow: rgb(255, 255, 0);
}
/* ------ MISC PROPERTIES ------ */
:root {
--input-xpadding: 1em;
}

View File

@ -1,32 +1,170 @@
/* /*
* noVNC general input element CSS * noVNC general input element CSS
* Copyright (C) 2022 The noVNC Authors * Copyright (C) 2025 The noVNC authors
* noVNC is licensed under the MPL 2.0 (see LICENSE.txt) * noVNC is licensed under the MPL 2.0 (see LICENSE.txt)
* This file is licensed under the 2-Clause BSD license (see LICENSE.txt). * This file is licensed under the 2-Clause BSD license (see LICENSE.txt).
*/ */
/* /* ------- SHARED BETWEEN INPUT ELEMENTS -------- */
* Common for all inputs
*/
input, input::file-selector-button, button, select, textarea {
/* Respect standard font settings */
font: inherit;
/* Disable default rendering */ input,
appearance: none; textarea,
background: none; button,
select,
input::file-selector-button {
padding: 0.5em var(--input-xpadding);
border-radius: 6px;
appearance: none;
text-overflow: ellipsis;
padding: 5px; /* Respect standard font settings */
border: 1px solid rgb(192, 192, 192); font: inherit;
border-radius: 5px; line-height: 1.6;
color: black; }
--bg-gradient: linear-gradient(to top, rgb(255, 255, 255) 80%, rgb(240, 240, 240)); input:disabled,
background-image: var(--bg-gradient); textarea:disabled,
button:disabled,
select:disabled,
label[disabled] {
opacity: 0.4;
} }
/* input:focus-visible,
* Buttons textarea:focus-visible,
*/ button:focus-visible,
select:focus-visible,
input:focus-visible::file-selector-button {
outline: 2px solid var(--novnc-lightblue);
outline-offset: 1px;
}
/* ------- TEXT INPUT -------- */
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 {
border: 1px solid var(--novnc-lightgrey);
/* Account for borders on text inputs, buttons dont have borders */
padding: calc(0.5em - 1px) var(--input-xpadding);
}
input:not([type]):focus-visible,
input[type=date]:focus-visible,
input[type=datetime-local]:focus-visible,
input[type=email]:focus-visible,
input[type=month]:focus-visible,
input[type=number]:focus-visible,
input[type=password]:focus-visible,
input[type=search]:focus-visible,
input[type=tel]:focus-visible,
input[type=text]:focus-visible,
input[type=time]:focus-visible,
input[type=url]:focus-visible,
input[type=week]:focus-visible,
textarea:focus-visible {
outline-offset: -1px;
}
textarea {
margin: unset; /* Remove Firefox's built in margin */
/* Prevent layout from shifting when scrollbars show */
scrollbar-gutter: stable;
/* Make textareas show at minimum one line. This does not work when
using box-sizing border-box, in which case, vertical padding and
border width needs to be taken into account. */
min-height: 1lh;
vertical-align: baseline; /* Firefox gives "text-bottom" by default */
}
/* ------- NUMBER PICKERS ------- */
/* We can't style the number spinner buttons:
https://github.com/w3c/csswg-drafts/issues/8777 */
input[type=number]::-webkit-inner-spin-button,
input[type=number]::-webkit-outer-spin-button {
/* Get rid of increase/decrease buttons in WebKit */
appearance: none;
}
input[type=number] {
/* Get rid of increase/decrease buttons in Firefox */
appearance: textfield;
}
/* ------- BUTTON ACTIVATIONS -------- */
/* A color overlay that depends on the activation level. The level can then be
set for different states on an element, for example hover and click on a
<button>. */
input, button, select, option,
input::file-selector-button,
.button-activations {
--button-activation-level: 0;
/* Note that CSS variables aren't functions, beware when inheriting */
--button-activation-alpha: calc(0.08 * var(--button-activation-level));
/* FIXME: We want the image() function instead of the linear-gradient()
function below. But it's not supported in the browsers yet. */
--button-activation-overlay:
linear-gradient(rgba(0, 0, 0, var(--button-activation-alpha))
100%, transparent);
--button-activation-overlay-light:
linear-gradient(rgba(255, 255, 255, calc(0.23 * var(--button-activation-level)))
100%, transparent);
}
.button-activations {
background-image: var(--button-activation-overlay);
/* Disable Chrome's touch tap highlight to avoid conflicts with overlay */
-webkit-tap-highlight-color: transparent;
}
/* When we want the light overlay on activations instead.
This is best used on elements with darker backgrounds. */
.button-activations.light-overlay {
background-image: var(--button-activation-overlay-light);
/* Can't use the normal blend mode since that gives washed out colors. */
/* FIXME: For elements with these activation overlays we'd like only
the luminosity to change. The proprty "background-blend-mode" set
to "luminosity" sounds good, but it doesn't work as intended,
see: https://bugzilla.mozilla.org/show_bug.cgi?id=1806417 */
background-blend-mode: overlay;
}
input:hover, button:hover, select:hover, option:hover,
input::file-selector-button:hover,
.button-activations:hover {
--button-activation-level: 1;
}
/* Unfortunately we have to disable the :hover effect on touch devices,
otherwise the style lingers after tapping the button. */
@media (any-pointer: coarse) {
input:hover, button:hover, select:hover, option:hover,
input::file-selector-button:hover,
.button-activations:hover {
--button-activation-level: 0;
}
}
input:active, button:active, select:active, option:active,
input::file-selector-button:active,
.button-activations:active {
--button-activation-level: 2;
}
input:disabled, button:disabled, select:disabled, select:disabled option,
input:disabled::file-selector-button,
.button-activations:disabled {
--button-activation-level: 0;
}
/* ------- BUTTONS -------- */
input[type=button], input[type=button],
input[type=color], input[type=color],
input[type=image], input[type=image],
@ -35,226 +173,15 @@ input[type=submit],
input::file-selector-button, input::file-selector-button,
button, button,
select { select {
border-bottom-width: 2px; min-width: 8em;
border: none;
/* This avoids it jumping around when :active */ color: black;
vertical-align: middle; font-weight: bold;
margin-top: 0; background-color: var(--novnc-buttongrey);
background-image: var(--button-activation-overlay);
padding-left: 20px; cursor: pointer;
padding-right: 20px; /* Disable Chrome's touch tap highlight */
-webkit-tap-highlight-color: transparent;
/* 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] {
background-color: white;
background-image: unset;
border: 1px solid dimgrey;
border-radius: 3px;
width: 13px;
height: 13px;
padding: 0;
margin-right: 6px;
vertical-align: bottom;
transition: 0.2s background-color linear;
}
input[type=checkbox]:checked {
background-color: rgb(110, 132, 163);
border-color: rgb(110, 132, 163);
}
input[type=checkbox]:checked::after {
content: "";
display: block; /* width & height doesn't work on inline elements */
position: relative;
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=button]:disabled,
input[type=color]:disabled, input[type=color]:disabled,
@ -264,18 +191,438 @@ input[type=submit]:disabled,
input:disabled::file-selector-button, input:disabled::file-selector-button,
button:disabled, button:disabled,
select:disabled { select:disabled {
background-image: var(--bg-gradient); /* See Firefox bug:
border-bottom-width: 2px; https://bugzilla.mozilla.org/show_bug.cgi?id=1798304 */
margin-top: 0; cursor: default;
} }
input[type=file]:disabled {
background-image: none; input[type=button],
input[type=color],
input[type=reset],
input[type=submit] {
/* Workaround for text-overflow bugs in Firefox and Chromium:
https://bugzilla.mozilla.org/show_bug.cgi?id=1800077
https://bugs.chromium.org/p/chromium/issues/detail?id=1383144 */
overflow: clip;
}
/* ------- COLOR PICKERS ------- */
input[type=color] {
min-width: unset;
box-sizing: content-box;
width: 1.4em;
height: 1.4em;
}
input[type=color]::-webkit-color-swatch-wrapper {
padding: 0;
}
/* -webkit-color-swatch & -moz-color-swatch cant be in a selector list:
https://bugs.chromium.org/p/chromium/issues/detail?id=1154623 */
input[type=color]::-webkit-color-swatch {
border: none;
border-radius: 6px;
}
input[type=color]::-moz-color-swatch {
border: none;
border-radius: 6px;
}
/* -- SHARED BETWEEN CHECKBOXES, RADIOBUTTONS AND THE TOGGLE CLASS -- */
input[type=radio],
input[type=checkbox] {
display: inline-flex;
justify-content: center;
align-items: center;
background-color: var(--novnc-buttongrey);
background-image: var(--button-activation-overlay);
/* Disable Chrome's touch tap highlight to avoid conflicts with overlay */
-webkit-tap-highlight-color: transparent;
width: 16px;
--checkradio-height: 16px;
height: var(--checkradio-height);
padding: 0;
margin: 0 6px 0 0;
/* Don't have transitions for outline in order to be consistent
with other elements */
transition: all 0.2s, outline-color 0s, outline-offset 0s;
/* A transparent outline in order to work around a graphical clipping issue
in WebKit. See bug: https://bugs.webkit.org/show_bug.cgi?id=256003 */
outline: 1px solid transparent;
position: relative; /* Since ::before & ::after are absolute positioned */
/* We want to align with the middle of capital letters, this requires
a workaround. The default behavior is to align the bottom of the element
on top of the text baseline, this is too far up.
We want to push the element down half the difference in height between
it and a capital X. In our font, the height of a capital "X" is 0.698em.
*/
vertical-align: calc(0px - (var(--checkradio-height) - 0.698em) / 2);
/* FIXME: Could write 1cap instead of 0.698em, but it's only supported in
Firefox as of 2023 */
/* FIXME: We probably want to use round() here, see bug 8148 */
}
input[type=radio]:focus-visible,
input[type=checkbox]:focus-visible {
outline-color: var(--novnc-lightblue);
}
input[type=checkbox]::before,
input[type=checkbox]:not(.toggle)::after,
input[type=radio]::before,
input[type=radio]::after {
content: "";
display: block; /* width & height doesn't work on inline elements */
transition: inherit;
/* Let's prevent the pseudo-elements from taking up layout space so that
the ::before and ::after pseudo-elements can be in the same place. This
is also required for vertical-align: baseline to work like we want it to
on radio/checkboxes. If the pseudo-elements take up layout space, the
baseline of text inside them will be used instead. */
position: absolute;
}
input[type=checkbox]:not(.toggle)::after,
input[type=radio]::after {
width: 10px;
height: 2px;
background-color: transparent;
border-radius: 2px;
}
/* ------- CHECKBOXES ------- */
input[type=checkbox]:not(.toggle) {
border-radius: 4px;
}
input[type=checkbox]:not(.toggle):checked,
input[type=checkbox]:not(.toggle):indeterminate {
background-color: var(--novnc-blue);
background-image: var(--button-activation-overlay-light);
background-blend-mode: overlay;
}
input[type=checkbox]:not(.toggle)::before {
width: 25%;
height: 55%;
border-style: solid;
border-color: transparent;
border-width: 0 2px 2px 0;
border-radius: 1px;
transform: translateY(-1px) rotate(35deg);
}
input[type=checkbox]:not(.toggle):checked::before {
border-color: white;
}
input[type=checkbox]:not(.toggle):indeterminate::after {
background-color: white;
}
/* ------- RADIO BUTTONS ------- */
input[type=radio] {
border-radius: 50%;
border: 1px solid transparent; /* To ensure a smooth transition */
}
input[type=radio]:checked {
border: 4px solid var(--novnc-blue);
background-color: white;
/* button-activation-overlay should be removed from the radio
element to not interfere with button-activation-overlay-light
that is set on the ::before element. */
background-image: none;
}
input[type=radio]::before {
width: inherit;
height: inherit;
border-radius: inherit;
/* We can achieve the highlight overlay effect on border colors by
setting button-activation-overlay-light on an element that stays
on top (z-axis) of the element with a border. */
background-image: var(--button-activation-overlay-light);
mix-blend-mode: overlay;
opacity: 0;
}
input[type=radio]:checked::before {
opacity: 1;
}
input[type=radio]:indeterminate::after {
background-color: black;
}
/* ------- TOGGLE SWITCHES ------- */
/* These are meant to be used instead of checkboxes in some cases. If all of
the following critera are true you should use a toggle switch:
* The choice is a simple ON/OFF or ENABLE/DISABLE
* The choice doesn't give the feeling of "I agree" or "I confirm"
* There are not multiple related & grouped options
*/
input[type=checkbox].toggle {
display: inline-block;
--checkradio-height: 18px; /* Height value used in calc, see above */
width: 31px;
cursor: pointer;
user-select: none;
-webkit-user-select: none;
border-radius: 9px;
}
input[type=checkbox].toggle:disabled {
cursor: default;
}
input[type=checkbox].toggle:indeterminate {
background-color: var(--novnc-buttongrey);
background-image: var(--button-activation-overlay);
}
input[type=checkbox].toggle:checked {
background-color: var(--novnc-blue);
background-image: var(--button-activation-overlay-light);
background-blend-mode: overlay;
}
input[type=checkbox].toggle::before {
--circle-diameter: 10px;
--circle-offset: 4px;
width: var(--circle-diameter);
height: var(--circle-diameter);
top: var(--circle-offset);
left: var(--circle-offset);
background: white;
border-radius: 6px;
}
input[type=checkbox].toggle:checked::before {
left: calc(100% - var(--circle-offset) - var(--circle-diameter));
}
input[type=checkbox].toggle:indeterminate::before {
left: calc(50% - var(--circle-diameter) / 2);
}
/* ------- RANGE SLIDERS ------- */
input[type=range] {
border: unset;
border-radius: 8px;
height: 15px;
padding: 0;
background: transparent;
/* Needed to get properly rounded corners on -moz-range-progress
when the thumb is all the way to the right. Without overflow
hidden, the pointy edges of the progress track shows to the
right of the thumb. */
overflow: hidden;
}
@supports selector(::-webkit-slider-thumb) {
input[type=range] {
/* Needs a fixed width to match clip-path */
width: 125px;
/* overflow: hidden is not ideal for hiding the left part of the box
shadow of -webkit-slider-thumb since it doesn't match the smaller
border-radius of the progress track. The below clip-path has two
circular sides to make the ends of the track have correctly rounded
corners. The clip path shape looks something like this:
+-------------------------------+
/---| |---\
| |
\---| |---/
+-------------------------------+
The larger middle part of the clip path is made to have room for the
thumb. By using margins on the track, we prevent the thumb from
touching the ends of the track.
*/
clip-path: path(' \
M 4.5 3 \
L 4.5 0 \
L 120.5 0 \
L 120.5 3 \
A 1 1 0 0 1 120.5 12 \
L 120.5 15 \
L 4.5 15 \
L 4.5 12 \
A 1 1 0 0 1 4.5 3 \
');
}
}
input[type=range]:hover {
cursor: grab;
}
input[type=range]:active {
cursor: grabbing;
}
input[type=range]:disabled {
cursor: default;
}
input[type=range]:focus-visible {
clip-path: none; /* Otherwise it hides the outline */
}
/* -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: var(--novnc-buttongrey);
height: 7px;
border-radius: 4px;
margin: 0 3px;
}
input[type=range]::-moz-range-track {
background-color: var(--novnc-buttongrey);
height: 7px;
border-radius: 4px;
}
input[type=range]::-moz-range-progress {
background-color: var(--novnc-blue);
height: 9px;
/* Needs rounded corners only on the left side. Otherwise the rounding of
the progress track starts before the thumb, when the thumb is close to
the left edge. */
border-radius: 5px 0 0 5px;
}
input[type=range]::-webkit-slider-thumb {
appearance: none;
width: 15px;
height: 15px;
border-radius: 50%;
background-color: white;
background-image: var(--button-activation-overlay);
/* Disable Chrome's touch tap highlight to avoid conflicts with overlay */
-webkit-tap-highlight-color: transparent;
border: 3px solid var(--novnc-blue);
margin-top: -4px; /* (track height / 2) - (thumb height /2) */
/* Since there is no way to style the left part of the range track in
webkit, we add a large shadow (1000px wide) to the left of the thumb and
then crop it with a clip-path shaped like this:
___
+-------------------/ \
| progress |Thumb|
+-------------------\ ___ /
The large left part of the shadow is clipped by another clip-path on on
the main range input element. */
/* FIXME: We can remove the box shadow workaround when this is standardized:
https://github.com/w3c/csswg-drafts/issues/4410 */
box-shadow: calc(-100vw - 8px) 0 0 100vw var(--novnc-blue);
clip-path: path(' \
M -1000 3 \
L 3 3 \
L 15 7.5 \
A 1 1 0 0 1 0 7.5 \
A 1 1 0 0 1 15 7.5 \
L 3 12 \
L -1000 12 Z \
');
}
input[type=range]::-moz-range-thumb {
appearance: none;
width: 15px;
height: 15px;
border-radius: 50%;
box-sizing: border-box;
background-color: white;
background-image: var(--button-activation-overlay);
border: 3px solid var(--novnc-blue);
margin-top: -7px;
}
/* ------- FILE CHOOSERS ------- */
input[type=file] {
background-image: none;
border: none;
}
input::file-selector-button {
margin-right: 6px;
}
input[type=file]:focus-visible {
outline: none; /* We outline the button instead of the entire element */
}
/* ------- SELECT BUTTONS ------- */
select {
--select-arrow: url('data:image/svg+xml;utf8, \
<svg width="11" height="6" version="1.1" viewBox="0 0 11 6" \
xmlns="http://www.w3.org/2000/svg"> \
<path d="m10.5.5-5 5-5-5" fill="none" \
stroke="black" stroke-width="1.5" \
stroke-linecap="round" stroke-linejoin="round"/> \
</svg>');
/* FIXME: A bug in Firefox, requires a workaround for the background:
https://bugzilla.mozilla.org/show_bug.cgi?id=1810958 */
/* The dropdown list will show the select element's background above and
below the options in Firefox. We want the entire dropdown to be white. */
background-color: white;
/* However, we don't want the select element to actually show a white
background, so let's place a gradient above it with the color we want. */
--grey-background: linear-gradient(var(--novnc-buttongrey) 100%,
transparent);
background-image:
var(--select-arrow),
var(--button-activation-overlay),
var(--grey-background);
background-position: calc(100% - var(--input-xpadding)), left top, left top;
background-repeat: no-repeat;
padding-right: calc(2*var(--input-xpadding) + 11px);
overflow: auto;
}
/* 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="11" height="6" version="1.1" viewBox="0 0 11 6" \
xmlns="http://www.w3.org/2000/svg" transform="rotate(180)"> \
<path d="m10.5.5-5 5-5-5" fill="none" \
stroke="black" stroke-width="1.5" \
stroke-linecap="round" stroke-linejoin="round"/> \
</svg>'),
var(--button-activation-overlay),
var(--grey-background);
} }
select:disabled { select:disabled {
background-image: var(--select-arrow), var(--bg-gradient); background-image:
var(--select-arrow),
var(--grey-background);
} }
input[type=image]:disabled { /* Note that styling for <option> doesn't work in all browsers
/* See Firefox bug: since its often drawn directly by the OS. We are generally very
https://bugzilla.mozilla.org/show_bug.cgi?id=1798304 */ limited in what we can change here. */
cursor: default; option {
/* Prevent Chrome from inheriting background-color from the <select> */
background-color: white;
color: black;
font-weight: normal;
background-image: var(--button-activation-overlay);
}
option:checked {
background-color: var(--novnc-lightgrey);
}
/* Change the look when the <select> isn't used as a dropdown. When "size"
or "multiple" are set, these elements behaves more like lists. */
select[size]:not([size="1"]), select[multiple] {
background-color: white;
background-image: unset; /* Don't show the arrow and other gradients */
border: 1px solid var(--novnc-lightgrey);
padding: 0;
font-weight: normal; /* Without this, options get bold font in WebKit. */
/* As an exception to the "list"-look, multi-selects in Chrome on Android,
and Safari on iOS, are unfortunately designed to be shown as a single
line. We can mitigate this inconsistency by at least fixing the height
here. By setting a min-height that matches other input elements, it
doesn't look too much out of place:
(1px border * 2) + (6.5px padding * 2) + 24px line-height = 39px */
min-height: 39px;
}
select[size]:not([size="1"]):focus-visible,
select[multiple]:focus-visible {
/* Text input style focus-visible highlight */
outline-offset: -1px;
}
select[size]:not([size="1"]) option, select[multiple] option {
overflow: hidden;
text-overflow: ellipsis;
padding: 4px var(--input-xpadding);
} }

198
app/ui.js
View File

@ -1,6 +1,6 @@
/* /*
* noVNC: HTML5 VNC client * noVNC: HTML5 VNC client
* Copyright (C) 2019 The noVNC Authors * Copyright (C) 2019 The noVNC authors
* Licensed under MPL 2.0 (see LICENSE.txt) * Licensed under MPL 2.0 (see LICENSE.txt)
* *
* See README.md for usage and integration instructions. * See README.md for usage and integration instructions.
@ -20,8 +20,12 @@ import * as WebUtil from "./webutil.js";
const PAGE_TITLE = "noVNC"; const PAGE_TITLE = "noVNC";
const LINGUAS = ["cs", "de", "el", "es", "fr", "it", "ja", "ko", "nl", "pl", "pt_BR", "ru", "sv", "tr", "zh_CN", "zh_TW"];
const UI = { const UI = {
customSettings: {},
connected: false, connected: false,
desktopName: "", desktopName: "",
@ -42,20 +46,31 @@ const UI = {
reconnectCallback: null, reconnectCallback: null,
reconnectPassword: null, reconnectPassword: null,
prime() { async start(options={}) {
return WebUtil.initSettings().then(() => { UI.customSettings = options.settings || {};
if (document.readyState === "interactive" || document.readyState === "complete") { if (UI.customSettings.defaults === undefined) {
return UI.start(); UI.customSettings.defaults = {};
} }
if (UI.customSettings.mandatory === undefined) {
UI.customSettings.mandatory = {};
}
return new Promise((resolve, reject) => { // Set up translations
document.addEventListener('DOMContentLoaded', () => UI.start().then(resolve).catch(reject)); try {
await l10n.setup(LINGUAS, "app/locale/");
} catch (err) {
Log.Error("Failed to load translations: " + err);
}
// Initialize setting storage
await WebUtil.initSettings();
// Wait for the page to load
if (document.readyState !== "interactive" && document.readyState !== "complete") {
await new Promise((resolve, reject) => {
document.addEventListener('DOMContentLoaded', resolve);
}); });
}); }
},
// Render default UI and initialize settings menu
start() {
UI.initSettings(); UI.initSettings();
@ -66,26 +81,24 @@ const UI = {
// insecure context // insecure context
if (!window.isSecureContext) { if (!window.isSecureContext) {
// FIXME: This gets hidden when connecting // FIXME: This gets hidden when connecting
UI.showStatus(_("HTTPS is required for full functionality"), 'error'); UI.showStatus(_("Running without HTTPS is not recommended, crashes or other issues are likely."), 'error');
} }
// Try to fetch version number // Try to fetch version number
fetch('./package.json') try {
.then((response) => { let response = await fetch('./package.json');
if (!response.ok) { if (!response.ok) {
throw Error("" + response.status + " " + response.statusText); throw Error("" + response.status + " " + response.statusText);
} }
return response.json();
}) let packageInfo = await response.json();
.then((packageInfo) => { Array.from(document.getElementsByClassName('noVNC_version')).forEach(el => el.innerText = packageInfo.version);
Array.from(document.getElementsByClassName('noVNC_version')).forEach(el => el.innerText = packageInfo.version); } catch (err) {
}) Log.Error("Couldn't fetch package.json: " + err);
.catch((err) => { Array.from(document.getElementsByClassName('noVNC_version_wrapper'))
Log.Error("Couldn't fetch package.json: " + err); .concat(Array.from(document.getElementsByClassName('noVNC_version_separator')))
Array.from(document.getElementsByClassName('noVNC_version_wrapper')) .forEach(el => el.style.display = 'none');
.concat(Array.from(document.getElementsByClassName('noVNC_version_separator'))) }
.forEach(el => el.style.display = 'none');
});
// Adapt the interface for touch screen devices // Adapt the interface for touch screen devices
if (isTouchDevice) { if (isTouchDevice) {
@ -120,7 +133,7 @@ const UI = {
document.documentElement.classList.remove("noVNC_loading"); document.documentElement.classList.remove("noVNC_loading");
let autoconnect = WebUtil.getConfigVar('autoconnect', false); let autoconnect = UI.getSetting('autoconnect');
if (autoconnect === 'true' || autoconnect == '1') { if (autoconnect === 'true' || autoconnect == '1') {
autoconnect = true; autoconnect = true;
UI.connect(); UI.connect();
@ -129,8 +142,6 @@ const UI = {
// Show the connect panel on first load unless autoconnecting // Show the connect panel on first load unless autoconnecting
UI.openConnectPanel(); UI.openConnectPanel();
} }
return Promise.resolve(UI.rfb);
}, },
initFullscreen() { initFullscreen() {
@ -158,34 +169,26 @@ const UI = {
UI.initSetting('logging', 'warn'); UI.initSetting('logging', 'warn');
UI.updateLogging(); UI.updateLogging();
// if port == 80 (or 443) then it won't be present and should be UI.setupSettingLabels();
// set manually
let port = window.location.port;
if (!port) {
if (window.location.protocol.substring(0, 5) == 'https') {
port = 443;
} else if (window.location.protocol.substring(0, 4) == 'http') {
port = 80;
}
}
/* Populate the controls if defaults are provided in the URL */ /* Populate the controls if defaults are provided in the URL */
UI.initSetting('host', window.location.hostname); UI.initSetting('host', '');
UI.initSetting('port', port); UI.initSetting('port', 0);
UI.initSetting('encrypt', (window.location.protocol === "https:")); UI.initSetting('encrypt', (window.location.protocol === "https:"));
UI.initSetting('password');
UI.initSetting('autoconnect', false);
UI.initSetting('view_clip', false); UI.initSetting('view_clip', false);
UI.initSetting('resize', 'off'); UI.initSetting('resize', 'off');
UI.initSetting('quality', 6); UI.initSetting('quality', 6);
UI.initSetting('compression', 2); UI.initSetting('compression', 2);
UI.initSetting('shared', true); UI.initSetting('shared', true);
UI.initSetting('bell', 'on');
UI.initSetting('view_only', false); UI.initSetting('view_only', false);
UI.initSetting('show_dot', false); UI.initSetting('show_dot', false);
UI.initSetting('path', 'websockify'); UI.initSetting('path', 'websockify');
UI.initSetting('repeaterID', ''); UI.initSetting('repeaterID', '');
UI.initSetting('reconnect', false); UI.initSetting('reconnect', false);
UI.initSetting('reconnect_delay', 5000); UI.initSetting('reconnect_delay', 5000);
UI.setupSettingLabels();
}, },
// Adds a link to the label elements on the corresponding input elements // Adds a link to the label elements on the corresponding input elements
setupSettingLabels() { setupSettingLabels() {
@ -747,6 +750,10 @@ const UI = {
// Initial page load read/initialization of settings // Initial page load read/initialization of settings
initSetting(name, defVal) { initSetting(name, defVal) {
// Has the user overridden the default value?
if (name in UI.customSettings.defaults) {
defVal = UI.customSettings.defaults[name];
}
// Check Query string followed by cookie // Check Query string followed by cookie
let val = WebUtil.getConfigVar(name); let val = WebUtil.getConfigVar(name);
if (val === null) { if (val === null) {
@ -754,6 +761,11 @@ const UI = {
} }
WebUtil.setSetting(name, val); WebUtil.setSetting(name, val);
UI.updateSetting(name); UI.updateSetting(name);
// Has the user forced a value?
if (name in UI.customSettings.mandatory) {
val = UI.customSettings.mandatory[name];
UI.forceSetting(name, val);
}
return val; return val;
}, },
@ -772,9 +784,12 @@ const UI = {
let value = UI.getSetting(name); let value = UI.getSetting(name);
const ctrl = document.getElementById('noVNC_setting_' + name); const ctrl = document.getElementById('noVNC_setting_' + name);
if (ctrl === null) {
return;
}
if (ctrl.type === 'checkbox') { if (ctrl.type === 'checkbox') {
ctrl.checked = value; ctrl.checked = value;
} else if (typeof ctrl.options !== 'undefined') { } else if (typeof ctrl.options !== 'undefined') {
for (let i = 0; i < ctrl.options.length; i += 1) { for (let i = 0; i < ctrl.options.length; i += 1) {
if (ctrl.options[i].value === value) { if (ctrl.options[i].value === value) {
@ -807,7 +822,8 @@ const UI = {
getSetting(name) { getSetting(name) {
const ctrl = document.getElementById('noVNC_setting_' + name); const ctrl = document.getElementById('noVNC_setting_' + name);
let val = WebUtil.readSetting(name); let val = WebUtil.readSetting(name);
if (typeof val !== 'undefined' && val !== null && ctrl.type === 'checkbox') { if (typeof val !== 'undefined' && val !== null &&
ctrl !== null && ctrl.type === 'checkbox') {
if (val.toString().toLowerCase() in {'0': 1, 'no': 1, 'false': 1}) { if (val.toString().toLowerCase() in {'0': 1, 'no': 1, 'false': 1}) {
val = false; val = false;
} else { } else {
@ -822,14 +838,22 @@ const UI = {
// disable the labels that belong to disabled input elements. // disable the labels that belong to disabled input elements.
disableSetting(name) { disableSetting(name) {
const ctrl = document.getElementById('noVNC_setting_' + name); const ctrl = document.getElementById('noVNC_setting_' + name);
ctrl.disabled = true; if (ctrl !== null) {
ctrl.label.classList.add('noVNC_disabled'); ctrl.disabled = true;
if (ctrl.label !== undefined) {
ctrl.label.classList.add('noVNC_disabled');
}
}
}, },
enableSetting(name) { enableSetting(name) {
const ctrl = document.getElementById('noVNC_setting_' + name); const ctrl = document.getElementById('noVNC_setting_' + name);
ctrl.disabled = false; if (ctrl !== null) {
ctrl.label.classList.remove('noVNC_disabled'); ctrl.disabled = false;
if (ctrl.label !== undefined) {
ctrl.label.classList.remove('noVNC_disabled');
}
}
}, },
/* ------^------- /* ------^-------
@ -1011,7 +1035,7 @@ const UI = {
const path = UI.getSetting('path'); const path = UI.getSetting('path');
if (typeof password === 'undefined') { if (typeof password === 'undefined') {
password = WebUtil.getConfigVar('password'); password = UI.getSetting('password');
UI.reconnectPassword = password; UI.reconnectPassword = password;
} }
@ -1021,30 +1045,46 @@ const UI = {
UI.hideStatus(); UI.hideStatus();
if (!host) {
Log.Error("Can't connect when host is: " + host);
UI.showStatus(_("Must set host"), 'error');
return;
}
UI.closeConnectPanel(); UI.closeConnectPanel();
UI.updateVisualState('connecting'); UI.updateVisualState('connecting');
let url; let url;
url = UI.getSetting('encrypt') ? 'wss' : 'ws'; if (host) {
url = new URL("https://" + host);
url += '://' + host; url.protocol = UI.getSetting('encrypt') ? 'wss:' : 'ws:';
if (port) { if (port) {
url += ':' + port; url.port = port;
}
// "./" is needed to force URL() to interpret the path-variable as
// a path and not as an URL. This is relevant if for example path
// starts with more than one "/", in which case it would be
// interpreted as a host name instead.
url = new URL("./" + path, url);
} else {
// Current (May 2024) browsers support relative WebSocket
// URLs natively, but we need to support older browsers for
// some time.
url = new URL(path, location.href);
url.protocol = (window.location.protocol === "https:") ? 'wss:' : 'ws:';
}
try {
UI.rfb = new RFB(document.getElementById('noVNC_container'),
url.href,
{ shared: UI.getSetting('shared'),
repeaterID: UI.getSetting('repeaterID'),
credentials: { password: password } });
} catch (exc) {
Log.Error("Failed to connect to server: " + exc);
UI.updateVisualState('disconnected');
UI.showStatus(_("Failed to connect to server: ") + exc, 'error');
return;
} }
url += '/' + path;
UI.rfb = new RFB(document.getElementById('noVNC_container'), url,
{ shared: UI.getSetting('shared'),
repeaterID: UI.getSetting('repeaterID'),
credentials: { password: password } });
UI.rfb.addEventListener("connect", UI.connectFinished); UI.rfb.addEventListener("connect", UI.connectFinished);
UI.rfb.addEventListener("disconnect", UI.disconnectFinished); UI.rfb.addEventListener("disconnect", UI.disconnectFinished);
UI.rfb.addEventListener("serververification", UI.serverVerify); UI.rfb.addEventListener("serververification", UI.serverVerify);
@ -1730,7 +1770,7 @@ const UI = {
}, },
bell(e) { bell(e) {
if (WebUtil.getConfigVar('bell', 'on') === 'on') { if (UI.getSetting('bell') === 'on') {
const promise = document.getElementById('noVNC_bell').play(); const promise = document.getElementById('noVNC_bell').play();
// The standards disagree on the return value here // The standards disagree on the return value here
if (promise) { if (promise) {
@ -1761,22 +1801,4 @@ const UI = {
*/ */
}; };
// Set up translations
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 {
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);
}
export default UI; export default UI;

View File

@ -1,21 +1,21 @@
/* /*
* noVNC: HTML5 VNC client * noVNC: HTML5 VNC client
* Copyright (C) 2019 The noVNC Authors * Copyright (C) 2019 The noVNC authors
* Licensed under MPL 2.0 (see LICENSE.txt) * Licensed under MPL 2.0 (see LICENSE.txt)
* *
* See README.md for usage and integration instructions. * See README.md for usage and integration instructions.
*/ */
import { initLogging as mainInitLogging } from '../core/util/logging.js'; import * as Log from '../core/util/logging.js';
// init log level reading the logging HTTP param // init log level reading the logging HTTP param
export function initLogging(level) { export function initLogging(level) {
"use strict"; "use strict";
if (typeof level !== "undefined") { if (typeof level !== "undefined") {
mainInitLogging(level); Log.initLogging(level);
} else { } else {
const param = document.location.href.match(/logging=([A-Za-z0-9._-]*)/); const param = document.location.href.match(/logging=([A-Za-z0-9._-]*)/);
mainInitLogging(param || undefined); Log.initLogging(param || undefined);
} }
} }
@ -25,14 +25,14 @@ export function initLogging(level) {
// //
// For privacy (Using a hastag #, the parameters will not be sent to the server) // For privacy (Using a hastag #, the parameters will not be sent to the server)
// the url can be requested in the following way: // the url can be requested in the following way:
// https://www.example.com#myqueryparam=myvalue&password=secreatvalue // https://www.example.com#myqueryparam=myvalue&password=secretvalue
// //
// Even Mixing public and non public parameters will work: // Even mixing public and non public parameters will work:
// https://www.example.com?nonsecretparam=example.com#password=secreatvalue // https://www.example.com?nonsecretparam=example.com#password=secretvalue
export function getQueryVar(name, defVal) { export function getQueryVar(name, defVal) {
"use strict"; "use strict";
const re = new RegExp('.*[?&]' + name + '=([^&#]*)'), const re = new RegExp('.*[?&]' + name + '=([^&#]*)'),
match = ''.concat(document.location.href, window.location.hash).match(re); match = document.location.href.match(re);
if (typeof defVal === 'undefined') { defVal = null; } if (typeof defVal === 'undefined') { defVal = null; }
if (match) { if (match) {
@ -146,7 +146,7 @@ export function writeSetting(name, value) {
if (window.chrome && window.chrome.storage) { if (window.chrome && window.chrome.storage) {
window.chrome.storage.sync.set(settings); window.chrome.storage.sync.set(settings);
} else { } else {
localStorage.setItem(name, value); localStorageSet(name, value);
} }
} }
@ -156,7 +156,7 @@ export function readSetting(name, defaultValue) {
if ((name in settings) || (window.chrome && window.chrome.storage)) { if ((name in settings) || (window.chrome && window.chrome.storage)) {
value = settings[name]; value = settings[name];
} else { } else {
value = localStorage.getItem(name); value = localStorageGet(name);
settings[name] = value; settings[name] = value;
} }
if (typeof value === "undefined") { if (typeof value === "undefined") {
@ -181,6 +181,70 @@ export function eraseSetting(name) {
if (window.chrome && window.chrome.storage) { if (window.chrome && window.chrome.storage) {
window.chrome.storage.sync.remove(name); window.chrome.storage.sync.remove(name);
} else { } else {
localStorage.removeItem(name); localStorageRemove(name);
}
}
let loggedMsgs = [];
function logOnce(msg, level = "warn") {
if (!loggedMsgs.includes(msg)) {
switch (level) {
case "error":
Log.Error(msg);
break;
case "warn":
Log.Warn(msg);
break;
case "debug":
Log.Debug(msg);
break;
default:
Log.Info(msg);
}
loggedMsgs.push(msg);
}
}
let cookiesMsg = "Couldn't access noVNC settings, are cookies disabled?";
function localStorageGet(name) {
let r;
try {
r = localStorage.getItem(name);
} catch (e) {
if (e instanceof DOMException) {
logOnce(cookiesMsg);
logOnce("'localStorage.getItem(" + name + ")' failed: " + e,
"debug");
} else {
throw e;
}
}
return r;
}
function localStorageSet(name, value) {
try {
localStorage.setItem(name, value);
} catch (e) {
if (e instanceof DOMException) {
logOnce(cookiesMsg);
logOnce("'localStorage.setItem(" + name + "," + value +
")' failed: " + e, "debug");
} else {
throw e;
}
}
}
function localStorageRemove(name) {
try {
localStorage.removeItem(name);
} catch (e) {
if (e instanceof DOMException) {
logOnce(cookiesMsg);
logOnce("'localStorage.removeItem(" + name + ")' failed: " + e,
"debug");
} else {
throw e;
}
} }
} }

178
core/crypto/aes.js Normal file
View File

@ -0,0 +1,178 @@
export class AESECBCipher {
constructor() {
this._key = null;
}
get algorithm() {
return { name: "AES-ECB" };
}
static async importKey(key, _algorithm, extractable, keyUsages) {
const cipher = new AESECBCipher;
await cipher._importKey(key, extractable, keyUsages);
return cipher;
}
async _importKey(key, extractable, keyUsages) {
this._key = await window.crypto.subtle.importKey(
"raw", key, {name: "AES-CBC"}, extractable, keyUsages);
}
async encrypt(_algorithm, plaintext) {
const x = new Uint8Array(plaintext);
if (x.length % 16 !== 0 || this._key === null) {
return null;
}
const n = x.length / 16;
for (let i = 0; i < n; i++) {
const y = new Uint8Array(await window.crypto.subtle.encrypt({
name: "AES-CBC",
iv: new Uint8Array(16),
}, this._key, x.slice(i * 16, i * 16 + 16))).slice(0, 16);
x.set(y, i * 16);
}
return x;
}
}
export class AESEAXCipher {
constructor() {
this._rawKey = null;
this._ctrKey = null;
this._cbcKey = null;
this._zeroBlock = new Uint8Array(16);
this._prefixBlock0 = this._zeroBlock;
this._prefixBlock1 = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);
this._prefixBlock2 = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2]);
}
get algorithm() {
return { name: "AES-EAX" };
}
async _encryptBlock(block) {
const encrypted = await window.crypto.subtle.encrypt({
name: "AES-CBC",
iv: this._zeroBlock,
}, this._cbcKey, block);
return new Uint8Array(encrypted).slice(0, 16);
}
async _initCMAC() {
const k1 = await this._encryptBlock(this._zeroBlock);
const k2 = new Uint8Array(16);
const v = k1[0] >>> 6;
for (let i = 0; i < 15; i++) {
k2[i] = (k1[i + 1] >> 6) | (k1[i] << 2);
k1[i] = (k1[i + 1] >> 7) | (k1[i] << 1);
}
const lut = [0x0, 0x87, 0x0e, 0x89];
k2[14] ^= v >>> 1;
k2[15] = (k1[15] << 2) ^ lut[v];
k1[15] = (k1[15] << 1) ^ lut[v >> 1];
this._k1 = k1;
this._k2 = k2;
}
async _encryptCTR(data, counter) {
const encrypted = await window.crypto.subtle.encrypt({
name: "AES-CTR",
counter: counter,
length: 128
}, this._ctrKey, data);
return new Uint8Array(encrypted);
}
async _decryptCTR(data, counter) {
const decrypted = await window.crypto.subtle.decrypt({
name: "AES-CTR",
counter: counter,
length: 128
}, this._ctrKey, data);
return new Uint8Array(decrypted);
}
async _computeCMAC(data, prefixBlock) {
if (prefixBlock.length !== 16) {
return null;
}
const n = Math.floor(data.length / 16);
const m = Math.ceil(data.length / 16);
const r = data.length - n * 16;
const cbcData = new Uint8Array((m + 1) * 16);
cbcData.set(prefixBlock);
cbcData.set(data, 16);
if (r === 0) {
for (let i = 0; i < 16; i++) {
cbcData[n * 16 + i] ^= this._k1[i];
}
} else {
cbcData[(n + 1) * 16 + r] = 0x80;
for (let i = 0; i < 16; i++) {
cbcData[(n + 1) * 16 + i] ^= this._k2[i];
}
}
let cbcEncrypted = await window.crypto.subtle.encrypt({
name: "AES-CBC",
iv: this._zeroBlock,
}, this._cbcKey, cbcData);
cbcEncrypted = new Uint8Array(cbcEncrypted);
const mac = cbcEncrypted.slice(cbcEncrypted.length - 32, cbcEncrypted.length - 16);
return mac;
}
static async importKey(key, _algorithm, _extractable, _keyUsages) {
const cipher = new AESEAXCipher;
await cipher._importKey(key);
return cipher;
}
async _importKey(key) {
this._rawKey = key;
this._ctrKey = await window.crypto.subtle.importKey(
"raw", key, {name: "AES-CTR"}, false, ["encrypt", "decrypt"]);
this._cbcKey = await window.crypto.subtle.importKey(
"raw", key, {name: "AES-CBC"}, false, ["encrypt"]);
await this._initCMAC();
}
async encrypt(algorithm, message) {
const ad = algorithm.additionalData;
const nonce = algorithm.iv;
const nCMAC = await this._computeCMAC(nonce, this._prefixBlock0);
const encrypted = await this._encryptCTR(message, nCMAC);
const adCMAC = await this._computeCMAC(ad, this._prefixBlock1);
const mac = await this._computeCMAC(encrypted, this._prefixBlock2);
for (let i = 0; i < 16; i++) {
mac[i] ^= nCMAC[i] ^ adCMAC[i];
}
const res = new Uint8Array(16 + encrypted.length);
res.set(encrypted);
res.set(mac, encrypted.length);
return res;
}
async decrypt(algorithm, data) {
const encrypted = data.slice(0, data.length - 16);
const ad = algorithm.additionalData;
const nonce = algorithm.iv;
const mac = data.slice(data.length - 16);
const nCMAC = await this._computeCMAC(nonce, this._prefixBlock0);
const adCMAC = await this._computeCMAC(ad, this._prefixBlock1);
const computedMac = await this._computeCMAC(encrypted, this._prefixBlock2);
for (let i = 0; i < 16; i++) {
computedMac[i] ^= nCMAC[i] ^ adCMAC[i];
}
if (computedMac.length !== mac.length) {
return null;
}
for (let i = 0; i < mac.length; i++) {
if (computedMac[i] !== mac[i]) {
return null;
}
}
const res = await this._decryptCTR(encrypted, nCMAC);
return res;
}
}

34
core/crypto/bigint.js Normal file
View File

@ -0,0 +1,34 @@
export function modPow(b, e, m) {
let r = 1n;
b = b % m;
while (e > 0n) {
if ((e & 1n) === 1n) {
r = (r * b) % m;
}
e = e >> 1n;
b = (b * b) % m;
}
return r;
}
export function bigIntToU8Array(bigint, padLength=0) {
let hex = bigint.toString(16);
if (padLength === 0) {
padLength = Math.ceil(hex.length / 2);
}
hex = hex.padStart(padLength * 2, '0');
const length = hex.length / 2;
const arr = new Uint8Array(length);
for (let i = 0; i < length; i++) {
arr[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
}
return arr;
}
export function u8ArrayToBigInt(arr) {
let hex = '0x';
for (let i = 0; i < arr.length; i++) {
hex += arr[i].toString(16).padStart(2, '0');
}
return BigInt(hex);
}

90
core/crypto/crypto.js Normal file
View File

@ -0,0 +1,90 @@
import { AESECBCipher, AESEAXCipher } from "./aes.js";
import { DESCBCCipher, DESECBCipher } from "./des.js";
import { RSACipher } from "./rsa.js";
import { DHCipher } from "./dh.js";
import { MD5 } from "./md5.js";
// A single interface for the cryptographic algorithms not supported by SubtleCrypto.
// Both synchronous and asynchronous implmentations are allowed.
class LegacyCrypto {
constructor() {
this._algorithms = {
"AES-ECB": AESECBCipher,
"AES-EAX": AESEAXCipher,
"DES-ECB": DESECBCipher,
"DES-CBC": DESCBCCipher,
"RSA-PKCS1-v1_5": RSACipher,
"DH": DHCipher,
"MD5": MD5,
};
}
encrypt(algorithm, key, data) {
if (key.algorithm.name !== algorithm.name) {
throw new Error("algorithm does not match");
}
if (typeof key.encrypt !== "function") {
throw new Error("key does not support encryption");
}
return key.encrypt(algorithm, data);
}
decrypt(algorithm, key, data) {
if (key.algorithm.name !== algorithm.name) {
throw new Error("algorithm does not match");
}
if (typeof key.decrypt !== "function") {
throw new Error("key does not support encryption");
}
return key.decrypt(algorithm, data);
}
importKey(format, keyData, algorithm, extractable, keyUsages) {
if (format !== "raw") {
throw new Error("key format is not supported");
}
const alg = this._algorithms[algorithm.name];
if (typeof alg === "undefined" || typeof alg.importKey !== "function") {
throw new Error("algorithm is not supported");
}
return alg.importKey(keyData, algorithm, extractable, keyUsages);
}
generateKey(algorithm, extractable, keyUsages) {
const alg = this._algorithms[algorithm.name];
if (typeof alg === "undefined" || typeof alg.generateKey !== "function") {
throw new Error("algorithm is not supported");
}
return alg.generateKey(algorithm, extractable, keyUsages);
}
exportKey(format, key) {
if (format !== "raw") {
throw new Error("key format is not supported");
}
if (typeof key.exportKey !== "function") {
throw new Error("key does not support exportKey");
}
return key.exportKey();
}
digest(algorithm, data) {
const alg = this._algorithms[algorithm];
if (typeof alg !== "function") {
throw new Error("algorithm is not supported");
}
return alg(data);
}
deriveBits(algorithm, key, length) {
if (key.algorithm.name !== algorithm.name) {
throw new Error("algorithm does not match");
}
if (typeof key.deriveBits !== "function") {
throw new Error("key does not support deriveBits");
}
return key.deriveBits(algorithm, length);
}
}
export default new LegacyCrypto;

View File

@ -128,7 +128,7 @@ const SP8 = [b|f,z|e,a|z,c|f,b|z,b|f,z|d,b|z,a|d,c|z,c|f,a|e,c|e,a|f,z|e,z|d,
/* eslint-enable comma-spacing */ /* eslint-enable comma-spacing */
export default class DES { class DES {
constructor(password) { constructor(password) {
this.keys = []; this.keys = [];
@ -258,9 +258,73 @@ export default class DES {
} }
return b; return b;
} }
}
// Encrypt 16 bytes of text using passwd as key export class DESECBCipher {
encrypt(t) { constructor() {
return this.enc8(t.slice(0, 8)).concat(this.enc8(t.slice(8, 16))); this._cipher = null;
}
get algorithm() {
return { name: "DES-ECB" };
}
static importKey(key, _algorithm, _extractable, _keyUsages) {
const cipher = new DESECBCipher;
cipher._importKey(key);
return cipher;
}
_importKey(key, _extractable, _keyUsages) {
this._cipher = new DES(key);
}
encrypt(_algorithm, plaintext) {
const x = new Uint8Array(plaintext);
if (x.length % 8 !== 0 || this._cipher === null) {
return null;
}
const n = x.length / 8;
for (let i = 0; i < n; i++) {
x.set(this._cipher.enc8(x.slice(i * 8, i * 8 + 8)), i * 8);
}
return x;
}
}
export class DESCBCCipher {
constructor() {
this._cipher = null;
}
get algorithm() {
return { name: "DES-CBC" };
}
static importKey(key, _algorithm, _extractable, _keyUsages) {
const cipher = new DESCBCCipher;
cipher._importKey(key);
return cipher;
}
_importKey(key) {
this._cipher = new DES(key);
}
encrypt(algorithm, plaintext) {
const x = new Uint8Array(plaintext);
let y = new Uint8Array(algorithm.iv);
if (x.length % 8 !== 0 || this._cipher === null) {
return null;
}
const n = x.length / 8;
for (let i = 0; i < n; i++) {
for (let j = 0; j < 8; j++) {
y[j] ^= plaintext[i * 8 + j];
}
y = this._cipher.enc8(y);
x.set(y, i * 8);
}
return x;
} }
} }

55
core/crypto/dh.js Normal file
View File

@ -0,0 +1,55 @@
import { modPow, bigIntToU8Array, u8ArrayToBigInt } from "./bigint.js";
class DHPublicKey {
constructor(key) {
this._key = key;
}
get algorithm() {
return { name: "DH" };
}
exportKey() {
return this._key;
}
}
export class DHCipher {
constructor() {
this._g = null;
this._p = null;
this._gBigInt = null;
this._pBigInt = null;
this._privateKey = null;
}
get algorithm() {
return { name: "DH" };
}
static generateKey(algorithm, _extractable) {
const cipher = new DHCipher;
cipher._generateKey(algorithm);
return { privateKey: cipher, publicKey: new DHPublicKey(cipher._publicKey) };
}
_generateKey(algorithm) {
const g = algorithm.g;
const p = algorithm.p;
this._keyBytes = p.length;
this._gBigInt = u8ArrayToBigInt(g);
this._pBigInt = u8ArrayToBigInt(p);
this._privateKey = window.crypto.getRandomValues(new Uint8Array(this._keyBytes));
this._privateKeyBigInt = u8ArrayToBigInt(this._privateKey);
this._publicKey = bigIntToU8Array(modPow(
this._gBigInt, this._privateKeyBigInt, this._pBigInt), this._keyBytes);
}
deriveBits(algorithm, length) {
const bytes = Math.ceil(length / 8);
const pkey = new Uint8Array(algorithm.public);
const len = bytes > this._keyBytes ? bytes : this._keyBytes;
const secret = modPow(u8ArrayToBigInt(pkey), this._privateKeyBigInt, this._pBigInt);
return bigIntToU8Array(secret, len).slice(0, len);
}
}

View File

@ -1,18 +1,21 @@
/* /*
* noVNC: HTML5 VNC client * noVNC: HTML5 VNC client
* Copyright (C) 2021 The noVNC Authors * Copyright (C) 2021 The noVNC authors
* Licensed under MPL 2.0 (see LICENSE.txt) * Licensed under MPL 2.0 (see LICENSE.txt)
* *
* See README.md for usage and integration instructions. * See README.md for usage and integration instructions.
*/ */
/* /*
* Performs MD5 hashing on a string of binary characters, returns an array of bytes * Performs MD5 hashing on an array of bytes, returns an array of bytes
*/ */
export function MD5(d) { export async function MD5(d) {
let r = M(V(Y(X(d), 8 * d.length))); let s = "";
return r; for (let i = 0; i < d.length; i++) {
s += String.fromCharCode(d[i]);
}
return M(V(Y(X(s), 8 * s.length)));
} }
function M(d) { function M(d) {
@ -76,4 +79,4 @@ function add(d, g) {
function rol(d, g) { function rol(d, g) {
return d << g | d >>> 32 - g; return d << g | d >>> 32 - g;
} }

132
core/crypto/rsa.js Normal file
View File

@ -0,0 +1,132 @@
import Base64 from "../base64.js";
import { modPow, bigIntToU8Array, u8ArrayToBigInt } from "./bigint.js";
export class RSACipher {
constructor() {
this._keyLength = 0;
this._keyBytes = 0;
this._n = null;
this._e = null;
this._d = null;
this._nBigInt = null;
this._eBigInt = null;
this._dBigInt = null;
this._extractable = false;
}
get algorithm() {
return { name: "RSA-PKCS1-v1_5" };
}
_base64urlDecode(data) {
data = data.replace(/-/g, "+").replace(/_/g, "/");
data = data.padEnd(Math.ceil(data.length / 4) * 4, "=");
return Base64.decode(data);
}
_padArray(arr, length) {
const res = new Uint8Array(length);
res.set(arr, length - arr.length);
return res;
}
static async generateKey(algorithm, extractable, _keyUsages) {
const cipher = new RSACipher;
await cipher._generateKey(algorithm, extractable);
return { privateKey: cipher };
}
async _generateKey(algorithm, extractable) {
this._keyLength = algorithm.modulusLength;
this._keyBytes = Math.ceil(this._keyLength / 8);
const key = await window.crypto.subtle.generateKey(
{
name: "RSA-OAEP",
modulusLength: algorithm.modulusLength,
publicExponent: algorithm.publicExponent,
hash: {name: "SHA-256"},
},
true, ["encrypt", "decrypt"]);
const privateKey = await window.crypto.subtle.exportKey("jwk", key.privateKey);
this._n = this._padArray(this._base64urlDecode(privateKey.n), this._keyBytes);
this._nBigInt = u8ArrayToBigInt(this._n);
this._e = this._padArray(this._base64urlDecode(privateKey.e), this._keyBytes);
this._eBigInt = u8ArrayToBigInt(this._e);
this._d = this._padArray(this._base64urlDecode(privateKey.d), this._keyBytes);
this._dBigInt = u8ArrayToBigInt(this._d);
this._extractable = extractable;
}
static async importKey(key, _algorithm, extractable, keyUsages) {
if (keyUsages.length !== 1 || keyUsages[0] !== "encrypt") {
throw new Error("only support importing RSA public key");
}
const cipher = new RSACipher;
await cipher._importKey(key, extractable);
return cipher;
}
async _importKey(key, extractable) {
const n = key.n;
const e = key.e;
if (n.length !== e.length) {
throw new Error("the sizes of modulus and public exponent do not match");
}
this._keyBytes = n.length;
this._keyLength = this._keyBytes * 8;
this._n = new Uint8Array(this._keyBytes);
this._e = new Uint8Array(this._keyBytes);
this._n.set(n);
this._e.set(e);
this._nBigInt = u8ArrayToBigInt(this._n);
this._eBigInt = u8ArrayToBigInt(this._e);
this._extractable = extractable;
}
async encrypt(_algorithm, message) {
if (message.length > this._keyBytes - 11) {
return null;
}
const ps = new Uint8Array(this._keyBytes - message.length - 3);
window.crypto.getRandomValues(ps);
for (let i = 0; i < ps.length; i++) {
ps[i] = Math.floor(ps[i] * 254 / 255 + 1);
}
const em = new Uint8Array(this._keyBytes);
em[1] = 0x02;
em.set(ps, 2);
em.set(message, ps.length + 3);
const emBigInt = u8ArrayToBigInt(em);
const c = modPow(emBigInt, this._eBigInt, this._nBigInt);
return bigIntToU8Array(c, this._keyBytes);
}
async decrypt(_algorithm, message) {
if (message.length !== this._keyBytes) {
return null;
}
const msgBigInt = u8ArrayToBigInt(message);
const emBigInt = modPow(msgBigInt, this._dBigInt, this._nBigInt);
const em = bigIntToU8Array(emBigInt, this._keyBytes);
if (em[0] !== 0x00 || em[1] !== 0x02) {
return null;
}
let i = 2;
for (; i < em.length; i++) {
if (em[i] === 0x00) {
break;
}
}
if (i === em.length) {
return null;
}
return em.slice(i + 1, em.length);
}
async exportKey() {
if (!this._extractable) {
throw new Error("key is not extractable");
}
return { n: this._n, e: this._e, d: this._d };
}
}

View File

@ -1,6 +1,6 @@
/* /*
* noVNC: HTML5 VNC client * noVNC: HTML5 VNC client
* Copyright (C) 2019 The noVNC Authors * Copyright (C) 2019 The noVNC authors
* Licensed under MPL 2.0 (see LICENSE.txt) * Licensed under MPL 2.0 (see LICENSE.txt)
* *
* See README.md for usage and integration instructions. * See README.md for usage and integration instructions.

321
core/decoders/h264.js Normal file
View File

@ -0,0 +1,321 @@
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2024 The noVNC authors
* Licensed under MPL 2.0 (see LICENSE.txt)
*
* See README.md for usage and integration instructions.
*
*/
import * as Log from '../util/logging.js';
export class H264Parser {
constructor(data) {
this._data = data;
this._index = 0;
this.profileIdc = null;
this.constraintSet = null;
this.levelIdc = null;
}
_getStartSequenceLen(index) {
let data = this._data;
if (data[index + 0] == 0 && data[index + 1] == 0 && data[index + 2] == 0 && data[index + 3] == 1) {
return 4;
}
if (data[index + 0] == 0 && data[index + 1] == 0 && data[index + 2] == 1) {
return 3;
}
return 0;
}
_indexOfNextNalUnit(index) {
let data = this._data;
for (let i = index; i < data.length; ++i) {
if (this._getStartSequenceLen(i) != 0) {
return i;
}
}
return -1;
}
_parseSps(index) {
this.profileIdc = this._data[index];
this.constraintSet = this._data[index + 1];
this.levelIdc = this._data[index + 2];
}
_parseNalUnit(index) {
const firstByte = this._data[index];
if (firstByte & 0x80) {
throw new Error('H264 parsing sanity check failed, forbidden zero bit is set');
}
const unitType = firstByte & 0x1f;
switch (unitType) {
case 1: // coded slice, non-idr
return { slice: true };
case 5: // coded slice, idr
return { slice: true, key: true };
case 6: // sei
return {};
case 7: // sps
this._parseSps(index + 1);
return {};
case 8: // pps
return {};
default:
Log.Warn("Unhandled unit type: ", unitType);
break;
}
return {};
}
parse() {
const startIndex = this._index;
let isKey = false;
while (this._index < this._data.length) {
const startSequenceLen = this._getStartSequenceLen(this._index);
if (startSequenceLen == 0) {
throw new Error('Invalid start sequence in bit stream');
}
const { slice, key } = this._parseNalUnit(this._index + startSequenceLen);
let nextIndex = this._indexOfNextNalUnit(this._index + startSequenceLen);
if (nextIndex == -1) {
this._index = this._data.length;
} else {
this._index = nextIndex;
}
if (key) {
isKey = true;
}
if (slice) {
break;
}
}
if (startIndex === this._index) {
return null;
}
return {
frame: this._data.subarray(startIndex, this._index),
key: isKey,
};
}
}
export class H264Context {
constructor(width, height) {
this.lastUsed = 0;
this._width = width;
this._height = height;
this._profileIdc = null;
this._constraintSet = null;
this._levelIdc = null;
this._decoder = null;
this._pendingFrames = [];
}
_handleFrame(frame) {
let pending = this._pendingFrames.shift();
if (pending === undefined) {
throw new Error("Pending frame queue empty when receiving frame from decoder");
}
if (pending.timestamp != frame.timestamp) {
throw new Error("Video frame timestamp mismatch. Expected " +
frame.timestamp + " but but got " + pending.timestamp);
}
pending.frame = frame;
pending.ready = true;
pending.resolve();
if (!pending.keep) {
frame.close();
}
}
_handleError(e) {
throw new Error("Failed to decode frame: " + e.message);
}
_configureDecoder(profileIdc, constraintSet, levelIdc) {
if (this._decoder === null || this._decoder.state === 'closed') {
this._decoder = new VideoDecoder({
output: frame => this._handleFrame(frame),
error: e => this._handleError(e),
});
}
const codec = 'avc1.' +
profileIdc.toString(16).padStart(2, '0') +
constraintSet.toString(16).padStart(2, '0') +
levelIdc.toString(16).padStart(2, '0');
this._decoder.configure({
codec: codec,
codedWidth: this._width,
codedHeight: this._height,
optimizeForLatency: true,
});
}
_preparePendingFrame(timestamp) {
let pending = {
timestamp: timestamp,
promise: null,
resolve: null,
frame: null,
ready: false,
keep: false,
};
pending.promise = new Promise((resolve) => {
pending.resolve = resolve;
});
this._pendingFrames.push(pending);
return pending;
}
decode(payload) {
let parser = new H264Parser(payload);
let result = null;
// Ideally, this timestamp should come from the server, but we'll just
// approximate it instead.
let timestamp = Math.round(window.performance.now() * 1e3);
while (true) {
let encodedFrame = parser.parse();
if (encodedFrame === null) {
break;
}
if (parser.profileIdc !== null) {
self._profileIdc = parser.profileIdc;
self._constraintSet = parser.constraintSet;
self._levelIdc = parser.levelIdc;
}
if (this._decoder === null || this._decoder.state !== 'configured') {
if (!encodedFrame.key) {
Log.Warn("Missing key frame. Can't decode until one arrives");
continue;
}
if (self._profileIdc === null) {
Log.Warn('Cannot config decoder. Have not received SPS and PPS yet.');
continue;
}
this._configureDecoder(self._profileIdc, self._constraintSet,
self._levelIdc);
}
result = this._preparePendingFrame(timestamp);
const chunk = new EncodedVideoChunk({
timestamp: timestamp,
type: encodedFrame.key ? 'key' : 'delta',
data: encodedFrame.frame,
});
try {
this._decoder.decode(chunk);
} catch (e) {
Log.Warn("Failed to decode:", e);
}
}
// We only keep last frame of each payload
if (result !== null) {
result.keep = true;
}
return result;
}
}
export default class H264Decoder {
constructor() {
this._tick = 0;
this._contexts = {};
}
_contextId(x, y, width, height) {
return [x, y, width, height].join(',');
}
_findOldestContextId() {
let oldestTick = Number.MAX_VALUE;
let oldestKey = undefined;
for (const [key, value] of Object.entries(this._contexts)) {
if (value.lastUsed < oldestTick) {
oldestTick = value.lastUsed;
oldestKey = key;
}
}
return oldestKey;
}
_createContext(x, y, width, height) {
const maxContexts = 64;
if (Object.keys(this._contexts).length >= maxContexts) {
let oldestContextId = this._findOldestContextId();
delete this._contexts[oldestContextId];
}
let context = new H264Context(width, height);
this._contexts[this._contextId(x, y, width, height)] = context;
return context;
}
_getContext(x, y, width, height) {
let context = this._contexts[this._contextId(x, y, width, height)];
return context !== undefined ? context : this._createContext(x, y, width, height);
}
_resetContext(x, y, width, height) {
delete this._contexts[this._contextId(x, y, width, height)];
}
_resetAllContexts() {
this._contexts = {};
}
decodeRect(x, y, width, height, sock, display, depth) {
const resetContextFlag = 1;
const resetAllContextsFlag = 2;
if (sock.rQwait("h264 header", 8)) {
return false;
}
const length = sock.rQshift32();
const flags = sock.rQshift32();
if (sock.rQwait("h264 payload", length, 8)) {
return false;
}
if (flags & resetAllContextsFlag) {
this._resetAllContexts();
} else if (flags & resetContextFlag) {
this._resetContext(x, y, width, height);
}
let context = this._getContext(x, y, width, height);
context.lastUsed = this._tick++;
if (length !== 0) {
let payload = sock.rQshiftBytes(length, false);
let frame = context.decode(payload);
if (frame !== null) {
display.videoFrame(x, y, width, height, frame);
}
}
return true;
}
}

View File

@ -1,6 +1,6 @@
/* /*
* noVNC: HTML5 VNC client * noVNC: HTML5 VNC client
* Copyright (C) 2019 The noVNC Authors * Copyright (C) 2019 The noVNC authors
* Licensed under MPL 2.0 (see LICENSE.txt) * Licensed under MPL 2.0 (see LICENSE.txt)
* *
* See README.md for usage and integration instructions. * See README.md for usage and integration instructions.
@ -31,10 +31,7 @@ export default class HextileDecoder {
return false; return false;
} }
let rQ = sock.rQ; let subencoding = sock.rQpeek8();
let rQi = sock.rQi;
let subencoding = rQ[rQi]; // Peek
if (subencoding > 30) { // Raw if (subencoding > 30) { // Raw
throw new Error("Illegal hextile subencoding (subencoding: " + throw new Error("Illegal hextile subencoding (subencoding: " +
subencoding + ")"); subencoding + ")");
@ -65,7 +62,7 @@ export default class HextileDecoder {
return false; return false;
} }
let subrects = rQ[rQi + bytes - 1]; // Peek let subrects = sock.rQpeekBytes(bytes).at(-1);
if (subencoding & 0x10) { // SubrectsColoured if (subencoding & 0x10) { // SubrectsColoured
bytes += subrects * (4 + 2); bytes += subrects * (4 + 2);
} else { } else {
@ -79,7 +76,7 @@ export default class HextileDecoder {
} }
// We know the encoding and have a whole tile // We know the encoding and have a whole tile
rQi++; sock.rQshift8();
if (subencoding === 0) { if (subencoding === 0) {
if (this._lastsubencoding & 0x01) { if (this._lastsubencoding & 0x01) {
// Weird: ignore blanks are RAW // Weird: ignore blanks are RAW
@ -89,42 +86,36 @@ export default class HextileDecoder {
} }
} else if (subencoding & 0x01) { // Raw } else if (subencoding & 0x01) { // Raw
let pixels = tw * th; let pixels = tw * th;
let data = sock.rQshiftBytes(pixels * 4, false);
// Max sure the image is fully opaque // Max sure the image is fully opaque
for (let i = 0;i < pixels;i++) { for (let i = 0;i < pixels;i++) {
rQ[rQi + i * 4 + 3] = 255; data[i * 4 + 3] = 255;
} }
display.blitImage(tx, ty, tw, th, rQ, rQi); display.blitImage(tx, ty, tw, th, data, 0);
rQi += bytes - 1;
} else { } else {
if (subencoding & 0x02) { // Background if (subencoding & 0x02) { // Background
this._background = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]]; this._background = new Uint8Array(sock.rQshiftBytes(4));
rQi += 4;
} }
if (subencoding & 0x04) { // Foreground if (subencoding & 0x04) { // Foreground
this._foreground = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]]; this._foreground = new Uint8Array(sock.rQshiftBytes(4));
rQi += 4;
} }
this._startTile(tx, ty, tw, th, this._background); this._startTile(tx, ty, tw, th, this._background);
if (subencoding & 0x08) { // AnySubrects if (subencoding & 0x08) { // AnySubrects
let subrects = rQ[rQi]; let subrects = sock.rQshift8();
rQi++;
for (let s = 0; s < subrects; s++) { for (let s = 0; s < subrects; s++) {
let color; let color;
if (subencoding & 0x10) { // SubrectsColoured if (subencoding & 0x10) { // SubrectsColoured
color = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]]; color = sock.rQshiftBytes(4);
rQi += 4;
} else { } else {
color = this._foreground; color = this._foreground;
} }
const xy = rQ[rQi]; const xy = sock.rQshift8();
rQi++;
const sx = (xy >> 4); const sx = (xy >> 4);
const sy = (xy & 0x0f); const sy = (xy & 0x0f);
const wh = rQ[rQi]; const wh = sock.rQshift8();
rQi++;
const sw = (wh >> 4) + 1; const sw = (wh >> 4) + 1;
const sh = (wh & 0x0f) + 1; const sh = (wh & 0x0f) + 1;
@ -133,7 +124,6 @@ export default class HextileDecoder {
} }
this._finishTile(display); this._finishTile(display);
} }
sock.rQi = rQi;
this._lastsubencoding = subencoding; this._lastsubencoding = subencoding;
this._tiles--; this._tiles--;
} }

View File

@ -1,6 +1,6 @@
/* /*
* noVNC: HTML5 VNC client * noVNC: HTML5 VNC client
* Copyright (C) 2019 The noVNC Authors * Copyright (C) 2019 The noVNC authors
* Licensed under MPL 2.0 (see LICENSE.txt) * Licensed under MPL 2.0 (see LICENSE.txt)
* *
* See README.md for usage and integration instructions. * See README.md for usage and integration instructions.
@ -11,131 +11,136 @@ export default class JPEGDecoder {
constructor() { constructor() {
// RealVNC will reuse the quantization tables // RealVNC will reuse the quantization tables
// and Huffman tables, so we need to cache them. // and Huffman tables, so we need to cache them.
this._quantTables = [];
this._huffmanTables = [];
this._cachedQuantTables = []; this._cachedQuantTables = [];
this._cachedHuffmanTables = []; this._cachedHuffmanTables = [];
this._jpegLength = 0;
this._segments = []; this._segments = [];
} }
decodeRect(x, y, width, height, sock, display, depth) { decodeRect(x, y, width, height, sock, display, depth) {
// A rect of JPEG encodings is simply a JPEG file // 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) { while (true) {
let j = i; let segment = this._readSegment(sock);
if (j + 2 > bufferLength) { if (segment === null) {
return false; 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); this._segments.push(segment);
i = j; // End of image?
if (segment[1] === 0xD9) {
break;
}
} }
let huffmanTables = [];
let quantTables = [];
for (let segment of this._segments) {
let type = segment[1];
if (type === 0xC4) {
// Huffman tables
huffmanTables.push(segment);
} else if (type === 0xDB) {
// Quantization tables
quantTables.push(segment);
}
}
const sofIndex = this._segments.findIndex(
x => x[1] == 0xC0 || x[1] == 0xC2
);
if (sofIndex == -1) {
throw new Error("Illegal JPEG image without SOF");
}
if (quantTables.length === 0) {
this._segments.splice(sofIndex+1, 0,
...this._cachedQuantTables);
}
if (huffmanTables.length === 0) {
this._segments.splice(sofIndex+1, 0,
...this._cachedHuffmanTables);
}
let length = 0;
for (let segment of this._segments) {
length += segment.length;
}
let data = new Uint8Array(length);
length = 0;
for (let segment of this._segments) {
data.set(segment, length);
length += segment.length;
}
display.imageRect(x, y, width, height, "image/jpeg", data);
if (huffmanTables.length !== 0) {
this._cachedHuffmanTables = huffmanTables;
}
if (quantTables.length !== 0) {
this._cachedQuantTables = quantTables;
}
this._segments = [];
return true;
}
_readSegment(sock) {
if (sock.rQwait("JPEG", 2)) {
return null;
}
let marker = sock.rQshift8();
if (marker != 0xFF) {
throw new Error("Illegal JPEG marker received (byte: " +
marker + ")");
}
let type = sock.rQshift8();
if (type >= 0xD0 && type <= 0xD9 || type == 0x01) {
// No length after marker
return new Uint8Array([marker, type]);
}
if (sock.rQwait("JPEG", 2, 2)) {
return null;
}
let length = sock.rQshift16();
if (length < 2) {
throw new Error("Illegal JPEG length received (length: " +
length + ")");
}
if (sock.rQwait("JPEG", length-2, 4)) {
return null;
}
let extra = 0;
if (type === 0xDA) {
// start of scan
extra += 2;
while (true) {
if (sock.rQwait("JPEG", length-2+extra, 4)) {
return null;
}
let data = sock.rQpeekBytes(length-2+extra, false);
if (data.at(-2) === 0xFF && data.at(-1) !== 0x00 &&
!(data.at(-1) >= 0xD0 && data.at(-1) <= 0xD7)) {
extra -= 2;
break;
}
extra++;
}
}
let segment = new Uint8Array(2 + length + extra);
segment[0] = marker;
segment[1] = type;
segment[2] = length >> 8;
segment[3] = length;
segment.set(sock.rQshiftBytes(length-2+extra, false), 4);
return segment;
} }
} }

View File

@ -1,6 +1,6 @@
/* /*
* noVNC: HTML5 VNC client * noVNC: HTML5 VNC client
* Copyright (C) 2019 The noVNC Authors * Copyright (C) 2019 The noVNC authors
* Licensed under MPL 2.0 (see LICENSE.txt) * Licensed under MPL 2.0 (see LICENSE.txt)
* *
* See README.md for usage and integration instructions. * See README.md for usage and integration instructions.
@ -24,41 +24,34 @@ export default class RawDecoder {
const pixelSize = depth == 8 ? 1 : 4; const pixelSize = depth == 8 ? 1 : 4;
const bytesPerLine = width * pixelSize; const bytesPerLine = width * pixelSize;
if (sock.rQwait("RAW", bytesPerLine)) { while (this._lines > 0) {
return false; if (sock.rQwait("RAW", bytesPerLine)) {
} return false;
const curY = y + (height - this._lines);
const currHeight = Math.min(this._lines,
Math.floor(sock.rQlen / bytesPerLine));
const pixels = width * currHeight;
let data = sock.rQ;
let index = sock.rQi;
// Convert data if needed
if (depth == 8) {
const newdata = new Uint8Array(pixels * 4);
for (let i = 0; i < pixels; i++) {
newdata[i * 4 + 0] = ((data[index + i] >> 0) & 0x3) * 255 / 3;
newdata[i * 4 + 1] = ((data[index + i] >> 2) & 0x3) * 255 / 3;
newdata[i * 4 + 2] = ((data[index + i] >> 4) & 0x3) * 255 / 3;
newdata[i * 4 + 3] = 255;
} }
data = newdata;
index = 0;
}
// Max sure the image is fully opaque const curY = y + (height - this._lines);
for (let i = 0; i < pixels; i++) {
data[index + i * 4 + 3] = 255;
}
display.blitImage(x, curY, width, currHeight, data, index); let data = sock.rQshiftBytes(bytesPerLine, false);
sock.rQskipBytes(currHeight * bytesPerLine);
this._lines -= currHeight; // Convert data if needed
if (this._lines > 0) { if (depth == 8) {
return false; const newdata = new Uint8Array(width * 4);
for (let i = 0; i < width; i++) {
newdata[i * 4 + 0] = ((data[i] >> 0) & 0x3) * 255 / 3;
newdata[i * 4 + 1] = ((data[i] >> 2) & 0x3) * 255 / 3;
newdata[i * 4 + 2] = ((data[i] >> 4) & 0x3) * 255 / 3;
newdata[i * 4 + 3] = 255;
}
data = newdata;
}
// Max sure the image is fully opaque
for (let i = 0; i < width; i++) {
data[i * 4 + 3] = 255;
}
display.blitImage(x, curY, width, 1, data, 0);
this._lines--;
} }
return true; return true;

View File

@ -1,6 +1,6 @@
/* /*
* noVNC: HTML5 VNC client * noVNC: HTML5 VNC client
* Copyright (C) 2019 The noVNC Authors * Copyright (C) 2019 The noVNC authors
* Licensed under MPL 2.0 (see LICENSE.txt) * Licensed under MPL 2.0 (see LICENSE.txt)
* *
* See README.md for usage and integration instructions. * See README.md for usage and integration instructions.

View File

@ -1,6 +1,6 @@
/* /*
* noVNC: HTML5 VNC client * noVNC: HTML5 VNC client
* Copyright (C) 2019 The noVNC Authors * Copyright (C) 2019 The noVNC authors
* (c) 2012 Michael Tinglof, Joe Balaz, Les Piech (Mercuri.ca) * (c) 2012 Michael Tinglof, Joe Balaz, Les Piech (Mercuri.ca)
* Licensed under MPL 2.0 (see LICENSE.txt) * Licensed under MPL 2.0 (see LICENSE.txt)
* *
@ -76,12 +76,8 @@ export default class TightDecoder {
return false; return false;
} }
const rQi = sock.rQi; let pixel = sock.rQshiftBytes(3);
const rQ = sock.rQ; display.fillRect(x, y, width, height, pixel, false);
display.fillRect(x, y, width, height,
[rQ[rQi], rQ[rQi + 1], rQ[rQi + 2]], false);
sock.rQskipBytes(3);
return true; return true;
} }
@ -289,7 +285,73 @@ export default class TightDecoder {
} }
_gradientFilter(streamId, x, y, width, height, sock, display, depth) { _gradientFilter(streamId, x, y, width, height, sock, display, depth) {
throw new Error("Gradient filter not implemented"); // assume the TPIXEL is 3 bytes long
const uncompressedSize = width * height * 3;
let data;
if (uncompressedSize === 0) {
return true;
}
if (uncompressedSize < 12) {
if (sock.rQwait("TIGHT", uncompressedSize)) {
return false;
}
data = sock.rQshiftBytes(uncompressedSize);
} else {
data = this._readData(sock);
if (data === null) {
return false;
}
this._zlibs[streamId].setInput(data);
data = this._zlibs[streamId].inflate(uncompressedSize);
this._zlibs[streamId].setInput(null);
}
let rgbx = new Uint8Array(4 * width * height);
let rgbxIndex = 0, dataIndex = 0;
let left = new Uint8Array(3);
for (let x = 0; x < width; x++) {
for (let c = 0; c < 3; c++) {
const prediction = left[c];
const value = data[dataIndex++] + prediction;
rgbx[rgbxIndex++] = value;
left[c] = value;
}
rgbx[rgbxIndex++] = 255;
}
let upperIndex = 0;
let upper = new Uint8Array(3),
upperleft = new Uint8Array(3);
for (let y = 1; y < height; y++) {
left.fill(0);
upperleft.fill(0);
for (let x = 0; x < width; x++) {
for (let c = 0; c < 3; c++) {
upper[c] = rgbx[upperIndex++];
let prediction = left[c] + upper[c] - upperleft[c];
if (prediction < 0) {
prediction = 0;
} else if (prediction > 255) {
prediction = 255;
}
const value = data[dataIndex++] + prediction;
rgbx[rgbxIndex++] = value;
upperleft[c] = upper[c];
left[c] = value;
}
rgbx[rgbxIndex++] = 255;
upperIndex++;
}
}
display.blitImage(x, y, width, height, rgbx, 0, false);
return true;
} }
_readData(sock) { _readData(sock) {
@ -316,7 +378,7 @@ export default class TightDecoder {
return null; return null;
} }
let data = sock.rQshiftBytes(this._len); let data = sock.rQshiftBytes(this._len, false);
this._len = 0; this._len = 0;
return data; return data;

View File

@ -1,6 +1,6 @@
/* /*
* noVNC: HTML5 VNC client * noVNC: HTML5 VNC client
* Copyright (C) 2019 The noVNC Authors * Copyright (C) 2019 The noVNC authors
* Licensed under MPL 2.0 (see LICENSE.txt) * Licensed under MPL 2.0 (see LICENSE.txt)
* *
* See README.md for usage and integration instructions. * See README.md for usage and integration instructions.

51
core/decoders/zlib.js Normal file
View File

@ -0,0 +1,51 @@
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2024 The noVNC authors
* Licensed under MPL 2.0 (see LICENSE.txt)
*
* See README.md for usage and integration instructions.
*
*/
import Inflator from "../inflator.js";
export default class ZlibDecoder {
constructor() {
this._zlib = new Inflator();
this._length = 0;
}
decodeRect(x, y, width, height, sock, display, depth) {
if ((width === 0) || (height === 0)) {
return true;
}
if (this._length === 0) {
if (sock.rQwait("ZLIB", 4)) {
return false;
}
this._length = sock.rQshift32();
}
if (sock.rQwait("ZLIB", this._length)) {
return false;
}
let data = new Uint8Array(sock.rQshiftBytes(this._length, false));
this._length = 0;
this._zlib.setInput(data);
data = this._zlib.inflate(width * height * 4);
this._zlib.setInput(null);
// Max sure the image is fully opaque
for (let i = 0; i < width * height; i++) {
data[i * 4 + 3] = 255;
}
display.blitImage(x, y, width, height, data, 0);
return true;
}
}

View File

@ -1,6 +1,6 @@
/* /*
* noVNC: HTML5 VNC client * noVNC: HTML5 VNC client
* Copyright (C) 2021 The noVNC Authors * Copyright (C) 2021 The noVNC authors
* Licensed under MPL 2.0 (see LICENSE.txt) * Licensed under MPL 2.0 (see LICENSE.txt)
* *
* See README.md for usage and integration instructions. * See README.md for usage and integration instructions.
@ -32,7 +32,7 @@ export default class ZRLEDecoder {
return false; return false;
} }
const data = sock.rQshiftBytes(this._length); const data = sock.rQshiftBytes(this._length, false);
this._inflator.setInput(data); this._inflator.setInput(data);

View File

@ -1,13 +1,13 @@
/* /*
* noVNC: HTML5 VNC client * noVNC: HTML5 VNC client
* Copyright (C) 2020 The noVNC Authors * Copyright (C) 2020 The noVNC authors
* Licensed under MPL 2.0 (see LICENSE.txt) * Licensed under MPL 2.0 (see LICENSE.txt)
* *
* See README.md for usage and integration instructions. * See README.md for usage and integration instructions.
*/ */
import { deflateInit, deflate } from "../vendor/pako/lib/zlib/deflate.js"; import { deflateInit, deflate } from "../vendor/pako/lib/zlib/deflate.js";
import { Z_FULL_FLUSH } from "../vendor/pako/lib/zlib/deflate.js"; import { Z_FULL_FLUSH, Z_DEFAULT_COMPRESSION } from "../vendor/pako/lib/zlib/deflate.js";
import ZStream from "../vendor/pako/lib/zlib/zstream.js"; import ZStream from "../vendor/pako/lib/zlib/zstream.js";
export default class Deflator { export default class Deflator {
@ -15,9 +15,8 @@ export default class Deflator {
this.strm = new ZStream(); this.strm = new ZStream();
this.chunkSize = 1024 * 10 * 10; this.chunkSize = 1024 * 10 * 10;
this.outputBuffer = new Uint8Array(this.chunkSize); this.outputBuffer = new Uint8Array(this.chunkSize);
this.windowBits = 5;
deflateInit(this.strm, this.windowBits); deflateInit(this.strm, Z_DEFAULT_COMPRESSION);
} }
deflate(inData) { deflate(inData) {

View File

@ -1,6 +1,6 @@
/* /*
* noVNC: HTML5 VNC client * noVNC: HTML5 VNC client
* Copyright (C) 2019 The noVNC Authors * Copyright (C) 2019 The noVNC authors
* Licensed under MPL 2.0 (see LICENSE.txt) * Licensed under MPL 2.0 (see LICENSE.txt)
* *
* See README.md for usage and integration instructions. * See README.md for usage and integration instructions.
@ -15,7 +15,7 @@ export default class Display {
this._drawCtx = null; this._drawCtx = null;
this._renderQ = []; // queue drawing actions for in-oder rendering this._renderQ = []; // queue drawing actions for in-oder rendering
this._flushing = false; this._flushPromise = null;
// the full frame buffer (logical canvas) size // the full frame buffer (logical canvas) size
this._fbWidth = 0; this._fbWidth = 0;
@ -61,10 +61,6 @@ export default class Display {
this._scale = 1.0; this._scale = 1.0;
this._clipViewport = false; this._clipViewport = false;
// ===== EVENT HANDLERS =====
this.onflush = () => {}; // A flush request has finished
} }
// ===== PROPERTIES ===== // ===== PROPERTIES =====
@ -306,9 +302,14 @@ export default class Display {
flush() { flush() {
if (this._renderQ.length === 0) { if (this._renderQ.length === 0) {
this.onflush(); return Promise.resolve();
} else { } else {
this._flushing = true; if (this._flushPromise === null) {
this._flushPromise = new Promise((resolve) => {
this._flushResolve = resolve;
});
}
return this._flushPromise;
} }
} }
@ -379,6 +380,17 @@ export default class Display {
}); });
} }
videoFrame(x, y, width, height, frame) {
this._renderQPush({
'type': 'frame',
'frame': frame,
'x': x,
'y': y,
'width': width,
'height': height
});
}
blitImage(x, y, width, height, arr, offset, fromQueue) { blitImage(x, y, width, height, arr, offset, fromQueue) {
if (this._renderQ.length !== 0 && !fromQueue) { if (this._renderQ.length !== 0 && !fromQueue) {
// NB(directxman12): it's technically more performant here to use preallocated arrays, // NB(directxman12): it's technically more performant here to use preallocated arrays,
@ -405,9 +417,16 @@ export default class Display {
} }
} }
drawImage(img, x, y) { drawImage(img, ...args) {
this._drawCtx.drawImage(img, x, y); this._drawCtx.drawImage(img, ...args);
this._damage(x, y, img.width, img.height);
if (args.length <= 4) {
const [x, y] = args;
this._damage(x, y, img.width, img.height);
} else {
const [,, sw, sh, dx, dy] = args;
this._damage(dx, dy, sw, sh);
}
} }
autoscale(containerWidth, containerHeight) { autoscale(containerWidth, containerHeight) {
@ -510,6 +529,35 @@ export default class Display {
ready = false; ready = false;
} }
break; break;
case 'frame':
if (a.frame.ready) {
// The encoded frame may be larger than the rect due to
// limitations of the encoder, so we need to crop the
// frame.
let frame = a.frame.frame;
if (frame.codedWidth < a.width || frame.codedHeight < a.height) {
Log.Warn("Decoded video frame does not cover its full rectangle area. Expecting at least " +
a.width + "x" + a.height + " but got " +
frame.codedWidth + "x" + frame.codedHeight);
}
const sx = 0;
const sy = 0;
const sw = a.width;
const sh = a.height;
const dx = a.x;
const dy = a.y;
const dw = sw;
const dh = sh;
this.drawImage(frame, sx, sy, sw, sh, dx, dy, dw, dh);
frame.close();
} else {
let display = this;
a.frame.promise.then(() => {
display._scanRenderQ();
});
ready = false;
}
break;
} }
if (ready) { if (ready) {
@ -517,9 +565,11 @@ export default class Display {
} }
} }
if (this._renderQ.length === 0 && this._flushing) { if (this._renderQ.length === 0 &&
this._flushing = false; this._flushPromise !== null) {
this.onflush(); this._flushResolve();
this._flushPromise = null;
this._flushResolve = null;
} }
} }
} }

View File

@ -1,6 +1,6 @@
/* /*
* noVNC: HTML5 VNC client * noVNC: HTML5 VNC client
* Copyright (C) 2019 The noVNC Authors * Copyright (C) 2019 The noVNC authors
* Licensed under MPL 2.0 (see LICENSE.txt) * Licensed under MPL 2.0 (see LICENSE.txt)
* *
* See README.md for usage and integration instructions. * See README.md for usage and integration instructions.
@ -11,10 +11,12 @@ export const encodings = {
encodingCopyRect: 1, encodingCopyRect: 1,
encodingRRE: 2, encodingRRE: 2,
encodingHextile: 5, encodingHextile: 5,
encodingZlib: 6,
encodingTight: 7, encodingTight: 7,
encodingZRLE: 16, encodingZRLE: 16,
encodingTightPNG: -260, encodingTightPNG: -260,
encodingJPEG: 21, encodingJPEG: 21,
encodingH264: 50,
pseudoEncodingQualityLevel9: -23, pseudoEncodingQualityLevel9: -23,
pseudoEncodingQualityLevel0: -32, pseudoEncodingQualityLevel0: -32,
@ -22,11 +24,13 @@ export const encodings = {
pseudoEncodingLastRect: -224, pseudoEncodingLastRect: -224,
pseudoEncodingCursor: -239, pseudoEncodingCursor: -239,
pseudoEncodingQEMUExtendedKeyEvent: -258, pseudoEncodingQEMUExtendedKeyEvent: -258,
pseudoEncodingQEMULedEvent: -261,
pseudoEncodingDesktopName: -307, pseudoEncodingDesktopName: -307,
pseudoEncodingExtendedDesktopSize: -308, pseudoEncodingExtendedDesktopSize: -308,
pseudoEncodingXvp: -309, pseudoEncodingXvp: -309,
pseudoEncodingFence: -312, pseudoEncodingFence: -312,
pseudoEncodingContinuousUpdates: -313, pseudoEncodingContinuousUpdates: -313,
pseudoEncodingExtendedMouseButtons: -316,
pseudoEncodingCompressLevel9: -247, pseudoEncodingCompressLevel9: -247,
pseudoEncodingCompressLevel0: -256, pseudoEncodingCompressLevel0: -256,
pseudoEncodingVMwareCursor: 0x574d5664, pseudoEncodingVMwareCursor: 0x574d5664,
@ -39,10 +43,12 @@ export function encodingName(num) {
case encodings.encodingCopyRect: return "CopyRect"; case encodings.encodingCopyRect: return "CopyRect";
case encodings.encodingRRE: return "RRE"; case encodings.encodingRRE: return "RRE";
case encodings.encodingHextile: return "Hextile"; case encodings.encodingHextile: return "Hextile";
case encodings.encodingZlib: return "Zlib";
case encodings.encodingTight: return "Tight"; case encodings.encodingTight: return "Tight";
case encodings.encodingZRLE: return "ZRLE"; case encodings.encodingZRLE: return "ZRLE";
case encodings.encodingTightPNG: return "TightPNG"; case encodings.encodingTightPNG: return "TightPNG";
case encodings.encodingJPEG: return "JPEG"; case encodings.encodingJPEG: return "JPEG";
case encodings.encodingH264: return "H.264";
default: return "[unknown encoding " + num + "]"; default: return "[unknown encoding " + num + "]";
} }
} }

View File

@ -1,6 +1,6 @@
/* /*
* noVNC: HTML5 VNC client * noVNC: HTML5 VNC client
* Copyright (C) 2020 The noVNC Authors * Copyright (C) 2020 The noVNC authors
* Licensed under MPL 2.0 (see LICENSE.txt) * Licensed under MPL 2.0 (see LICENSE.txt)
* *
* See README.md for usage and integration instructions. * See README.md for usage and integration instructions.
@ -14,9 +14,8 @@ export default class Inflate {
this.strm = new ZStream(); this.strm = new ZStream();
this.chunkSize = 1024 * 10 * 10; this.chunkSize = 1024 * 10 * 10;
this.strm.output = new Uint8Array(this.chunkSize); this.strm.output = new Uint8Array(this.chunkSize);
this.windowBits = 5;
inflateInit(this.strm, this.windowBits); inflateInit(this.strm);
} }
setInput(data) { setInput(data) {

View File

@ -1,6 +1,6 @@
/* /*
* noVNC: HTML5 VNC client * noVNC: HTML5 VNC client
* Copyright (C) 2018 The noVNC Authors * Copyright (C) 2018 The noVNC authors
* Licensed under MPL 2.0 or any later version (see LICENSE.txt) * Licensed under MPL 2.0 or any later version (see LICENSE.txt)
*/ */

View File

@ -1,6 +1,6 @@
/* /*
* noVNC: HTML5 VNC client * noVNC: HTML5 VNC client
* Copyright (C) 2018 The noVNC Authors * Copyright (C) 2018 The noVNC authors
* Licensed under MPL 2.0 or any later version (see LICENSE.txt) * Licensed under MPL 2.0 or any later version (see LICENSE.txt)
*/ */

View File

@ -1,6 +1,6 @@
/* /*
* noVNC: HTML5 VNC client * noVNC: HTML5 VNC client
* Copyright (C) 2020 The noVNC Authors * Copyright (C) 2020 The noVNC authors
* Licensed under MPL 2.0 (see LICENSE.txt) * Licensed under MPL 2.0 (see LICENSE.txt)
* *
* See README.md for usage and integration instructions. * See README.md for usage and integration instructions.

View File

@ -1,6 +1,6 @@
/* /*
* noVNC: HTML5 VNC client * noVNC: HTML5 VNC client
* Copyright (C) 2019 The noVNC Authors * Copyright (C) 2019 The noVNC authors
* Licensed under MPL 2.0 or any later version (see LICENSE.txt) * Licensed under MPL 2.0 or any later version (see LICENSE.txt)
*/ */
@ -36,7 +36,7 @@ export default class Keyboard {
// ===== PRIVATE METHODS ===== // ===== PRIVATE METHODS =====
_sendKeyEvent(keysym, code, down) { _sendKeyEvent(keysym, code, down, numlock = null, capslock = null) {
if (down) { if (down) {
this._keyDownList[code] = keysym; this._keyDownList[code] = keysym;
} else { } else {
@ -48,8 +48,9 @@ export default class Keyboard {
} }
Log.Debug("onkeyevent " + (down ? "down" : "up") + Log.Debug("onkeyevent " + (down ? "down" : "up") +
", keysym: " + keysym, ", code: " + code); ", keysym: " + keysym, ", code: " + code +
this.onkeyevent(keysym, code, down); ", numlock: " + numlock + ", capslock: " + capslock);
this.onkeyevent(keysym, code, down, numlock, capslock);
} }
_getKeyCode(e) { _getKeyCode(e) {
@ -86,6 +87,14 @@ export default class Keyboard {
_handleKeyDown(e) { _handleKeyDown(e) {
const code = this._getKeyCode(e); const code = this._getKeyCode(e);
let keysym = KeyboardUtil.getKeysym(e); let keysym = KeyboardUtil.getKeysym(e);
let numlock = e.getModifierState('NumLock');
let capslock = e.getModifierState('CapsLock');
// getModifierState for NumLock is not supported on mac and ios and always returns false.
// Set to null to indicate unknown/unsupported instead.
if (browser.isMac() || browser.isIOS()) {
numlock = null;
}
// Windows doesn't have a proper AltGr, but handles it using // Windows doesn't have a proper AltGr, but handles it using
// fake Ctrl+Alt. However the remote end might not be Windows, // fake Ctrl+Alt. However the remote end might not be Windows,
@ -107,7 +116,7 @@ export default class Keyboard {
// key to "AltGraph". // key to "AltGraph".
keysym = KeyTable.XK_ISO_Level3_Shift; keysym = KeyTable.XK_ISO_Level3_Shift;
} else { } else {
this._sendKeyEvent(KeyTable.XK_Control_L, "ControlLeft", true); this._sendKeyEvent(KeyTable.XK_Control_L, "ControlLeft", true, numlock, capslock);
} }
} }
@ -118,8 +127,8 @@ export default class Keyboard {
// If it's a virtual keyboard then it should be // If it's a virtual keyboard then it should be
// sufficient to just send press and release right // sufficient to just send press and release right
// after each other // after each other
this._sendKeyEvent(keysym, code, true); this._sendKeyEvent(keysym, code, true, numlock, capslock);
this._sendKeyEvent(keysym, code, false); this._sendKeyEvent(keysym, code, false, numlock, capslock);
} }
stopEvent(e); stopEvent(e);
@ -157,8 +166,8 @@ export default class Keyboard {
// while meta is held down // while meta is held down
if ((browser.isMac() || browser.isIOS()) && if ((browser.isMac() || browser.isIOS()) &&
(e.metaKey && code !== 'MetaLeft' && code !== 'MetaRight')) { (e.metaKey && code !== 'MetaLeft' && code !== 'MetaRight')) {
this._sendKeyEvent(keysym, code, true); this._sendKeyEvent(keysym, code, true, numlock, capslock);
this._sendKeyEvent(keysym, code, false); this._sendKeyEvent(keysym, code, false, numlock, capslock);
stopEvent(e); stopEvent(e);
return; return;
} }
@ -168,8 +177,8 @@ export default class Keyboard {
// which toggles on each press, but not on release. So pretend // which toggles on each press, but not on release. So pretend
// it was a quick press and release of the button. // it was a quick press and release of the button.
if ((browser.isMac() || browser.isIOS()) && (code === 'CapsLock')) { if ((browser.isMac() || browser.isIOS()) && (code === 'CapsLock')) {
this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', true); this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', true, numlock, capslock);
this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', false); this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', false, numlock, capslock);
stopEvent(e); stopEvent(e);
return; return;
} }
@ -182,8 +191,8 @@ export default class Keyboard {
KeyTable.XK_Hiragana, KeyTable.XK_Hiragana,
KeyTable.XK_Romaji ]; KeyTable.XK_Romaji ];
if (browser.isWindows() && jpBadKeys.includes(keysym)) { if (browser.isWindows() && jpBadKeys.includes(keysym)) {
this._sendKeyEvent(keysym, code, true); this._sendKeyEvent(keysym, code, true, numlock, capslock);
this._sendKeyEvent(keysym, code, false); this._sendKeyEvent(keysym, code, false, numlock, capslock);
stopEvent(e); stopEvent(e);
return; return;
} }
@ -194,12 +203,12 @@ export default class Keyboard {
if ((code === "ControlLeft") && browser.isWindows() && if ((code === "ControlLeft") && browser.isWindows() &&
!("ControlLeft" in this._keyDownList)) { !("ControlLeft" in this._keyDownList)) {
this._altGrArmed = true; this._altGrArmed = true;
this._altGrTimeout = setTimeout(this._handleAltGrTimeout.bind(this), 100); this._altGrTimeout = setTimeout(this._interruptAltGrSequence.bind(this), 100);
this._altGrCtrlTime = e.timeStamp; this._altGrCtrlTime = e.timeStamp;
return; return;
} }
this._sendKeyEvent(keysym, code, true); this._sendKeyEvent(keysym, code, true, numlock, capslock);
} }
_handleKeyUp(e) { _handleKeyUp(e) {
@ -209,11 +218,7 @@ export default class Keyboard {
// We can't get a release in the middle of an AltGr sequence, so // We can't get a release in the middle of an AltGr sequence, so
// abort that detection // abort that detection
if (this._altGrArmed) { this._interruptAltGrSequence();
this._altGrArmed = false;
clearTimeout(this._altGrTimeout);
this._sendKeyEvent(KeyTable.XK_Control_L, "ControlLeft", true);
}
// See comment in _handleKeyDown() // See comment in _handleKeyDown()
if ((browser.isMac() || browser.isIOS()) && (code === 'CapsLock')) { if ((browser.isMac() || browser.isIOS()) && (code === 'CapsLock')) {
@ -240,14 +245,20 @@ export default class Keyboard {
} }
} }
_handleAltGrTimeout() { _interruptAltGrSequence() {
this._altGrArmed = false; if (this._altGrArmed) {
clearTimeout(this._altGrTimeout); this._altGrArmed = false;
this._sendKeyEvent(KeyTable.XK_Control_L, "ControlLeft", true); clearTimeout(this._altGrTimeout);
this._sendKeyEvent(KeyTable.XK_Control_L, "ControlLeft", true);
}
} }
_allKeysUp() { _allKeysUp() {
Log.Debug(">> Keyboard.allKeysUp"); Log.Debug(">> Keyboard.allKeysUp");
// Prevent control key being processed after losing focus.
this._interruptAltGrSequence();
for (let code in this._keyDownList) { for (let code in this._keyDownList) {
this._sendKeyEvent(this._keyDownList[code], code, false); this._sendKeyEvent(this._keyDownList[code], code, false);
} }

View File

@ -67,7 +67,7 @@ export function getKeycode(evt) {
// Get 'KeyboardEvent.key', handling legacy browsers // Get 'KeyboardEvent.key', handling legacy browsers
export function getKey(evt) { export function getKey(evt) {
// Are we getting a proper key value? // Are we getting a proper key value?
if (evt.key !== undefined) { if ((evt.key !== undefined) && (evt.key !== 'Unidentified')) {
// Mozilla isn't fully in sync with the spec yet // Mozilla isn't fully in sync with the spec yet
switch (evt.key) { switch (evt.key) {
case 'OS': return 'Meta'; case 'OS': return 'Meta';

View File

@ -1,6 +1,6 @@
/* /*
* noVNC: HTML5 VNC client * noVNC: HTML5 VNC client
* Copyright (C) 2018 The noVNC Authors * Copyright (C) 2018 The noVNC authors
* Licensed under MPL 2.0 or any later version (see LICENSE.txt) * Licensed under MPL 2.0 or any later version (see LICENSE.txt)
*/ */

View File

@ -1,146 +1,25 @@
import Base64 from './base64.js';
import { encodeUTF8 } from './util/strings.js'; import { encodeUTF8 } from './util/strings.js';
import EventTargetMixin from './util/eventtarget.js'; import EventTargetMixin from './util/eventtarget.js';
import legacyCrypto from './crypto/crypto.js';
export class AESEAXCipher { class RA2Cipher {
constructor() { constructor() {
this._rawKey = null; this._cipher = 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); this._counter = new Uint8Array(16);
} }
async setKey(key) { async setKey(key) {
await this._cipher.setKey(key); this._cipher = await legacyCrypto.importKey(
"raw", key, { name: "AES-EAX" }, false, ["encrypt, decrypt"]);
} }
async makeMessage(message) { async makeMessage(message) {
const ad = new Uint8Array([(message.length & 0xff00) >>> 8, message.length & 0xff]); const ad = new Uint8Array([(message.length & 0xff00) >>> 8, message.length & 0xff]);
const encrypted = await this._cipher.encrypt(message, ad, this._counter); const encrypted = await legacyCrypto.encrypt({
name: "AES-EAX",
iv: this._counter,
additionalData: ad,
}, this._cipher, message);
for (let i = 0; i < 16 && this._counter[i]++ === 255; i++); for (let i = 0; i < 16 && this._counter[i]++ === 255; i++);
const res = new Uint8Array(message.length + 2 + 16); const res = new Uint8Array(message.length + 2 + 16);
res.set(ad); res.set(ad);
@ -148,164 +27,18 @@ export class RA2Cipher {
return res; return res;
} }
async receiveMessage(length, encrypted, mac) { async receiveMessage(length, encrypted) {
const ad = new Uint8Array([(length & 0xff00) >>> 8, length & 0xff]); const ad = new Uint8Array([(length & 0xff00) >>> 8, length & 0xff]);
const res = await this._cipher.decrypt(encrypted, ad, this._counter, mac); const res = await legacyCrypto.decrypt({
name: "AES-EAX",
iv: this._counter,
additionalData: ad,
}, this._cipher, encrypted);
for (let i = 0; i < 16 && this._counter[i]++ === 255; i++); for (let i = 0; i < 16 && this._counter[i]++ === 255; i++);
return res; 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 { export default class RSAAESAuthenticationState extends EventTargetMixin {
constructor(sock, getCredentials) { constructor(sock, getCredentials) {
super(); super();
@ -406,7 +139,7 @@ export default class RSAAESAuthenticationState extends EventTargetMixin {
this._hasStarted = true; this._hasStarted = true;
// 1: Receive server public key // 1: Receive server public key
await this._waitSockAsync(4); await this._waitSockAsync(4);
const serverKeyLengthBuffer = this._sock.rQslice(0, 4); const serverKeyLengthBuffer = this._sock.rQpeekBytes(4);
const serverKeyLength = this._sock.rQshift32(); const serverKeyLength = this._sock.rQshift32();
if (serverKeyLength < 1024) { if (serverKeyLength < 1024) {
throw new Error("RA2: server public key is too short: " + serverKeyLength); throw new Error("RA2: server public key is too short: " + serverKeyLength);
@ -417,26 +150,31 @@ export default class RSAAESAuthenticationState extends EventTargetMixin {
await this._waitSockAsync(serverKeyBytes * 2); await this._waitSockAsync(serverKeyBytes * 2);
const serverN = this._sock.rQshiftBytes(serverKeyBytes); const serverN = this._sock.rQshiftBytes(serverKeyBytes);
const serverE = this._sock.rQshiftBytes(serverKeyBytes); const serverE = this._sock.rQshiftBytes(serverKeyBytes);
const serverRSACipher = new RSACipher(serverKeyLength); const serverRSACipher = await legacyCrypto.importKey(
serverRSACipher.setPublicKey(serverN, serverE); "raw", { n: serverN, e: serverE }, { name: "RSA-PKCS1-v1_5" }, false, ["encrypt"]);
const serverPublickey = new Uint8Array(4 + serverKeyBytes * 2); const serverPublickey = new Uint8Array(4 + serverKeyBytes * 2);
serverPublickey.set(serverKeyLengthBuffer); serverPublickey.set(serverKeyLengthBuffer);
serverPublickey.set(serverN, 4); serverPublickey.set(serverN, 4);
serverPublickey.set(serverE, 4 + serverKeyBytes); serverPublickey.set(serverE, 4 + serverKeyBytes);
// verify server public key // verify server public key
let approveKey = this._waitApproveKeyAsync();
this.dispatchEvent(new CustomEvent("serververification", { this.dispatchEvent(new CustomEvent("serververification", {
detail: { type: "RSA", publickey: serverPublickey } detail: { type: "RSA", publickey: serverPublickey }
})); }));
await this._waitApproveKeyAsync(); await approveKey;
// 2: Send client public key // 2: Send client public key
const clientKeyLength = 2048; const clientKeyLength = 2048;
const clientKeyBytes = Math.ceil(clientKeyLength / 8); const clientKeyBytes = Math.ceil(clientKeyLength / 8);
const clientRSACipher = new RSACipher(clientKeyLength); const clientRSACipher = (await legacyCrypto.generateKey({
await clientRSACipher.generateKey(); name: "RSA-PKCS1-v1_5",
const clientN = clientRSACipher.n; modulusLength: clientKeyLength,
const clientE = clientRSACipher.e; publicExponent: new Uint8Array([1, 0, 1]),
}, true, ["encrypt"])).privateKey;
const clientExportedRSAKey = await legacyCrypto.exportKey("raw", clientRSACipher);
const clientN = clientExportedRSAKey.n;
const clientE = clientExportedRSAKey.e;
const clientPublicKey = new Uint8Array(4 + clientKeyBytes * 2); const clientPublicKey = new Uint8Array(4 + clientKeyBytes * 2);
clientPublicKey[0] = (clientKeyLength & 0xff000000) >>> 24; clientPublicKey[0] = (clientKeyLength & 0xff000000) >>> 24;
clientPublicKey[1] = (clientKeyLength & 0xff0000) >>> 16; clientPublicKey[1] = (clientKeyLength & 0xff0000) >>> 16;
@ -444,17 +182,20 @@ export default class RSAAESAuthenticationState extends EventTargetMixin {
clientPublicKey[3] = clientKeyLength & 0xff; clientPublicKey[3] = clientKeyLength & 0xff;
clientPublicKey.set(clientN, 4); clientPublicKey.set(clientN, 4);
clientPublicKey.set(clientE, 4 + clientKeyBytes); clientPublicKey.set(clientE, 4 + clientKeyBytes);
this._sock.send(clientPublicKey); this._sock.sQpushBytes(clientPublicKey);
this._sock.flush();
// 3: Send client random // 3: Send client random
const clientRandom = new Uint8Array(16); const clientRandom = new Uint8Array(16);
window.crypto.getRandomValues(clientRandom); window.crypto.getRandomValues(clientRandom);
const clientEncryptedRandom = serverRSACipher.encrypt(clientRandom); const clientEncryptedRandom = await legacyCrypto.encrypt(
{ name: "RSA-PKCS1-v1_5" }, serverRSACipher, clientRandom);
const clientRandomMessage = new Uint8Array(2 + serverKeyBytes); const clientRandomMessage = new Uint8Array(2 + serverKeyBytes);
clientRandomMessage[0] = (serverKeyBytes & 0xff00) >>> 8; clientRandomMessage[0] = (serverKeyBytes & 0xff00) >>> 8;
clientRandomMessage[1] = serverKeyBytes & 0xff; clientRandomMessage[1] = serverKeyBytes & 0xff;
clientRandomMessage.set(clientEncryptedRandom, 2); clientRandomMessage.set(clientEncryptedRandom, 2);
this._sock.send(clientRandomMessage); this._sock.sQpushBytes(clientRandomMessage);
this._sock.flush();
// 4: Receive server random // 4: Receive server random
await this._waitSockAsync(2); await this._waitSockAsync(2);
@ -462,7 +203,8 @@ export default class RSAAESAuthenticationState extends EventTargetMixin {
throw new Error("RA2: wrong encrypted message length"); throw new Error("RA2: wrong encrypted message length");
} }
const serverEncryptedRandom = this._sock.rQshiftBytes(clientKeyBytes); const serverEncryptedRandom = this._sock.rQshiftBytes(clientKeyBytes);
const serverRandom = clientRSACipher.decrypt(serverEncryptedRandom); const serverRandom = await legacyCrypto.decrypt(
{ name: "RSA-PKCS1-v1_5" }, clientRSACipher, serverEncryptedRandom);
if (serverRandom === null || serverRandom.length !== 16) { if (serverRandom === null || serverRandom.length !== 16) {
throw new Error("RA2: corrupted server encrypted random"); throw new Error("RA2: corrupted server encrypted random");
} }
@ -494,13 +236,14 @@ export default class RSAAESAuthenticationState extends EventTargetMixin {
clientHash = await window.crypto.subtle.digest("SHA-1", clientHash); clientHash = await window.crypto.subtle.digest("SHA-1", clientHash);
serverHash = new Uint8Array(serverHash); serverHash = new Uint8Array(serverHash);
clientHash = new Uint8Array(clientHash); clientHash = new Uint8Array(clientHash);
this._sock.send(await clientCipher.makeMessage(clientHash)); this._sock.sQpushBytes(await clientCipher.makeMessage(clientHash));
this._sock.flush();
await this._waitSockAsync(2 + 20 + 16); await this._waitSockAsync(2 + 20 + 16);
if (this._sock.rQshift16() !== 20) { if (this._sock.rQshift16() !== 20) {
throw new Error("RA2: wrong server hash"); throw new Error("RA2: wrong server hash");
} }
const serverHashReceived = await serverCipher.receiveMessage( const serverHashReceived = await serverCipher.receiveMessage(
20, this._sock.rQshiftBytes(20), this._sock.rQshiftBytes(16)); 20, this._sock.rQshiftBytes(20 + 16));
if (serverHashReceived === null) { if (serverHashReceived === null) {
throw new Error("RA2: failed to authenticate the message"); throw new Error("RA2: failed to authenticate the message");
} }
@ -516,11 +259,12 @@ export default class RSAAESAuthenticationState extends EventTargetMixin {
throw new Error("RA2: wrong subtype"); throw new Error("RA2: wrong subtype");
} }
let subtype = (await serverCipher.receiveMessage( let subtype = (await serverCipher.receiveMessage(
1, this._sock.rQshiftBytes(1), this._sock.rQshiftBytes(16))); 1, this._sock.rQshiftBytes(1 + 16)));
if (subtype === null) { if (subtype === null) {
throw new Error("RA2: failed to authenticate the message"); throw new Error("RA2: failed to authenticate the message");
} }
subtype = subtype[0]; subtype = subtype[0];
let waitCredentials = this._waitCredentialsAsync(subtype);
if (subtype === 1) { if (subtype === 1) {
if (this._getCredentials().username === undefined || if (this._getCredentials().username === undefined ||
this._getCredentials().password === undefined) { this._getCredentials().password === undefined) {
@ -537,7 +281,7 @@ export default class RSAAESAuthenticationState extends EventTargetMixin {
} else { } else {
throw new Error("RA2: wrong subtype"); throw new Error("RA2: wrong subtype");
} }
await this._waitCredentialsAsync(subtype); await waitCredentials;
let username; let username;
if (subtype === 1) { if (subtype === 1) {
username = encodeUTF8(this._getCredentials().username).slice(0, 255); username = encodeUTF8(this._getCredentials().username).slice(0, 255);
@ -554,7 +298,8 @@ export default class RSAAESAuthenticationState extends EventTargetMixin {
for (let i = 0; i < password.length; i++) { for (let i = 0; i < password.length; i++) {
credentials[username.length + 2 + i] = password.charCodeAt(i); credentials[username.length + 2 + i] = password.charCodeAt(i);
} }
this._sock.send(await clientCipher.makeMessage(credentials)); this._sock.sQpushBytes(await clientCipher.makeMessage(credentials));
this._sock.flush();
} }
get hasStarted() { get hasStarted() {
@ -564,4 +309,4 @@ export default class RSAAESAuthenticationState extends EventTargetMixin {
set hasStarted(s) { set hasStarted(s) {
this._hasStarted = s; this._hasStarted = s;
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
/* /*
* noVNC: HTML5 VNC client * noVNC: HTML5 VNC client
* Copyright (C) 2019 The noVNC Authors * Copyright (C) 2019 The noVNC authors
* Licensed under MPL 2.0 (see LICENSE.txt) * Licensed under MPL 2.0 (see LICENSE.txt)
* *
* See README.md for usage and integration instructions. * See README.md for usage and integration instructions.
@ -9,10 +9,11 @@
*/ */
import * as Log from './logging.js'; import * as Log from './logging.js';
import Base64 from '../base64.js';
// Touch detection // Touch detection
export let isTouchDevice = ('ontouchstart' in document.documentElement) || export let isTouchDevice = ('ontouchstart' in document.documentElement) ||
// requried for Chrome debugger // required for Chrome debugger
(document.ontouchstart !== undefined) || (document.ontouchstart !== undefined) ||
// required for MS Surface // required for MS Surface
(navigator.maxTouchPoints > 0) || (navigator.maxTouchPoints > 0) ||
@ -70,6 +71,86 @@ try {
} }
export const hasScrollbarGutter = _hasScrollbarGutter; export const hasScrollbarGutter = _hasScrollbarGutter;
export let supportsWebCodecsH264Decode = false;
async function _checkWebCodecsH264DecodeSupport() {
if (!('VideoDecoder' in window)) {
return false;
}
// We'll need to make do with some placeholders here
const config = {
codec: 'avc1.42401f',
codedWidth: 1920,
codedHeight: 1080,
optimizeForLatency: true,
};
let support = await VideoDecoder.isConfigSupported(config);
if (!support.supported) {
return false;
}
// Firefox incorrectly reports supports for H.264 under some
// circumstances, so we need to actually test a real frame
// https://bugzilla.mozilla.org/show_bug.cgi?id=1932392
const data = new Uint8Array(Base64.decode(
'AAAAAWdCwBTZnpuAgICgAAADACAAAAZB4oVNAAAAAWjJYyyAAAABBgX//4Hc' +
'Rem95tlIt5Ys2CDZI+7veDI2NCAtIGNvcmUgMTY0IHIzMTA4IDMxZTE5Zjkg' +
'LSBILjI2NC9NUEVHLTQgQVZDIGNvZGVjIC0gQ29weWxlZnQgMjAwMy0yMDIz' +
'IC0gaHR0cDovL3d3dy52aWRlb2xhbi5vcmcveDI2NC5odG1sIC0gb3B0aW9u' +
'czogY2FiYWM9MCByZWY9NSBkZWJsb2NrPTE6MDowIGFuYWx5c2U9MHgxOjB4' +
'MTExIG1lPWhleCBzdWJtZT04IHBzeT0xIHBzeV9yZD0xLjAwOjAuMDAgbWl4' +
'ZWRfcmVmPTEgbWVfcmFuZ2U9MTYgY2hyb21hX21lPTEgdHJlbGxpcz0yIDh4' +
'OGRjdD0wIGNxbT0wIGRlYWR6b25lPTIxLDExIGZhc3RfcHNraXA9MSBjaHJv' +
'bWFfcXBfb2Zmc2V0PS0yIHRocmVhZHM9MSBsb29rYWhlYWRfdGhyZWFkcz0x' +
'IHNsaWNlZF90aHJlYWRzPTAgbnI9MCBkZWNpbWF0ZT0xIGludGVybGFjZWQ9' +
'MCBibHVyYXlfY29tcGF0PTAgY29uc3RyYWluZWRfaW50cmE9MCBiZnJhbWVz' +
'PTAgd2VpZ2h0cD0wIGtleWludD1pbmZpbml0ZSBrZXlpbnRfbWluPTI1IHNj' +
'ZW5lY3V0PTQwIGludHJhX3JlZnJlc2g9MCByY19sb29rYWhlYWQ9NTAgcmM9' +
'YWJyIG1idHJlZT0xIGJpdHJhdGU9NDAwIHJhdGV0b2w9MS4wIHFjb21wPTAu' +
'NjAgcXBtaW49MCBxcG1heD02OSBxcHN0ZXA9NCBpcF9yYXRpbz0xLjQwIGFx' +
'PTE6MS4wMACAAAABZYiEBrxmKAAPVccAAS044AA5DRJMnkycJk4TPw=='));
let gotframe = false;
let error = null;
let decoder = new VideoDecoder({
output: (frame) => { gotframe = true; },
error: (e) => { error = e; },
});
let chunk = new EncodedVideoChunk({
timestamp: 0,
type: 'key',
data: data,
});
decoder.configure(config);
decoder.decode(chunk);
try {
await decoder.flush();
} catch (e) {
// Firefox incorrectly throws an exception here
// https://bugzilla.mozilla.org/show_bug.cgi?id=1932566
error = e;
}
// Firefox fails to deliver the error on Windows, so we need to
// check if we got a frame instead
// https://bugzilla.mozilla.org/show_bug.cgi?id=1932579
if (!gotframe) {
return false;
}
if (error !== null) {
return false;
}
return true;
}
supportsWebCodecsH264Decode = await _checkWebCodecsH264DecodeSupport();
/* /*
* The functions for detection of platforms and browsers below are exported * The functions for detection of platforms and browsers below are exported
* but the use of these should be minimized as much as possible. * but the use of these should be minimized as much as possible.

View File

@ -1,6 +1,6 @@
/* /*
* noVNC: HTML5 VNC client * noVNC: HTML5 VNC client
* Copyright (C) 2019 The noVNC Authors * Copyright (C) 2019 The noVNC authors
* Licensed under MPL 2.0 or any later version (see LICENSE.txt) * Licensed under MPL 2.0 or any later version (see LICENSE.txt)
*/ */
@ -69,7 +69,9 @@ export default class Cursor {
this._target.removeEventListener('mousemove', this._eventHandlers.mousemove, options); this._target.removeEventListener('mousemove', this._eventHandlers.mousemove, options);
this._target.removeEventListener('mouseup', this._eventHandlers.mouseup, options); this._target.removeEventListener('mouseup', this._eventHandlers.mouseup, options);
document.body.removeChild(this._canvas); if (document.contains(this._canvas)) {
document.body.removeChild(this._canvas);
}
} }
this._target = null; this._target = null;

View File

@ -1,6 +1,6 @@
/* /*
* noVNC: HTML5 VNC client * noVNC: HTML5 VNC client
* Copyright (C) 2020 The noVNC Authors * Copyright (C) 2020 The noVNC authors
* Licensed under MPL 2.0 (see LICENSE.txt) * Licensed under MPL 2.0 (see LICENSE.txt)
* *
* See README.md for usage and integration instructions. * See README.md for usage and integration instructions.

View File

@ -1,6 +1,6 @@
/* /*
* noVNC: HTML5 VNC client * noVNC: HTML5 VNC client
* Copyright (C) 2018 The noVNC Authors * Copyright (C) 2018 The noVNC authors
* Licensed under MPL 2.0 (see LICENSE.txt) * Licensed under MPL 2.0 (see LICENSE.txt)
* *
* See README.md for usage and integration instructions. * See README.md for usage and integration instructions.

View File

@ -1,6 +1,6 @@
/* /*
* noVNC: HTML5 VNC client * noVNC: HTML5 VNC client
* Copyright (C) 2019 The noVNC Authors * Copyright (C) 2019 The noVNC authors
* Licensed under MPL 2.0 (see LICENSE.txt) * Licensed under MPL 2.0 (see LICENSE.txt)
* *
* See README.md for usage and integration instructions. * See README.md for usage and integration instructions.

View File

@ -1,6 +1,6 @@
/* /*
* noVNC: HTML5 VNC client * noVNC: HTML5 VNC client
* Copyright (C) 2020 The noVNC Authors * Copyright (C) 2020 The noVNC authors
* Licensed under MPL 2.0 (see LICENSE.txt) * Licensed under MPL 2.0 (see LICENSE.txt)
* *
* See README.md for usage and integration instructions. * See README.md for usage and integration instructions.

View File

@ -1,6 +1,6 @@
/* /*
* noVNC: HTML5 VNC client * noVNC: HTML5 VNC client
* Copyright (C) 2019 The noVNC Authors * Copyright (C) 2019 The noVNC authors
* Licensed under MPL 2.0 (see LICENSE.txt) * Licensed under MPL 2.0 (see LICENSE.txt)
* *
* See README.md for usage and integration instructions. * See README.md for usage and integration instructions.

View File

@ -1,6 +1,6 @@
/* /*
* noVNC: HTML5 VNC client * noVNC: HTML5 VNC client
* Copyright (C) 2019 The noVNC Authors * Copyright (C) 2019 The noVNC authors
* Licensed under MPL 2.0 (see LICENSE.txt) * Licensed under MPL 2.0 (see LICENSE.txt)
* *
* See README.md for usage and integration instructions. * See README.md for usage and integration instructions.

View File

@ -1,6 +1,6 @@
/* /*
* Websock: high-performance buffering wrapper * Websock: high-performance buffering wrapper
* Copyright (C) 2019 The noVNC Authors * Copyright (C) 2019 The noVNC authors
* Licensed under MPL 2.0 (see LICENSE.txt) * Licensed under MPL 2.0 (see LICENSE.txt)
* *
* Websock is similar to the standard WebSocket / RTCDataChannel object * Websock is similar to the standard WebSocket / RTCDataChannel object
@ -70,7 +70,7 @@ export default class Websock {
}; };
} }
// Getters and Setters // Getters and setters
get readyState() { get readyState() {
let subState; let subState;
@ -94,27 +94,7 @@ export default class Websock {
return "unknown"; return "unknown";
} }
get sQ() { // Receive queue
return this._sQ;
}
get rQ() {
return this._rQ;
}
get rQi() {
return this._rQi;
}
set rQi(val) {
this._rQi = val;
}
// Receive Queue
get rQlen() {
return this._rQlen - this._rQi;
}
rQpeek8() { rQpeek8() {
return this._rQ[this._rQi]; return this._rQ[this._rQi];
} }
@ -141,42 +121,47 @@ export default class Websock {
for (let byte = bytes - 1; byte >= 0; byte--) { for (let byte = bytes - 1; byte >= 0; byte--) {
res += this._rQ[this._rQi++] << (byte * 8); res += this._rQ[this._rQi++] << (byte * 8);
} }
return res; return res >>> 0;
} }
rQshiftStr(len) { rQshiftStr(len) {
if (typeof(len) === 'undefined') { len = this.rQlen; }
let str = ""; let str = "";
// Handle large arrays in steps to avoid long strings on the stack // Handle large arrays in steps to avoid long strings on the stack
for (let i = 0; i < len; i += 4096) { for (let i = 0; i < len; i += 4096) {
let part = this.rQshiftBytes(Math.min(4096, len - i)); let part = this.rQshiftBytes(Math.min(4096, len - i), false);
str += String.fromCharCode.apply(null, part); str += String.fromCharCode.apply(null, part);
} }
return str; return str;
} }
rQshiftBytes(len) { rQshiftBytes(len, copy=true) {
if (typeof(len) === 'undefined') { len = this.rQlen; }
this._rQi += len; this._rQi += len;
return new Uint8Array(this._rQ.buffer, this._rQi - len, len); if (copy) {
return this._rQ.slice(this._rQi - len, this._rQi);
} else {
return this._rQ.subarray(this._rQi - len, this._rQi);
}
} }
rQshiftTo(target, len) { rQshiftTo(target, len) {
if (len === undefined) { len = this.rQlen; }
// TODO: make this just use set with views when using a ArrayBuffer to store the rQ // TODO: make this just use set with views when using a ArrayBuffer to store the rQ
target.set(new Uint8Array(this._rQ.buffer, this._rQi, len)); target.set(new Uint8Array(this._rQ.buffer, this._rQi, len));
this._rQi += len; this._rQi += len;
} }
rQslice(start, end = this.rQlen) { rQpeekBytes(len, copy=true) {
return new Uint8Array(this._rQ.buffer, this._rQi + start, end - start); if (copy) {
return this._rQ.slice(this._rQi, this._rQi + len);
} else {
return this._rQ.subarray(this._rQi, this._rQi + len);
}
} }
// Check to see if we must wait for 'num' bytes (default to FBU.bytes) // Check to see if we must wait for 'num' bytes (default to FBU.bytes)
// to be available in the receive queue. Return true if we need to // to be available in the receive queue. Return true if we need to
// wait (and possibly print a debug message), otherwise false. // wait (and possibly print a debug message), otherwise false.
rQwait(msg, num, goback) { rQwait(msg, num, goback) {
if (this.rQlen < num) { if (this._rQlen - this._rQi < num) {
if (goback) { if (goback) {
if (this._rQi < goback) { if (this._rQi < goback) {
throw new Error("rQwait cannot backup " + goback + " bytes"); throw new Error("rQwait cannot backup " + goback + " bytes");
@ -188,26 +173,61 @@ export default class Websock {
return false; return false;
} }
// Send Queue // Send queue
sQpush8(num) {
this._sQensureSpace(1);
this._sQ[this._sQlen++] = num;
}
sQpush16(num) {
this._sQensureSpace(2);
this._sQ[this._sQlen++] = (num >> 8) & 0xff;
this._sQ[this._sQlen++] = (num >> 0) & 0xff;
}
sQpush32(num) {
this._sQensureSpace(4);
this._sQ[this._sQlen++] = (num >> 24) & 0xff;
this._sQ[this._sQlen++] = (num >> 16) & 0xff;
this._sQ[this._sQlen++] = (num >> 8) & 0xff;
this._sQ[this._sQlen++] = (num >> 0) & 0xff;
}
sQpushString(str) {
let bytes = str.split('').map(chr => chr.charCodeAt(0));
this.sQpushBytes(new Uint8Array(bytes));
}
sQpushBytes(bytes) {
for (let offset = 0;offset < bytes.length;) {
this._sQensureSpace(1);
let chunkSize = this._sQbufferSize - this._sQlen;
if (chunkSize > bytes.length - offset) {
chunkSize = bytes.length - offset;
}
this._sQ.set(bytes.subarray(offset, offset + chunkSize), this._sQlen);
this._sQlen += chunkSize;
offset += chunkSize;
}
}
flush() { flush() {
if (this._sQlen > 0 && this.readyState === 'open') { if (this._sQlen > 0 && this.readyState === 'open') {
this._websocket.send(this._encodeMessage()); this._websocket.send(new Uint8Array(this._sQ.buffer, 0, this._sQlen));
this._sQlen = 0; this._sQlen = 0;
} }
} }
send(arr) { _sQensureSpace(bytes) {
this._sQ.set(arr, this._sQlen); if (this._sQbufferSize - this._sQlen < bytes) {
this._sQlen += arr.length; this.flush();
this.flush(); }
} }
sendString(str) { // Event handlers
this.send(str.split('').map(chr => chr.charCodeAt(0)));
}
// Event Handlers
off(evt) { off(evt) {
this._eventHandlers[evt] = () => {}; this._eventHandlers[evt] = () => {};
} }
@ -283,17 +303,12 @@ export default class Websock {
} }
// private methods // private methods
_encodeMessage() {
// Put in a binary arraybuffer
// according to the spec, you can send ArrayBufferViews with the send method
return new Uint8Array(this._sQ.buffer, 0, this._sQlen);
}
// We want to move all the unread data to the start of the queue, // We want to move all the unread data to the start of the queue,
// e.g. compacting. // e.g. compacting.
// The function also expands the receive que if needed, and for // The function also expands the receive que if needed, and for
// performance reasons we combine these two actions to avoid // performance reasons we combine these two actions to avoid
// unneccessary copying. // unnecessary copying.
_expandCompactRQ(minFit) { _expandCompactRQ(minFit) {
// if we're using less than 1/8th of the buffer even with the incoming bytes, compact in place // if we're using less than 1/8th of the buffer even with the incoming bytes, compact in place
// instead of resizing // instead of resizing
@ -309,8 +324,8 @@ export default class Websock {
// we don't want to grow unboundedly // we don't want to grow unboundedly
if (this._rQbufferSize > MAX_RQ_GROW_SIZE) { if (this._rQbufferSize > MAX_RQ_GROW_SIZE) {
this._rQbufferSize = MAX_RQ_GROW_SIZE; this._rQbufferSize = MAX_RQ_GROW_SIZE;
if (this._rQbufferSize - this.rQlen < minFit) { if (this._rQbufferSize - (this._rQlen - this._rQi) < minFit) {
throw new Error("Receive Queue buffer exceeded " + MAX_RQ_GROW_SIZE + " bytes, and the new message could not fit"); throw new Error("Receive queue buffer exceeded " + MAX_RQ_GROW_SIZE + " bytes, and the new message could not fit");
} }
} }
@ -327,25 +342,22 @@ export default class Websock {
} }
// push arraybuffer values onto the end of the receive que // push arraybuffer values onto the end of the receive que
_DecodeMessage(data) { _recvMessage(e) {
const u8 = new Uint8Array(data); if (this._rQlen == this._rQi) {
// All data has now been processed, this means we
// can reset the receive queue.
this._rQlen = 0;
this._rQi = 0;
}
const u8 = new Uint8Array(e.data);
if (u8.length > this._rQbufferSize - this._rQlen) { if (u8.length > this._rQbufferSize - this._rQlen) {
this._expandCompactRQ(u8.length); this._expandCompactRQ(u8.length);
} }
this._rQ.set(u8, this._rQlen); this._rQ.set(u8, this._rQlen);
this._rQlen += u8.length; this._rQlen += u8.length;
}
_recvMessage(e) { if (this._rQlen - this._rQi > 0) {
this._DecodeMessage(e.data);
if (this.rQlen > 0) {
this._eventHandlers.message(); this._eventHandlers.message();
if (this._rQlen == this._rQi) {
// All data has now been processed, this means we
// can reset the receive queue.
this._rQlen = 0;
this._rQi = 0;
}
} else { } else {
Log.Debug("Ignoring empty message"); Log.Debug("Ignoring empty message");
} }

1
defaults.json Normal file
View File

@ -0,0 +1 @@
{}

View File

@ -1,4 +1,4 @@
# 1. Internal Modules # 1. Internal modules
The noVNC client is composed of several internal modules that handle The noVNC client is composed of several internal modules that handle
rendering, input, networking, etc. Each of the modules is designed to rendering, input, networking, etc. Each of the modules is designed to
@ -9,7 +9,7 @@ stable, and this documentation is not maintained as well as the
official external API. official external API.
## 1.1 Module List ## 1.1 Module list
* __Keyboard__ (core/input/keyboard.js): Keyboard input event handler with * __Keyboard__ (core/input/keyboard.js): Keyboard input event handler with
non-US keyboard support. Translates keyDown and keyUp events to X11 non-US keyboard support. Translates keyDown and keyUp events to X11
@ -32,9 +32,9 @@ callback event name, and the callback function.
## 2. Modules ## 2. Modules
## 2.1 Keyboard Module ## 2.1 Keyboard module
### 2.1.1 Configuration Attributes ### 2.1.1 Configuration attributes
None None
@ -52,9 +52,9 @@ None
| onkeypress | (keysym, code, down) | Handler for key press/release | onkeypress | (keysym, code, down) | Handler for key press/release
## 2.2 Display Module ## 2.2 Display module
### 2.2.1 Configuration Attributes ### 2.2.1 Configuration attributes
| name | type | mode | default | description | name | type | mode | default | description
| ------------ | ----- | ---- | ------- | ------------ | ------------ | ----- | ---- | ------- | ------------
@ -81,9 +81,3 @@ None
| blitImage | (x, y, width, height, arr, offset, from_queue) | Blit pixels (of R,G,B,A) to the display | blitImage | (x, y, width, height, arr, offset, from_queue) | Blit pixels (of R,G,B,A) to the display
| drawImage | (img, x, y) | Draw image and track damage | drawImage | (img, x, y) | Draw image and track damage
| autoscale | (containerWidth, containerHeight) | Scale the display | autoscale | (containerWidth, containerHeight) | Scale the display
### 2.2.3 Callbacks
| name | parameters | description
| ------- | ---------- | ------------
| onflush | () | A display flush has been requested and we are now ready to resume FBU processing

View File

@ -1,4 +1,4 @@
# Embedding and Deploying noVNC Application # Embedding and deploying noVNC application
This document describes how to embed and deploy the noVNC application, which This document describes how to embed and deploy the noVNC application, which
includes settings and a full user interface. If you are looking for includes settings and a full user interface. If you are looking for
@ -71,8 +71,8 @@ query string. Currently the following options are available:
* `logging` - The console log level. Can be one of `error`, `warn`, `info` or * `logging` - The console log level. Can be one of `error`, `warn`, `info` or
`debug`. `debug`.
## HTTP Serving Considerations ## HTTP serving considerations
### Browser Cache Issue ### Browser cache issue
If you serve noVNC files using a web server that provides an ETag header, and If you serve noVNC files using a web server that provides an ETag header, and
include any options in the query string, a nasty browser cache issue can bite include any options in the query string, a nasty browser cache issue can bite
@ -84,7 +84,7 @@ to always revalidate cached files using conditional requests. The correct
semantics are achieved via the (confusingly named) `Cache-Control: no-cache` semantics are achieved via the (confusingly named) `Cache-Control: no-cache`
header that needs to be provided in the web server responses. header that needs to be provided in the web server responses.
### Example Server Configurations ### Example server configurations
Apache: Apache:

View File

@ -16,7 +16,7 @@ noVNC includes a small example application called `vnc_lite.html`. This does
not make use of all the features of noVNC, but is a good start to see how to not make use of all the features of noVNC, but is a good start to see how to
do things. do things.
## Conversion of Modules ## Conversion of modules
noVNC is written using ECMAScript 6 modules. This is not supported by older 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 versions of Node.js. To use noVNC with those older versions of Node.js the

View File

@ -3,13 +3,13 @@
.SH NAME .SH NAME
novnc_proxy - noVNC proxy server novnc_proxy - noVNC proxy server
.SH SYNOPSIS .SH SYNOPSIS
.B novnc_proxy [--listen PORT] [--vnc VNC_HOST:PORT] [--cert CERT] [--ssl-only] .B novnc_proxy [--listen [HOST:]PORT] [--vnc VNC_HOST:PORT] [--cert CERT] [--ssl-only]
Starts the WebSockets proxy and a mini-webserver and Starts the WebSockets proxy and a mini-webserver and
provides a cut-and-paste URL to go to. provides a cut-and-paste URL to go to.
--listen PORT Port for proxy/webserver to listen on --listen [HOST:]PORT Port for proxy/webserver to listen on
Default: 6080 Default: 6080 (on all interfaces)
--vnc VNC_HOST:PORT VNC server host:port proxy target --vnc VNC_HOST:PORT VNC server host:port proxy target
Default: localhost:5900 Default: localhost:5900
--cert CERT Path to combined cert/key file, or just --cert CERT Path to combined cert/key file, or just
@ -30,7 +30,7 @@ provides a cut-and-paste URL to go to.
active connections active connections
.SH AUTHOR .SH AUTHOR
The noVNC Authors The noVNC authors
https://github.com/novnc/noVNC https://github.com/novnc/noVNC
.SH SEE ALSO .SH SEE ALSO

102
eslint.config.mjs Normal file
View File

@ -0,0 +1,102 @@
import globals from "globals";
import js from "@eslint/js";
export default [
js.configs.recommended,
{
languageOptions: {
ecmaVersion: 2022,
sourceType: "module",
globals: {
...globals.browser,
...globals.es2022,
}
},
ignores: ["**/xtscancodes.js"],
rules: {
// Unsafe or confusing stuff that we forbid
"no-unused-vars": ["error", { "vars": "all",
"args": "none",
"ignoreRestSiblings": true,
"caughtErrors": "none" }],
"no-constant-condition": ["error", { "checkLoops": false }],
"no-var": "error",
"no-useless-constructor": "error",
"object-shorthand": ["error", "methods", { "avoidQuotes": true }],
"prefer-arrow-callback": "error",
"arrow-body-style": ["error", "as-needed", { "requireReturnForObjectLiteral": false } ],
"arrow-parens": ["error", "as-needed", { "requireForBlockBody": true }],
"arrow-spacing": ["error"],
"no-confusing-arrow": ["error", { "allowParens": true }],
// Enforced coding style
"brace-style": ["error", "1tbs", { "allowSingleLine": true }],
"indent": ["error", 4, { "SwitchCase": 1,
"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"],
"curly": ["error", "multi-line"],
"func-call-spacing": ["error"],
"func-names": ["error"],
"func-style": ["error", "declaration", { "allowArrowFunctions": true }],
"key-spacing": ["error"],
"keyword-spacing": ["error"],
"no-trailing-spaces": ["error"],
"semi": ["error"],
"space-before-blocks": ["error"],
"space-before-function-paren": ["error", { "anonymous": "always",
"named": "never",
"asyncArrow": "always" }],
"switch-colon-spacing": ["error"],
"camelcase": ["error", { "allow": ["^XK_", "^XF86XK_"] }],
"no-console": ["error"],
}
},
{
files: ["po/po2js", "po/xgettext-html"],
languageOptions: {
globals: {
...globals.node,
}
},
rules: {
"no-console": 0,
},
},
{
files: ["tests/*"],
languageOptions: {
globals: {
...globals.node,
...globals.mocha,
sinon: false,
expect: false,
}
},
rules: {
"prefer-arrow-callback": 0,
// Too many anonymous callbacks
"func-names": "off",
},
},
{
files: ["utils/*"],
languageOptions: {
globals: {
...globals.node,
}
},
rules: {
"no-console": 0,
},
},
];

View File

@ -27,15 +27,22 @@ module.exports = (config) => {
// frameworks to use // frameworks to use
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
frameworks: ['mocha', 'sinon-chai'], frameworks: ['mocha'],
// list of files / patterns to load in the browser (loaded in order) // list of files / patterns to load in the browser
files: [ files: [
// node modules
{ pattern: 'node_modules/chai/**', included: false },
{ pattern: 'node_modules/sinon/**', included: false },
{ pattern: 'node_modules/sinon-chai/**', included: false },
// modules to test
{ pattern: 'app/localization.js', included: false, type: 'module' }, { pattern: 'app/localization.js', included: false, type: 'module' },
{ pattern: 'app/webutil.js', included: false, type: 'module' }, { pattern: 'app/webutil.js', included: false, type: 'module' },
{ pattern: 'core/**/*.js', included: false, type: 'module' }, { pattern: 'core/**/*.js', included: false, type: 'module' },
{ pattern: 'vendor/pako/**/*.js', included: false, type: 'module' }, { pattern: 'vendor/pako/**/*.js', included: false, type: 'module' },
// tests
{ pattern: 'tests/test.*.js', type: 'module' }, { pattern: 'tests/test.*.js', type: 'module' },
// test support files
{ pattern: 'tests/fake.*.js', included: false, type: 'module' }, { pattern: 'tests/fake.*.js', included: false, type: 'module' },
{ pattern: 'tests/assertions.js', type: 'module' }, { pattern: 'tests/assertions.js', type: 'module' },
], ],

1
mandatory.json Normal file
View File

@ -0,0 +1 @@
{}

View File

@ -1,6 +1,6 @@
{ {
"name": "@novnc/novnc", "name": "@novnc/novnc",
"version": "1.4.0", "version": "1.6.0",
"description": "An HTML5 VNC client", "description": "An HTML5 VNC client",
"browser": "lib/rfb", "browser": "lib/rfb",
"directories": { "directories": {
@ -14,9 +14,7 @@
"VERSION", "VERSION",
"docs/API.md", "docs/API.md",
"docs/LIBRARY.md", "docs/LIBRARY.md",
"docs/LICENSE*", "docs/LICENSE*"
"core",
"vendor/pako"
], ],
"scripts": { "scripts": {
"lint": "eslint app core po/po2js po/xgettext-html tests utils", "lint": "eslint app core po/po2js po/xgettext-html tests utils",
@ -39,19 +37,14 @@
"homepage": "https://github.com/novnc/noVNC", "homepage": "https://github.com/novnc/noVNC",
"devDependencies": { "devDependencies": {
"@babel/core": "latest", "@babel/core": "latest",
"@babel/plugin-syntax-dynamic-import": "latest",
"@babel/plugin-transform-modules-commonjs": "latest",
"@babel/preset-env": "latest", "@babel/preset-env": "latest",
"@babel/cli": "latest",
"babel-plugin-import-redirect": "latest", "babel-plugin-import-redirect": "latest",
"browserify": "latest", "browserify": "latest",
"babelify": "latest",
"core-js": "latest",
"chai": "latest", "chai": "latest",
"commander": "latest", "commander": "latest",
"es-module-loader": "latest",
"eslint": "latest", "eslint": "latest",
"fs-extra": "latest", "fs-extra": "latest",
"globals": "latest",
"jsdom": "latest", "jsdom": "latest",
"karma": "latest", "karma": "latest",
"karma-mocha": "latest", "karma-mocha": "latest",
@ -62,10 +55,8 @@
"karma-mocha-reporter": "latest", "karma-mocha-reporter": "latest",
"karma-safari-launcher": "latest", "karma-safari-launcher": "latest",
"karma-script-launcher": "latest", "karma-script-launcher": "latest",
"karma-sinon-chai": "latest",
"mocha": "latest", "mocha": "latest",
"node-getopt": "latest", "pofile": "latest",
"po2json": "latest",
"sinon": "latest", "sinon": "latest",
"sinon-chai": "latest" "sinon-chai": "latest"
}, },

View File

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

View File

@ -19,7 +19,7 @@ update-js: $(JSONFILES)
update-pot: update-pot:
xgettext --output=noVNC.js.pot \ xgettext --output=noVNC.js.pot \
--copyright-holder="The noVNC Authors" \ --copyright-holder="The noVNC authors" \
--package-name="noVNC" \ --package-name="noVNC" \
--package-version="$(VERSION)" \ --package-version="$(VERSION)" \
--msgid-bugs-address="novnc@googlegroups.com" \ --msgid-bugs-address="novnc@googlegroups.com" \

View File

@ -1,5 +1,5 @@
# Czech translations for noVNC package. # Czech translations for noVNC package.
# Copyright (C) 2018 The noVNC Authors # Copyright (C) 2018 The noVNC authors
# This file is distributed under the same license as the noVNC package. # This file is distributed under the same license as the noVNC package.
# Petr <petr@kle.cz>, 2018. # Petr <petr@kle.cz>, 2018.
# #
@ -78,7 +78,7 @@ msgid "Hide/Show the control bar"
msgstr "Skrýt/zobrazit ovládací panel" msgstr "Skrýt/zobrazit ovládací panel"
#: ../vnc.html:101 #: ../vnc.html:101
msgid "Move/Drag Viewport" msgid "Move/Drag viewport"
msgstr "Přesunout/přetáhnout výřez" msgstr "Přesunout/přetáhnout výřez"
#: ../vnc.html:101 #: ../vnc.html:101
@ -110,7 +110,7 @@ msgid "Keyboard"
msgstr "Klávesnice" msgstr "Klávesnice"
#: ../vnc.html:119 #: ../vnc.html:119
msgid "Show Keyboard" msgid "Show keyboard"
msgstr "Zobrazit klávesnici" msgstr "Zobrazit klávesnici"
#: ../vnc.html:126 #: ../vnc.html:126
@ -118,7 +118,7 @@ msgid "Extra keys"
msgstr "Extra klávesy" msgstr "Extra klávesy"
#: ../vnc.html:126 #: ../vnc.html:126
msgid "Show Extra Keys" msgid "Show extra keys"
msgstr "Zobrazit extra klávesy" msgstr "Zobrazit extra klávesy"
#: ../vnc.html:131 #: ../vnc.html:131
@ -202,19 +202,19 @@ msgid "Settings"
msgstr "Nastavení" msgstr "Nastavení"
#: ../vnc.html:197 #: ../vnc.html:197
msgid "Shared Mode" msgid "Shared mode"
msgstr "Sdílený režim" msgstr "Sdílený režim"
#: ../vnc.html:200 #: ../vnc.html:200
msgid "View Only" msgid "View only"
msgstr "Pouze prohlížení" msgstr "Pouze prohlížení"
#: ../vnc.html:204 #: ../vnc.html:204
msgid "Clip to Window" msgid "Clip to window"
msgstr "Přizpůsobit oknu" msgstr "Přizpůsobit oknu"
#: ../vnc.html:207 #: ../vnc.html:207
msgid "Scaling Mode:" msgid "Scaling mode:"
msgstr "Přizpůsobení velikosti" msgstr "Přizpůsobení velikosti"
#: ../vnc.html:209 #: ../vnc.html:209
@ -222,11 +222,11 @@ msgid "None"
msgstr "Žádné" msgstr "Žádné"
#: ../vnc.html:210 #: ../vnc.html:210
msgid "Local Scaling" msgid "Local scaling"
msgstr "Místní" msgstr "Místní"
#: ../vnc.html:211 #: ../vnc.html:211
msgid "Remote Resizing" msgid "Remote resizing"
msgstr "Vzdálené" msgstr "Vzdálené"
#: ../vnc.html:216 #: ../vnc.html:216
@ -258,15 +258,15 @@ msgid "Path:"
msgstr "Cesta" msgstr "Cesta"
#: ../vnc.html:244 #: ../vnc.html:244
msgid "Automatic Reconnect" msgid "Automatic reconnect"
msgstr "Automatická obnova připojení" msgstr "Automatická obnova připojení"
#: ../vnc.html:247 #: ../vnc.html:247
msgid "Reconnect Delay (ms):" msgid "Reconnect delay (ms):"
msgstr "Zpoždění připojení (ms)" msgstr "Zpoždění připojení (ms)"
#: ../vnc.html:252 #: ../vnc.html:252
msgid "Show Dot when No Cursor" msgid "Show dot when no cursor"
msgstr "Tečka místo chybějícího kurzoru myši" msgstr "Tečka místo chybějícího kurzoru myši"
#: ../vnc.html:257 #: ../vnc.html:257

View File

@ -1,6 +1,6 @@
# German translations for noVNC package # German translations for noVNC package
# German translation for noVNC. # German translation for noVNC.
# Copyright (C) 2018 The noVNC Authors # Copyright (C) 2018 The noVNC authors
# This file is distributed under the same license as the noVNC package. # This file is distributed under the same license as the noVNC package.
# Loek Janssen <loekjanssen@gmail.com>, 2016. # Loek Janssen <loekjanssen@gmail.com>, 2016.
# #
@ -76,7 +76,7 @@ msgid "Hide/Show the control bar"
msgstr "Kontrollleiste verstecken/anzeigen" msgstr "Kontrollleiste verstecken/anzeigen"
#: ../vnc.html:106 #: ../vnc.html:106
msgid "Move/Drag Viewport" msgid "Move/Drag viewport"
msgstr "Ansichtsfenster verschieben/ziehen" msgstr "Ansichtsfenster verschieben/ziehen"
#: ../vnc.html:106 #: ../vnc.html:106
@ -108,7 +108,7 @@ msgid "Keyboard"
msgstr "Tastatur" msgstr "Tastatur"
#: ../vnc.html:124 #: ../vnc.html:124
msgid "Show Keyboard" msgid "Show keyboard"
msgstr "Tastatur anzeigen" msgstr "Tastatur anzeigen"
#: ../vnc.html:131 #: ../vnc.html:131
@ -116,7 +116,7 @@ msgid "Extra keys"
msgstr "Zusatztasten" msgstr "Zusatztasten"
#: ../vnc.html:131 #: ../vnc.html:131
msgid "Show Extra Keys" msgid "Show extra keys"
msgstr "Zusatztasten anzeigen" msgstr "Zusatztasten anzeigen"
#: ../vnc.html:136 #: ../vnc.html:136
@ -200,19 +200,19 @@ msgid "Settings"
msgstr "Einstellungen" msgstr "Einstellungen"
#: ../vnc.html:202 #: ../vnc.html:202
msgid "Shared Mode" msgid "Shared mode"
msgstr "Geteilter Modus" msgstr "Geteilter Modus"
#: ../vnc.html:205 #: ../vnc.html:205
msgid "View Only" msgid "View only"
msgstr "Nur betrachten" msgstr "Nur betrachten"
#: ../vnc.html:209 #: ../vnc.html:209
msgid "Clip to Window" msgid "Clip to window"
msgstr "Auf Fenster begrenzen" msgstr "Auf Fenster begrenzen"
#: ../vnc.html:212 #: ../vnc.html:212
msgid "Scaling Mode:" msgid "Scaling mode:"
msgstr "Skalierungsmodus:" msgstr "Skalierungsmodus:"
#: ../vnc.html:214 #: ../vnc.html:214
@ -220,11 +220,11 @@ msgid "None"
msgstr "Keiner" msgstr "Keiner"
#: ../vnc.html:215 #: ../vnc.html:215
msgid "Local Scaling" msgid "Local scaling"
msgstr "Lokales skalieren" msgstr "Lokales skalieren"
#: ../vnc.html:216 #: ../vnc.html:216
msgid "Remote Resizing" msgid "Remote resizing"
msgstr "Serverseitiges skalieren" msgstr "Serverseitiges skalieren"
#: ../vnc.html:221 #: ../vnc.html:221
@ -256,11 +256,11 @@ msgid "Path:"
msgstr "Pfad:" msgstr "Pfad:"
#: ../vnc.html:249 #: ../vnc.html:249
msgid "Automatic Reconnect" msgid "Automatic reconnect"
msgstr "Automatisch wiederverbinden" msgstr "Automatisch wiederverbinden"
#: ../vnc.html:252 #: ../vnc.html:252
msgid "Reconnect Delay (ms):" msgid "Reconnect delay (ms):"
msgstr "Wiederverbindungsverzögerung (ms):" msgstr "Wiederverbindungsverzögerung (ms):"
#: ../vnc.html:258 #: ../vnc.html:258

264
po/el.po
View File

@ -1,5 +1,5 @@
# Greek translations for noVNC package. # Greek translations for noVNC package.
# Copyright (C) 2018 The noVNC Authors # Copyright (C) 2018 The noVNC authors
# This file is distributed under the same license as the noVNC package. # This file is distributed under the same license as the noVNC package.
# Giannis Kosmas <kosmasgiannis@gmail.com>, 2016. # Giannis Kosmas <kosmasgiannis@gmail.com>, 2016.
# #
@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: noVNC 0.6.1\n" "Project-Id-Version: noVNC 0.6.1\n"
"Report-Msgid-Bugs-To: novnc@googlegroups.com\n" "Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
"POT-Creation-Date: 2017-11-17 21:40+0200\n" "POT-Creation-Date: 2022-12-27 15:24+0100\n"
"PO-Revision-Date: 2017-10-11 16:16+0200\n" "PO-Revision-Date: 2017-10-11 16:16+0200\n"
"Last-Translator: Giannis Kosmas <kosmasgiannis@gmail.com>\n" "Last-Translator: Giannis Kosmas <kosmasgiannis@gmail.com>\n"
"Language-Team: none\n" "Language-Team: none\n"
@ -17,273 +17,349 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: ../app/ui.js:404 #: ../app/ui.js:69
msgid "HTTPS is required for full functionality"
msgstr "Το HTTPS είναι απαιτούμενο για πλήρη λειτουργικότητα"
#: ../app/ui.js:410
msgid "Connecting..." msgid "Connecting..."
msgstr "Συνδέεται..." msgstr "Συνδέεται..."
#: ../app/ui.js:411 #: ../app/ui.js:417
msgid "Disconnecting..." msgid "Disconnecting..."
msgstr "Aποσυνδέεται..." msgstr "Aποσυνδέεται..."
#: ../app/ui.js:417 #: ../app/ui.js:423
msgid "Reconnecting..." msgid "Reconnecting..."
msgstr "Επανασυνδέεται..." msgstr "Επανασυνδέεται..."
#: ../app/ui.js:422 #: ../app/ui.js:428
msgid "Internal error" msgid "Internal error"
msgstr "Εσωτερικό σφάλμα" msgstr "Εσωτερικό σφάλμα"
#: ../app/ui.js:1019 #: ../app/ui.js:1026
msgid "Must set host" msgid "Must set host"
msgstr "Πρέπει να οριστεί ο διακομιστής" msgstr "Πρέπει να οριστεί ο διακομιστής"
#: ../app/ui.js:1099 #: ../app/ui.js:1110
msgid "Connected (encrypted) to " msgid "Connected (encrypted) to "
msgstr "Συνδέθηκε (κρυπτογραφημένα) με το " msgstr "Συνδέθηκε (κρυπτογραφημένα) με το "
#: ../app/ui.js:1101 #: ../app/ui.js:1112
msgid "Connected (unencrypted) to " msgid "Connected (unencrypted) to "
msgstr "Συνδέθηκε (μη κρυπτογραφημένα) με το " msgstr "Συνδέθηκε (μη κρυπτογραφημένα) με το "
#: ../app/ui.js:1119 #: ../app/ui.js:1135
msgid "Something went wrong, connection is closed" msgid "Something went wrong, connection is closed"
msgstr "Κάτι πήγε στραβά, η σύνδεση διακόπηκε" msgstr "Κάτι πήγε στραβά, η σύνδεση διακόπηκε"
#: ../app/ui.js:1129 #: ../app/ui.js:1138
msgid "Failed to connect to server"
msgstr "Αποτυχία στη σύνδεση με το διακομιστή"
#: ../app/ui.js:1150
msgid "Disconnected" msgid "Disconnected"
msgstr "Αποσυνδέθηκε" msgstr "Αποσυνδέθηκε"
#: ../app/ui.js:1142 #: ../app/ui.js:1165
msgid "New connection has been rejected with reason: " msgid "New connection has been rejected with reason: "
msgstr "Η νέα σύνδεση απορρίφθηκε διότι: " msgstr "Η νέα σύνδεση απορρίφθηκε διότι: "
#: ../app/ui.js:1145 #: ../app/ui.js:1168
msgid "New connection has been rejected" msgid "New connection has been rejected"
msgstr "Η νέα σύνδεση απορρίφθηκε " msgstr "Η νέα σύνδεση απορρίφθηκε "
#: ../app/ui.js:1166 #: ../app/ui.js:1234
msgid "Password is required" msgid "Credentials are required"
msgstr "Απαιτείται ο κωδικός πρόσβασης" msgstr "Απαιτούνται διαπιστευτήρια"
#: ../vnc.html:89 #: ../vnc.html:57
msgid "noVNC encountered an error:" msgid "noVNC encountered an error:"
msgstr "το noVNC αντιμετώπισε ένα σφάλμα:" msgstr "το noVNC αντιμετώπισε ένα σφάλμα:"
#: ../vnc.html:99 #: ../vnc.html:67
msgid "Hide/Show the control bar" msgid "Hide/Show the control bar"
msgstr "Απόκρυψη/Εμφάνιση γραμμής ελέγχου" msgstr "Απόκρυψη/Εμφάνιση γραμμής ελέγχου"
#: ../vnc.html:106 #: ../vnc.html:76
msgid "Drag"
msgstr "Σύρσιμο"
#: ../vnc.html:76
msgid "Move/Drag Viewport" msgid "Move/Drag Viewport"
msgstr "Μετακίνηση/Σύρσιμο Θεατού πεδίου" msgstr "Μετακίνηση/Σύρσιμο Θεατού πεδίου"
#: ../vnc.html:106 #: ../vnc.html:82
msgid "viewport drag"
msgstr "σύρσιμο θεατού πεδίου"
#: ../vnc.html:112 ../vnc.html:115 ../vnc.html:118 ../vnc.html:121
msgid "Active Mouse Button"
msgstr "Ενεργό Πλήκτρο Ποντικιού"
#: ../vnc.html:112
msgid "No mousebutton"
msgstr "Χωρίς Πλήκτρο Ποντικιού"
#: ../vnc.html:115
msgid "Left mousebutton"
msgstr "Αριστερό Πλήκτρο Ποντικιού"
#: ../vnc.html:118
msgid "Middle mousebutton"
msgstr "Μεσαίο Πλήκτρο Ποντικιού"
#: ../vnc.html:121
msgid "Right mousebutton"
msgstr "Δεξί Πλήκτρο Ποντικιού"
#: ../vnc.html:124
msgid "Keyboard" msgid "Keyboard"
msgstr "Πληκτρολόγιο" msgstr "Πληκτρολόγιο"
#: ../vnc.html:124 #: ../vnc.html:82
msgid "Show Keyboard" msgid "Show Keyboard"
msgstr "Εμφάνιση Πληκτρολογίου" msgstr "Εμφάνιση Πληκτρολογίου"
#: ../vnc.html:131 #: ../vnc.html:87
msgid "Extra keys" msgid "Extra keys"
msgstr "Επιπλέον πλήκτρα" msgstr "Επιπλέον πλήκτρα"
#: ../vnc.html:131 #: ../vnc.html:87
msgid "Show Extra Keys" msgid "Show Extra Keys"
msgstr "Εμφάνιση Επιπλέον Πλήκτρων" msgstr "Εμφάνιση Επιπλέον Πλήκτρων"
#: ../vnc.html:136 #: ../vnc.html:92
msgid "Ctrl" msgid "Ctrl"
msgstr "Ctrl" msgstr "Ctrl"
#: ../vnc.html:136 #: ../vnc.html:92
msgid "Toggle Ctrl" msgid "Toggle Ctrl"
msgstr "Εναλλαγή Ctrl" msgstr "Εναλλαγή Ctrl"
#: ../vnc.html:139 #: ../vnc.html:95
msgid "Alt" msgid "Alt"
msgstr "Alt" msgstr "Alt"
#: ../vnc.html:139 #: ../vnc.html:95
msgid "Toggle Alt" msgid "Toggle Alt"
msgstr "Εναλλαγή Alt" msgstr "Εναλλαγή Alt"
#: ../vnc.html:142 #: ../vnc.html:98
msgid "Toggle Windows"
msgstr "Εναλλαγή Παράθυρων"
#: ../vnc.html:98
msgid "Windows"
msgstr "Παράθυρα"
#: ../vnc.html:101
msgid "Send Tab" msgid "Send Tab"
msgstr "Αποστολή Tab" msgstr "Αποστολή Tab"
#: ../vnc.html:142 #: ../vnc.html:101
msgid "Tab" msgid "Tab"
msgstr "Tab" msgstr "Tab"
#: ../vnc.html:145 #: ../vnc.html:104
msgid "Esc" msgid "Esc"
msgstr "Esc" msgstr "Esc"
#: ../vnc.html:145 #: ../vnc.html:104
msgid "Send Escape" msgid "Send Escape"
msgstr "Αποστολή Escape" msgstr "Αποστολή Escape"
#: ../vnc.html:148 #: ../vnc.html:107
msgid "Ctrl+Alt+Del" msgid "Ctrl+Alt+Del"
msgstr "Ctrl+Alt+Del" msgstr "Ctrl+Alt+Del"
#: ../vnc.html:148 #: ../vnc.html:107
msgid "Send Ctrl-Alt-Del" msgid "Send Ctrl-Alt-Del"
msgstr "Αποστολή Ctrl-Alt-Del" msgstr "Αποστολή Ctrl-Alt-Del"
#: ../vnc.html:156 #: ../vnc.html:114
msgid "Shutdown/Reboot" msgid "Shutdown/Reboot"
msgstr "Κλείσιμο/Επανεκκίνηση" msgstr "Κλείσιμο/Επανεκκίνηση"
#: ../vnc.html:156 #: ../vnc.html:114
msgid "Shutdown/Reboot..." msgid "Shutdown/Reboot..."
msgstr "Κλείσιμο/Επανεκκίνηση..." msgstr "Κλείσιμο/Επανεκκίνηση..."
#: ../vnc.html:162 #: ../vnc.html:120
msgid "Power" msgid "Power"
msgstr "Απενεργοποίηση" msgstr "Απενεργοποίηση"
#: ../vnc.html:164 #: ../vnc.html:122
msgid "Shutdown" msgid "Shutdown"
msgstr "Κλείσιμο" msgstr "Κλείσιμο"
#: ../vnc.html:165 #: ../vnc.html:123
msgid "Reboot" msgid "Reboot"
msgstr "Επανεκκίνηση" msgstr "Επανεκκίνηση"
#: ../vnc.html:166 #: ../vnc.html:124
msgid "Reset" msgid "Reset"
msgstr "Επαναφορά" msgstr "Επαναφορά"
#: ../vnc.html:171 ../vnc.html:177 #: ../vnc.html:129 ../vnc.html:135
msgid "Clipboard" msgid "Clipboard"
msgstr "Πρόχειρο" msgstr "Πρόχειρο"
#: ../vnc.html:181 #: ../vnc.html:137
msgid "Clear" msgid "Edit clipboard content in the textarea below."
msgstr "Καθάρισμα" msgstr "Επεξεργαστείτε το περιεχόμενο του πρόχειρου στην περιοχή κειμένου παρακάτω."
#: ../vnc.html:187 #: ../vnc.html:145
msgid "Fullscreen" #, fuzzy
msgid "Full Screen"
msgstr "Πλήρης Οθόνη" msgstr "Πλήρης Οθόνη"
#: ../vnc.html:192 ../vnc.html:199 #: ../vnc.html:150 ../vnc.html:156
msgid "Settings" msgid "Settings"
msgstr "Ρυθμίσεις" msgstr "Ρυθμίσεις"
#: ../vnc.html:202 #: ../vnc.html:160
msgid "Shared Mode" msgid "Shared Mode"
msgstr "Κοινόχρηστη Λειτουργία" msgstr "Κοινόχρηστη Λειτουργία"
#: ../vnc.html:205 #: ../vnc.html:163
msgid "View Only" msgid "View Only"
msgstr "Μόνο Θέαση" msgstr "Μόνο Θέαση"
#: ../vnc.html:209 #: ../vnc.html:167
msgid "Clip to Window" msgid "Clip to Window"
msgstr "Αποκοπή στο όριο του Παράθυρου" msgstr "Αποκοπή στο όριο του Παράθυρου"
#: ../vnc.html:212 #: ../vnc.html:170
msgid "Scaling Mode:" msgid "Scaling Mode:"
msgstr "Λειτουργία Κλιμάκωσης:" msgstr "Λειτουργία Κλιμάκωσης:"
#: ../vnc.html:214 #: ../vnc.html:172
msgid "None" msgid "None"
msgstr "Καμία" msgstr "Καμία"
#: ../vnc.html:215 #: ../vnc.html:173
msgid "Local Scaling" msgid "Local Scaling"
msgstr "Τοπική Κλιμάκωση" msgstr "Τοπική Κλιμάκωση"
#: ../vnc.html:216 #: ../vnc.html:174
msgid "Remote Resizing" msgid "Remote Resizing"
msgstr "Απομακρυσμένη Αλλαγή μεγέθους" msgstr "Απομακρυσμένη Αλλαγή μεγέθους"
#: ../vnc.html:221 #: ../vnc.html:179
msgid "Advanced" msgid "Advanced"
msgstr "Για προχωρημένους" msgstr "Για προχωρημένους"
#: ../vnc.html:224 #: ../vnc.html:182
msgid "Quality:"
msgstr "Ποιότητα:"
#: ../vnc.html:186
msgid "Compression level:"
msgstr "Επίπεδο συμπίεσης:"
#: ../vnc.html:191
msgid "Repeater ID:" msgid "Repeater ID:"
msgstr "Repeater ID:" msgstr "Repeater ID:"
#: ../vnc.html:228 #: ../vnc.html:195
msgid "WebSocket" msgid "WebSocket"
msgstr "WebSocket" msgstr "WebSocket"
#: ../vnc.html:231 #: ../vnc.html:198
msgid "Encrypt" msgid "Encrypt"
msgstr "Κρυπτογράφηση" msgstr "Κρυπτογράφηση"
#: ../vnc.html:234 #: ../vnc.html:201
msgid "Host:" msgid "Host:"
msgstr "Όνομα διακομιστή:" msgstr "Όνομα διακομιστή:"
#: ../vnc.html:238 #: ../vnc.html:205
msgid "Port:" msgid "Port:"
msgstr "Πόρτα διακομιστή:" msgstr "Πόρτα διακομιστή:"
#: ../vnc.html:242 #: ../vnc.html:209
msgid "Path:" msgid "Path:"
msgstr "Διαδρομή:" msgstr "Διαδρομή:"
#: ../vnc.html:249 #: ../vnc.html:216
msgid "Automatic Reconnect" msgid "Automatic Reconnect"
msgstr "Αυτόματη επανασύνδεση" msgstr "Αυτόματη επανασύνδεση"
#: ../vnc.html:252 #: ../vnc.html:219
msgid "Reconnect Delay (ms):" msgid "Reconnect Delay (ms):"
msgstr "Καθυστέρηση επανασύνδεσης (ms):" msgstr "Καθυστέρηση επανασύνδεσης (ms):"
#: ../vnc.html:258 #: ../vnc.html:224
msgid "Show Dot when No Cursor"
msgstr "Εμφάνιση Τελείας όταν δεν υπάρχει Δρομέας"
#: ../vnc.html:229
msgid "Logging:" msgid "Logging:"
msgstr "Καταγραφή:" msgstr "Καταγραφή:"
#: ../vnc.html:270 #: ../vnc.html:238
msgid "Version:"
msgstr "Έκδοση:"
#: ../vnc.html:246
msgid "Disconnect" msgid "Disconnect"
msgstr "Αποσύνδεση" msgstr "Αποσύνδεση"
#: ../vnc.html:289 #: ../vnc.html:269
msgid "Connect" msgid "Connect"
msgstr "Σύνδεση" msgstr "Σύνδεση"
#: ../vnc.html:299 #: ../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:" msgid "Password:"
msgstr "Κωδικός Πρόσβασης:" msgstr "Κωδικός Πρόσβασης:"
#: ../vnc.html:313 #: ../vnc.html:314
msgid "Send Credentials"
msgstr "Αποστολή Διαπιστευτηρίων"
#: ../vnc.html:323
msgid "Cancel" msgid "Cancel"
msgstr "Ακύρωση" msgstr "Ακύρωση"
#: ../vnc.html:329 #~ msgid "Password is required"
msgid "Canvas not supported." #~ msgstr "Απαιτείται ο κωδικός πρόσβασης"
msgstr "Δεν υποστηρίζεται το στοιχείο Canvas"
#~ 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 "Clear"
#~ msgstr "Καθάρισμα"
#~ msgid "Canvas not supported."
#~ msgstr "Δεν υποστηρίζεται το στοιχείο Canvas"
#~ msgid "Disconnect timeout" #~ msgid "Disconnect timeout"
#~ msgstr "Παρέλευση χρονικού ορίου αποσύνδεσης" #~ msgstr "Παρέλευση χρονικού ορίου αποσύνδεσης"

View File

@ -1,6 +1,6 @@
# Spanish translations for noVNC package # Spanish translations for noVNC package
# Traducciones al español para el paquete noVNC. # Traducciones al español para el paquete noVNC.
# Copyright (C) 2018 The noVNC Authors # Copyright (C) 2018 The noVNC authors
# This file is distributed under the same license as the noVNC package. # This file is distributed under the same license as the noVNC package.
# Juanjo Diaz <juanjo.diazmo@gmail.com>, 2018. # Juanjo Diaz <juanjo.diazmo@gmail.com>, 2018.
# Adrian Scillato <ascillato@gmail.com>, 2021. # Adrian Scillato <ascillato@gmail.com>, 2021.
@ -64,7 +64,7 @@ msgid "Hide/Show the control bar"
msgstr "Ocultar/Mostrar la barra de control" msgstr "Ocultar/Mostrar la barra de control"
#: ../vnc.html:106 #: ../vnc.html:106
msgid "Move/Drag Viewport" msgid "Move/Drag viewport"
msgstr "Mover/Arrastrar la ventana" msgstr "Mover/Arrastrar la ventana"
#: ../vnc.html:106 #: ../vnc.html:106
@ -96,7 +96,7 @@ msgid "Keyboard"
msgstr "Teclado" msgstr "Teclado"
#: ../vnc.html:124 #: ../vnc.html:124
msgid "Show Keyboard" msgid "Show keyboard"
msgstr "Mostrar teclado" msgstr "Mostrar teclado"
#: ../vnc.html:131 #: ../vnc.html:131
@ -196,15 +196,15 @@ msgid "Shared Mode"
msgstr "Modo Compartido" msgstr "Modo Compartido"
#: ../vnc.html:205 #: ../vnc.html:205
msgid "View Only" msgid "View only"
msgstr "Solo visualización" msgstr "Solo visualización"
#: ../vnc.html:209 #: ../vnc.html:209
msgid "Clip to Window" msgid "Clip to window"
msgstr "Recortar al tamaño de la ventana" msgstr "Recortar al tamaño de la ventana"
#: ../vnc.html:212 #: ../vnc.html:212
msgid "Scaling Mode:" msgid "Scaling mode:"
msgstr "Modo de escalado:" msgstr "Modo de escalado:"
#: ../vnc.html:214 #: ../vnc.html:214
@ -220,7 +220,7 @@ msgid "Local Downscaling"
msgstr "Reducción de escala local" msgstr "Reducción de escala local"
#: ../vnc.html:217 #: ../vnc.html:217
msgid "Remote Resizing" msgid "Remote resizing"
msgstr "Cambio de tamaño remoto" msgstr "Cambio de tamaño remoto"
#: ../vnc.html:222 #: ../vnc.html:222
@ -252,11 +252,11 @@ msgid "Path:"
msgstr "Ruta:" msgstr "Ruta:"
#: ../vnc.html:254 #: ../vnc.html:254
msgid "Automatic Reconnect" msgid "Automatic reconnect"
msgstr "Reconexión automática" msgstr "Reconexión automática"
#: ../vnc.html:257 #: ../vnc.html:257
msgid "Reconnect Delay (ms):" msgid "Reconnect delay (ms):"
msgstr "Retraso en la reconexión (ms):" msgstr "Retraso en la reconexión (ms):"
#: ../vnc.html:263 #: ../vnc.html:263

243
po/fr.po
View File

@ -1,300 +1,345 @@
# French translations for noVNC package # French translations for noVNC package
# Traductions françaises du paquet noVNC. # Traductions françaises du paquet noVNC.
# Copyright (C) 2021 The noVNC Authors # Copyright (C) 2021 The noVNC authors
# This file is distributed under the same license as the noVNC package. # This file is distributed under the same license as the noVNC package.
# Jose <jose.matsuda@canada.ca>, 2021. # Jose <jose.matsuda@canada.ca>, 2021.
# Lowxorx <lowxorx@lahan.fr>, 2022. # Lowxorx <lowxorx@lahan.fr>, 2022.
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: noVNC 1.2.0\n" "Project-Id-Version: noVNC 1.6.0\n"
"Report-Msgid-Bugs-To: novnc@googlegroups.com\n" "Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
"POT-Creation-Date: 2020-07-03 16:11+0200\n" "POT-Creation-Date: 2025-02-14 10:14+0100\n"
"PO-Revision-Date: 2022-04-25 23:40+0200\n" "PO-Revision-Date: 2025-02-17 10:04+0100\n"
"Last-Translator: Lowxorx <lowxorx@lahan.fr>\n" "Last-Translator: Martine & Philippe <martineke.breizh@gmail.com>\n"
"Language-Team: French\n" "Language-Team: French\n"
"Language: fr\n" "Language: fr\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n"
"X-Generator: Poedit 3.5\n"
#: ../app/ui.js:394 #: ../app/ui.js:84
msgid ""
"Running without HTTPS is not recommended, crashes or other issues are likely."
msgstr ""
"Lancer sans HTTPS n'est pas recommandé, crashs ou autres problèmes en vue."
#: ../app/ui.js:413
msgid "Connecting..." msgid "Connecting..."
msgstr "En cours de connexion..." msgstr "En cours de connexion..."
#: ../app/ui.js:401 #: ../app/ui.js:420
msgid "Disconnecting..." msgid "Disconnecting..."
msgstr "Déconnexion en cours..." msgstr "Déconnexion en cours..."
#: ../app/ui.js:407 #: ../app/ui.js:426
msgid "Reconnecting..." msgid "Reconnecting..."
msgstr "Reconnexion en cours..." msgstr "Reconnexion en cours..."
#: ../app/ui.js:412 #: ../app/ui.js:431
msgid "Internal error" msgid "Internal error"
msgstr "Erreur interne" msgstr "Erreur interne"
#: ../app/ui.js:1008 #: ../app/ui.js:1079
msgid "Must set host" msgid "Failed to connect to server: "
msgstr "Doit définir l'hôte" msgstr "Échec de connexion au serveur "
#: ../app/ui.js:1090 #: ../app/ui.js:1145
msgid "Connected (encrypted) to " msgid "Connected (encrypted) to "
msgstr "Connecté (chiffré) à " msgstr "Connecté (chiffré) à "
#: ../app/ui.js:1092 #: ../app/ui.js:1147
msgid "Connected (unencrypted) to " msgid "Connected (unencrypted) to "
msgstr "Connecté (non chiffré) à " msgstr "Connecté (non chiffré) à "
#: ../app/ui.js:1115 #: ../app/ui.js:1170
msgid "Something went wrong, connection is closed" msgid "Something went wrong, connection is closed"
msgstr "Quelque chose s'est mal passé, la connexion a été fermée" msgstr "Quelque chose s'est mal passé, la connexion a été fermée"
#: ../app/ui.js:1118 #: ../app/ui.js:1173
msgid "Failed to connect to server" msgid "Failed to connect to server"
msgstr "Échec de connexion au serveur" msgstr "Échec de connexion au serveur"
#: ../app/ui.js:1128 #: ../app/ui.js:1185
msgid "Disconnected" msgid "Disconnected"
msgstr "Déconnecté" msgstr "Déconnecté"
#: ../app/ui.js:1143 #: ../app/ui.js:1200
msgid "New connection has been rejected with reason: " msgid "New connection has been rejected with reason: "
msgstr "Une nouvelle connexion a été rejetée avec motif : " msgstr "Une nouvelle connexion a été rejetée avec motif : "
#: ../app/ui.js:1146 #: ../app/ui.js:1203
msgid "New connection has been rejected" msgid "New connection has been rejected"
msgstr "Une nouvelle connexion a été rejetée" msgstr "Une nouvelle connexion a été rejetée"
#: ../app/ui.js:1181 #: ../app/ui.js:1269
msgid "Credentials are required" msgid "Credentials are required"
msgstr "Les identifiants sont requis" msgstr "Les identifiants sont requis"
#: ../vnc.html:74 #: ../vnc.html:106
msgid "noVNC encountered an error:" msgid "noVNC encountered an error:"
msgstr "noVNC a rencontré une erreur :" msgstr "noVNC a rencontré une erreur :"
#: ../vnc.html:84 #: ../vnc.html:116
msgid "Hide/Show the control bar" msgid "Hide/Show the control bar"
msgstr "Masquer/Afficher la barre de contrôle" msgstr "Masquer/Afficher la barre de contrôle"
#: ../vnc.html:91 #: ../vnc.html:125
msgid "Drag" msgid "Drag"
msgstr "Faire glisser" msgstr "Faire glisser"
#: ../vnc.html:91 #: ../vnc.html:125
msgid "Move/Drag Viewport" msgid "Move/Drag viewport"
msgstr "Déplacer/faire glisser le Viewport" msgstr "Déplacer la fenêtre de visualisation"
#: ../vnc.html:97 #: ../vnc.html:131
msgid "Keyboard" msgid "Keyboard"
msgstr "Clavier" msgstr "Clavier"
#: ../vnc.html:97 #: ../vnc.html:131
msgid "Show Keyboard" msgid "Show keyboard"
msgstr "Afficher le clavier" msgstr "Afficher le clavier"
#: ../vnc.html:102 #: ../vnc.html:136
msgid "Extra keys" msgid "Extra keys"
msgstr "Touches supplémentaires" msgstr "Touches supplémentaires"
#: ../vnc.html:102 #: ../vnc.html:136
msgid "Show Extra Keys" msgid "Show extra keys"
msgstr "Afficher les touches supplémentaires" msgstr "Afficher les touches supplémentaires"
#: ../vnc.html:107 #: ../vnc.html:141
msgid "Ctrl" msgid "Ctrl"
msgstr "Ctrl" msgstr "Ctrl"
#: ../vnc.html:107 #: ../vnc.html:141
msgid "Toggle Ctrl" msgid "Toggle Ctrl"
msgstr "Basculer Ctrl" msgstr "Basculer Ctrl"
#: ../vnc.html:110 #: ../vnc.html:144
msgid "Alt" msgid "Alt"
msgstr "Alt" msgstr "Alt"
#: ../vnc.html:110 #: ../vnc.html:144
msgid "Toggle Alt" msgid "Toggle Alt"
msgstr "Basculer Alt" msgstr "Basculer Alt"
#: ../vnc.html:113 #: ../vnc.html:147
msgid "Toggle Windows" msgid "Toggle Windows"
msgstr "Basculer Windows" msgstr "Basculer Windows"
#: ../vnc.html:113 #: ../vnc.html:147
msgid "Windows" msgid "Windows"
msgstr "Windows" msgstr "Fenêtre"
#: ../vnc.html:116 #: ../vnc.html:150
msgid "Send Tab" msgid "Send Tab"
msgstr "Envoyer l'onglet" msgstr "Envoyer Tab"
#: ../vnc.html:116 #: ../vnc.html:150
msgid "Tab" msgid "Tab"
msgstr "l'onglet" msgstr "Tabulation"
#: ../vnc.html:119 #: ../vnc.html:153
msgid "Esc" msgid "Esc"
msgstr "Esc" msgstr "Esc"
#: ../vnc.html:119 #: ../vnc.html:153
msgid "Send Escape" msgid "Send Escape"
msgstr "Envoyer Escape" msgstr "Envoyer Escape"
#: ../vnc.html:122 #: ../vnc.html:156
msgid "Ctrl+Alt+Del" msgid "Ctrl+Alt+Del"
msgstr "Ctrl+Alt+Del" msgstr "Ctrl+Alt+Del"
#: ../vnc.html:122 #: ../vnc.html:156
msgid "Send Ctrl-Alt-Del" msgid "Send Ctrl-Alt-Del"
msgstr "Envoyer Ctrl-Alt-Del" msgstr "Envoyer Ctrl-Alt-Del"
#: ../vnc.html:129 #: ../vnc.html:163
msgid "Shutdown/Reboot" msgid "Shutdown/Reboot"
msgstr "Arrêter/Redémarrer" msgstr "Arrêter/Redémarrer"
#: ../vnc.html:129 #: ../vnc.html:163
msgid "Shutdown/Reboot..." msgid "Shutdown/Reboot..."
msgstr "Arrêter/Redémarrer..." msgstr "Arrêter/Redémarrer..."
#: ../vnc.html:135 #: ../vnc.html:169
msgid "Power" msgid "Power"
msgstr "Alimentation" msgstr "Alimentation"
#: ../vnc.html:137 #: ../vnc.html:171
msgid "Shutdown" msgid "Shutdown"
msgstr "Arrêter" msgstr "Arrêter"
#: ../vnc.html:138 #: ../vnc.html:172
msgid "Reboot" msgid "Reboot"
msgstr "Redémarrer" msgstr "Redémarrer"
#: ../vnc.html:139 #: ../vnc.html:173
msgid "Reset" msgid "Reset"
msgstr "Réinitialiser" msgstr "Réinitialiser"
#: ../vnc.html:144 ../vnc.html:150 #: ../vnc.html:178 ../vnc.html:184
msgid "Clipboard" msgid "Clipboard"
msgstr "Presse-papiers" msgstr "Presse-papiers"
#: ../vnc.html:154 #: ../vnc.html:186
msgid "Clear" msgid "Edit clipboard content in the textarea below."
msgstr "Effacer" msgstr "Editer le contenu du presse-papier dans la zone ci-dessous."
#: ../vnc.html:160 #: ../vnc.html:194
msgid "Fullscreen" msgid "Full screen"
msgstr "Plein écran" msgstr "Plein écran"
#: ../vnc.html:165 ../vnc.html:172 #: ../vnc.html:199 ../vnc.html:205
msgid "Settings" msgid "Settings"
msgstr "Paramètres" msgstr "Paramètres"
#: ../vnc.html:175 #: ../vnc.html:211
msgid "Shared Mode" msgid "Shared mode"
msgstr "Mode partagé" msgstr "Mode partagé"
#: ../vnc.html:178 #: ../vnc.html:218
msgid "View Only" msgid "View only"
msgstr "Afficher uniquement" msgstr "Afficher uniquement"
#: ../vnc.html:182 #: ../vnc.html:226
msgid "Clip to Window" msgid "Clip to window"
msgstr "Clip à fenêtre" msgstr "Ajuster à la fenêtre"
#: ../vnc.html:185 #: ../vnc.html:231
msgid "Scaling Mode:" msgid "Scaling mode:"
msgstr "Mode mise à l'échelle :" msgstr "Mode mise à l'échelle :"
#: ../vnc.html:187 #: ../vnc.html:233
msgid "None" msgid "None"
msgstr "Aucun" msgstr "Aucun"
#: ../vnc.html:188 #: ../vnc.html:234
msgid "Local Scaling" msgid "Local scaling"
msgstr "Mise à l'échelle locale" msgstr "Mise à l'échelle locale"
#: ../vnc.html:189 #: ../vnc.html:235
msgid "Remote Resizing" msgid "Remote resizing"
msgstr "Redimensionnement à distance" msgstr "Redimensionnement à distance"
#: ../vnc.html:194 #: ../vnc.html:240
msgid "Advanced" msgid "Advanced"
msgstr "Avancé" msgstr "Avancé"
#: ../vnc.html:197 #: ../vnc.html:243
msgid "Quality:" msgid "Quality:"
msgstr "Qualité :" msgstr "Qualité :"
#: ../vnc.html:201 #: ../vnc.html:247
msgid "Compression level:" msgid "Compression level:"
msgstr "Niveau de compression :" msgstr "Niveau de compression :"
#: ../vnc.html:206 #: ../vnc.html:252
msgid "Repeater ID:" msgid "Repeater ID:"
msgstr "ID Répéteur :" msgstr "ID Répéteur :"
#: ../vnc.html:210 #: ../vnc.html:256
msgid "WebSocket" msgid "WebSocket"
msgstr "WebSocket" msgstr "WebSocket"
#: ../vnc.html:213 #: ../vnc.html:261
msgid "Encrypt" msgid "Encrypt"
msgstr "Chiffrer" msgstr "Chiffrer"
#: ../vnc.html:216 #: ../vnc.html:266
msgid "Host:" msgid "Host:"
msgstr "Hôte :" msgstr "Hôte :"
#: ../vnc.html:220 #: ../vnc.html:270
msgid "Port:" msgid "Port:"
msgstr "Port :" msgstr "Port :"
#: ../vnc.html:224 #: ../vnc.html:274
msgid "Path:" msgid "Path:"
msgstr "Chemin :" msgstr "Chemin :"
#: ../vnc.html:231 #: ../vnc.html:283
msgid "Automatic Reconnect" msgid "Automatic reconnect"
msgstr "Reconnecter automatiquemen" msgstr "Reconnecter automatiquement"
#: ../vnc.html:234 #: ../vnc.html:288
msgid "Reconnect Delay (ms):" msgid "Reconnect delay (ms):"
msgstr "Délai de reconnexion (ms) :" msgstr "Délai de reconnexion (ms) :"
#: ../vnc.html:239 #: ../vnc.html:295
msgid "Show Dot when No Cursor" msgid "Show dot when no cursor"
msgstr "Afficher le point lorsqu'il n'y a pas de curseur" msgstr "Afficher le point lorsqu'il n'y a pas de curseur"
#: ../vnc.html:244 #: ../vnc.html:302
msgid "Logging:" msgid "Logging:"
msgstr "Se connecter :" msgstr "Se connecter :"
#: ../vnc.html:253 #: ../vnc.html:311
msgid "Version:" msgid "Version:"
msgstr "Version :" msgstr "Version :"
#: ../vnc.html:261 #: ../vnc.html:319
msgid "Disconnect" msgid "Disconnect"
msgstr "Déconnecter" msgstr "Déconnecter"
#: ../vnc.html:280 #: ../vnc.html:342
msgid "Connect" msgid "Connect"
msgstr "Connecter" msgstr "Connecter"
#: ../vnc.html:290 #: ../vnc.html:351
msgid "Server identity"
msgstr "Identité du serveur"
#: ../vnc.html:354
msgid "The server has provided the following identifying information:"
msgstr "Le serveur a fourni l'identification suivante :"
#: ../vnc.html:357
msgid "Fingerprint:"
msgstr "Empreinte digitale :"
#: ../vnc.html:361
msgid ""
"Please verify that the information is correct and press \"Approve\". "
"Otherwise press \"Reject\"."
msgstr ""
"SVP, verifiez que l'information est correcte et pressez \"Accepter\". Sinon "
"pressez \"Refuser\"."
#: ../vnc.html:366
msgid "Approve"
msgstr "Accepter"
#: ../vnc.html:367
msgid "Reject"
msgstr "Refuser"
#: ../vnc.html:375
msgid "Credentials"
msgstr "Envoyer les identifiants"
#: ../vnc.html:379
msgid "Username:" msgid "Username:"
msgstr "Nom d'utilisateur :" msgstr "Nom d'utilisateur :"
#: ../vnc.html:294 #: ../vnc.html:383
msgid "Password:" msgid "Password:"
msgstr "Mot de passe :" msgstr "Mot de passe :"
#: ../vnc.html:298 #: ../vnc.html:387
msgid "Send Credentials" msgid "Send credentials"
msgstr "Envoyer les identifiants" msgstr "Envoyer les identifiants"
#: ../vnc.html:308 #: ../vnc.html:396
msgid "Cancel" msgid "Cancel"
msgstr "Annuler" msgstr "Annuler"
#~ msgid "Must set host"
#~ msgstr "Doit définir l'hôte"
#~ msgid "Clear"
#~ msgstr "Effacer"

View File

@ -1,6 +1,6 @@
# Italian translations for noVNC # Italian translations for noVNC
# Traduzione italiana di noVNC # Traduzione italiana di noVNC
# Copyright (C) 2022 The noVNC Authors # Copyright (C) 2022 The noVNC authors
# This file is distributed under the same license as the noVNC package. # This file is distributed under the same license as the noVNC package.
# Fabio Fantoni <fabio.fantoni@m2r.biz>, 2022. # Fabio Fantoni <fabio.fantoni@m2r.biz>, 2022.
# #
@ -84,7 +84,7 @@ msgid "Drag"
msgstr "" msgstr ""
#: ../vnc.html:78 #: ../vnc.html:78
msgid "Move/Drag Viewport" msgid "Move/Drag viewport"
msgstr "" msgstr ""
#: ../vnc.html:84 #: ../vnc.html:84
@ -92,7 +92,7 @@ msgid "Keyboard"
msgstr "Tastiera" msgstr "Tastiera"
#: ../vnc.html:84 #: ../vnc.html:84
msgid "Show Keyboard" msgid "Show keyboard"
msgstr "Mostra tastiera" msgstr "Mostra tastiera"
#: ../vnc.html:89 #: ../vnc.html:89
@ -192,7 +192,7 @@ msgid "Settings"
msgstr "Impostazioni" msgstr "Impostazioni"
#: ../vnc.html:162 #: ../vnc.html:162
msgid "Shared Mode" msgid "Shared mode"
msgstr "Modalità condivisa" msgstr "Modalità condivisa"
#: ../vnc.html:165 #: ../vnc.html:165
@ -200,11 +200,11 @@ msgid "View Only"
msgstr "Sola Visualizzazione" msgstr "Sola Visualizzazione"
#: ../vnc.html:169 #: ../vnc.html:169
msgid "Clip to Window" msgid "Clip to window"
msgstr "" msgstr ""
#: ../vnc.html:172 #: ../vnc.html:172
msgid "Scaling Mode:" msgid "Scaling mode:"
msgstr "Modalità di ridimensionamento:" msgstr "Modalità di ridimensionamento:"
#: ../vnc.html:174 #: ../vnc.html:174

262
po/ja.po
View File

@ -1,15 +1,15 @@
# Japanese translations for noVNC package # Japanese translations for noVNC package
# noVNC パッケージに対する日訳 # noVNC パッケージに対する日訳
# Copyright (C) 2019 The noVNC Authors # Copyright (C) 2019-2024 The noVNC authors
# This file is distributed under the same license as the noVNC package. # This file is distributed under the same license as the noVNC package.
# nnn1590 <nnn1590@nnn1590.org>, 2019-2020. # nnn1590 <nnn1590@nnn1590.org>, 2019-2024.
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: noVNC 1.1.0\n" "Project-Id-Version: noVNC 1.5.0\n"
"Report-Msgid-Bugs-To: novnc@googlegroups.com\n" "Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
"POT-Creation-Date: 2020-07-03 16:11+0200\n" "POT-Creation-Date: 2024-06-03 14:10+0200\n"
"PO-Revision-Date: 2021-01-15 12:37+0900\n" "PO-Revision-Date: 2024-12-14 15:22+0900\n"
"Last-Translator: nnn1590 <nnn1590@nnn1590.org>\n" "Last-Translator: nnn1590 <nnn1590@nnn1590.org>\n"
"Language-Team: Japanese\n" "Language-Team: Japanese\n"
"Language: ja\n" "Language: ja\n"
@ -19,306 +19,324 @@ msgstr ""
"Plural-Forms: nplurals=1; plural=0;\n" "Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Poedit 2.3\n" "X-Generator: Poedit 2.3\n"
#: ../app/ui.js:394 #: ../app/ui.js:69
msgid ""
"Running without HTTPS is not recommended, crashes or other issues are likely."
msgstr ""
"HTTPS接続なしで実行することは推奨されません。クラッシュしたりその他の問題が発"
"生したりする可能性があります。"
#: ../app/ui.js:410
msgid "Connecting..." msgid "Connecting..."
msgstr "接続しています..." msgstr "接続しています..."
#: ../app/ui.js:401 #: ../app/ui.js:417
msgid "Disconnecting..." msgid "Disconnecting..."
msgstr "切断しています..." msgstr "切断しています..."
#: ../app/ui.js:407 #: ../app/ui.js:423
msgid "Reconnecting..." msgid "Reconnecting..."
msgstr "再接続しています..." msgstr "再接続しています..."
#: ../app/ui.js:412 #: ../app/ui.js:428
msgid "Internal error" msgid "Internal error"
msgstr "内部エラー" msgstr "内部エラー"
#: ../app/ui.js:1008 #: ../app/ui.js:1026
msgid "Must set host" msgid "Must set host"
msgstr "ホストを設定する必要があります" msgstr "ホストを設定する必要があります"
#: ../app/ui.js:1090 #: ../app/ui.js:1052
msgid "Failed to connect to server: "
msgstr "サーバーへの接続に失敗しました: "
#: ../app/ui.js:1118
msgid "Connected (encrypted) to " msgid "Connected (encrypted) to "
msgstr "接続しました (暗号化済み): " msgstr "接続しました (暗号化済み): "
#: ../app/ui.js:1092 #: ../app/ui.js:1120
msgid "Connected (unencrypted) to " msgid "Connected (unencrypted) to "
msgstr "接続しました (暗号化されていません): " msgstr "接続しました (暗号化されていません): "
#: ../app/ui.js:1115 #: ../app/ui.js:1143
msgid "Something went wrong, connection is closed" msgid "Something went wrong, connection is closed"
msgstr "何らかの問題で、接続が閉じられました" msgstr "問題が発生したため、接続が閉じられました"
#: ../app/ui.js:1118 #: ../app/ui.js:1146
msgid "Failed to connect to server" msgid "Failed to connect to server"
msgstr "サーバーへの接続に失敗しました" msgstr "サーバーへの接続に失敗しました"
#: ../app/ui.js:1128 #: ../app/ui.js:1158
msgid "Disconnected" msgid "Disconnected"
msgstr "切断しました" msgstr "切断しました"
#: ../app/ui.js:1143 #: ../app/ui.js:1173
msgid "New connection has been rejected with reason: " msgid "New connection has been rejected with reason: "
msgstr "新規接続は次の理由で拒否されました: " msgstr "新規接続は次の理由で拒否されました: "
#: ../app/ui.js:1146 #: ../app/ui.js:1176
msgid "New connection has been rejected" msgid "New connection has been rejected"
msgstr "新規接続は拒否されました" msgstr "新規接続は拒否されました"
#: ../app/ui.js:1181 #: ../app/ui.js:1242
msgid "Credentials are required" msgid "Credentials are required"
msgstr "資格情報が必要です" msgstr "資格情報が必要です"
#: ../vnc.html:74 #: ../vnc.html:55
msgid "noVNC encountered an error:" msgid "noVNC encountered an error:"
msgstr "noVNC でエラーが発生しました:" msgstr "noVNC でエラーが発生しました:"
#: ../vnc.html:84 #: ../vnc.html:65
msgid "Hide/Show the control bar" msgid "Hide/Show the control bar"
msgstr "コントロールバーを隠す/表示する" msgstr "コントロールバーを隠す/表示する"
#: ../vnc.html:91 #: ../vnc.html:74
msgid "Drag" msgid "Drag"
msgstr "ドラッグ" msgstr "ドラッグ"
#: ../vnc.html:91 #: ../vnc.html:74
msgid "Move/Drag Viewport" msgid "Move/Drag viewport"
msgstr "ビューポートを移動/ドラッグ" msgstr "ビューポートを移動/ドラッグ"
#: ../vnc.html:97 #: ../vnc.html:80
msgid "Keyboard" msgid "Keyboard"
msgstr "キーボード" msgstr "キーボード"
#: ../vnc.html:97 #: ../vnc.html:80
msgid "Show Keyboard" msgid "Show keyboard"
msgstr "キーボードを表示" msgstr "キーボードを表示"
#: ../vnc.html:102 #: ../vnc.html:85
msgid "Extra keys" msgid "Extra keys"
msgstr "追加キー" msgstr "追加キー"
#: ../vnc.html:102 #: ../vnc.html:85
msgid "Show Extra Keys" msgid "Show extra keys"
msgstr "追加キーを表示" msgstr "追加キーを表示"
#: ../vnc.html:107 #: ../vnc.html:90
msgid "Ctrl" msgid "Ctrl"
msgstr "Ctrl" msgstr "Ctrl"
#: ../vnc.html:107 #: ../vnc.html:90
msgid "Toggle Ctrl" msgid "Toggle Ctrl"
msgstr "Ctrl キーを切り替え" msgstr "Ctrl キーをトグル"
#: ../vnc.html:110 #: ../vnc.html:93
msgid "Alt" msgid "Alt"
msgstr "Alt" msgstr "Alt"
#: ../vnc.html:110 #: ../vnc.html:93
msgid "Toggle Alt" msgid "Toggle Alt"
msgstr "Alt キーを切り替え" msgstr "Alt キーをトグル"
#: ../vnc.html:113 #: ../vnc.html:96
msgid "Toggle Windows" msgid "Toggle Windows"
msgstr "Windows キーを切り替え" msgstr "Windows キーをトグル"
#: ../vnc.html:113 #: ../vnc.html:96
msgid "Windows" msgid "Windows"
msgstr "Windows" msgstr "Windows"
#: ../vnc.html:116 #: ../vnc.html:99
msgid "Send Tab" msgid "Send Tab"
msgstr "Tab キーを送信" msgstr "Tab キーを送信"
#: ../vnc.html:116 #: ../vnc.html:99
msgid "Tab" msgid "Tab"
msgstr "Tab" msgstr "Tab"
#: ../vnc.html:119 #: ../vnc.html:102
msgid "Esc" msgid "Esc"
msgstr "Esc" msgstr "Esc"
#: ../vnc.html:119 #: ../vnc.html:102
msgid "Send Escape" msgid "Send Escape"
msgstr "Escape キーを送信" msgstr "Escape キーを送信"
#: ../vnc.html:122 #: ../vnc.html:105
msgid "Ctrl+Alt+Del" msgid "Ctrl+Alt+Del"
msgstr "Ctrl+Alt+Del" msgstr "Ctrl+Alt+Del"
#: ../vnc.html:122 #: ../vnc.html:105
msgid "Send Ctrl-Alt-Del" msgid "Send Ctrl-Alt-Del"
msgstr "Ctrl-Alt-Del を送信" msgstr "Ctrl-Alt-Del を送信"
#: ../vnc.html:129 #: ../vnc.html:112
msgid "Shutdown/Reboot" msgid "Shutdown/Reboot"
msgstr "シャットダウン/再起動" msgstr "シャットダウン/再起動"
#: ../vnc.html:129 #: ../vnc.html:112
msgid "Shutdown/Reboot..." msgid "Shutdown/Reboot..."
msgstr "シャットダウン/再起動..." msgstr "シャットダウン/再起動..."
#: ../vnc.html:135 #: ../vnc.html:118
msgid "Power" msgid "Power"
msgstr "電源" msgstr "電源"
#: ../vnc.html:137 #: ../vnc.html:120
msgid "Shutdown" msgid "Shutdown"
msgstr "シャットダウン" msgstr "シャットダウン"
#: ../vnc.html:138 #: ../vnc.html:121
msgid "Reboot" msgid "Reboot"
msgstr "再起動" msgstr "再起動"
#: ../vnc.html:139 #: ../vnc.html:122
msgid "Reset" msgid "Reset"
msgstr "リセット" msgstr "リセット"
#: ../vnc.html:144 ../vnc.html:150 #: ../vnc.html:127 ../vnc.html:133
msgid "Clipboard" msgid "Clipboard"
msgstr "クリップボード" msgstr "クリップボード"
#: ../vnc.html:154 #: ../vnc.html:135
msgid "Clear" msgid "Edit clipboard content in the textarea below."
msgstr "クリア" msgstr "以下の入力欄からクリップボードの内容を編集できます。"
#: ../vnc.html:160 #: ../vnc.html:143
msgid "Fullscreen" msgid "Full screen"
msgstr "全画面表示" msgstr "全画面表示"
#: ../vnc.html:165 ../vnc.html:172 #: ../vnc.html:148 ../vnc.html:154
msgid "Settings" msgid "Settings"
msgstr "設定" msgstr "設定"
#: ../vnc.html:175 #: ../vnc.html:158
msgid "Shared Mode" msgid "Shared mode"
msgstr "共有モード" msgstr "共有モード"
#: ../vnc.html:178 #: ../vnc.html:161
msgid "View Only" msgid "View only"
msgstr "表示のみ" msgstr "表示専用"
#: ../vnc.html:182 #: ../vnc.html:165
msgid "Clip to Window" msgid "Clip to window"
msgstr "ウィンドウにクリップ" msgstr "ウィンドウにクリップ"
#: ../vnc.html:185 #: ../vnc.html:168
msgid "Scaling Mode:" msgid "Scaling mode:"
msgstr "スケーリングモード:" msgstr "スケーリングモード:"
#: ../vnc.html:187 #: ../vnc.html:170
msgid "None" msgid "None"
msgstr "なし" msgstr "なし"
#: ../vnc.html:188 #: ../vnc.html:171
msgid "Local Scaling" msgid "Local scaling"
msgstr "ローカルスケーリング" msgstr "ローカルスケーリング"
#: ../vnc.html:189 #: ../vnc.html:172
msgid "Remote Resizing" msgid "Remote resizing"
msgstr "リモートでリサイズ" msgstr "リモートでリサイズ"
#: ../vnc.html:194 #: ../vnc.html:177
msgid "Advanced" msgid "Advanced"
msgstr "高度" msgstr "高度"
#: ../vnc.html:197 #: ../vnc.html:180
msgid "Quality:" msgid "Quality:"
msgstr "品質:" msgstr "品質:"
#: ../vnc.html:201 #: ../vnc.html:184
msgid "Compression level:" msgid "Compression level:"
msgstr "圧縮レベル:" msgstr "圧縮レベル:"
#: ../vnc.html:206 #: ../vnc.html:189
msgid "Repeater ID:" msgid "Repeater ID:"
msgstr "リピーター ID:" msgstr "リピーター ID:"
#: ../vnc.html:210 #: ../vnc.html:193
msgid "WebSocket" msgid "WebSocket"
msgstr "WebSocket" msgstr "WebSocket"
#: ../vnc.html:213 #: ../vnc.html:196
msgid "Encrypt" msgid "Encrypt"
msgstr "暗号化" msgstr "暗号化"
#: ../vnc.html:216 #: ../vnc.html:199
msgid "Host:" msgid "Host:"
msgstr "ホスト:" msgstr "ホスト:"
#: ../vnc.html:220 #: ../vnc.html:203
msgid "Port:" msgid "Port:"
msgstr "ポート:" msgstr "ポート:"
#: ../vnc.html:224 #: ../vnc.html:207
msgid "Path:" msgid "Path:"
msgstr "パス:" msgstr "パス:"
#: ../vnc.html:231 #: ../vnc.html:214
msgid "Automatic Reconnect" msgid "Automatic reconnect"
msgstr "自動再接続" msgstr "自動再接続"
#: ../vnc.html:234 #: ../vnc.html:217
msgid "Reconnect Delay (ms):" msgid "Reconnect delay (ms):"
msgstr "再接続する遅延 (ミリ秒):" msgstr "再接続する遅延 (ミリ秒):"
#: ../vnc.html:239 #: ../vnc.html:222
msgid "Show Dot when No Cursor" msgid "Show dot when no cursor"
msgstr "カーソルがないときにドットを表示" msgstr "カーソルがないときにドットを表示する"
#: ../vnc.html:244 #: ../vnc.html:227
msgid "Logging:" msgid "Logging:"
msgstr "ロギング:" msgstr "ロギング:"
#: ../vnc.html:253 #: ../vnc.html:236
msgid "Version:" msgid "Version:"
msgstr "バージョン:" msgstr "バージョン:"
#: ../vnc.html:261 #: ../vnc.html:244
msgid "Disconnect" msgid "Disconnect"
msgstr "切断" msgstr "切断"
#: ../vnc.html:280 #: ../vnc.html:267
msgid "Connect" msgid "Connect"
msgstr "接続" msgstr "接続"
#: ../vnc.html:290 #: ../vnc.html:276
msgid "Server identity"
msgstr "サーバーの識別情報"
#: ../vnc.html:279
msgid "The server has provided the following identifying information:"
msgstr "サーバーは以下の識別情報を提供しています:"
#: ../vnc.html:283
msgid "Fingerprint:"
msgstr "フィンガープリント:"
#: ../vnc.html:286
msgid ""
"Please verify that the information is correct and press \"Approve\". "
"Otherwise press \"Reject\"."
msgstr ""
"この情報が正しい場合は「承認」を、そうでない場合は「拒否」を押してください。"
#: ../vnc.html:291
msgid "Approve"
msgstr "承認"
#: ../vnc.html:292
msgid "Reject"
msgstr "拒否"
#: ../vnc.html:300
msgid "Credentials"
msgstr "資格情報"
#: ../vnc.html:304
msgid "Username:" msgid "Username:"
msgstr "ユーザー名:" msgstr "ユーザー名:"
#: ../vnc.html:294 #: ../vnc.html:308
msgid "Password:" msgid "Password:"
msgstr "パスワード:" msgstr "パスワード:"
#: ../vnc.html:298 #: ../vnc.html:312
msgid "Send Credentials" msgid "Send credentials"
msgstr "資格情報を送信" msgstr "資格情報を送信"
#: ../vnc.html:308 #: ../vnc.html:321
msgid "Cancel" msgid "Cancel"
msgstr "キャンセル" 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 "パスワードを送信"

View File

@ -1,5 +1,5 @@
# SOME DESCRIPTIVE TITLE. # SOME DESCRIPTIVE TITLE.
# Copyright (C) 2018 The noVNC Authors # Copyright (C) 2018 The noVNC authors
# This file is distributed under the same license as the noVNC package. # This file is distributed under the same license as the noVNC package.
# Baw Appie <pp121324@gmail.com>, 2018. # Baw Appie <pp121324@gmail.com>, 2018.
# #
@ -78,7 +78,7 @@ msgid "Hide/Show the control bar"
msgstr "컨트롤 바 숨기기/보이기" msgstr "컨트롤 바 숨기기/보이기"
#: ../vnc.html:108 #: ../vnc.html:108
msgid "Move/Drag Viewport" msgid "Move/Drag viewport"
msgstr "움직이기/드래그 뷰포트" msgstr "움직이기/드래그 뷰포트"
#: ../vnc.html:108 #: ../vnc.html:108
@ -110,7 +110,7 @@ msgid "Keyboard"
msgstr "키보드" msgstr "키보드"
#: ../vnc.html:126 #: ../vnc.html:126
msgid "Show Keyboard" msgid "Show keyboard"
msgstr "키보드 보이기" msgstr "키보드 보이기"
#: ../vnc.html:133 #: ../vnc.html:133
@ -118,7 +118,7 @@ msgid "Extra keys"
msgstr "기타 키들" msgstr "기타 키들"
#: ../vnc.html:133 #: ../vnc.html:133
msgid "Show Extra Keys" msgid "Show extra keys"
msgstr "기타 키들 보이기" msgstr "기타 키들 보이기"
#: ../vnc.html:138 #: ../vnc.html:138
@ -202,19 +202,19 @@ msgid "Settings"
msgstr "설정" msgstr "설정"
#: ../vnc.html:204 #: ../vnc.html:204
msgid "Shared Mode" msgid "Shared mode"
msgstr "공유 모드" msgstr "공유 모드"
#: ../vnc.html:207 #: ../vnc.html:207
msgid "View Only" msgid "View only"
msgstr "보기 전용" msgstr "보기 전용"
#: ../vnc.html:211 #: ../vnc.html:211
msgid "Clip to Window" msgid "Clip to window"
msgstr "창에 클립" msgstr "창에 클립"
#: ../vnc.html:214 #: ../vnc.html:214
msgid "Scaling Mode:" msgid "Scaling mode:"
msgstr "스케일링 모드:" msgstr "스케일링 모드:"
#: ../vnc.html:216 #: ../vnc.html:216
@ -222,11 +222,11 @@ msgid "None"
msgstr "없음" msgstr "없음"
#: ../vnc.html:217 #: ../vnc.html:217
msgid "Local Scaling" msgid "Local scaling"
msgstr "로컬 스케일링" msgstr "로컬 스케일링"
#: ../vnc.html:218 #: ../vnc.html:218
msgid "Remote Resizing" msgid "Remote resizing"
msgstr "원격 크기 조절" msgstr "원격 크기 조절"
#: ../vnc.html:223 #: ../vnc.html:223
@ -258,11 +258,11 @@ msgid "Path:"
msgstr "위치:" msgstr "위치:"
#: ../vnc.html:251 #: ../vnc.html:251
msgid "Automatic Reconnect" msgid "Automatic reconnect"
msgstr "자동 재연결" msgstr "자동 재연결"
#: ../vnc.html:254 #: ../vnc.html:254
msgid "Reconnect Delay (ms):" msgid "Reconnect delay (ms):"
msgstr "재연결 지연 시간 (ms)" msgstr "재연결 지연 시간 (ms)"
#: ../vnc.html:260 #: ../vnc.html:260

318
po/nl.po
View File

@ -1,307 +1,385 @@
# Dutch translations for noVNC package # Dutch translations for noVNC package
# Nederlandse vertalingen voor het pakket noVNC. # Nederlandse vertalingen voor het pakket noVNC.
# Copyright (C) 2018 The noVNC Authors # Copyright (C) 2018 The noVNC authors
# This file is distributed under the same license as the noVNC package. # This file is distributed under the same license as the noVNC package.
# Loek Janssen <loekjanssen@gmail.com>, 2016. # Loek Janssen <loekjanssen@gmail.com>, 2016.
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: noVNC 1.1.0\n" "Project-Id-Version: noVNC 1.6.0\n"
"Report-Msgid-Bugs-To: novnc@googlegroups.com\n" "Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
"POT-Creation-Date: 2019-04-09 11:06+0100\n" "POT-Creation-Date: 2025-02-14 10:14+0100\n"
"PO-Revision-Date: 2019-04-09 17:17+0100\n" "PO-Revision-Date: 2025-03-03 18:20+0100\n"
"Last-Translator: Arend Lapere <arend.lapere@gmail.com>\n" "Last-Translator: Harold Horsman <haroldhorsman@gmail.com>\n"
"Language-Team: none\n" "Language-Team: none\n"
"Language: nl\n" "Language: nl\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 3.5\n"
#: ../app/ui.js:383 #: ../app/ui.js:84
msgid ""
"Running without HTTPS is not recommended, crashes or other issues are likely."
msgstr ""
"Het is niet aan te raden om zonder HTTPS te werken, crashes of andere "
"problemen zijn dan waarschijnlijk."
#: ../app/ui.js:413
msgid "Connecting..." msgid "Connecting..."
msgstr "Verbinden..." msgstr "Aan het verbinden…"
#: ../app/ui.js:390 #: ../app/ui.js:420
msgid "Disconnecting..." msgid "Disconnecting..."
msgstr "Verbinding verbreken..." msgstr "Bezig om verbinding te verbreken..."
#: ../app/ui.js:396 #: ../app/ui.js:426
msgid "Reconnecting..." msgid "Reconnecting..."
msgstr "Opnieuw verbinding maken..." msgstr "Opnieuw verbinding maken..."
#: ../app/ui.js:401 #: ../app/ui.js:431
msgid "Internal error" msgid "Internal error"
msgstr "Interne fout" msgstr "Interne fout"
#: ../app/ui.js:991 #: ../app/ui.js:1079
msgid "Must set host" #, fuzzy
msgstr "Host moeten worden ingesteld" msgid "Failed to connect to server: "
msgstr "Verbinding maken met server is mislukt"
#: ../app/ui.js:1073 #: ../app/ui.js:1145
msgid "Connected (encrypted) to " msgid "Connected (encrypted) to "
msgstr "Verbonden (versleuteld) met " msgstr "Verbonden (versleuteld) met "
#: ../app/ui.js:1075 #: ../app/ui.js:1147
msgid "Connected (unencrypted) to " msgid "Connected (unencrypted) to "
msgstr "Verbonden (onversleuteld) met " msgstr "Verbonden (onversleuteld) met "
#: ../app/ui.js:1098 #: ../app/ui.js:1170
msgid "Something went wrong, connection is closed" msgid "Something went wrong, connection is closed"
msgstr "Er iets fout gelopen, verbinding werd verbroken" msgstr "Er iets fout gelopen, verbinding werd verbroken"
#: ../app/ui.js:1101 #: ../app/ui.js:1173
msgid "Failed to connect to server" msgid "Failed to connect to server"
msgstr "Verbinding maken met server is mislukt" msgstr "Verbinding maken met server is mislukt"
#: ../app/ui.js:1111 #: ../app/ui.js:1185
msgid "Disconnected" msgid "Disconnected"
msgstr "Verbinding verbroken" msgstr "Verbinding verbroken"
#: ../app/ui.js:1124 #: ../app/ui.js:1200
msgid "New connection has been rejected with reason: " msgid "New connection has been rejected with reason: "
msgstr "Nieuwe verbinding is geweigerd omwille van de volgende reden: " msgstr "Nieuwe verbinding is geweigerd met de volgende reden: "
#: ../app/ui.js:1127 #: ../app/ui.js:1203
msgid "New connection has been rejected" msgid "New connection has been rejected"
msgstr "Nieuwe verbinding is geweigerd" msgstr "Nieuwe verbinding is geweigerd"
#: ../app/ui.js:1147 #: ../app/ui.js:1269
msgid "Password is required" msgid "Credentials are required"
msgstr "Wachtwoord is vereist" msgstr "Inloggegevens zijn nodig"
#: ../vnc.html:80 #: ../vnc.html:106
msgid "noVNC encountered an error:" msgid "noVNC encountered an error:"
msgstr "noVNC heeft een fout bemerkt:" msgstr "noVNC heeft een fout bemerkt:"
#: ../vnc.html:90 #: ../vnc.html:116
msgid "Hide/Show the control bar" msgid "Hide/Show the control bar"
msgstr "Verberg/Toon de bedieningsbalk" msgstr "Verberg/Toon de bedieningsbalk"
#: ../vnc.html:97 #: ../vnc.html:125
msgid "Move/Drag Viewport" msgid "Drag"
msgstr "Sleep"
#: ../vnc.html:125
#, fuzzy
msgid "Move/Drag viewport"
msgstr "Verplaats/Versleep Kijkvenster" msgstr "Verplaats/Versleep Kijkvenster"
#: ../vnc.html:97 #: ../vnc.html:131
msgid "viewport drag"
msgstr "kijkvenster slepen"
#: ../vnc.html:103 ../vnc.html:106 ../vnc.html:109 ../vnc.html:112
msgid "Active Mouse Button"
msgstr "Actieve Muisknop"
#: ../vnc.html:103
msgid "No mousebutton"
msgstr "Geen muisknop"
#: ../vnc.html:106
msgid "Left mousebutton"
msgstr "Linker muisknop"
#: ../vnc.html:109
msgid "Middle mousebutton"
msgstr "Middelste muisknop"
#: ../vnc.html:112
msgid "Right mousebutton"
msgstr "Rechter muisknop"
#: ../vnc.html:115
msgid "Keyboard" msgid "Keyboard"
msgstr "Toetsenbord" msgstr "Toetsenbord"
#: ../vnc.html:115 #: ../vnc.html:131
msgid "Show Keyboard" #, fuzzy
msgid "Show keyboard"
msgstr "Toon Toetsenbord" msgstr "Toon Toetsenbord"
#: ../vnc.html:121 #: ../vnc.html:136
msgid "Extra keys" msgid "Extra keys"
msgstr "Extra toetsen" msgstr "Extra toetsen"
#: ../vnc.html:121 #: ../vnc.html:136
msgid "Show Extra Keys" #, fuzzy
msgid "Show extra keys"
msgstr "Toon Extra Toetsen" msgstr "Toon Extra Toetsen"
#: ../vnc.html:126 #: ../vnc.html:141
msgid "Ctrl" msgid "Ctrl"
msgstr "Ctrl" msgstr "Ctrl"
#: ../vnc.html:126 #: ../vnc.html:141
msgid "Toggle Ctrl" msgid "Toggle Ctrl"
msgstr "Ctrl omschakelen" msgstr "Ctrl omschakelen"
#: ../vnc.html:129 #: ../vnc.html:144
msgid "Alt" msgid "Alt"
msgstr "Alt" msgstr "Alt"
#: ../vnc.html:129 #: ../vnc.html:144
msgid "Toggle Alt" msgid "Toggle Alt"
msgstr "Alt omschakelen" msgstr "Alt omschakelen"
#: ../vnc.html:132 #: ../vnc.html:147
msgid "Toggle Windows" msgid "Toggle Windows"
msgstr "Windows omschakelen" msgstr "Vensters omschakelen"
#: ../vnc.html:132 #: ../vnc.html:147
msgid "Windows" msgid "Windows"
msgstr "Windows" msgstr "Vensters"
#: ../vnc.html:135 #: ../vnc.html:150
msgid "Send Tab" msgid "Send Tab"
msgstr "Tab Sturen" msgstr "Tab Sturen"
#: ../vnc.html:135 #: ../vnc.html:150
msgid "Tab" msgid "Tab"
msgstr "Tab" msgstr "Tab"
#: ../vnc.html:138 #: ../vnc.html:153
msgid "Esc" msgid "Esc"
msgstr "Esc" msgstr "Esc"
#: ../vnc.html:138 #: ../vnc.html:153
msgid "Send Escape" msgid "Send Escape"
msgstr "Escape Sturen" msgstr "Escape Sturen"
#: ../vnc.html:141 #: ../vnc.html:156
msgid "Ctrl+Alt+Del" msgid "Ctrl+Alt+Del"
msgstr "Ctrl-Alt-Del" msgstr "Ctrl-Alt-Del"
#: ../vnc.html:141 #: ../vnc.html:156
msgid "Send Ctrl-Alt-Del" msgid "Send Ctrl-Alt-Del"
msgstr "Ctrl-Alt-Del Sturen" msgstr "Ctrl-Alt-Del Sturen"
#: ../vnc.html:149 #: ../vnc.html:163
msgid "Shutdown/Reboot" msgid "Shutdown/Reboot"
msgstr "Uitschakelen/Herstarten" msgstr "Uitschakelen/Herstarten"
#: ../vnc.html:149 #: ../vnc.html:163
msgid "Shutdown/Reboot..." msgid "Shutdown/Reboot..."
msgstr "Uitschakelen/Herstarten..." msgstr "Uitschakelen/Herstarten..."
#: ../vnc.html:155 #: ../vnc.html:169
msgid "Power" msgid "Power"
msgstr "Systeem" msgstr "Systeem"
#: ../vnc.html:157 #: ../vnc.html:171
msgid "Shutdown" msgid "Shutdown"
msgstr "Uitschakelen" msgstr "Uitschakelen"
#: ../vnc.html:158 #: ../vnc.html:172
msgid "Reboot" msgid "Reboot"
msgstr "Herstarten" msgstr "Herstarten"
#: ../vnc.html:159 #: ../vnc.html:173
msgid "Reset" msgid "Reset"
msgstr "Resetten" msgstr "Resetten"
#: ../vnc.html:164 ../vnc.html:170 #: ../vnc.html:178 ../vnc.html:184
msgid "Clipboard" msgid "Clipboard"
msgstr "Klembord" msgstr "Klembord"
#: ../vnc.html:174 #: ../vnc.html:186
msgid "Clear" msgid "Edit clipboard content in the textarea below."
msgstr "Wissen" msgstr "Edit de inhoud van het klembord in het tekstveld hieronder"
#: ../vnc.html:180 #: ../vnc.html:194
msgid "Fullscreen" #, fuzzy
msgid "Full screen"
msgstr "Volledig Scherm" msgstr "Volledig Scherm"
#: ../vnc.html:185 ../vnc.html:192 #: ../vnc.html:199 ../vnc.html:205
msgid "Settings" msgid "Settings"
msgstr "Instellingen" msgstr "Instellingen"
#: ../vnc.html:195 #: ../vnc.html:211
msgid "Shared Mode" #, fuzzy
msgid "Shared mode"
msgstr "Gedeelde Modus" msgstr "Gedeelde Modus"
#: ../vnc.html:198 #: ../vnc.html:218
msgid "View Only" #, fuzzy
msgid "View only"
msgstr "Alleen Kijken" msgstr "Alleen Kijken"
#: ../vnc.html:202 #: ../vnc.html:226
msgid "Clip to Window" msgid "Clip to window"
msgstr "Randen buiten venster afsnijden" msgstr "Randen buiten venster afsnijden"
#: ../vnc.html:205 #: ../vnc.html:231
msgid "Scaling Mode:" msgid "Scaling mode:"
msgstr "Schaalmodus:" msgstr "Schaalmodus:"
#: ../vnc.html:207 #: ../vnc.html:233
msgid "None" msgid "None"
msgstr "Geen" msgstr "Geen"
#: ../vnc.html:208 #: ../vnc.html:234
msgid "Local Scaling" #, fuzzy
msgid "Local scaling"
msgstr "Lokaal Schalen" msgstr "Lokaal Schalen"
#: ../vnc.html:209 #: ../vnc.html:235
msgid "Remote Resizing" #, fuzzy
msgid "Remote resizing"
msgstr "Op Afstand Formaat Wijzigen" msgstr "Op Afstand Formaat Wijzigen"
#: ../vnc.html:214 #: ../vnc.html:240
msgid "Advanced" msgid "Advanced"
msgstr "Geavanceerd" msgstr "Geavanceerd"
#: ../vnc.html:217 #: ../vnc.html:243
msgid "Quality:"
msgstr "Kwaliteit:"
#: ../vnc.html:247
msgid "Compression level:"
msgstr "Compressieniveau:"
#: ../vnc.html:252
msgid "Repeater ID:" msgid "Repeater ID:"
msgstr "Repeater ID:" msgstr "Repeater ID:"
#: ../vnc.html:221 #: ../vnc.html:256
msgid "WebSocket" msgid "WebSocket"
msgstr "WebSocket" msgstr "WebSocket"
#: ../vnc.html:224 #: ../vnc.html:261
msgid "Encrypt" msgid "Encrypt"
msgstr "Versleutelen" msgstr "Versleutelen"
#: ../vnc.html:227 #: ../vnc.html:266
msgid "Host:" msgid "Host:"
msgstr "Host:" msgstr "Host:"
#: ../vnc.html:231 #: ../vnc.html:270
msgid "Port:" msgid "Port:"
msgstr "Poort:" msgstr "Poort:"
#: ../vnc.html:235 #: ../vnc.html:274
msgid "Path:" msgid "Path:"
msgstr "Pad:" msgstr "Pad:"
#: ../vnc.html:242 #: ../vnc.html:283
msgid "Automatic Reconnect" #, fuzzy
msgid "Automatic reconnect"
msgstr "Automatisch Opnieuw Verbinden" msgstr "Automatisch Opnieuw Verbinden"
#: ../vnc.html:245 #: ../vnc.html:288
msgid "Reconnect Delay (ms):" #, fuzzy
msgid "Reconnect delay (ms):"
msgstr "Vertraging voor Opnieuw Verbinden (ms):" msgstr "Vertraging voor Opnieuw Verbinden (ms):"
#: ../vnc.html:250 #: ../vnc.html:295
msgid "Show Dot when No Cursor" msgid "Show dot when no cursor"
msgstr "Geef stip weer indien geen cursor" msgstr "Geef stip weer indien geen cursor"
#: ../vnc.html:255 #: ../vnc.html:302
msgid "Logging:" msgid "Logging:"
msgstr "Logmeldingen:" msgstr "Logmeldingen:"
#: ../vnc.html:267 #: ../vnc.html:311
msgid "Version:"
msgstr "Versie:"
#: ../vnc.html:319
msgid "Disconnect" msgid "Disconnect"
msgstr "Verbinding verbreken" msgstr "Verbinding verbreken"
#: ../vnc.html:286 #: ../vnc.html:342
msgid "Connect" msgid "Connect"
msgstr "Verbinden" msgstr "Verbinden"
#: ../vnc.html:296 #: ../vnc.html:351
msgid "Server identity"
msgstr "Serveridentiteit"
#: ../vnc.html:354
msgid "The server has provided the following identifying information:"
msgstr "De server geeft de volgende identificerende informatie:"
#: ../vnc.html:357
msgid "Fingerprint:"
msgstr "Vingerafdruk:"
#: ../vnc.html:361
msgid ""
"Please verify that the information is correct and press \"Approve\". "
"Otherwise press \"Reject\"."
msgstr ""
"Verifieer dat de informatie is correct en druk “OK”. Druk anders op "
"“Afwijzen”."
#: ../vnc.html:366
msgid "Approve"
msgstr "OK"
#: ../vnc.html:367
msgid "Reject"
msgstr "Afwijzen"
#: ../vnc.html:375
msgid "Credentials"
msgstr "Inloggegevens"
#: ../vnc.html:379
msgid "Username:"
msgstr "Gebruikersnaam:"
#: ../vnc.html:383
msgid "Password:" msgid "Password:"
msgstr "Wachtwoord:" msgstr "Wachtwoord:"
#: ../vnc.html:300 # Translated by Harold Horsman
msgid "Send Password" #: ../vnc.html:387
msgstr "Verzend Wachtwoord:" msgid "Send credentials"
msgstr "Stuur inloggegevens"
#: ../vnc.html:310 #: ../vnc.html:396
msgid "Cancel" msgid "Cancel"
msgstr "Annuleren" msgstr "Annuleren"
#~ msgid "Must set host"
#~ msgstr "Host moeten worden ingesteld"
#~ msgid "Password is required"
#~ msgstr "Wachtwoord is vereist"
#~ msgid "viewport drag"
#~ msgstr "kijkvenster slepen"
#~ msgid "Active Mouse Button"
#~ msgstr "Actieve Muisknop"
#~ msgid "No mousebutton"
#~ msgstr "Geen muisknop"
#~ msgid "Left mousebutton"
#~ msgstr "Linker muisknop"
#~ msgid "Middle mousebutton"
#~ msgstr "Middelste muisknop"
#~ msgid "Right mousebutton"
#~ msgstr "Rechter muisknop"
#~ msgid "Clear"
#~ msgstr "Wissen"
#~ msgid "Send Password"
#~ msgstr "Verzend Wachtwoord:"
#~ msgid "Disconnect timeout" #~ msgid "Disconnect timeout"
#~ msgstr "Timeout tijdens verbreken van verbinding" #~ msgstr "Timeout tijdens verbreken van verbinding"

View File

@ -1,14 +1,14 @@
# SOME DESCRIPTIVE TITLE. # SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR The noVNC Authors # Copyright (C) YEAR The noVNC authors
# This file is distributed under the same license as the noVNC package. # This file is distributed under the same license as the noVNC package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
# #
#, fuzzy #, fuzzy
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: noVNC 1.4.0\n" "Project-Id-Version: noVNC 1.6.0\n"
"Report-Msgid-Bugs-To: novnc@googlegroups.com\n" "Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
"POT-Creation-Date: 2022-12-27 15:24+0100\n" "POT-Creation-Date: 2025-02-14 10:14+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -17,316 +17,317 @@ msgstr ""
"Content-Type: text/plain; charset=CHARSET\n" "Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
#: ../app/ui.js:69 #: ../app/ui.js:84
msgid "HTTPS is required for full functionality" msgid ""
"Running without HTTPS is not recommended, crashes or other issues are likely."
msgstr "" msgstr ""
#: ../app/ui.js:410 #: ../app/ui.js:413
msgid "Connecting..." msgid "Connecting..."
msgstr "" msgstr ""
#: ../app/ui.js:417 #: ../app/ui.js:420
msgid "Disconnecting..." msgid "Disconnecting..."
msgstr "" msgstr ""
#: ../app/ui.js:423 #: ../app/ui.js:426
msgid "Reconnecting..." msgid "Reconnecting..."
msgstr "" msgstr ""
#: ../app/ui.js:428 #: ../app/ui.js:431
msgid "Internal error" msgid "Internal error"
msgstr "" msgstr ""
#: ../app/ui.js:1026 #: ../app/ui.js:1079
msgid "Must set host" msgid "Failed to connect to server: "
msgstr "" msgstr ""
#: ../app/ui.js:1110 #: ../app/ui.js:1145
msgid "Connected (encrypted) to " msgid "Connected (encrypted) to "
msgstr "" msgstr ""
#: ../app/ui.js:1112 #: ../app/ui.js:1147
msgid "Connected (unencrypted) to " msgid "Connected (unencrypted) to "
msgstr "" msgstr ""
#: ../app/ui.js:1135 #: ../app/ui.js:1170
msgid "Something went wrong, connection is closed" msgid "Something went wrong, connection is closed"
msgstr "" msgstr ""
#: ../app/ui.js:1138 #: ../app/ui.js:1173
msgid "Failed to connect to server" msgid "Failed to connect to server"
msgstr "" msgstr ""
#: ../app/ui.js:1150 #: ../app/ui.js:1185
msgid "Disconnected" msgid "Disconnected"
msgstr "" msgstr ""
#: ../app/ui.js:1165 #: ../app/ui.js:1200
msgid "New connection has been rejected with reason: " msgid "New connection has been rejected with reason: "
msgstr "" msgstr ""
#: ../app/ui.js:1168 #: ../app/ui.js:1203
msgid "New connection has been rejected" msgid "New connection has been rejected"
msgstr "" msgstr ""
#: ../app/ui.js:1234 #: ../app/ui.js:1269
msgid "Credentials are required" msgid "Credentials are required"
msgstr "" msgstr ""
#: ../vnc.html:57 #: ../vnc.html:106
msgid "noVNC encountered an error:" msgid "noVNC encountered an error:"
msgstr "" msgstr ""
#: ../vnc.html:67 #: ../vnc.html:116
msgid "Hide/Show the control bar" msgid "Hide/Show the control bar"
msgstr "" msgstr ""
#: ../vnc.html:76 #: ../vnc.html:125
msgid "Drag" msgid "Drag"
msgstr "" msgstr ""
#: ../vnc.html:76 #: ../vnc.html:125
msgid "Move/Drag Viewport" msgid "Move/Drag viewport"
msgstr "" msgstr ""
#: ../vnc.html:82 #: ../vnc.html:131
msgid "Keyboard" msgid "Keyboard"
msgstr "" msgstr ""
#: ../vnc.html:82 #: ../vnc.html:131
msgid "Show Keyboard" msgid "Show keyboard"
msgstr "" msgstr ""
#: ../vnc.html:87 #: ../vnc.html:136
msgid "Extra keys" msgid "Extra keys"
msgstr "" msgstr ""
#: ../vnc.html:87 #: ../vnc.html:136
msgid "Show Extra Keys" msgid "Show extra keys"
msgstr "" msgstr ""
#: ../vnc.html:92 #: ../vnc.html:141
msgid "Ctrl" msgid "Ctrl"
msgstr "" msgstr ""
#: ../vnc.html:92 #: ../vnc.html:141
msgid "Toggle Ctrl" msgid "Toggle Ctrl"
msgstr "" msgstr ""
#: ../vnc.html:95 #: ../vnc.html:144
msgid "Alt" msgid "Alt"
msgstr "" msgstr ""
#: ../vnc.html:95 #: ../vnc.html:144
msgid "Toggle Alt" msgid "Toggle Alt"
msgstr "" msgstr ""
#: ../vnc.html:98 #: ../vnc.html:147
msgid "Toggle Windows" msgid "Toggle Windows"
msgstr "" msgstr ""
#: ../vnc.html:98 #: ../vnc.html:147
msgid "Windows" msgid "Windows"
msgstr "" msgstr ""
#: ../vnc.html:101 #: ../vnc.html:150
msgid "Send Tab" msgid "Send Tab"
msgstr "" msgstr ""
#: ../vnc.html:101 #: ../vnc.html:150
msgid "Tab" msgid "Tab"
msgstr "" msgstr ""
#: ../vnc.html:104 #: ../vnc.html:153
msgid "Esc" msgid "Esc"
msgstr "" msgstr ""
#: ../vnc.html:104 #: ../vnc.html:153
msgid "Send Escape" msgid "Send Escape"
msgstr "" msgstr ""
#: ../vnc.html:107 #: ../vnc.html:156
msgid "Ctrl+Alt+Del" msgid "Ctrl+Alt+Del"
msgstr "" msgstr ""
#: ../vnc.html:107 #: ../vnc.html:156
msgid "Send Ctrl-Alt-Del" msgid "Send Ctrl-Alt-Del"
msgstr "" msgstr ""
#: ../vnc.html:114 #: ../vnc.html:163
msgid "Shutdown/Reboot" msgid "Shutdown/Reboot"
msgstr "" msgstr ""
#: ../vnc.html:114 #: ../vnc.html:163
msgid "Shutdown/Reboot..." msgid "Shutdown/Reboot..."
msgstr "" msgstr ""
#: ../vnc.html:120 #: ../vnc.html:169
msgid "Power" msgid "Power"
msgstr "" msgstr ""
#: ../vnc.html:122 #: ../vnc.html:171
msgid "Shutdown" msgid "Shutdown"
msgstr "" msgstr ""
#: ../vnc.html:123 #: ../vnc.html:172
msgid "Reboot" msgid "Reboot"
msgstr "" msgstr ""
#: ../vnc.html:124 #: ../vnc.html:173
msgid "Reset" msgid "Reset"
msgstr "" msgstr ""
#: ../vnc.html:129 ../vnc.html:135 #: ../vnc.html:178 ../vnc.html:184
msgid "Clipboard" msgid "Clipboard"
msgstr "" msgstr ""
#: ../vnc.html:137 #: ../vnc.html:186
msgid "Edit clipboard content in the textarea below." msgid "Edit clipboard content in the textarea below."
msgstr "" msgstr ""
#: ../vnc.html:145 #: ../vnc.html:194
msgid "Full Screen" msgid "Full screen"
msgstr "" msgstr ""
#: ../vnc.html:150 ../vnc.html:156 #: ../vnc.html:199 ../vnc.html:205
msgid "Settings" msgid "Settings"
msgstr "" msgstr ""
#: ../vnc.html:160 #: ../vnc.html:211
msgid "Shared Mode" msgid "Shared mode"
msgstr "" msgstr ""
#: ../vnc.html:163 #: ../vnc.html:218
msgid "View Only" msgid "View only"
msgstr "" msgstr ""
#: ../vnc.html:167 #: ../vnc.html:226
msgid "Clip to Window" msgid "Clip to window"
msgstr "" msgstr ""
#: ../vnc.html:170 #: ../vnc.html:231
msgid "Scaling Mode:" msgid "Scaling mode:"
msgstr "" msgstr ""
#: ../vnc.html:172 #: ../vnc.html:233
msgid "None" msgid "None"
msgstr "" msgstr ""
#: ../vnc.html:173 #: ../vnc.html:234
msgid "Local Scaling" msgid "Local scaling"
msgstr "" msgstr ""
#: ../vnc.html:174 #: ../vnc.html:235
msgid "Remote Resizing" msgid "Remote resizing"
msgstr "" msgstr ""
#: ../vnc.html:179 #: ../vnc.html:240
msgid "Advanced" msgid "Advanced"
msgstr "" msgstr ""
#: ../vnc.html:182 #: ../vnc.html:243
msgid "Quality:" msgid "Quality:"
msgstr "" msgstr ""
#: ../vnc.html:186 #: ../vnc.html:247
msgid "Compression level:" msgid "Compression level:"
msgstr "" msgstr ""
#: ../vnc.html:191 #: ../vnc.html:252
msgid "Repeater ID:" msgid "Repeater ID:"
msgstr "" msgstr ""
#: ../vnc.html:195 #: ../vnc.html:256
msgid "WebSocket" msgid "WebSocket"
msgstr "" msgstr ""
#: ../vnc.html:198 #: ../vnc.html:261
msgid "Encrypt" msgid "Encrypt"
msgstr "" msgstr ""
#: ../vnc.html:201 #: ../vnc.html:266
msgid "Host:" msgid "Host:"
msgstr "" msgstr ""
#: ../vnc.html:205 #: ../vnc.html:270
msgid "Port:" msgid "Port:"
msgstr "" msgstr ""
#: ../vnc.html:209 #: ../vnc.html:274
msgid "Path:" msgid "Path:"
msgstr "" msgstr ""
#: ../vnc.html:216 #: ../vnc.html:283
msgid "Automatic Reconnect" msgid "Automatic reconnect"
msgstr ""
#: ../vnc.html:219
msgid "Reconnect Delay (ms):"
msgstr ""
#: ../vnc.html:224
msgid "Show Dot when No Cursor"
msgstr ""
#: ../vnc.html:229
msgid "Logging:"
msgstr ""
#: ../vnc.html:238
msgid "Version:"
msgstr ""
#: ../vnc.html:246
msgid "Disconnect"
msgstr ""
#: ../vnc.html:269
msgid "Connect"
msgstr ""
#: ../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 "" msgstr ""
#: ../vnc.html:288 #: ../vnc.html:288
msgid "Reconnect delay (ms):"
msgstr ""
#: ../vnc.html:295
msgid "Show dot when no cursor"
msgstr ""
#: ../vnc.html:302
msgid "Logging:"
msgstr ""
#: ../vnc.html:311
msgid "Version:"
msgstr ""
#: ../vnc.html:319
msgid "Disconnect"
msgstr ""
#: ../vnc.html:342
msgid "Connect"
msgstr ""
#: ../vnc.html:351
msgid "Server identity"
msgstr ""
#: ../vnc.html:354
msgid "The server has provided the following identifying information:"
msgstr ""
#: ../vnc.html:357
msgid "Fingerprint:"
msgstr ""
#: ../vnc.html:361
msgid "" msgid ""
"Please verify that the information is correct and press \"Approve\". " "Please verify that the information is correct and press \"Approve\". "
"Otherwise press \"Reject\"." "Otherwise press \"Reject\"."
msgstr "" msgstr ""
#: ../vnc.html:293 #: ../vnc.html:366
msgid "Approve" msgid "Approve"
msgstr "" msgstr ""
#: ../vnc.html:294 #: ../vnc.html:367
msgid "Reject" msgid "Reject"
msgstr "" msgstr ""
#: ../vnc.html:302 #: ../vnc.html:375
msgid "Credentials" msgid "Credentials"
msgstr "" msgstr ""
#: ../vnc.html:306 #: ../vnc.html:379
msgid "Username:" msgid "Username:"
msgstr "" msgstr ""
#: ../vnc.html:310 #: ../vnc.html:383
msgid "Password:" msgid "Password:"
msgstr "" msgstr ""
#: ../vnc.html:314 #: ../vnc.html:387
msgid "Send Credentials" msgid "Send credentials"
msgstr "" msgstr ""
#: ../vnc.html:323 #: ../vnc.html:396
msgid "Cancel" msgid "Cancel"
msgstr "" msgstr ""

View File

@ -1,5 +1,5 @@
# Polish translations for noVNC package. # Polish translations for noVNC package.
# Copyright (C) 2018 The noVNC Authors # Copyright (C) 2018 The noVNC authors
# This file is distributed under the same license as the noVNC package. # This file is distributed under the same license as the noVNC package.
# Mariusz Jamro <mariusz.jamro@gmail.com>, 2017. # Mariusz Jamro <mariusz.jamro@gmail.com>, 2017.
# #
@ -108,7 +108,7 @@ msgid "Keyboard"
msgstr "Klawiatura" msgstr "Klawiatura"
#: ../vnc.html:124 #: ../vnc.html:124
msgid "Show Keyboard" msgid "Show keyboard"
msgstr "Pokaż klawiaturę" msgstr "Pokaż klawiaturę"
#: ../vnc.html:131 #: ../vnc.html:131
@ -116,7 +116,7 @@ msgid "Extra keys"
msgstr "Przyciski dodatkowe" msgstr "Przyciski dodatkowe"
#: ../vnc.html:131 #: ../vnc.html:131
msgid "Show Extra Keys" msgid "Show extra keys"
msgstr "Pokaż przyciski dodatkowe" msgstr "Pokaż przyciski dodatkowe"
#: ../vnc.html:136 #: ../vnc.html:136
@ -220,11 +220,11 @@ msgid "None"
msgstr "Brak" msgstr "Brak"
#: ../vnc.html:215 #: ../vnc.html:215
msgid "Local Scaling" msgid "Local scaling"
msgstr "Skalowanie lokalne" msgstr "Skalowanie lokalne"
#: ../vnc.html:216 #: ../vnc.html:216
msgid "Remote Resizing" msgid "Remote resizing"
msgstr "Skalowanie zdalne" msgstr "Skalowanie zdalne"
#: ../vnc.html:221 #: ../vnc.html:221
@ -256,11 +256,11 @@ msgid "Path:"
msgstr "Ścieżka:" msgstr "Ścieżka:"
#: ../vnc.html:249 #: ../vnc.html:249
msgid "Automatic Reconnect" msgid "Automatic reconnect"
msgstr "Automatycznie wznawiaj połączenie" msgstr "Automatycznie wznawiaj połączenie"
#: ../vnc.html:252 #: ../vnc.html:252
msgid "Reconnect Delay (ms):" msgid "Reconnect delay (ms):"
msgstr "Opóźnienie wznawiania (ms):" msgstr "Opóźnienie wznawiania (ms):"
#: ../vnc.html:258 #: ../vnc.html:258

View File

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

View File

@ -1,5 +1,5 @@
# Portuguese translations for noVNC package. # Portuguese translations for noVNC package.
# Copyright (C) 2021 The noVNC Authors # Copyright (C) 2021 The noVNC authors
# This file is distributed under the same license as the noVNC package. # This file is distributed under the same license as the noVNC package.
# <liddack@outlook.com>, 2021. # <liddack@outlook.com>, 2021.
# #
@ -83,7 +83,7 @@ msgid "Drag"
msgstr "Arrastar" msgstr "Arrastar"
#: ../vnc.html:78 #: ../vnc.html:78
msgid "Move/Drag Viewport" msgid "Move/Drag viewport"
msgstr "Mover/arrastar a janela" msgstr "Mover/arrastar a janela"
#: ../vnc.html:84 #: ../vnc.html:84
@ -91,7 +91,7 @@ msgid "Keyboard"
msgstr "Teclado" msgstr "Teclado"
#: ../vnc.html:84 #: ../vnc.html:84
msgid "Show Keyboard" msgid "Show keyboard"
msgstr "Mostrar teclado" msgstr "Mostrar teclado"
#: ../vnc.html:89 #: ../vnc.html:89
@ -99,7 +99,7 @@ msgid "Extra keys"
msgstr "Teclas adicionais" msgstr "Teclas adicionais"
#: ../vnc.html:89 #: ../vnc.html:89
msgid "Show Extra Keys" msgid "Show extra keys"
msgstr "Mostar teclas adicionais" msgstr "Mostar teclas adicionais"
#: ../vnc.html:94 #: ../vnc.html:94
@ -191,19 +191,19 @@ msgid "Settings"
msgstr "Configurações" msgstr "Configurações"
#: ../vnc.html:162 #: ../vnc.html:162
msgid "Shared Mode" msgid "Shared mode"
msgstr "Modo compartilhado" msgstr "Modo compartilhado"
#: ../vnc.html:165 #: ../vnc.html:165
msgid "View Only" msgid "View only"
msgstr "Apenas visualizar" msgstr "Apenas visualizar"
#: ../vnc.html:169 #: ../vnc.html:169
msgid "Clip to Window" msgid "Clip to window"
msgstr "Recortar à janela" msgstr "Recortar à janela"
#: ../vnc.html:172 #: ../vnc.html:172
msgid "Scaling Mode:" msgid "Scaling mode:"
msgstr "Modo de dimensionamento:" msgstr "Modo de dimensionamento:"
#: ../vnc.html:174 #: ../vnc.html:174
@ -211,11 +211,11 @@ msgid "None"
msgstr "Nenhum" msgstr "Nenhum"
#: ../vnc.html:175 #: ../vnc.html:175
msgid "Local Scaling" msgid "Local scaling"
msgstr "Local" msgstr "Local"
#: ../vnc.html:176 #: ../vnc.html:176
msgid "Remote Resizing" msgid "Remote resizing"
msgstr "Remoto" msgstr "Remoto"
#: ../vnc.html:181 #: ../vnc.html:181
@ -255,15 +255,15 @@ msgid "Path:"
msgstr "Caminho:" msgstr "Caminho:"
#: ../vnc.html:218 #: ../vnc.html:218
msgid "Automatic Reconnect" msgid "Automatic reconnect"
msgstr "Reconexão automática" msgstr "Reconexão automática"
#: ../vnc.html:221 #: ../vnc.html:221
msgid "Reconnect Delay (ms):" msgid "Reconnect delay (ms):"
msgstr "Atraso da reconexão (ms)" msgstr "Atraso da reconexão (ms)"
#: ../vnc.html:226 #: ../vnc.html:226
msgid "Show Dot when No Cursor" msgid "Show dot when no cursor"
msgstr "Mostrar ponto quando não há cursor" msgstr "Mostrar ponto quando não há cursor"
#: ../vnc.html:231 #: ../vnc.html:231
@ -291,7 +291,7 @@ msgid "Password:"
msgstr "Senha:" msgstr "Senha:"
#: ../vnc.html:285 #: ../vnc.html:285
msgid "Send Credentials" msgid "Send credentials"
msgstr "Enviar credenciais" msgstr "Enviar credenciais"
#: ../vnc.html:295 #: ../vnc.html:295

View File

@ -6,11 +6,11 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: noVNC 1.3.0\n" "Project-Id-Version: noVNC 1.5.0\n"
"Report-Msgid-Bugs-To: novnc@googlegroups.com\n" "Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
"POT-Creation-Date: 2021-08-27 16:03+0200\n" "POT-Creation-Date: 2021-08-27 16:03+0200\n"
"PO-Revision-Date: 2021-09-09 10:29+0400\n" "PO-Revision-Date: 2024-02-11 03:58+0300\n"
"Last-Translator: Nia Remez <nia.remez@cendio.com>\n" "Last-Translator: Dim5x <dim5x@yahoo.com>\n"
"Language-Team: Russian\n" "Language-Team: Russian\n"
"Language: ru\n" "Language: ru\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
@ -86,7 +86,7 @@ msgid "Drag"
msgstr "Переместить" msgstr "Переместить"
#: ../vnc.html:78 #: ../vnc.html:78
msgid "Move/Drag Viewport" msgid "Move/Drag viewport"
msgstr "Переместить окно" msgstr "Переместить окно"
#: ../vnc.html:84 #: ../vnc.html:84
@ -94,7 +94,7 @@ msgid "Keyboard"
msgstr "Клавиатура" msgstr "Клавиатура"
#: ../vnc.html:84 #: ../vnc.html:84
msgid "Show Keyboard" msgid "Show keyboard"
msgstr "Показать клавиатуру" msgstr "Показать клавиатуру"
#: ../vnc.html:89 #: ../vnc.html:89
@ -111,7 +111,7 @@ msgstr "Ctrl"
#: ../vnc.html:94 #: ../vnc.html:94
msgid "Toggle Ctrl" msgid "Toggle Ctrl"
msgstr "Переключение нажатия Ctrl" msgstr "Зажать Ctrl"
#: ../vnc.html:97 #: ../vnc.html:97
msgid "Alt" msgid "Alt"
@ -119,11 +119,11 @@ msgstr "Alt"
#: ../vnc.html:97 #: ../vnc.html:97
msgid "Toggle Alt" msgid "Toggle Alt"
msgstr "Переключение нажатия Alt" msgstr "Зажать Alt"
#: ../vnc.html:100 #: ../vnc.html:100
msgid "Toggle Windows" msgid "Toggle Windows"
msgstr "Переключение вкладок" msgstr "Зажать Windows"
#: ../vnc.html:100 #: ../vnc.html:100
msgid "Windows" msgid "Windows"
@ -194,7 +194,7 @@ msgid "Settings"
msgstr "Настройки" msgstr "Настройки"
#: ../vnc.html:162 #: ../vnc.html:162
msgid "Shared Mode" msgid "Shared mode"
msgstr "Общий режим" msgstr "Общий режим"
#: ../vnc.html:165 #: ../vnc.html:165
@ -202,11 +202,11 @@ msgid "View Only"
msgstr "Только Просмотр" msgstr "Только Просмотр"
#: ../vnc.html:169 #: ../vnc.html:169
msgid "Clip to Window" msgid "Clip to window"
msgstr "В окно" msgstr "В окно"
#: ../vnc.html:172 #: ../vnc.html:172
msgid "Scaling Mode:" msgid "Scaling mode:"
msgstr "Масштаб:" msgstr "Масштаб:"
#: ../vnc.html:174 #: ../vnc.html:174
@ -214,11 +214,11 @@ msgid "None"
msgstr "Нет" msgstr "Нет"
#: ../vnc.html:175 #: ../vnc.html:175
msgid "Local Scaling" msgid "Local scaling"
msgstr "Локльный масштаб" msgstr "Локальный масштаб"
#: ../vnc.html:176 #: ../vnc.html:176
msgid "Remote Resizing" msgid "Remote resizing"
msgstr "Удаленная перенастройка размера" msgstr "Удаленная перенастройка размера"
#: ../vnc.html:181 #: ../vnc.html:181
@ -258,15 +258,15 @@ msgid "Path:"
msgstr "Путь:" msgstr "Путь:"
#: ../vnc.html:218 #: ../vnc.html:218
msgid "Automatic Reconnect" msgid "Automatic reconnect"
msgstr "Автоматическое переподключение" msgstr "Автоматическое переподключение"
#: ../vnc.html:221 #: ../vnc.html:221
msgid "Reconnect Delay (ms):" msgid "Reconnect delay (ms):"
msgstr "Задержка переподключения (мс):" msgstr "Задержка переподключения (мс):"
#: ../vnc.html:226 #: ../vnc.html:226
msgid "Show Dot when No Cursor" msgid "Show dot when no cursor"
msgstr "Показать точку вместо курсора" msgstr "Показать точку вместо курсора"
#: ../vnc.html:231 #: ../vnc.html:231

237
po/sv.po
View File

@ -1,339 +1,348 @@
# Swedish translations for noVNC package # Swedish translations for noVNC package
# Svenska översättningar för paketet noVNC. # Svenska översättningar för paketet noVNC.
# Copyright (C) 2020 The noVNC Authors # Copyright (C) 2025 The noVNC authors
# This file is distributed under the same license as the noVNC package. # This file is distributed under the same license as the noVNC package.
# Samuel Mannehed <samuel@cendio.se>, 2020. # Samuel Mannehed <samuel@cendio.se>, 2020.
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: noVNC 1.3.0\n" "Project-Id-Version: noVNC 1.6.0\n"
"Report-Msgid-Bugs-To: novnc@googlegroups.com\n" "Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
"POT-Creation-Date: 2023-01-20 12:54+0100\n" "POT-Creation-Date: 2025-02-14 10:14+0100\n"
"PO-Revision-Date: 2023-01-20 12:58+0100\n" "PO-Revision-Date: 2025-02-14 10:29+0100\n"
"Last-Translator: Samuel Mannehed <samuel@cendio.se>\n" "Last-Translator: Alexander Zeijlon <aleze@cendio.com>\n"
"Language-Team: none\n" "Language-Team: none\n"
"Language: sv\n" "Language: sv\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 3.2.2\n" "X-Generator: Poedit 3.5\n"
#: ../app/ui.js:69 #: ../app/ui.js:84
msgid "HTTPS is required for full functionality" msgid ""
msgstr "HTTPS krävs för full funktionalitet" "Running without HTTPS is not recommended, crashes or other issues are likely."
msgstr ""
"Det är ej rekommenderat att köra utan HTTPS, krascher och andra problem är "
"troliga."
#: ../app/ui.js:410 #: ../app/ui.js:413
msgid "Connecting..." msgid "Connecting..."
msgstr "Ansluter..." msgstr "Ansluter..."
#: ../app/ui.js:417 #: ../app/ui.js:420
msgid "Disconnecting..." msgid "Disconnecting..."
msgstr "Kopplar ner..." msgstr "Kopplar ner..."
#: ../app/ui.js:423 #: ../app/ui.js:426
msgid "Reconnecting..." msgid "Reconnecting..."
msgstr "Återansluter..." msgstr "Återansluter..."
#: ../app/ui.js:428 #: ../app/ui.js:431
msgid "Internal error" msgid "Internal error"
msgstr "Internt fel" msgstr "Internt fel"
#: ../app/ui.js:1026 #: ../app/ui.js:1079
msgid "Must set host" msgid "Failed to connect to server: "
msgstr "Du måste specifiera en värd" msgstr "Misslyckades att ansluta till servern: "
#: ../app/ui.js:1110 #: ../app/ui.js:1145
msgid "Connected (encrypted) to " msgid "Connected (encrypted) to "
msgstr "Ansluten (krypterat) till " msgstr "Ansluten (krypterat) till "
#: ../app/ui.js:1112 #: ../app/ui.js:1147
msgid "Connected (unencrypted) to " msgid "Connected (unencrypted) to "
msgstr "Ansluten (okrypterat) till " msgstr "Ansluten (okrypterat) till "
#: ../app/ui.js:1135 #: ../app/ui.js:1170
msgid "Something went wrong, connection is closed" msgid "Something went wrong, connection is closed"
msgstr "Något gick fel, anslutningen avslutades" msgstr "Något gick fel, anslutningen avslutades"
#: ../app/ui.js:1138 #: ../app/ui.js:1173
msgid "Failed to connect to server" msgid "Failed to connect to server"
msgstr "Misslyckades att ansluta till servern" msgstr "Misslyckades att ansluta till servern"
#: ../app/ui.js:1150 #: ../app/ui.js:1185
msgid "Disconnected" msgid "Disconnected"
msgstr "Frånkopplad" msgstr "Frånkopplad"
#: ../app/ui.js:1165 #: ../app/ui.js:1200
msgid "New connection has been rejected with reason: " msgid "New connection has been rejected with reason: "
msgstr "Ny anslutning har blivit nekad med följande skäl: " msgstr "Ny anslutning har blivit nekad med följande skäl: "
#: ../app/ui.js:1168 #: ../app/ui.js:1203
msgid "New connection has been rejected" msgid "New connection has been rejected"
msgstr "Ny anslutning har blivit nekad" msgstr "Ny anslutning har blivit nekad"
#: ../app/ui.js:1234 #: ../app/ui.js:1269
msgid "Credentials are required" msgid "Credentials are required"
msgstr "Användaruppgifter krävs" msgstr "Användaruppgifter krävs"
#: ../vnc.html:55 #: ../vnc.html:106
msgid "noVNC encountered an error:" msgid "noVNC encountered an error:"
msgstr "noVNC stötte på ett problem:" msgstr "noVNC stötte på ett problem:"
#: ../vnc.html:65 #: ../vnc.html:116
msgid "Hide/Show the control bar" msgid "Hide/Show the control bar"
msgstr "Göm/Visa kontrollbaren" msgstr "Göm/Visa kontrollbaren"
#: ../vnc.html:74 #: ../vnc.html:125
msgid "Drag" msgid "Drag"
msgstr "Dra" msgstr "Dra"
#: ../vnc.html:74 #: ../vnc.html:125
msgid "Move/Drag Viewport" msgid "Move/Drag viewport"
msgstr "Flytta/Dra Vyn" msgstr "Flytta/Dra vyn"
#: ../vnc.html:80 #: ../vnc.html:131
msgid "Keyboard" msgid "Keyboard"
msgstr "Tangentbord" msgstr "Tangentbord"
#: ../vnc.html:80 #: ../vnc.html:131
msgid "Show Keyboard" msgid "Show keyboard"
msgstr "Visa Tangentbord" msgstr "Visa tangentbord"
#: ../vnc.html:85 #: ../vnc.html:136
msgid "Extra keys" msgid "Extra keys"
msgstr "Extraknappar" msgstr "Extraknappar"
#: ../vnc.html:85 #: ../vnc.html:136
msgid "Show Extra Keys" msgid "Show extra keys"
msgstr "Visa Extraknappar" msgstr "Visa extraknappar"
#: ../vnc.html:90 #: ../vnc.html:141
msgid "Ctrl" msgid "Ctrl"
msgstr "Ctrl" msgstr "Ctrl"
#: ../vnc.html:90 #: ../vnc.html:141
msgid "Toggle Ctrl" msgid "Toggle Ctrl"
msgstr "Växla Ctrl" msgstr "Växla Ctrl"
#: ../vnc.html:93 #: ../vnc.html:144
msgid "Alt" msgid "Alt"
msgstr "Alt" msgstr "Alt"
#: ../vnc.html:93 #: ../vnc.html:144
msgid "Toggle Alt" msgid "Toggle Alt"
msgstr "Växla Alt" msgstr "Växla Alt"
#: ../vnc.html:96 #: ../vnc.html:147
msgid "Toggle Windows" msgid "Toggle Windows"
msgstr "Växla Windows" msgstr "Växla Windows"
#: ../vnc.html:96 #: ../vnc.html:147
msgid "Windows" msgid "Windows"
msgstr "Windows" msgstr "Windows"
#: ../vnc.html:99 #: ../vnc.html:150
msgid "Send Tab" msgid "Send Tab"
msgstr "Skicka Tab" msgstr "Skicka Tab"
#: ../vnc.html:99 #: ../vnc.html:150
msgid "Tab" msgid "Tab"
msgstr "Tab" msgstr "Tab"
#: ../vnc.html:102 #: ../vnc.html:153
msgid "Esc" msgid "Esc"
msgstr "Esc" msgstr "Esc"
#: ../vnc.html:102 #: ../vnc.html:153
msgid "Send Escape" msgid "Send Escape"
msgstr "Skicka Escape" msgstr "Skicka Escape"
#: ../vnc.html:105 #: ../vnc.html:156
msgid "Ctrl+Alt+Del" msgid "Ctrl+Alt+Del"
msgstr "Ctrl+Alt+Del" msgstr "Ctrl+Alt+Del"
#: ../vnc.html:105 #: ../vnc.html:156
msgid "Send Ctrl-Alt-Del" msgid "Send Ctrl-Alt-Del"
msgstr "Skicka Ctrl-Alt-Del" msgstr "Skicka Ctrl-Alt-Del"
#: ../vnc.html:112 #: ../vnc.html:163
msgid "Shutdown/Reboot" msgid "Shutdown/Reboot"
msgstr "Stäng av/Boota om" msgstr "Stäng av/Boota om"
#: ../vnc.html:112 #: ../vnc.html:163
msgid "Shutdown/Reboot..." msgid "Shutdown/Reboot..."
msgstr "Stäng av/Boota om..." msgstr "Stäng av/Boota om..."
#: ../vnc.html:118 #: ../vnc.html:169
msgid "Power" msgid "Power"
msgstr "Ström" msgstr "Ström"
#: ../vnc.html:120 #: ../vnc.html:171
msgid "Shutdown" msgid "Shutdown"
msgstr "Stäng av" msgstr "Stäng av"
#: ../vnc.html:121 #: ../vnc.html:172
msgid "Reboot" msgid "Reboot"
msgstr "Boota om" msgstr "Boota om"
#: ../vnc.html:122 #: ../vnc.html:173
msgid "Reset" msgid "Reset"
msgstr "Återställ" msgstr "Återställ"
#: ../vnc.html:127 ../vnc.html:133 #: ../vnc.html:178 ../vnc.html:184
msgid "Clipboard" msgid "Clipboard"
msgstr "Urklipp" msgstr "Urklipp"
#: ../vnc.html:135 #: ../vnc.html:186
msgid "Edit clipboard content in the textarea below." msgid "Edit clipboard content in the textarea below."
msgstr "Redigera urklippets innehåll i fältet nedan." msgstr "Redigera urklippets innehåll i fältet nedan."
#: ../vnc.html:143 #: ../vnc.html:194
msgid "Full Screen" msgid "Full screen"
msgstr "Fullskärm" msgstr "Fullskärm"
#: ../vnc.html:148 ../vnc.html:154 #: ../vnc.html:199 ../vnc.html:205
msgid "Settings" msgid "Settings"
msgstr "Inställningar" msgstr "Inställningar"
#: ../vnc.html:158 #: ../vnc.html:211
msgid "Shared Mode" msgid "Shared mode"
msgstr "Delat Läge" msgstr "Delat läge"
#: ../vnc.html:161 #: ../vnc.html:218
msgid "View Only" msgid "View only"
msgstr "Endast Visning" msgstr "Endast visning"
#: ../vnc.html:165 #: ../vnc.html:226
msgid "Clip to Window" msgid "Clip to window"
msgstr "Begränsa till Fönster" msgstr "Begränsa till fönster"
#: ../vnc.html:168 #: ../vnc.html:231
msgid "Scaling Mode:" msgid "Scaling mode:"
msgstr "Skalningsläge:" msgstr "Skalningsläge:"
#: ../vnc.html:170 #: ../vnc.html:233
msgid "None" msgid "None"
msgstr "Ingen" msgstr "Ingen"
#: ../vnc.html:171 #: ../vnc.html:234
msgid "Local Scaling" msgid "Local scaling"
msgstr "Lokal Skalning" msgstr "Lokal skalning"
#: ../vnc.html:172 #: ../vnc.html:235
msgid "Remote Resizing" msgid "Remote resizing"
msgstr "Ändra Storlek" msgstr "Ändra storlek"
#: ../vnc.html:177 #: ../vnc.html:240
msgid "Advanced" msgid "Advanced"
msgstr "Avancerat" msgstr "Avancerat"
#: ../vnc.html:180 #: ../vnc.html:243
msgid "Quality:" msgid "Quality:"
msgstr "Kvalitet:" msgstr "Kvalitet:"
#: ../vnc.html:184 #: ../vnc.html:247
msgid "Compression level:" msgid "Compression level:"
msgstr "Kompressionsnivå:" msgstr "Kompressionsnivå:"
#: ../vnc.html:189 #: ../vnc.html:252
msgid "Repeater ID:" msgid "Repeater ID:"
msgstr "Repeater-ID:" msgstr "Repeater-ID:"
#: ../vnc.html:193 #: ../vnc.html:256
msgid "WebSocket" msgid "WebSocket"
msgstr "WebSocket" msgstr "WebSocket"
#: ../vnc.html:196 #: ../vnc.html:261
msgid "Encrypt" msgid "Encrypt"
msgstr "Kryptera" msgstr "Kryptera"
#: ../vnc.html:199 #: ../vnc.html:266
msgid "Host:" msgid "Host:"
msgstr "Värd:" msgstr "Värd:"
#: ../vnc.html:203 #: ../vnc.html:270
msgid "Port:" msgid "Port:"
msgstr "Port:" msgstr "Port:"
#: ../vnc.html:207 #: ../vnc.html:274
msgid "Path:" msgid "Path:"
msgstr "Sökväg:" msgstr "Sökväg:"
#: ../vnc.html:214 #: ../vnc.html:283
msgid "Automatic Reconnect" msgid "Automatic reconnect"
msgstr "Automatisk Återanslutning" msgstr "Automatisk återanslutning"
#: ../vnc.html:217 #: ../vnc.html:288
msgid "Reconnect Delay (ms):" msgid "Reconnect delay (ms):"
msgstr "Fördröjning (ms):" msgstr "Fördröjning (ms):"
#: ../vnc.html:222 #: ../vnc.html:295
msgid "Show Dot when No Cursor" msgid "Show dot when no cursor"
msgstr "Visa prick när ingen muspekare finns" msgstr "Visa prick när ingen muspekare finns"
#: ../vnc.html:227 #: ../vnc.html:302
msgid "Logging:" msgid "Logging:"
msgstr "Loggning:" msgstr "Loggning:"
#: ../vnc.html:236 #: ../vnc.html:311
msgid "Version:" msgid "Version:"
msgstr "Version:" msgstr "Version:"
#: ../vnc.html:244 #: ../vnc.html:319
msgid "Disconnect" msgid "Disconnect"
msgstr "Koppla från" msgstr "Koppla från"
#: ../vnc.html:267 #: ../vnc.html:342
msgid "Connect" msgid "Connect"
msgstr "Anslut" msgstr "Anslut"
#: ../vnc.html:276 #: ../vnc.html:351
msgid "Server identity" msgid "Server identity"
msgstr "Server-identitet" msgstr "Server-identitet"
#: ../vnc.html:279 #: ../vnc.html:354
msgid "The server has provided the following identifying information:" msgid "The server has provided the following identifying information:"
msgstr "Servern har gett följande identifierande information:" msgstr "Servern har gett följande identifierande information:"
#: ../vnc.html:283 #: ../vnc.html:357
msgid "Fingerprint:" msgid "Fingerprint:"
msgstr "Fingeravtryck:" msgstr "Fingeravtryck:"
#: ../vnc.html:286 #: ../vnc.html:361
msgid "" msgid ""
"Please verify that the information is correct and press \"Approve\". " "Please verify that the information is correct and press \"Approve\". "
"Otherwise press \"Reject\"." "Otherwise press \"Reject\"."
msgstr "" msgstr ""
"Kontrollera att informationen är korrekt och tryck sedan " "Kontrollera att informationen är korrekt och tryck sedan \"Godkänn\". Tryck "
"\"Godkänn\". Tryck annars \"Neka\"." "annars \"Neka\"."
#: ../vnc.html:291 #: ../vnc.html:366
msgid "Approve" msgid "Approve"
msgstr "Godkänn" msgstr "Godkänn"
#: ../vnc.html:292 #: ../vnc.html:367
msgid "Reject" msgid "Reject"
msgstr "Neka" msgstr "Neka"
#: ../vnc.html:300 #: ../vnc.html:375
msgid "Credentials" msgid "Credentials"
msgstr "Användaruppgifter" msgstr "Användaruppgifter"
#: ../vnc.html:304 #: ../vnc.html:379
msgid "Username:" msgid "Username:"
msgstr "Användarnamn:" msgstr "Användarnamn:"
#: ../vnc.html:308 #: ../vnc.html:383
msgid "Password:" msgid "Password:"
msgstr "Lösenord:" msgstr "Lösenord:"
#: ../vnc.html:312 #: ../vnc.html:387
msgid "Send Credentials" msgid "Send credentials"
msgstr "Skicka Användaruppgifter" msgstr "Skicka användaruppgifter"
#: ../vnc.html:321 #: ../vnc.html:396
msgid "Cancel" msgid "Cancel"
msgstr "Avbryt" msgstr "Avbryt"
#~ msgid "Must set host"
#~ msgstr "Du måste specifiera en värd"
#~ msgid "HTTPS is required for full functionality"
#~ msgstr "HTTPS krävs för full funktionalitet"
#~ msgid "Clear" #~ msgid "Clear"
#~ msgstr "Rensa" #~ msgstr "Rensa"

View File

@ -1,6 +1,6 @@
# Turkish translations for noVNC package # Turkish translations for noVNC package
# Turkish translation for noVNC. # Turkish translation for noVNC.
# Copyright (C) 2018 The noVNC Authors # Copyright (C) 2018 The noVNC authors
# This file is distributed under the same license as the noVNC package. # This file is distributed under the same license as the noVNC package.
# Ömer ÇAKMAK <farukomercakmak@gmail.com>, 2018. # Ömer ÇAKMAK <farukomercakmak@gmail.com>, 2018.
# #
@ -116,7 +116,7 @@ msgid "Extra keys"
msgstr "Ekstra tuşlar" msgstr "Ekstra tuşlar"
#: ../vnc.html:131 #: ../vnc.html:131
msgid "Show Extra Keys" msgid "Show extra keys"
msgstr "Ekstra tuşları göster" msgstr "Ekstra tuşları göster"
#: ../vnc.html:136 #: ../vnc.html:136

View File

@ -1,18 +1,18 @@
#!/usr/bin/env node #!/usr/bin/env node
/* /*
* xgettext-html: HTML gettext parser * xgettext-html: HTML gettext parser
* Copyright (C) 2018 The noVNC Authors * Copyright (C) 2018 The noVNC authors
* Licensed under MPL 2.0 (see LICENSE.txt) * Licensed under MPL 2.0 (see LICENSE.txt)
*/ */
const getopt = require('node-getopt'); const { program } = require('commander');
const jsdom = require("jsdom"); const jsdom = require("jsdom");
const fs = require("fs"); const fs = require("fs");
const opt = getopt.create([ program
['o', 'output=FILE', 'write output to specified file'], .argument('<INPUT...>')
['h', 'help', 'display this help'], .requiredOption('-o, --output <FILE>', 'write output to specified file')
]).bindHelp().parseSystem(); .parse(process.argv);
const strings = {}; const strings = {};
@ -87,8 +87,8 @@ function process(elem, locator, enabled) {
} }
} }
for (let i = 0; i < opt.argv.length; i++) { for (let i = 0; i < program.args.length; i++) {
const fn = opt.argv[i]; const fn = program.args[i];
const file = fs.readFileSync(fn, "utf8"); const file = fs.readFileSync(fn, "utf8");
const dom = new jsdom.JSDOM(file, { includeNodeLocations: true }); const dom = new jsdom.JSDOM(file, { includeNodeLocations: true });
const body = dom.window.document.body; const body = dom.window.document.body;
@ -116,4 +116,4 @@ for (let str in strings) {
output += "\n"; output += "\n";
} }
fs.writeFileSync(opt.options.output, output); fs.writeFileSync(program.opts().output, output);

View File

@ -1,5 +1,5 @@
# Simplified Chinese translations for noVNC package. # Simplified Chinese translations for noVNC package.
# Copyright (C) 2020 The noVNC Authors # Copyright (C) 2020 The noVNC authors
# This file is distributed under the same license as the noVNC package. # This file is distributed under the same license as the noVNC package.
# Peter Dave Hello <hsu@peterdavehello.org>, 2018. # Peter Dave Hello <hsu@peterdavehello.org>, 2018.
# #
@ -7,278 +7,367 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: noVNC 1.1.0\n" "Project-Id-Version: noVNC 1.1.0\n"
"Report-Msgid-Bugs-To: novnc@googlegroups.com\n" "Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
"POT-Creation-Date: 2018-01-10 00:53+0800\n" "POT-Creation-Date: 2024-06-03 14:10+0200\n"
"PO-Revision-Date: 2020-01-02 13:19+0800\n" "PO-Revision-Date: 2024-11-23 15:29+0800\n"
"Last-Translator: CUI Wei <ghostplant@qq.com>\n" "Last-Translator: wxtewx <wxtewx@qq.com>\n"
"Language-Team: \n"
"Language: zh_CN\n" "Language: zh_CN\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 3.5\n"
#: ../app/ui.js:395 #: ../app/ui.js:69
msgid ""
"Running without HTTPS is not recommended, crashes or other issues are likely."
msgstr "不建议在没有 HTTPS 的情况下运行,可能会出现崩溃或出现其他问题。"
#: ../app/ui.js:410
msgid "Connecting..." msgid "Connecting..."
msgstr "连接中..." msgstr "连接中..."
#: ../app/ui.js:402 #: ../app/ui.js:417
msgid "Disconnecting..." msgid "Disconnecting..."
msgstr "正在断开连接..." msgstr "正在断开连接..."
#: ../app/ui.js:408 #: ../app/ui.js:423
msgid "Reconnecting..." msgid "Reconnecting..."
msgstr "重新连接中..." msgstr "重新连接中..."
#: ../app/ui.js:413 #: ../app/ui.js:428
msgid "Internal error" msgid "Internal error"
msgstr "内部错误" msgstr "内部错误"
#: ../app/ui.js:1015 #: ../app/ui.js:1026
msgid "Must set host" msgid "Must set host"
msgstr "请提供主机名" msgstr "必须设置主机"
#: ../app/ui.js:1097 #: ../app/ui.js:1052
msgid "Failed to connect to server: "
msgstr "无法连接到服务器:"
#: ../app/ui.js:1118
msgid "Connected (encrypted) to " msgid "Connected (encrypted) to "
msgstr "已连接到(加密)" msgstr "已连接(已加密)到"
#: ../app/ui.js:1099
msgid "Connected (unencrypted) to "
msgstr "已连接到(未加密)"
#: ../app/ui.js:1120 #: ../app/ui.js:1120
msgid "Something went wrong, connection is closed" msgid "Connected (unencrypted) to "
msgstr "发生错误,连接已关闭" msgstr "已连接(未加密)到"
#: ../app/ui.js:1123 #: ../app/ui.js:1143
msgid "Something went wrong, connection is closed"
msgstr "出了点问题,连接已关闭"
#: ../app/ui.js:1146
msgid "Failed to connect to server" msgid "Failed to connect to server"
msgstr "无法连接到服务器" msgstr "无法连接到服务器"
#: ../app/ui.js:1133 #: ../app/ui.js:1158
msgid "Disconnected" msgid "Disconnected"
msgstr "已断开连接" msgstr "已断开连接"
#: ../app/ui.js:1146 #: ../app/ui.js:1173
msgid "New connection has been rejected with reason: " msgid "New connection has been rejected with reason: "
msgstr "连接被拒绝,原因:" msgstr "连接被拒绝,原因如下"
#: ../app/ui.js:1149 #: ../app/ui.js:1176
msgid "New connection has been rejected" msgid "New connection has been rejected"
msgstr "连接被拒绝" msgstr "连接被拒绝"
#: ../app/ui.js:1170 #: ../app/ui.js:1242
msgid "Password is required" msgid "Credentials are required"
msgstr "请提供密码" msgstr "需要凭证"
#: ../vnc.html:89 #: ../vnc.html:55
msgid "noVNC encountered an error:" msgid "noVNC encountered an error:"
msgstr "noVNC 遇到一个错误:" msgstr "noVNC 遇到一个错误:"
#: ../vnc.html:99 #: ../vnc.html:65
msgid "Hide/Show the control bar" msgid "Hide/Show the control bar"
msgstr "显示/隐藏控制栏" msgstr "显示/隐藏控制栏"
#: ../vnc.html:106 #: ../vnc.html:74
msgid "Move/Drag Viewport" msgid "Drag"
msgstr "拖放显示范围" msgstr "拖"
#: ../vnc.html:106 #: ../vnc.html:74
msgid "viewport drag" msgid "Move/Drag viewport"
msgstr "显示范围拖放" msgstr "移动/拖动窗口"
#: ../vnc.html:112 ../vnc.html:115 ../vnc.html:118 ../vnc.html:121 #: ../vnc.html:80
msgid "Active Mouse Button"
msgstr "启动鼠标按鍵"
#: ../vnc.html:112
msgid "No mousebutton"
msgstr "禁用鼠标按鍵"
#: ../vnc.html:115
msgid "Left mousebutton"
msgstr "鼠标左鍵"
#: ../vnc.html:118
msgid "Middle mousebutton"
msgstr "鼠标中鍵"
#: ../vnc.html:121
msgid "Right mousebutton"
msgstr "鼠标右鍵"
#: ../vnc.html:124
msgid "Keyboard" msgid "Keyboard"
msgstr "键盘" msgstr "键盘"
#: ../vnc.html:124 #: ../vnc.html:80
msgid "Show Keyboard" msgid "Show keyboard"
msgstr "显示键盘" msgstr "显示键盘"
#: ../vnc.html:131 #: ../vnc.html:85
msgid "Extra keys" msgid "Extra keys"
msgstr "额外按键" msgstr "额外按键"
#: ../vnc.html:131 #: ../vnc.html:85
msgid "Show Extra Keys" msgid "Show extra keys"
msgstr "显示额外按键" msgstr "显示额外按键"
#: ../vnc.html:136 #: ../vnc.html:90
msgid "Ctrl" msgid "Ctrl"
msgstr "Ctrl" msgstr "Ctrl"
#: ../vnc.html:136 #: ../vnc.html:90
msgid "Toggle Ctrl" msgid "Toggle Ctrl"
msgstr "切换 Ctrl" msgstr "切换 Ctrl"
#: ../vnc.html:139 #: ../vnc.html:93
msgid "Alt" msgid "Alt"
msgstr "Alt" msgstr "Alt"
#: ../vnc.html:139 #: ../vnc.html:93
msgid "Toggle Alt" msgid "Toggle Alt"
msgstr "切换 Alt" msgstr "切换 Alt"
#: ../vnc.html:142 #: ../vnc.html:96
msgid "Toggle Windows"
msgstr "切换窗口"
#: ../vnc.html:96
msgid "Windows"
msgstr "窗口"
#: ../vnc.html:99
msgid "Send Tab" msgid "Send Tab"
msgstr "发送 Tab 键" msgstr "发送 Tab 键"
#: ../vnc.html:142 #: ../vnc.html:99
msgid "Tab" msgid "Tab"
msgstr "Tab" msgstr "Tab"
#: ../vnc.html:145 #: ../vnc.html:102
msgid "Esc" msgid "Esc"
msgstr "Esc" msgstr "Esc"
#: ../vnc.html:145 #: ../vnc.html:102
msgid "Send Escape" msgid "Send Escape"
msgstr "发送 Escape 键" msgstr "发送 Escape 键"
#: ../vnc.html:148 #: ../vnc.html:105
msgid "Ctrl+Alt+Del" msgid "Ctrl+Alt+Del"
msgstr "Ctrl-Alt-Del" msgstr "Ctrl+Alt+Del"
#: ../vnc.html:148 #: ../vnc.html:105
msgid "Send Ctrl-Alt-Del" msgid "Send Ctrl-Alt-Del"
msgstr "发送 Ctrl-Alt-Del 键" msgstr "发送 Ctrl+Alt+Del 键"
#: ../vnc.html:156 #: ../vnc.html:112
msgid "Shutdown/Reboot" msgid "Shutdown/Reboot"
msgstr "关机/重" msgstr "关机/重启"
#: ../vnc.html:156 #: ../vnc.html:112
msgid "Shutdown/Reboot..." msgid "Shutdown/Reboot..."
msgstr "关机/重..." msgstr "关机/重启..."
#: ../vnc.html:162 #: ../vnc.html:118
msgid "Power" msgid "Power"
msgstr "电源" msgstr "电源"
#: ../vnc.html:164 #: ../vnc.html:120
msgid "Shutdown" msgid "Shutdown"
msgstr "关机" msgstr "关机"
#: ../vnc.html:165 #: ../vnc.html:121
msgid "Reboot" msgid "Reboot"
msgstr "重" msgstr "重启"
#: ../vnc.html:166 #: ../vnc.html:122
msgid "Reset" msgid "Reset"
msgstr "重置" msgstr "重置"
#: ../vnc.html:171 ../vnc.html:177 #: ../vnc.html:127 ../vnc.html:133
msgid "Clipboard" msgid "Clipboard"
msgstr "剪贴板" msgstr "剪贴板"
#: ../vnc.html:181 #: ../vnc.html:135
msgid "Clear" msgid "Edit clipboard content in the textarea below."
msgstr "清除" msgstr "在下面的文本区域中编辑剪贴板内容。"
#: ../vnc.html:187 #: ../vnc.html:143
msgid "Fullscreen" msgid "Full screen"
msgstr "全屏" msgstr "全屏"
#: ../vnc.html:192 ../vnc.html:199 #: ../vnc.html:148 ../vnc.html:154
msgid "Settings" msgid "Settings"
msgstr "设置" msgstr "设置"
#: ../vnc.html:202 #: ../vnc.html:158
msgid "Shared Mode" msgid "Shared mode"
msgstr "分享模式" msgstr "分享模式"
#: ../vnc.html:205 #: ../vnc.html:161
msgid "View Only" msgid "View only"
msgstr "仅查看" msgstr "仅查看"
#: ../vnc.html:209 #: ../vnc.html:165
msgid "Clip to Window" msgid "Clip to window"
msgstr "限制/裁切窗口大小" msgstr "限制/裁切窗口大小"
#: ../vnc.html:212 #: ../vnc.html:168
msgid "Scaling Mode:" msgid "Scaling mode:"
msgstr "缩放模式:" msgstr "缩放模式:"
#: ../vnc.html:214 #: ../vnc.html:170
msgid "None" msgid "None"
msgstr "无" msgstr "无"
#: ../vnc.html:215 #: ../vnc.html:171
msgid "Local Scaling" msgid "Local scaling"
msgstr "本地缩放" msgstr "本地缩放"
#: ../vnc.html:216 #: ../vnc.html:172
msgid "Remote Resizing" msgid "Remote resizing"
msgstr "远程调整大小" msgstr "远程调整大小"
#: ../vnc.html:221 #: ../vnc.html:177
msgid "Advanced" msgid "Advanced"
msgstr "高级" msgstr "高级"
#: ../vnc.html:224 #: ../vnc.html:180
msgid "Quality:"
msgstr "品质:"
#: ../vnc.html:184
msgid "Compression level:"
msgstr "压缩级别:"
#: ../vnc.html:189
msgid "Repeater ID:" msgid "Repeater ID:"
msgstr "中继站 ID" msgstr "中继站 ID"
#: ../vnc.html:228 #: ../vnc.html:193
msgid "WebSocket" msgid "WebSocket"
msgstr "WebSocket" msgstr "WebSocket"
#: ../vnc.html:231 #: ../vnc.html:196
msgid "Encrypt" msgid "Encrypt"
msgstr "加密" msgstr "加密"
#: ../vnc.html:234 #: ../vnc.html:199
msgid "Host:" msgid "Host:"
msgstr "主机:" msgstr "主机:"
#: ../vnc.html:238 #: ../vnc.html:203
msgid "Port:" msgid "Port:"
msgstr "端口:" msgstr "端口:"
#: ../vnc.html:242 #: ../vnc.html:207
msgid "Path:" msgid "Path:"
msgstr "路径:" msgstr "路径:"
#: ../vnc.html:249 #: ../vnc.html:214
msgid "Automatic Reconnect" msgid "Automatic reconnect"
msgstr "自动重新连接" msgstr "自动重新连接"
#: ../vnc.html:252 #: ../vnc.html:217
msgid "Reconnect Delay (ms):" msgid "Reconnect delay (ms):"
msgstr "重新连接间隔 (ms)" msgstr "重新连接间隔 (ms)"
#: ../vnc.html:258 #: ../vnc.html:222
msgid "Show dot when no cursor"
msgstr "无光标时显示点"
#: ../vnc.html:227
msgid "Logging:" msgid "Logging:"
msgstr "日志级别:" msgstr "日志级别:"
#: ../vnc.html:270 #: ../vnc.html:236
msgid "Disconnect" msgid "Version:"
msgstr "中断连接" msgstr "版本:"
#: ../vnc.html:289 #: ../vnc.html:244
msgid "Disconnect"
msgstr "断开连接"
#: ../vnc.html:267
msgid "Connect" msgid "Connect"
msgstr "连接" msgstr "连接"
#: ../vnc.html:299 #: ../vnc.html:276
msgid "Server identity"
msgstr "服务器身份"
#: ../vnc.html:279
msgid "The server has provided the following identifying information:"
msgstr "服务器提供了以下识别信息:"
#: ../vnc.html:283
msgid "Fingerprint:"
msgstr "指纹:"
#: ../vnc.html:286
msgid ""
"Please verify that the information is correct and press \"Approve\". "
"Otherwise press \"Reject\"."
msgstr "请核实信息是否正确,并按 “同意”,否则按 “拒绝”。"
#: ../vnc.html:291
msgid "Approve"
msgstr "同意"
#: ../vnc.html:292
msgid "Reject"
msgstr "拒绝"
#: ../vnc.html:300
msgid "Credentials"
msgstr "凭证"
#: ../vnc.html:304
msgid "Username:"
msgstr "用户名:"
#: ../vnc.html:308
msgid "Password:" msgid "Password:"
msgstr "密码:" msgstr "密码:"
#: ../vnc.html:313 #: ../vnc.html:312
msgid "Send credentials"
msgstr "发送凭证"
#: ../vnc.html:321
msgid "Cancel" msgid "Cancel"
msgstr "取消" msgstr "取消"
#~ msgid "Password is required"
#~ msgstr "请提供密码"
#~ msgid "Disconnect timeout"
#~ 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 "Clear"
#~ msgstr "清除"
#~ msgid "Local Downscaling"
#~ msgstr "降低本地尺寸"
#~ msgid "Local Cursor"
#~ msgstr "本地光标"
#~ msgid "Canvas not supported."
#~ msgstr "不支持 Canvas。"

View File

@ -1,5 +1,5 @@
# Traditional Chinese translations for noVNC package. # Traditional Chinese translations for noVNC package.
# Copyright (C) 2018 The noVNC Authors # Copyright (C) 2018 The noVNC authors
# This file is distributed under the same license as the noVNC package. # This file is distributed under the same license as the noVNC package.
# Peter Dave Hello <hsu@peterdavehello.org>, 2018. # Peter Dave Hello <hsu@peterdavehello.org>, 2018.
# #
@ -77,7 +77,7 @@ msgid "Hide/Show the control bar"
msgstr "顯示/隱藏控制列" msgstr "顯示/隱藏控制列"
#: ../vnc.html:106 #: ../vnc.html:106
msgid "Move/Drag Viewport" msgid "Move/Drag viewport"
msgstr "拖放顯示範圍" msgstr "拖放顯示範圍"
#: ../vnc.html:106 #: ../vnc.html:106
@ -109,7 +109,7 @@ msgid "Keyboard"
msgstr "鍵盤" msgstr "鍵盤"
#: ../vnc.html:124 #: ../vnc.html:124
msgid "Show Keyboard" msgid "Show keyboard"
msgstr "顯示鍵盤" msgstr "顯示鍵盤"
#: ../vnc.html:131 #: ../vnc.html:131
@ -117,7 +117,7 @@ msgid "Extra keys"
msgstr "額外按鍵" msgstr "額外按鍵"
#: ../vnc.html:131 #: ../vnc.html:131
msgid "Show Extra Keys" msgid "Show extra keys"
msgstr "顯示額外按鍵" msgstr "顯示額外按鍵"
#: ../vnc.html:136 #: ../vnc.html:136
@ -201,19 +201,19 @@ msgid "Settings"
msgstr "設定" msgstr "設定"
#: ../vnc.html:202 #: ../vnc.html:202
msgid "Shared Mode" msgid "Shared mode"
msgstr "分享模式" msgstr "分享模式"
#: ../vnc.html:205 #: ../vnc.html:205
msgid "View Only" msgid "View only"
msgstr "僅檢視" msgstr "僅檢視"
#: ../vnc.html:209 #: ../vnc.html:209
msgid "Clip to Window" msgid "Clip to window"
msgstr "限制/裁切視窗大小" msgstr "限制/裁切視窗大小"
#: ../vnc.html:212 #: ../vnc.html:212
msgid "Scaling Mode:" msgid "Scaling mode:"
msgstr "縮放模式:" msgstr "縮放模式:"
#: ../vnc.html:214 #: ../vnc.html:214
@ -221,11 +221,11 @@ msgid "None"
msgstr "無" msgstr "無"
#: ../vnc.html:215 #: ../vnc.html:215
msgid "Local Scaling" msgid "Local scaling"
msgstr "本機縮放" msgstr "本機縮放"
#: ../vnc.html:216 #: ../vnc.html:216
msgid "Remote Resizing" msgid "Remote resizing"
msgstr "遠端調整大小" msgstr "遠端調整大小"
#: ../vnc.html:221 #: ../vnc.html:221
@ -257,11 +257,11 @@ msgid "Path:"
msgstr "路徑:" msgstr "路徑:"
#: ../vnc.html:249 #: ../vnc.html:249
msgid "Automatic Reconnect" msgid "Automatic reconnect"
msgstr "自動重新連線" msgstr "自動重新連線"
#: ../vnc.html:252 #: ../vnc.html:252
msgid "Reconnect Delay (ms):" msgid "Reconnect delay (ms):"
msgstr "重新連線間隔 (ms)" msgstr "重新連線間隔 (ms)"
#: ../vnc.html:258 #: ../vnc.html:258

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