Merge main into branch

This commit is contained in:
Rui Reis 2025-03-12 13:35:42 +01:00
commit 08198a6781
20 changed files with 2393 additions and 1495 deletions

View File

@ -66,8 +66,9 @@ profits such as:
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, Zlib 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
@ -142,7 +143,7 @@ If you want to use certificate files, due to standard snap confinement restricti
#### 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):

View File

@ -1,13 +1,14 @@
{ {
"HTTPS is required for full functionality": "すべての機能を使用するにはHTTPS接続が必要です", "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: ": "新規接続は次の理由で拒否されました: ",
@ -48,7 +49,7 @@
"Clip to window": "ウィンドウにクリップ", "Clip to window": "ウィンドウにクリップ",
"Scaling mode:": "スケーリングモード:", "Scaling mode:": "スケーリングモード:",
"None": "なし", "None": "なし",
"Local scaling": "ローカルスケーリング", "Local scaling": "ローカルスケーリング",
"Remote resizing": "リモートでリサイズ", "Remote resizing": "リモートでリサイズ",
"Advanced": "高度", "Advanced": "高度",
"Quality:": "品質:", "Quality:": "品質:",

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] {
display: inline-flex;
justify-content: center;
align-items: center;
background-color: white;
background-image: unset;
border: 1px solid dimgrey;
border-radius: 3px;
width: 13px;
height: 13px;
padding: 0;
margin-right: 6px;
vertical-align: bottom;
transition: 0.2s background-color linear;
}
input[type=checkbox]:checked {
background-color: rgb(110, 132, 163);
border-color: rgb(110, 132, 163);
}
input[type=checkbox]:checked::after {
content: "";
display: block; /* width & height doesn't work on inline elements */
width: 3px;
height: 7px;
border: 1px solid white;
border-width: 0 2px 2px 0;
transform: rotate(40deg) translateY(-1px);
}
/*
* Radiobuttons
*/
input[type=radio] {
border-radius: 50%;
border: 1px solid dimgrey;
width: 12px;
height: 12px;
padding: 0;
margin-right: 6px;
transition: 0.2s border linear;
}
input[type=radio]:checked {
border: 6px solid rgb(110, 132, 163);
}
/*
* Range sliders
*/
input[type=range] {
border: unset;
border-radius: 3px;
height: 20px;
padding: 0;
background: transparent;
}
/* -webkit-slider.. & -moz-range.. cant be in selector lists:
https://bugs.chromium.org/p/chromium/issues/detail?id=1154623 */
input[type=range]::-webkit-slider-runnable-track {
background-color: rgb(110, 132, 163);
height: 6px;
border-radius: 3px;
}
input[type=range]::-moz-range-track {
background-color: rgb(110, 132, 163);
height: 6px;
border-radius: 3px;
}
input[type=range]::-webkit-slider-thumb {
appearance: none;
width: 18px;
height: 20px;
border-radius: 5px;
background-color: white;
border: 1px solid dimgray;
margin-top: -7px;
}
input[type=range]::-moz-range-thumb {
appearance: none;
width: 18px;
height: 20px;
border-radius: 5px;
background-color: white;
border: 1px solid dimgray;
margin-top: -7px;
}
/*
* File choosers
*/
input[type=file] {
background-image: none;
border: none;
}
input::file-selector-button {
margin-right: 6px;
}
/*
* Hover
*/
input[type=button]:hover,
input[type=color]:hover,
input[type=image]:hover,
input[type=reset]:hover,
input[type=submit]:hover,
input::file-selector-button:hover,
button:hover {
background-image: linear-gradient(to top, rgb(255, 255, 255), rgb(250, 250, 250));
}
select:hover {
background-image: var(--select-arrow),
linear-gradient(to top, rgb(255, 255, 255), rgb(250, 250, 250));
background-position: calc(100% - 7px), left top;
background-repeat: no-repeat;
}
@media (any-pointer: coarse) {
/* We don't want a hover style after touch input */
input[type=button]:hover,
input[type=color]:hover,
input[type=image]:hover,
input[type=reset]:hover,
input[type=submit]:hover,
input::file-selector-button:hover,
button:hover {
background-image: var(--bg-gradient);
}
select:hover {
background-image: var(--select-arrow), var(--bg-gradient);
}
}
/*
* Active (clicked)
*/
input[type=button]:active,
input[type=color]:active,
input[type=image]:active,
input[type=reset]:active,
input[type=submit]:active,
input::file-selector-button:active,
button:active,
select:active {
border-bottom-width: 1px;
margin-top: 1px;
}
/*
* Focus (tab)
*/
input:focus-visible,
input:focus-visible::file-selector-button,
button:focus-visible,
select:focus-visible,
textarea:focus-visible {
outline: 2px solid rgb(74, 144, 217);
outline-offset: 1px;
}
input[type=file]:focus-visible {
outline: none; /* We outline the button instead of the entire element */
}
/*
* Disabled
*/
input:disabled,
input:disabled::file-selector-button,
button:disabled,
select:disabled,
textarea:disabled {
opacity: 0.4;
} }
input[type=button]:disabled, input[type=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);
} }

View File

@ -31,6 +31,7 @@ export const encodings = {
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,

View File

@ -151,9 +151,13 @@ export default class RFB extends EventTargetMixin {
this._supportsSetDesktopSize = false; this._supportsSetDesktopSize = false;
this._screenID = 0; this._screenID = 0;
this._screenFlags = 0; this._screenFlags = 0;
this._pendingRemoteResize = false;
this._lastResize = 0;
this._qemuExtKeyEventSupported = false; this._qemuExtKeyEventSupported = false;
this._extendedPointerEventSupported = false;
this._clipboardText = null; this._clipboardText = null;
this._clipboardServerCapabilitiesActions = {}; this._clipboardServerCapabilitiesActions = {};
this._clipboardServerCapabilitiesFormats = {}; this._clipboardServerCapabilitiesFormats = {};
@ -743,6 +747,7 @@ export default class RFB extends EventTargetMixin {
currentHeight == this._expectedClientHeight; currentHeight == this._expectedClientHeight;
} }
// Handle browser window resizes
_handleResize() { _handleResize() {
// Don't change anything if the client size is already as expected // Don't change anything if the client size is already as expected
if (this._clientHasExpectedSize()) { if (this._clientHasExpectedSize()) {
@ -753,17 +758,12 @@ export default class RFB extends EventTargetMixin {
window.requestAnimationFrame(() => { window.requestAnimationFrame(() => {
this._updateClip(); this._updateClip();
this._updateScale(); this._updateScale();
this._saveExpectedClientSize();
}); });
if (this._resizeSession) { // Request changing the resolution of the remote display to
// Request changing the resolution of the remote display to // the size of the local browser viewport.
// the size of the local browser viewport. this._requestRemoteResize();
// In order to not send multiple requests before the browser-resize
// is finished we wait 0.5 seconds before sending the request.
clearTimeout(this._resizeTimeout);
this._resizeTimeout = setTimeout(this._requestRemoteResize.bind(this), 500);
}
} }
// Update state of clipping in Display object, and make sure the // Update state of clipping in Display object, and make sure the
@ -813,16 +813,39 @@ export default class RFB extends EventTargetMixin {
// Requests a change of remote desktop size. This message is an extension // Requests a change of remote desktop size. This message is an extension
// and may only be sent if we have received an ExtendedDesktopSize message // and may only be sent if we have received an ExtendedDesktopSize message
_requestRemoteResize() { _requestRemoteResize() {
clearTimeout(this._resizeTimeout); if (!this._resizeSession) {
this._resizeTimeout = null; return;
}
if (!this._resizeSession || this._viewOnly || if (this._viewOnly) {
!this._supportsSetDesktopSize) { return;
}
if (!this._supportsSetDesktopSize) {
return; return;
} }
// Rate limit to one pending resize at a time
if (this._pendingRemoteResize) {
return;
}
// And no more than once every 100ms
if ((Date.now() - this._lastResize) < 100) {
clearTimeout(this._resizeTimeout);
this._resizeTimeout = setTimeout(this._requestRemoteResize.bind(this),
100 - (Date.now() - this._lastResize));
return;
}
this._resizeTimeout = null;
const size = this._screenSize(); const size = this._screenSize();
// Do we actually change anything?
if (size.w === this._fbWidth && size.h === this._fbHeight) {
return;
}
this._pendingRemoteResize = true;
this._lastResize = Date.now();
RFB.messages.setDesktopSize(this._sock, RFB.messages.setDesktopSize(this._sock,
Math.floor(size.w), Math.floor(size.h), Math.floor(size.w), Math.floor(size.h),
this._screenID, this._screenFlags); this._screenID, this._screenFlags);
@ -1054,6 +1077,36 @@ export default class RFB extends EventTargetMixin {
this.sendKey(keysym, code, down); this.sendKey(keysym, code, down);
} }
static _convertButtonMask(buttons) {
/* The bits in MouseEvent.buttons property correspond
* to the following mouse buttons:
* 0: Left
* 1: Right
* 2: Middle
* 3: Back
* 4: Forward
*
* These bits needs to be converted to what they are defined as
* in the RFB protocol.
*/
const buttonMaskMap = {
0: 1 << 0, // Left
1: 1 << 2, // Right
2: 1 << 1, // Middle
3: 1 << 7, // Back
4: 1 << 8, // Forward
};
let bmask = 0;
for (let i = 0; i < 5; i++) {
if (buttons & (1 << i)) {
bmask |= buttonMaskMap[i];
}
}
return bmask;
}
_handleMouse(ev) { _handleMouse(ev) {
/* /*
* We don't check connection status or viewOnly here as the * We don't check connection status or viewOnly here as the
@ -1083,80 +1136,75 @@ export default class RFB extends EventTargetMixin {
let pos = clientToElement(ev.clientX, ev.clientY, let pos = clientToElement(ev.clientX, ev.clientY,
this._canvas); this._canvas);
let bmask = RFB._convertButtonMask(ev.buttons);
let down = ev.type == 'mousedown';
switch (ev.type) { switch (ev.type) {
case 'mousedown': case 'mousedown':
setCapture(this._canvas);
this._handleMouseButton(pos.x, pos.y,
true, 1 << ev.button);
break;
case 'mouseup': case 'mouseup':
this._handleMouseButton(pos.x, pos.y, if (this.dragViewport) {
false, 1 << ev.button); if (down && !this._viewportDragging) {
this._viewportDragging = true;
this._viewportDragPos = {'x': pos.x, 'y': pos.y};
this._viewportHasMoved = false;
this._flushMouseMoveTimer(pos.x, pos.y);
// Skip sending mouse events, instead save the current
// mouse mask so we can send it later.
this._mouseButtonMask = bmask;
break;
} else {
this._viewportDragging = false;
// If we actually performed a drag then we are done
// here and should not send any mouse events
if (this._viewportHasMoved) {
this._mouseButtonMask = bmask;
break;
}
// Otherwise we treat this as a mouse click event.
// Send the previously saved button mask, followed
// by the current button mask at the end of this
// function.
this._sendMouse(pos.x, pos.y, this._mouseButtonMask);
}
}
if (down) {
setCapture(this._canvas);
}
this._handleMouseButton(pos.x, pos.y, bmask);
break; break;
case 'mousemove': case 'mousemove':
if (this._viewportDragging) {
const deltaX = this._viewportDragPos.x - pos.x;
const deltaY = this._viewportDragPos.y - pos.y;
if (this._viewportHasMoved || (Math.abs(deltaX) > dragThreshold ||
Math.abs(deltaY) > dragThreshold)) {
this._viewportHasMoved = true;
this._viewportDragPos = {'x': pos.x, 'y': pos.y};
this._display.viewportChangePos(deltaX, deltaY);
}
// Skip sending mouse events
break;
}
this._handleMouseMove(pos.x, pos.y); this._handleMouseMove(pos.x, pos.y);
break; break;
} }
} }
_handleMouseButton(x, y, down, bmask) { _handleMouseButton(x, y, bmask) {
if (this.dragViewport) {
if (down && !this._viewportDragging) {
this._viewportDragging = true;
this._viewportDragPos = {'x': x, 'y': y};
this._viewportHasMoved = false;
// Skip sending mouse events
return;
} else {
this._viewportDragging = false;
// If we actually performed a drag then we are done
// here and should not send any mouse events
if (this._viewportHasMoved) {
return;
}
// Otherwise we treat this as a mouse click event.
// Send the button down event here, as the button up
// event is sent at the end of this function.
this._sendMouse(x, y, bmask);
}
}
// Flush waiting move event first // Flush waiting move event first
if (this._mouseMoveTimer !== null) { this._flushMouseMoveTimer(x, y);
clearTimeout(this._mouseMoveTimer);
this._mouseMoveTimer = null;
this._sendMouse(x, y, this._mouseButtonMask);
}
if (down) {
this._mouseButtonMask |= bmask;
} else {
this._mouseButtonMask &= ~bmask;
}
this._mouseButtonMask = bmask;
this._sendMouse(x, y, this._mouseButtonMask); this._sendMouse(x, y, this._mouseButtonMask);
} }
_handleMouseMove(x, y) { _handleMouseMove(x, y) {
if (this._viewportDragging) {
const deltaX = this._viewportDragPos.x - x;
const deltaY = this._viewportDragPos.y - y;
if (this._viewportHasMoved || (Math.abs(deltaX) > dragThreshold ||
Math.abs(deltaY) > dragThreshold)) {
this._viewportHasMoved = true;
this._viewportDragPos = {'x': x, 'y': y};
this._display.viewportChangePos(deltaX, deltaY);
}
// Skip sending mouse events
return;
}
this._mousePos = { 'x': x, 'y': y }; this._mousePos = { 'x': x, 'y': y };
// Limit many mouse move events to one every MOUSE_MOVE_DELAY ms // Limit many mouse move events to one every MOUSE_MOVE_DELAY ms
@ -1186,8 +1234,20 @@ export default class RFB extends EventTargetMixin {
if (this._rfbConnectionState !== 'connected') { return; } if (this._rfbConnectionState !== 'connected') { return; }
if (this._viewOnly) { return; } // View only, skip mouse events if (this._viewOnly) { return; } // View only, skip mouse events
RFB.messages.pointerEvent(this._sock, this._display.absX(x), // Highest bit in mask is never sent to the server
this._display.absY(y), mask); if (mask & 0x8000) {
throw new Error("Illegal mouse button mask (mask: " + mask + ")");
}
let extendedMouseButtons = mask & 0x7f80;
if (this._extendedPointerEventSupported && extendedMouseButtons) {
RFB.messages.extendedPointerEvent(this._sock, this._display.absX(x),
this._display.absY(y), mask);
} else {
RFB.messages.pointerEvent(this._sock, this._display.absX(x),
this._display.absY(y), mask);
}
} }
_handleWheel(ev) { _handleWheel(ev) {
@ -1200,6 +1260,7 @@ export default class RFB extends EventTargetMixin {
let pos = clientToElement(ev.clientX, ev.clientY, let pos = clientToElement(ev.clientX, ev.clientY,
this._canvas); this._canvas);
let bmask = RFB._convertButtonMask(ev.buttons);
let dX = ev.deltaX; let dX = ev.deltaX;
let dY = ev.deltaY; let dY = ev.deltaY;
@ -1219,26 +1280,27 @@ export default class RFB extends EventTargetMixin {
this._accumulatedWheelDeltaX += dX; this._accumulatedWheelDeltaX += dX;
this._accumulatedWheelDeltaY += dY; this._accumulatedWheelDeltaY += dY;
// Generate a mouse wheel step event when the accumulated delta // Generate a mouse wheel step event when the accumulated delta
// for one of the axes is large enough. // for one of the axes is large enough.
if (Math.abs(this._accumulatedWheelDeltaX) >= WHEEL_STEP) { if (Math.abs(this._accumulatedWheelDeltaX) >= WHEEL_STEP) {
if (this._accumulatedWheelDeltaX < 0) { if (this._accumulatedWheelDeltaX < 0) {
this._handleMouseButton(pos.x, pos.y, true, 1 << 5); this._handleMouseButton(pos.x, pos.y, bmask | 1 << 5);
this._handleMouseButton(pos.x, pos.y, false, 1 << 5); this._handleMouseButton(pos.x, pos.y, bmask);
} else if (this._accumulatedWheelDeltaX > 0) { } else if (this._accumulatedWheelDeltaX > 0) {
this._handleMouseButton(pos.x, pos.y, true, 1 << 6); this._handleMouseButton(pos.x, pos.y, bmask | 1 << 6);
this._handleMouseButton(pos.x, pos.y, false, 1 << 6); this._handleMouseButton(pos.x, pos.y, bmask);
} }
this._accumulatedWheelDeltaX = 0; this._accumulatedWheelDeltaX = 0;
} }
if (Math.abs(this._accumulatedWheelDeltaY) >= WHEEL_STEP) { if (Math.abs(this._accumulatedWheelDeltaY) >= WHEEL_STEP) {
if (this._accumulatedWheelDeltaY < 0) { if (this._accumulatedWheelDeltaY < 0) {
this._handleMouseButton(pos.x, pos.y, true, 1 << 3); this._handleMouseButton(pos.x, pos.y, bmask | 1 << 3);
this._handleMouseButton(pos.x, pos.y, false, 1 << 3); this._handleMouseButton(pos.x, pos.y, bmask);
} else if (this._accumulatedWheelDeltaY > 0) { } else if (this._accumulatedWheelDeltaY > 0) {
this._handleMouseButton(pos.x, pos.y, true, 1 << 4); this._handleMouseButton(pos.x, pos.y, bmask | 1 << 4);
this._handleMouseButton(pos.x, pos.y, false, 1 << 4); this._handleMouseButton(pos.x, pos.y, bmask);
} }
this._accumulatedWheelDeltaY = 0; this._accumulatedWheelDeltaY = 0;
@ -1277,8 +1339,8 @@ export default class RFB extends EventTargetMixin {
this._gestureLastTapTime = Date.now(); this._gestureLastTapTime = Date.now();
this._fakeMouseMove(this._gestureFirstDoubleTapEv, pos.x, pos.y); this._fakeMouseMove(this._gestureFirstDoubleTapEv, pos.x, pos.y);
this._handleMouseButton(pos.x, pos.y, true, bmask); this._handleMouseButton(pos.x, pos.y, bmask);
this._handleMouseButton(pos.x, pos.y, false, bmask); this._handleMouseButton(pos.x, pos.y, 0x0);
} }
_handleGesture(ev) { _handleGesture(ev) {
@ -1299,14 +1361,27 @@ export default class RFB extends EventTargetMixin {
this._handleTapEvent(ev, 0x2); this._handleTapEvent(ev, 0x2);
break; break;
case 'drag': case 'drag':
this._fakeMouseMove(ev, pos.x, pos.y); if (this.dragViewport) {
this._handleMouseButton(pos.x, pos.y, true, 0x1); this._viewportHasMoved = false;
this._viewportDragging = true;
this._viewportDragPos = {'x': pos.x, 'y': pos.y};
} else {
this._fakeMouseMove(ev, pos.x, pos.y);
this._handleMouseButton(pos.x, pos.y, 0x1);
}
break; break;
case 'longpress': case 'longpress':
this._fakeMouseMove(ev, pos.x, pos.y); if (this.dragViewport) {
this._handleMouseButton(pos.x, pos.y, true, 0x4); // If dragViewport is true, we need to wait to see
// if we have dragged outside the threshold before
// sending any events to the server.
this._viewportHasMoved = false;
this._viewportDragPos = {'x': pos.x, 'y': pos.y};
} else {
this._fakeMouseMove(ev, pos.x, pos.y);
this._handleMouseButton(pos.x, pos.y, 0x4);
}
break; break;
case 'twodrag': case 'twodrag':
this._gestureLastMagnitudeX = ev.detail.magnitudeX; this._gestureLastMagnitudeX = ev.detail.magnitudeX;
this._gestureLastMagnitudeY = ev.detail.magnitudeY; this._gestureLastMagnitudeY = ev.detail.magnitudeY;
@ -1328,7 +1403,21 @@ export default class RFB extends EventTargetMixin {
break; break;
case 'drag': case 'drag':
case 'longpress': case 'longpress':
this._fakeMouseMove(ev, pos.x, pos.y); if (this.dragViewport) {
this._viewportDragging = true;
const deltaX = this._viewportDragPos.x - pos.x;
const deltaY = this._viewportDragPos.y - pos.y;
if (this._viewportHasMoved || (Math.abs(deltaX) > dragThreshold ||
Math.abs(deltaY) > dragThreshold)) {
this._viewportHasMoved = true;
this._viewportDragPos = {'x': pos.x, 'y': pos.y};
this._display.viewportChangePos(deltaX, deltaY);
}
} else {
this._fakeMouseMove(ev, pos.x, pos.y);
}
break; break;
case 'twodrag': case 'twodrag':
// Always scroll in the same position. // Always scroll in the same position.
@ -1336,23 +1425,23 @@ export default class RFB extends EventTargetMixin {
// every update. // every update.
this._fakeMouseMove(ev, pos.x, pos.y); this._fakeMouseMove(ev, pos.x, pos.y);
while ((ev.detail.magnitudeY - this._gestureLastMagnitudeY) > GESTURE_SCRLSENS) { while ((ev.detail.magnitudeY - this._gestureLastMagnitudeY) > GESTURE_SCRLSENS) {
this._handleMouseButton(pos.x, pos.y, true, 0x8); this._handleMouseButton(pos.x, pos.y, 0x8);
this._handleMouseButton(pos.x, pos.y, false, 0x8); this._handleMouseButton(pos.x, pos.y, 0x0);
this._gestureLastMagnitudeY += GESTURE_SCRLSENS; this._gestureLastMagnitudeY += GESTURE_SCRLSENS;
} }
while ((ev.detail.magnitudeY - this._gestureLastMagnitudeY) < -GESTURE_SCRLSENS) { while ((ev.detail.magnitudeY - this._gestureLastMagnitudeY) < -GESTURE_SCRLSENS) {
this._handleMouseButton(pos.x, pos.y, true, 0x10); this._handleMouseButton(pos.x, pos.y, 0x10);
this._handleMouseButton(pos.x, pos.y, false, 0x10); this._handleMouseButton(pos.x, pos.y, 0x0);
this._gestureLastMagnitudeY -= GESTURE_SCRLSENS; this._gestureLastMagnitudeY -= GESTURE_SCRLSENS;
} }
while ((ev.detail.magnitudeX - this._gestureLastMagnitudeX) > GESTURE_SCRLSENS) { while ((ev.detail.magnitudeX - this._gestureLastMagnitudeX) > GESTURE_SCRLSENS) {
this._handleMouseButton(pos.x, pos.y, true, 0x20); this._handleMouseButton(pos.x, pos.y, 0x20);
this._handleMouseButton(pos.x, pos.y, false, 0x20); this._handleMouseButton(pos.x, pos.y, 0x0);
this._gestureLastMagnitudeX += GESTURE_SCRLSENS; this._gestureLastMagnitudeX += GESTURE_SCRLSENS;
} }
while ((ev.detail.magnitudeX - this._gestureLastMagnitudeX) < -GESTURE_SCRLSENS) { while ((ev.detail.magnitudeX - this._gestureLastMagnitudeX) < -GESTURE_SCRLSENS) {
this._handleMouseButton(pos.x, pos.y, true, 0x40); this._handleMouseButton(pos.x, pos.y, 0x40);
this._handleMouseButton(pos.x, pos.y, false, 0x40); this._handleMouseButton(pos.x, pos.y, 0x0);
this._gestureLastMagnitudeX -= GESTURE_SCRLSENS; this._gestureLastMagnitudeX -= GESTURE_SCRLSENS;
} }
break; break;
@ -1365,13 +1454,13 @@ export default class RFB extends EventTargetMixin {
if (Math.abs(magnitude - this._gestureLastMagnitudeX) > GESTURE_ZOOMSENS) { if (Math.abs(magnitude - this._gestureLastMagnitudeX) > GESTURE_ZOOMSENS) {
this._handleKeyEvent(KeyTable.XK_Control_L, "ControlLeft", true); this._handleKeyEvent(KeyTable.XK_Control_L, "ControlLeft", true);
while ((magnitude - this._gestureLastMagnitudeX) > GESTURE_ZOOMSENS) { while ((magnitude - this._gestureLastMagnitudeX) > GESTURE_ZOOMSENS) {
this._handleMouseButton(pos.x, pos.y, true, 0x8); this._handleMouseButton(pos.x, pos.y, 0x8);
this._handleMouseButton(pos.x, pos.y, false, 0x8); this._handleMouseButton(pos.x, pos.y, 0x0);
this._gestureLastMagnitudeX += GESTURE_ZOOMSENS; this._gestureLastMagnitudeX += GESTURE_ZOOMSENS;
} }
while ((magnitude - this._gestureLastMagnitudeX) < -GESTURE_ZOOMSENS) { while ((magnitude - this._gestureLastMagnitudeX) < -GESTURE_ZOOMSENS) {
this._handleMouseButton(pos.x, pos.y, true, 0x10); this._handleMouseButton(pos.x, pos.y, 0x10);
this._handleMouseButton(pos.x, pos.y, false, 0x10); this._handleMouseButton(pos.x, pos.y, 0x0);
this._gestureLastMagnitudeX -= GESTURE_ZOOMSENS; this._gestureLastMagnitudeX -= GESTURE_ZOOMSENS;
} }
} }
@ -1389,12 +1478,32 @@ export default class RFB extends EventTargetMixin {
case 'twodrag': case 'twodrag':
break; break;
case 'drag': case 'drag':
this._fakeMouseMove(ev, pos.x, pos.y); if (this.dragViewport) {
this._handleMouseButton(pos.x, pos.y, false, 0x1); this._viewportDragging = false;
} else {
this._fakeMouseMove(ev, pos.x, pos.y);
this._handleMouseButton(pos.x, pos.y, 0x0);
}
break; break;
case 'longpress': case 'longpress':
this._fakeMouseMove(ev, pos.x, pos.y); if (this._viewportHasMoved) {
this._handleMouseButton(pos.x, pos.y, false, 0x4); // We don't want to send any events if we have moved
// our viewport
break;
}
if (this.dragViewport && !this._viewportHasMoved) {
this._fakeMouseMove(ev, pos.x, pos.y);
// If dragViewport is true, we need to wait to see
// if we have dragged outside the threshold before
// sending any events to the server.
this._handleMouseButton(pos.x, pos.y, 0x4);
this._handleMouseButton(pos.x, pos.y, 0x0);
this._viewportDragging = false;
} else {
this._fakeMouseMove(ev, pos.x, pos.y);
this._handleMouseButton(pos.x, pos.y, 0x0);
}
break; break;
} }
break; break;
@ -1479,6 +1588,14 @@ export default class RFB extends EventTargetMixin {
this._sock.flush(); this._sock.flush();
} }
_flushMouseMoveTimer(x, y) {
if (this._mouseMoveTimer !== null) {
clearTimeout(this._mouseMoveTimer);
this._mouseMoveTimer = null;
this._sendMouse(x, y, this._mouseButtonMask);
}
}
// Message handlers // Message handlers
_negotiateProtocolVersion() { _negotiateProtocolVersion() {
@ -2247,6 +2364,7 @@ export default class RFB extends EventTargetMixin {
encs.push(encodings.pseudoEncodingContinuousUpdates); encs.push(encodings.pseudoEncodingContinuousUpdates);
encs.push(encodings.pseudoEncodingDesktopName); encs.push(encodings.pseudoEncodingDesktopName);
encs.push(encodings.pseudoEncodingExtendedClipboard); encs.push(encodings.pseudoEncodingExtendedClipboard);
encs.push(encodings.pseudoEncodingExtendedMouseButtons);
if (this._gesturesMode === 'ultravnc') { if (this._gesturesMode === 'ultravnc') {
encs.push(encodings.pseudoEncodingGii); encs.push(encodings.pseudoEncodingGii);
@ -2707,6 +2825,10 @@ export default class RFB extends EventTargetMixin {
case encodings.pseudoEncodingExtendedDesktopSize: case encodings.pseudoEncodingExtendedDesktopSize:
return this._handleExtendedDesktopSize(); return this._handleExtendedDesktopSize();
case encodings.pseudoEncodingExtendedMouseButtons:
this._extendedPointerEventSupported = true;
return true;
case encodings.pseudoEncodingQEMULedEvent: case encodings.pseudoEncodingQEMULedEvent:
return this._handleLedEvent(); return this._handleLedEvent();
@ -2942,6 +3064,10 @@ export default class RFB extends EventTargetMixin {
* 2 - another client requested the resize * 2 - another client requested the resize
*/ */
if (this._FBU.x === 1) {
this._pendingRemoteResize = false;
}
// We need to handle errors when we requested the resize. // We need to handle errors when we requested the resize.
if (this._FBU.x === 1 && this._FBU.y !== 0) { if (this._FBU.x === 1 && this._FBU.y !== 0) {
let msg = ""; let msg = "";
@ -2974,6 +3100,12 @@ export default class RFB extends EventTargetMixin {
this._requestRemoteResize(); this._requestRemoteResize();
} }
if (this._FBU.x === 1 && this._FBU.y === 0) {
// We might have resized again whilst waiting for the
// previous request, so check if we are in sync
this._requestRemoteResize();
}
return true; return true;
} }
@ -3003,6 +3135,7 @@ export default class RFB extends EventTargetMixin {
this._fbWidth, this._fbHeight); this._fbWidth, this._fbHeight);
} }
// Handle resize-messages from the server
_resize(width, height) { _resize(width, height) {
this._fbWidth = width; this._fbWidth = width;
this._fbHeight = height; this._fbHeight = height;
@ -3125,6 +3258,10 @@ RFB.messages = {
pointerEvent(sock, x, y, mask) { pointerEvent(sock, x, y, mask) {
sock.sQpush8(5); // msg-type sock.sQpush8(5); // msg-type
// Marker bit must be set to 0, otherwise the server might
// confuse the marker bit with the highest bit in a normal
// PointerEvent message.
mask = mask & 0x7f;
sock.sQpush8(mask); sock.sQpush8(mask);
sock.sQpush16(x); sock.sQpush16(x);
@ -3133,6 +3270,27 @@ RFB.messages = {
sock.flush(); sock.flush();
}, },
extendedPointerEvent(sock, x, y, mask) {
sock.sQpush8(5); // msg-type
let higherBits = (mask >> 7) & 0xff;
// Bits 2-7 are reserved
if (higherBits & 0xfc) {
throw new Error("Invalid mouse button mask: " + mask);
}
let lowerBits = mask & 0x7f;
lowerBits |= 0x80; // Set marker bit to 1
sock.sQpush8(lowerBits);
sock.sQpush16(x);
sock.sQpush16(y);
sock.sQpush8(higherBits);
sock.flush();
},
// Used to build Notify and Request data. // Used to build Notify and Request data.
_buildExtendedClipboardFlags(actions, formats) { _buildExtendedClipboardFlags(actions, formats) {
let data = new Uint8Array(4); let data = new Uint8Array(4);

View File

@ -13,7 +13,7 @@ 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) ||

View File

@ -1,6 +1,6 @@
{ {
"name": "@novnc/novnc", "name": "@novnc/novnc",
"version": "1.5.0", "version": "1.6.0-beta",
"description": "An HTML5 VNC client", "description": "An HTML5 VNC client",
"browser": "lib/rfb", "browser": "lib/rfb",
"directories": { "directories": {
@ -56,8 +56,7 @@
"karma-safari-launcher": "latest", "karma-safari-launcher": "latest",
"karma-script-launcher": "latest", "karma-script-launcher": "latest",
"mocha": "latest", "mocha": "latest",
"node-getopt": "latest", "pofile": "latest",
"po2json": "latest",
"sinon": "latest", "sinon": "latest",
"sinon-chai": "latest" "sinon-chai": "latest"
}, },

199
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: 2022-12-27 15:24+0100\n" "POT-Creation-Date: 2024-06-03 14:10+0200\n"
"PO-Revision-Date: 2023-03-21 12:42+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"
@ -20,8 +20,11 @@ msgstr ""
"X-Generator: Poedit 2.3\n" "X-Generator: Poedit 2.3\n"
#: ../app/ui.js:69 #: ../app/ui.js:69
msgid "HTTPS is required for full functionality" msgid ""
msgstr "すべての機能を使用するにはHTTPS接続が必要です" "Running without HTTPS is not recommended, crashes or other issues are likely."
msgstr ""
"HTTPS接続なしで実行することは推奨されません。クラッシュしたりその他の問題が発"
"生したりする可能性があります。"
#: ../app/ui.js:410 #: ../app/ui.js:410
msgid "Connecting..." msgid "Connecting..."
@ -43,321 +46,297 @@ msgstr "内部エラー"
msgid "Must set host" msgid "Must set host"
msgstr "ホストを設定する必要があります" msgstr "ホストを設定する必要があります"
#: ../app/ui.js:1110 #: ../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:1112 #: ../app/ui.js:1120
msgid "Connected (unencrypted) to " msgid "Connected (unencrypted) to "
msgstr "接続しました (暗号化されていません): " msgstr "接続しました (暗号化されていません): "
#: ../app/ui.js:1135 #: ../app/ui.js:1143
msgid "Something went wrong, connection is closed" msgid "Something went wrong, connection is closed"
msgstr "何らかの問題で、接続が閉じられました" msgstr "問題が発生したため、接続が閉じられました"
#: ../app/ui.js:1138 #: ../app/ui.js:1146
msgid "Failed to connect to server" msgid "Failed to connect to server"
msgstr "サーバーへの接続に失敗しました" msgstr "サーバーへの接続に失敗しました"
#: ../app/ui.js:1150 #: ../app/ui.js:1158
msgid "Disconnected" msgid "Disconnected"
msgstr "切断しました" msgstr "切断しました"
#: ../app/ui.js:1165 #: ../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:1168 #: ../app/ui.js:1176
msgid "New connection has been rejected" msgid "New connection has been rejected"
msgstr "新規接続は拒否されました" msgstr "新規接続は拒否されました"
#: ../app/ui.js:1234 #: ../app/ui.js:1242
msgid "Credentials are required" msgid "Credentials are required"
msgstr "資格情報が必要です" msgstr "資格情報が必要です"
#: ../vnc.html:57 #: ../vnc.html:55
msgid "noVNC encountered an error:" msgid "noVNC encountered an error:"
msgstr "noVNC でエラーが発生しました:" msgstr "noVNC でエラーが発生しました:"
#: ../vnc.html:67 #: ../vnc.html:65
msgid "Hide/Show the control bar" msgid "Hide/Show the control bar"
msgstr "コントロールバーを隠す/表示する" msgstr "コントロールバーを隠す/表示する"
#: ../vnc.html:76 #: ../vnc.html:74
msgid "Drag" msgid "Drag"
msgstr "ドラッグ" msgstr "ドラッグ"
#: ../vnc.html:76 #: ../vnc.html:74
msgid "Move/Drag viewport" msgid "Move/Drag viewport"
msgstr "ビューポートを移動/ドラッグ" msgstr "ビューポートを移動/ドラッグ"
#: ../vnc.html:82 #: ../vnc.html:80
msgid "Keyboard" msgid "Keyboard"
msgstr "キーボード" msgstr "キーボード"
#: ../vnc.html:82 #: ../vnc.html:80
msgid "Show keyboard" msgid "Show keyboard"
msgstr "キーボードを表示" msgstr "キーボードを表示"
#: ../vnc.html:87 #: ../vnc.html:85
msgid "Extra keys" msgid "Extra keys"
msgstr "追加キー" msgstr "追加キー"
#: ../vnc.html:87 #: ../vnc.html:85
msgid "Show extra keys" msgid "Show extra keys"
msgstr "追加キーを表示" msgstr "追加キーを表示"
#: ../vnc.html:92 #: ../vnc.html:90
msgid "Ctrl" msgid "Ctrl"
msgstr "Ctrl" msgstr "Ctrl"
#: ../vnc.html:92 #: ../vnc.html:90
msgid "Toggle Ctrl" msgid "Toggle Ctrl"
msgstr "Ctrl キーをトグル" msgstr "Ctrl キーをトグル"
#: ../vnc.html:95 #: ../vnc.html:93
msgid "Alt" msgid "Alt"
msgstr "Alt" msgstr "Alt"
#: ../vnc.html:95 #: ../vnc.html:93
msgid "Toggle Alt" msgid "Toggle Alt"
msgstr "Alt キーをトグル" msgstr "Alt キーをトグル"
#: ../vnc.html:98 #: ../vnc.html:96
msgid "Toggle Windows" msgid "Toggle Windows"
msgstr "Windows キーをトグル" msgstr "Windows キーをトグル"
#: ../vnc.html:98 #: ../vnc.html:96
msgid "Windows" msgid "Windows"
msgstr "Windows" msgstr "Windows"
#: ../vnc.html:101 #: ../vnc.html:99
msgid "Send Tab" msgid "Send Tab"
msgstr "Tab キーを送信" msgstr "Tab キーを送信"
#: ../vnc.html:101 #: ../vnc.html:99
msgid "Tab" msgid "Tab"
msgstr "Tab" msgstr "Tab"
#: ../vnc.html:104 #: ../vnc.html:102
msgid "Esc" msgid "Esc"
msgstr "Esc" msgstr "Esc"
#: ../vnc.html:104 #: ../vnc.html:102
msgid "Send Escape" msgid "Send Escape"
msgstr "Escape キーを送信" msgstr "Escape キーを送信"
#: ../vnc.html:107 #: ../vnc.html:105
msgid "Ctrl+Alt+Del" msgid "Ctrl+Alt+Del"
msgstr "Ctrl+Alt+Del" msgstr "Ctrl+Alt+Del"
#: ../vnc.html:107 #: ../vnc.html:105
msgid "Send Ctrl-Alt-Del" msgid "Send Ctrl-Alt-Del"
msgstr "Ctrl-Alt-Del を送信" msgstr "Ctrl-Alt-Del を送信"
#: ../vnc.html:114 #: ../vnc.html:112
msgid "Shutdown/Reboot" msgid "Shutdown/Reboot"
msgstr "シャットダウン/再起動" msgstr "シャットダウン/再起動"
#: ../vnc.html:114 #: ../vnc.html:112
msgid "Shutdown/Reboot..." msgid "Shutdown/Reboot..."
msgstr "シャットダウン/再起動..." msgstr "シャットダウン/再起動..."
#: ../vnc.html:120 #: ../vnc.html:118
msgid "Power" msgid "Power"
msgstr "電源" msgstr "電源"
#: ../vnc.html:122 #: ../vnc.html:120
msgid "Shutdown" msgid "Shutdown"
msgstr "シャットダウン" msgstr "シャットダウン"
#: ../vnc.html:123 #: ../vnc.html:121
msgid "Reboot" msgid "Reboot"
msgstr "再起動" msgstr "再起動"
#: ../vnc.html:124 #: ../vnc.html:122
msgid "Reset" msgid "Reset"
msgstr "リセット" msgstr "リセット"
#: ../vnc.html:129 ../vnc.html:135 #: ../vnc.html:127 ../vnc.html:133
msgid "Clipboard" msgid "Clipboard"
msgstr "クリップボード" msgstr "クリップボード"
#: ../vnc.html:137 #: ../vnc.html:135
msgid "Edit clipboard content in the textarea below." msgid "Edit clipboard content in the textarea below."
msgstr "以下の入力欄からクリップボードの内容を編集できます。" msgstr "以下の入力欄からクリップボードの内容を編集できます。"
#: ../vnc.html:145 #: ../vnc.html:143
msgid "Full screen" msgid "Full screen"
msgstr "全画面表示" msgstr "全画面表示"
#: ../vnc.html:150 ../vnc.html:156 #: ../vnc.html:148 ../vnc.html:154
msgid "Settings" msgid "Settings"
msgstr "設定" msgstr "設定"
#: ../vnc.html:160 #: ../vnc.html:158
msgid "Shared mode" msgid "Shared mode"
msgstr "共有モード" msgstr "共有モード"
#: ../vnc.html:163 #: ../vnc.html:161
msgid "View only" msgid "View only"
msgstr "表示専用" msgstr "表示専用"
#: ../vnc.html:167 #: ../vnc.html:165
msgid "Clip to window" msgid "Clip to window"
msgstr "ウィンドウにクリップ" msgstr "ウィンドウにクリップ"
#: ../vnc.html:170 #: ../vnc.html:168
msgid "Scaling mode:" msgid "Scaling mode:"
msgstr "スケーリングモード:" msgstr "スケーリングモード:"
#: ../vnc.html:172 #: ../vnc.html:170
msgid "None" msgid "None"
msgstr "なし" msgstr "なし"
#: ../vnc.html:173 #: ../vnc.html:171
msgid "Local scaling" msgid "Local scaling"
msgstr "ローカルスケーリング" msgstr "ローカルスケーリング"
#: ../vnc.html:174 #: ../vnc.html:172
msgid "Remote resizing" msgid "Remote resizing"
msgstr "リモートでリサイズ" msgstr "リモートでリサイズ"
#: ../vnc.html:179 #: ../vnc.html:177
msgid "Advanced" msgid "Advanced"
msgstr "高度" msgstr "高度"
#: ../vnc.html:182 #: ../vnc.html:180
msgid "Quality:" msgid "Quality:"
msgstr "品質:" msgstr "品質:"
#: ../vnc.html:186 #: ../vnc.html:184
msgid "Compression level:" msgid "Compression level:"
msgstr "圧縮レベル:" msgstr "圧縮レベル:"
#: ../vnc.html:191 #: ../vnc.html:189
msgid "Repeater ID:" msgid "Repeater ID:"
msgstr "リピーター ID:" msgstr "リピーター ID:"
#: ../vnc.html:195 #: ../vnc.html:193
msgid "WebSocket" msgid "WebSocket"
msgstr "WebSocket" msgstr "WebSocket"
#: ../vnc.html:198 #: ../vnc.html:196
msgid "Encrypt" msgid "Encrypt"
msgstr "暗号化" msgstr "暗号化"
#: ../vnc.html:201 #: ../vnc.html:199
msgid "Host:" msgid "Host:"
msgstr "ホスト:" msgstr "ホスト:"
#: ../vnc.html:205 #: ../vnc.html:203
msgid "Port:" msgid "Port:"
msgstr "ポート:" msgstr "ポート:"
#: ../vnc.html:209 #: ../vnc.html:207
msgid "Path:" msgid "Path:"
msgstr "パス:" msgstr "パス:"
#: ../vnc.html:216 #: ../vnc.html:214
msgid "Automatic reconnect" msgid "Automatic reconnect"
msgstr "自動再接続" msgstr "自動再接続"
#: ../vnc.html:219 #: ../vnc.html:217
msgid "Reconnect delay (ms):" msgid "Reconnect delay (ms):"
msgstr "再接続する遅延 (ミリ秒):" msgstr "再接続する遅延 (ミリ秒):"
#: ../vnc.html:224 #: ../vnc.html:222
msgid "Show dot when no cursor" msgid "Show dot when no cursor"
msgstr "カーソルがないときにドットを表示する" msgstr "カーソルがないときにドットを表示する"
#: ../vnc.html:229 #: ../vnc.html:227
msgid "Logging:" msgid "Logging:"
msgstr "ロギング:" msgstr "ロギング:"
#: ../vnc.html:238 #: ../vnc.html:236
msgid "Version:" msgid "Version:"
msgstr "バージョン:" msgstr "バージョン:"
#: ../vnc.html:246 #: ../vnc.html:244
msgid "Disconnect" msgid "Disconnect"
msgstr "切断" msgstr "切断"
#: ../vnc.html:269 #: ../vnc.html:267
msgid "Connect" msgid "Connect"
msgstr "接続" msgstr "接続"
#: ../vnc.html:278 #: ../vnc.html:276
msgid "Server identity" msgid "Server identity"
msgstr "サーバーの識別情報" msgstr "サーバーの識別情報"
#: ../vnc.html:281 #: ../vnc.html:279
msgid "The server has provided the following identifying information:" msgid "The server has provided the following identifying information:"
msgstr "サーバーは以下の識別情報を提供しています:" msgstr "サーバーは以下の識別情報を提供しています:"
#: ../vnc.html:285 #: ../vnc.html:283
msgid "Fingerprint:" msgid "Fingerprint:"
msgstr "フィンガープリント:" msgstr "フィンガープリント:"
#: ../vnc.html:288 #: ../vnc.html:286
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:291
msgid "Approve" msgid "Approve"
msgstr "承認" msgstr "承認"
#: ../vnc.html:294 #: ../vnc.html:292
msgid "Reject" msgid "Reject"
msgstr "拒否" msgstr "拒否"
#: ../vnc.html:302 #: ../vnc.html:300
msgid "Credentials" msgid "Credentials"
msgstr "資格情報" msgstr "資格情報"
#: ../vnc.html:306 #: ../vnc.html:304
msgid "Username:" msgid "Username:"
msgstr "ユーザー名:" msgstr "ユーザー名:"
#: ../vnc.html:310 #: ../vnc.html:308
msgid "Password:" msgid "Password:"
msgstr "パスワード:" msgstr "パスワード:"
#: ../vnc.html:314 #: ../vnc.html:312
msgid "Send credentials" msgid "Send credentials"
msgstr "資格情報を送信" msgstr "資格情報を送信"
#: ../vnc.html:323 #: ../vnc.html:321
msgid "Cancel" msgid "Cancel"
msgstr "キャンセル" msgstr "キャンセル"
#~ msgid "Clear"
#~ 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

@ -6,9 +6,9 @@
#, fuzzy #, fuzzy
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: noVNC 1.5.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: 2024-06-03 14:10+0200\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,321 +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 "" msgid ""
"Running without HTTPS is not recommended, crashes or other issues are likely." "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"
msgstr ""
#: ../app/ui.js:1052
msgid "Failed to connect to server: " msgid "Failed to connect to server: "
msgstr "" msgstr ""
#: ../app/ui.js:1118 #: ../app/ui.js:1145
msgid "Connected (encrypted) to " msgid "Connected (encrypted) to "
msgstr "" msgstr ""
#: ../app/ui.js:1120 #: ../app/ui.js:1147
msgid "Connected (unencrypted) to " msgid "Connected (unencrypted) to "
msgstr "" msgstr ""
#: ../app/ui.js:1143 #: ../app/ui.js:1170
msgid "Something went wrong, connection is closed" msgid "Something went wrong, connection is closed"
msgstr "" msgstr ""
#: ../app/ui.js:1146 #: ../app/ui.js:1173
msgid "Failed to connect to server" msgid "Failed to connect to server"
msgstr "" msgstr ""
#: ../app/ui.js:1158 #: ../app/ui.js:1185
msgid "Disconnected" msgid "Disconnected"
msgstr "" msgstr ""
#: ../app/ui.js:1173 #: ../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:1176 #: ../app/ui.js:1203
msgid "New connection has been rejected" msgid "New connection has been rejected"
msgstr "" msgstr ""
#: ../app/ui.js:1242 #: ../app/ui.js:1269
msgid "Credentials are required" msgid "Credentials are required"
msgstr "" msgstr ""
#: ../vnc.html:55 #: ../vnc.html:106
msgid "noVNC encountered an error:" msgid "noVNC encountered an error:"
msgstr "" msgstr ""
#: ../vnc.html:65 #: ../vnc.html:116
msgid "Hide/Show the control bar" msgid "Hide/Show the control bar"
msgstr "" msgstr ""
#: ../vnc.html:74 #: ../vnc.html:125
msgid "Drag" msgid "Drag"
msgstr "" msgstr ""
#: ../vnc.html:74 #: ../vnc.html:125
msgid "Move/Drag viewport" msgid "Move/Drag viewport"
msgstr "" msgstr ""
#: ../vnc.html:80 #: ../vnc.html:131
msgid "Keyboard" msgid "Keyboard"
msgstr "" msgstr ""
#: ../vnc.html:80 #: ../vnc.html:131
msgid "Show keyboard" msgid "Show keyboard"
msgstr "" msgstr ""
#: ../vnc.html:85 #: ../vnc.html:136
msgid "Extra keys" msgid "Extra keys"
msgstr "" msgstr ""
#: ../vnc.html:85 #: ../vnc.html:136
msgid "Show extra keys" msgid "Show extra keys"
msgstr "" msgstr ""
#: ../vnc.html:90 #: ../vnc.html:141
msgid "Ctrl" msgid "Ctrl"
msgstr "" msgstr ""
#: ../vnc.html:90 #: ../vnc.html:141
msgid "Toggle Ctrl" msgid "Toggle Ctrl"
msgstr "" msgstr ""
#: ../vnc.html:93 #: ../vnc.html:144
msgid "Alt" msgid "Alt"
msgstr "" msgstr ""
#: ../vnc.html:93 #: ../vnc.html:144
msgid "Toggle Alt" msgid "Toggle Alt"
msgstr "" msgstr ""
#: ../vnc.html:96 #: ../vnc.html:147
msgid "Toggle Windows" msgid "Toggle Windows"
msgstr "" msgstr ""
#: ../vnc.html:96 #: ../vnc.html:147
msgid "Windows" msgid "Windows"
msgstr "" msgstr ""
#: ../vnc.html:99 #: ../vnc.html:150
msgid "Send Tab" msgid "Send Tab"
msgstr "" msgstr ""
#: ../vnc.html:99 #: ../vnc.html:150
msgid "Tab" msgid "Tab"
msgstr "" msgstr ""
#: ../vnc.html:102 #: ../vnc.html:153
msgid "Esc" msgid "Esc"
msgstr "" msgstr ""
#: ../vnc.html:102 #: ../vnc.html:153
msgid "Send Escape" msgid "Send Escape"
msgstr "" msgstr ""
#: ../vnc.html:105 #: ../vnc.html:156
msgid "Ctrl+Alt+Del" msgid "Ctrl+Alt+Del"
msgstr "" msgstr ""
#: ../vnc.html:105 #: ../vnc.html:156
msgid "Send Ctrl-Alt-Del" msgid "Send Ctrl-Alt-Del"
msgstr "" msgstr ""
#: ../vnc.html:112 #: ../vnc.html:163
msgid "Shutdown/Reboot" msgid "Shutdown/Reboot"
msgstr "" msgstr ""
#: ../vnc.html:112 #: ../vnc.html:163
msgid "Shutdown/Reboot..." msgid "Shutdown/Reboot..."
msgstr "" msgstr ""
#: ../vnc.html:118 #: ../vnc.html:169
msgid "Power" msgid "Power"
msgstr "" msgstr ""
#: ../vnc.html:120 #: ../vnc.html:171
msgid "Shutdown" msgid "Shutdown"
msgstr "" msgstr ""
#: ../vnc.html:121 #: ../vnc.html:172
msgid "Reboot" msgid "Reboot"
msgstr "" msgstr ""
#: ../vnc.html:122 #: ../vnc.html:173
msgid "Reset" msgid "Reset"
msgstr "" msgstr ""
#: ../vnc.html:127 ../vnc.html:133 #: ../vnc.html:178 ../vnc.html:184
msgid "Clipboard" msgid "Clipboard"
msgstr "" msgstr ""
#: ../vnc.html:135 #: ../vnc.html:186
msgid "Edit clipboard content in the textarea below." msgid "Edit clipboard content in the textarea below."
msgstr "" msgstr ""
#: ../vnc.html:143 #: ../vnc.html:194
msgid "Full screen" msgid "Full screen"
msgstr "" msgstr ""
#: ../vnc.html:148 ../vnc.html:154 #: ../vnc.html:199 ../vnc.html:205
msgid "Settings" msgid "Settings"
msgstr "" msgstr ""
#: ../vnc.html:158 #: ../vnc.html:211
msgid "Shared mode" msgid "Shared mode"
msgstr "" msgstr ""
#: ../vnc.html:161 #: ../vnc.html:218
msgid "View only" msgid "View only"
msgstr "" msgstr ""
#: ../vnc.html:165 #: ../vnc.html:226
msgid "Clip to window" msgid "Clip to window"
msgstr "" msgstr ""
#: ../vnc.html:168 #: ../vnc.html:231
msgid "Scaling mode:" msgid "Scaling mode:"
msgstr "" msgstr ""
#: ../vnc.html:170 #: ../vnc.html:233
msgid "None" msgid "None"
msgstr "" msgstr ""
#: ../vnc.html:171 #: ../vnc.html:234
msgid "Local scaling" msgid "Local scaling"
msgstr "" msgstr ""
#: ../vnc.html:172 #: ../vnc.html:235
msgid "Remote resizing" msgid "Remote resizing"
msgstr "" msgstr ""
#: ../vnc.html:177 #: ../vnc.html:240
msgid "Advanced" msgid "Advanced"
msgstr "" msgstr ""
#: ../vnc.html:180 #: ../vnc.html:243
msgid "Quality:" msgid "Quality:"
msgstr "" msgstr ""
#: ../vnc.html:184 #: ../vnc.html:247
msgid "Compression level:" msgid "Compression level:"
msgstr "" msgstr ""
#: ../vnc.html:189 #: ../vnc.html:252
msgid "Repeater ID:" msgid "Repeater ID:"
msgstr "" msgstr ""
#: ../vnc.html:193 #: ../vnc.html:256
msgid "WebSocket" msgid "WebSocket"
msgstr "" msgstr ""
#: ../vnc.html:196 #: ../vnc.html:261
msgid "Encrypt" msgid "Encrypt"
msgstr "" msgstr ""
#: ../vnc.html:199 #: ../vnc.html:266
msgid "Host:" msgid "Host:"
msgstr "" msgstr ""
#: ../vnc.html:203 #: ../vnc.html:270
msgid "Port:" msgid "Port:"
msgstr "" msgstr ""
#: ../vnc.html:207 #: ../vnc.html:274
msgid "Path:" msgid "Path:"
msgstr "" msgstr ""
#: ../vnc.html:214 #: ../vnc.html:283
msgid "Automatic reconnect" msgid "Automatic reconnect"
msgstr "" msgstr ""
#: ../vnc.html:217 #: ../vnc.html:288
msgid "Reconnect delay (ms):" msgid "Reconnect delay (ms):"
msgstr "" msgstr ""
#: ../vnc.html:222 #: ../vnc.html:295
msgid "Show dot when no cursor" msgid "Show dot when no cursor"
msgstr "" msgstr ""
#: ../vnc.html:227 #: ../vnc.html:302
msgid "Logging:" msgid "Logging:"
msgstr "" msgstr ""
#: ../vnc.html:236 #: ../vnc.html:311
msgid "Version:" msgid "Version:"
msgstr "" msgstr ""
#: ../vnc.html:244 #: ../vnc.html:319
msgid "Disconnect" msgid "Disconnect"
msgstr "" msgstr ""
#: ../vnc.html:267 #: ../vnc.html:342
msgid "Connect" msgid "Connect"
msgstr "" msgstr ""
#: ../vnc.html:276 #: ../vnc.html:351
msgid "Server identity" msgid "Server identity"
msgstr "" msgstr ""
#: ../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 "" msgstr ""
#: ../vnc.html:283 #: ../vnc.html:357
msgid "Fingerprint:" msgid "Fingerprint:"
msgstr "" msgstr ""
#: ../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 ""
#: ../vnc.html:291 #: ../vnc.html:366
msgid "Approve" msgid "Approve"
msgstr "" msgstr ""
#: ../vnc.html:292 #: ../vnc.html:367
msgid "Reject" msgid "Reject"
msgstr "" msgstr ""
#: ../vnc.html:300 #: ../vnc.html:375
msgid "Credentials" msgid "Credentials"
msgstr "" msgstr ""
#: ../vnc.html:304 #: ../vnc.html:379
msgid "Username:" msgid "Username:"
msgstr "" msgstr ""
#: ../vnc.html:308 #: ../vnc.html:383
msgid "Password:" msgid "Password:"
msgstr "" msgstr ""
#: ../vnc.html:312 #: ../vnc.html:387
msgid "Send credentials" msgid "Send credentials"
msgstr "" msgstr ""
#: ../vnc.html:321 #: ../vnc.html:396
msgid "Cancel" msgid "Cancel"
msgstr "" msgstr ""

View File

@ -17,29 +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(item => item.msgstr[0] !== "")
.filter(msgid => msgid !== "") .map(item => " " + JSON.stringify(item.msgid) + ": " + JSON.stringify(item.msgstr[0]))
.filter(msgid => data[msgid][1] !== "") .join(",\n");
.map((msgid) => {
const msgstr = data[msgid][1];
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);

215
po/sv.po
View File

@ -1,312 +1,308 @@
# 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: 2024-06-03 14:10+0200\n" "POT-Creation-Date: 2025-02-14 10:14+0100\n"
"PO-Revision-Date: 2024-06-18 13:52+0200\n" "PO-Revision-Date: 2025-02-14 10:29+0100\n"
"Last-Translator: Pierre Ossman <ossman@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.4.4\n" "X-Generator: Poedit 3.5\n"
#: ../app/ui.js:69 #: ../app/ui.js:84
msgid "" msgid ""
"Running without HTTPS is not recommended, crashes or other issues are likely." "Running without HTTPS is not recommended, crashes or other issues are likely."
msgstr "" msgstr ""
"Det är ej rekommenderat att köra utan HTTPS, krascher och andra problem är " "Det är ej rekommenderat att köra utan HTTPS, krascher och andra problem är "
"troliga." "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"
msgstr "Du måste specifiera en värd"
#: ../app/ui.js:1052
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:1118 #: ../app/ui.js:1145
msgid "Connected (encrypted) to " msgid "Connected (encrypted) to "
msgstr "Ansluten (krypterat) till " msgstr "Ansluten (krypterat) till "
#: ../app/ui.js:1120 #: ../app/ui.js:1147
msgid "Connected (unencrypted) to " msgid "Connected (unencrypted) to "
msgstr "Ansluten (okrypterat) till " msgstr "Ansluten (okrypterat) till "
#: ../app/ui.js:1143 #: ../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:1146 #: ../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:1158 #: ../app/ui.js:1185
msgid "Disconnected" msgid "Disconnected"
msgstr "Frånkopplad" msgstr "Frånkopplad"
#: ../app/ui.js:1173 #: ../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:1176 #: ../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:1242 #: ../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\"."
@ -314,34 +310,37 @@ msgstr ""
"Kontrollera att informationen är korrekt och tryck sedan \"Godkänn\". Tryck " "Kontrollera att informationen är korrekt och tryck sedan \"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" #~ msgid "HTTPS is required for full functionality"
#~ msgstr "HTTPS krävs för full funktionalitet" #~ msgstr "HTTPS krävs för full funktionalitet"

View File

@ -5,14 +5,14 @@
* 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

@ -11,7 +11,7 @@
# "vnc": "localhost:5902" # "vnc": "localhost:5902"
#} #}
#} #}
snapctl get services | jq -c '.[]' | while read service; do # for each service the user sepcified.. snapctl get services | jq -c '.[]' | while read service; do # for each service the user specified..
# get the important data for the service (listen port, VNC host:port) # get the important data for the service (listen port, VNC host:port)
listen_port="$(echo $service | jq --raw-output '.listen')" listen_port="$(echo $service | jq --raw-output '.listen')"
vnc_host_port="$(echo $service | jq --raw-output '.vnc')" # --raw-output removes any quotation marks from the output vnc_host_port="$(echo $service | jq --raw-output '.vnc')" # --raw-output removes any quotation marks from the output

View File

@ -42,7 +42,7 @@ parts:
- jq - jq
websockify: websockify:
source: https://github.com/novnc/websockify/archive/v0.12.0.tar.gz source: https://github.com/novnc/websockify/archive/v0.13.0.tar.gz
plugin: python plugin: python
stage-packages: stage-packages:
- python3-numpy - python3-numpy

View File

@ -525,6 +525,37 @@ describe('Key event handling', function () {
expect(kbd.onkeyevent).to.not.have.been.called; expect(kbd.onkeyevent).to.not.have.been.called;
}); });
it('should generate AltGraph for quick Ctrl+AltGraph sequence', function () {
const kbd = new Keyboard(document);
kbd.onkeyevent = sinon.spy();
kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1, timeStamp: Date.now()}));
this.clock.tick(20);
kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'AltGraph', location: 2, timeStamp: Date.now()}));
expect(kbd.onkeyevent).to.have.been.calledOnce;
expect(kbd.onkeyevent).to.have.been.calledWith(0xfe03, 'AltRight', true);
// Check that the timer is properly dead
kbd.onkeyevent.resetHistory();
this.clock.tick(100);
expect(kbd.onkeyevent).to.not.have.been.called;
});
it('should generate Ctrl, AltGraph for slow Ctrl+AltGraph sequence', function () {
const kbd = new Keyboard(document);
kbd.onkeyevent = sinon.spy();
kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1, timeStamp: Date.now()}));
this.clock.tick(60);
kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'AltGraph', location: 2, timeStamp: Date.now()}));
expect(kbd.onkeyevent).to.have.been.calledTwice;
expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0xffe3, "ControlLeft", true);
expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0xfe03, "AltRight", true);
// Check that the timer is properly dead
kbd.onkeyevent.resetHistory();
this.clock.tick(100);
expect(kbd.onkeyevent).to.not.have.been.called;
});
it('should pass through single Alt', function () { it('should pass through single Alt', function () {
const kbd = new Keyboard(document); const kbd = new Keyboard(document);
kbd.onkeyevent = sinon.spy(); kbd.onkeyevent = sinon.spy();

File diff suppressed because it is too large Load Diff

View File

@ -28,16 +28,63 @@ for fn in "$@"; do
curl --silent \ curl --silent \
--header "Content-Type: ${type}; charset=utf-8" \ --header "Content-Type: ${type}; charset=utf-8" \
--data-binary @${fn} \ --data-binary @${fn} \
https://validator.w3.org/nu/?out=text > $OUT "https://validator.w3.org/nu/?out=gnu&level=error&asciiquotes=yes" \
cat $OUT > $OUT
echo
# We don't fail the check for warnings as some warnings are # We don't fail the check for warnings as some warnings are
# not relevant for us, and we don't currently have a way to # not relevant for us, and we don't currently have a way to
# ignore just those # ignore just those
if grep -q -s -E "^Error:" $OUT; then while read -r line; do
echo
line_info=$(echo $line | cut -d ":" -f 2)
start_info=$(echo $line_info | cut -d "-" -f 1)
end_info=$(echo $line_info | cut -d "-" -f 2)
line_start=$(echo $start_info | cut -d "." -f 1)
col_start=$(echo $start_info | cut -d "." -f 2)
line_end=$(echo $end_info | cut -d "." -f 1)
col_end=$(echo $end_info | cut -d "." -f 2)
error=$(echo $line | cut -d ":" -f 4-)
case $error in
*"\"scrollbar-gutter\": Property \"scrollbar-gutter\" doesn't exist.")
# FIXME: https://github.com/validator/validator/issues/1788
echo "Ignoring below error on line ${line_start}," \
"the scrollbar-gutter property actually exist and is widely" \
"supported:"
echo $error
continue
;;
*"\"clip-path\": \"path("*)
# FIXME: https://github.com/validator/validator/issues/1786
echo "Ignoring below error on line ${line_start}," \
"the path() function is valid for clip-path and is" \
"widely supported:"
echo $error
continue
;;
*"Parse Error.")
# FIXME: https://github.com/validator/validator/issues/1786
lineofselector=$(grep -n "@supports selector(" $fn | cut -d ":" -f 1)
linediff=$((lineofselector-line_start))
# Only ignore if parse error is within 50 lines of "selector()"
if [ ${linediff#-} -lt 50 ]; then
echo "Ignoring below error on line ${line_start}," \
"the @supports selector() function should not give a parse" \
"error:"
echo $error
continue
fi
;;
esac
echo "ERROR between line ${line_start} (col ${col_start})" \
"and line ${line_end} (col ${col_end}):"
echo $error
RET=1 RET=1
fi done < "$OUT"
done done
rm $OUT rm $OUT

View File

@ -37,6 +37,7 @@
<link rel="apple-touch-icon" sizes="180x180" type="image/png" href="app/images/icons/novnc-ios-180.png"> <link rel="apple-touch-icon" sizes="180x180" type="image/png" href="app/images/icons/novnc-ios-180.png">
<!-- Stylesheets --> <!-- Stylesheets -->
<link rel="stylesheet" href="app/styles/constants.css">
<link rel="stylesheet" href="app/styles/base.css"> <link rel="stylesheet" href="app/styles/base.css">
<link rel="stylesheet" href="app/styles/input.css"> <link rel="stylesheet" href="app/styles/input.css">
@ -205,14 +206,26 @@
</div> </div>
<ul> <ul>
<li> <li>
<label><input id="noVNC_setting_shared" type="checkbox"> Shared mode</label> <label>
<input id="noVNC_setting_shared" type="checkbox"
class="toggle">
Shared mode
</label>
</li> </li>
<li> <li>
<label><input id="noVNC_setting_view_only" type="checkbox"> View only</label> <label>
<input id="noVNC_setting_view_only" type="checkbox"
class="toggle">
View only
</label>
</li> </li>
<li><hr></li> <li><hr></li>
<li> <li>
<label><input id="noVNC_setting_view_clip" type="checkbox"> Clip to window</label> <label>
<input id="noVNC_setting_view_clip" type="checkbox"
class="toggle">
Clip to window
</label>
</li> </li>
<li> <li>
<label for="noVNC_setting_resize">Scaling mode:</label> <label for="noVNC_setting_resize">Scaling mode:</label>
@ -243,7 +256,11 @@
<div class="noVNC_expander">WebSocket</div> <div class="noVNC_expander">WebSocket</div>
<div><ul> <div><ul>
<li> <li>
<label><input id="noVNC_setting_encrypt" type="checkbox"> Encrypt</label> <label>
<input id="noVNC_setting_encrypt" type="checkbox"
class="toggle">
Encrypt
</label>
</li> </li>
<li> <li>
<label for="noVNC_setting_host">Host:</label> <label for="noVNC_setting_host">Host:</label>
@ -261,7 +278,11 @@
</li> </li>
<li><hr></li> <li><hr></li>
<li> <li>
<label><input id="noVNC_setting_reconnect" type="checkbox"> Automatic reconnect</label> <label>
<input id="noVNC_setting_reconnect" type="checkbox"
class="toggle">
Automatic reconnect
</label>
</li> </li>
<li> <li>
<label for="noVNC_setting_reconnect_delay">Reconnect delay (ms):</label> <label for="noVNC_setting_reconnect_delay">Reconnect delay (ms):</label>
@ -269,7 +290,11 @@
</li> </li>
<li><hr></li> <li><hr></li>
<li> <li>
<label><input id="noVNC_setting_show_dot" type="checkbox"> Show dot when no cursor</label> <label>
<input id="noVNC_setting_show_dot" type="checkbox"
class="toggle">
Show dot when no cursor
</label>
<label for="noVNC_setting_gestures_mode">Gestures mode:</label> <label for="noVNC_setting_gestures_mode">Gestures mode:</label>
<select id="noVNC_setting_gestures_mode" name="vncGestures"> <select id="noVNC_setting_gestures_mode" name="vncGestures">
<option value="novnc">Default (noVNC)</option> <option value="novnc">Default (noVNC)</option>
@ -335,16 +360,16 @@
The server has provided the following identifying information: The server has provided the following identifying information:
</div> </div>
<div id="noVNC_fingerprint_block"> <div id="noVNC_fingerprint_block">
<b>Fingerprint:</b> Fingerprint:
<span id="noVNC_fingerprint"></span> <span id="noVNC_fingerprint"></span>
</div> </div>
<div> <div>
Please verify that the information is correct and press Please verify that the information is correct and press
"Approve". Otherwise press "Reject". "Approve". Otherwise press "Reject".
</div> </div>
<div> <div class="button_row">
<input id="noVNC_approve_server_button" type="submit" value="Approve" class="noVNC_submit"> <input id="noVNC_approve_server_button" type="submit" value="Approve">
<input id="noVNC_reject_server_button" type="button" value="Reject" class="noVNC_submit"> <input id="noVNC_reject_server_button" type="button" value="Reject">
</div> </div>
</form></div> </form></div>
</div> </div>
@ -363,8 +388,8 @@
<label for="noVNC_password_input">Password:</label> <label for="noVNC_password_input">Password:</label>
<input id="noVNC_password_input" type="password"> <input id="noVNC_password_input" type="password">
</div> </div>
<div> <div class="button_row">
<input id="noVNC_credentials_button" type="submit" value="Send credentials" class="noVNC_submit"> <input id="noVNC_credentials_button" type="submit" value="Send credentials">
</div> </div>
</form></div> </form></div>
</div> </div>
@ -373,7 +398,7 @@
<div id="noVNC_transition"> <div id="noVNC_transition">
<div id="noVNC_transition_text"></div> <div id="noVNC_transition_text"></div>
<div> <div>
<input type="button" id="noVNC_cancel_reconnect_button" value="Cancel" class="noVNC_submit"> <input type="button" id="noVNC_cancel_reconnect_button" value="Cancel">
</div> </div>
<div class="noVNC_spinner"></div> <div class="noVNC_spinner"></div>
</div> </div>