rebase
This commit is contained in:
commit
e5a62ea66b
|
|
@ -26,6 +26,12 @@
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Skip allowed errors
|
||||||
|
let allowedErrors = [ "The user has exited the lock before this request was completed." ];
|
||||||
|
if (event.message && allowedErrors.includes(event.message)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
let div = document.createElement("div");
|
let div = document.createElement("div");
|
||||||
div.classList.add('noVNC_message');
|
div.classList.add('noVNC_message');
|
||||||
div.appendChild(document.createTextNode(event.message));
|
div.appendChild(document.createTextNode(event.message));
|
||||||
|
|
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 2.0 KiB |
|
|
@ -0,0 +1,78 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
width="25"
|
||||||
|
height="25"
|
||||||
|
viewBox="0 0 25 25"
|
||||||
|
id="svg2"
|
||||||
|
version="1.1"
|
||||||
|
inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)"
|
||||||
|
sodipodi:docname="pointer.svg"
|
||||||
|
inkscape:export-filename="/home/ossman/devel/noVNC/images/keyboard.png"
|
||||||
|
inkscape:export-xdpi="90"
|
||||||
|
inkscape:export-ydpi="90">
|
||||||
|
<defs
|
||||||
|
id="defs4" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#717171"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="22.627417"
|
||||||
|
inkscape:cx="6.9841519"
|
||||||
|
inkscape:cy="18.584699"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
showgrid="false"
|
||||||
|
units="px"
|
||||||
|
inkscape:snap-bbox="true"
|
||||||
|
inkscape:bbox-paths="true"
|
||||||
|
inkscape:bbox-nodes="true"
|
||||||
|
inkscape:snap-bbox-edge-midpoints="true"
|
||||||
|
inkscape:snap-bbox-midpoints="false"
|
||||||
|
inkscape:window-width="2560"
|
||||||
|
inkscape:window-height="1403"
|
||||||
|
inkscape:window-x="2560"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:object-paths="true"
|
||||||
|
inkscape:snap-intersection-paths="true"
|
||||||
|
inkscape:object-nodes="true"
|
||||||
|
inkscape:snap-midpoints="true"
|
||||||
|
inkscape:snap-smooth-nodes="true"
|
||||||
|
inkscape:document-rotation="0">
|
||||||
|
<inkscape:grid
|
||||||
|
type="xygrid"
|
||||||
|
id="grid4136" />
|
||||||
|
</sodipodi:namedview>
|
||||||
|
<metadata
|
||||||
|
id="metadata7">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title />
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(0,-1027.3622)">
|
||||||
|
<path
|
||||||
|
style="fill:#ffffff;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
d="m 6.3910823,1030.3965 v 17.497 l 3.5465954,-2.6671 1.5862113,4.1015 4.336661,-1.5752 -1.59331,-4.2624 4.341678,-0.3562 z"
|
||||||
|
id="path879" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.4 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 93 KiB |
|
|
@ -22,7 +22,8 @@
|
||||||
body {
|
body {
|
||||||
margin:0;
|
margin:0;
|
||||||
padding:0;
|
padding:0;
|
||||||
font-family: Helvetica;
|
font-family: "Poppins", "Helvetica";
|
||||||
|
letter-spacing: 0.05em;
|
||||||
background: white url('../images/icons/kasm_logo.png') no-repeat fixed center;
|
background: white url('../images/icons/kasm_logo.png') no-repeat fixed center;
|
||||||
height:100%;
|
height:100%;
|
||||||
touch-action: none;
|
touch-action: none;
|
||||||
|
|
@ -37,7 +38,7 @@ html {
|
||||||
}
|
}
|
||||||
|
|
||||||
.noVNC_disabled {
|
.noVNC_disabled {
|
||||||
color: rgb(128, 128, 128);
|
color: rgb(128, 128, 128) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ----------------------------------------
|
/* ----------------------------------------
|
||||||
|
|
@ -351,8 +352,9 @@ select:active {
|
||||||
/* Edge misrenders animations wihthout this */
|
/* Edge misrenders animations wihthout this */
|
||||||
transform: translateX(0);
|
transform: translateX(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
:root.noVNC_connected #noVNC_control_bar_anchor.noVNC_idle {
|
:root.noVNC_connected #noVNC_control_bar_anchor.noVNC_idle {
|
||||||
opacity: 0.8;
|
/* opacity: 0.8; */
|
||||||
}
|
}
|
||||||
#noVNC_control_bar_anchor.noVNC_right {
|
#noVNC_control_bar_anchor.noVNC_right {
|
||||||
left: auto;
|
left: auto;
|
||||||
|
|
@ -365,12 +367,12 @@ select:active {
|
||||||
|
|
||||||
transition: 0.5s ease-in-out;
|
transition: 0.5s ease-in-out;
|
||||||
|
|
||||||
background-color: rgb(80, 89, 101);
|
background-color: rgb(9 2 2 / 0.6);
|
||||||
border-radius: 0 10px 10px 0;
|
border-radius: 0 10px 10px 0;
|
||||||
|
border-style: inset;
|
||||||
|
border-color: rgb(255 255 255 / 0.6);
|
||||||
}
|
}
|
||||||
#noVNC_control_bar.noVNC_open {
|
#noVNC_control_bar.noVNC_open {
|
||||||
box-shadow: 6px 6px 0px rgba(0, 0, 0, 0.5);
|
|
||||||
left: 0;
|
left: 0;
|
||||||
}
|
}
|
||||||
#noVNC_control_bar::before {
|
#noVNC_control_bar::before {
|
||||||
|
|
@ -400,28 +402,31 @@ select:active {
|
||||||
#noVNC_control_bar_handle {
|
#noVNC_control_bar_handle {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: -15px;
|
left: -15px;
|
||||||
top: 0;
|
|
||||||
transform: translateY(35px);
|
transform: translateY(35px);
|
||||||
width: calc(100% + 30px);
|
width: calc(100% + 30px);
|
||||||
height: 50px;
|
height: 50px;
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
background-color: rgb(83, 99, 122);
|
|
||||||
background-image: url("../images/handle_bg.svg");
|
background-image: url("../images/handle_bg.svg");
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-position: right;
|
background-position: right;
|
||||||
box-shadow: 3px 3px 0px rgba(0, 0, 0, 0.5);
|
|
||||||
}
|
}
|
||||||
#noVNC_control_bar_handle:after {
|
#noVNC_control_bar_handle:after {
|
||||||
content: "";
|
content: "";
|
||||||
transition: transform 0.5s ease-in-out;
|
transition: transform 0.5s ease-in-out;
|
||||||
background: url("../images/handle.svg");
|
background: url("../images/handle.svg");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 22px; /* (50px-6px)/2 */
|
right: 0px;
|
||||||
right: 5px;
|
width: 15px;
|
||||||
width: 5px;
|
height: 60px;
|
||||||
height: 6px;
|
background-color: rgb(9 2 2 / 0.6);
|
||||||
|
border-bottom-right-radius: 10px;
|
||||||
|
border-top-right-radius: 10px;
|
||||||
|
border-color: rgb(255 255 255 / 0.6);
|
||||||
|
border-style: inset;
|
||||||
}
|
}
|
||||||
#noVNC_control_bar.noVNC_open #noVNC_control_bar_handle:after {
|
#noVNC_control_bar.noVNC_open #noVNC_control_bar_handle:after {
|
||||||
transform: translateX(1px) rotate(180deg);
|
transform: translateX(1px) rotate(180deg);
|
||||||
|
|
@ -431,6 +436,7 @@ select:active {
|
||||||
}
|
}
|
||||||
.noVNC_right #noVNC_control_bar_handle {
|
.noVNC_right #noVNC_control_bar_handle {
|
||||||
background-position: left;
|
background-position: left;
|
||||||
|
|
||||||
}
|
}
|
||||||
.noVNC_right #noVNC_control_bar_handle:after {
|
.noVNC_right #noVNC_control_bar_handle:after {
|
||||||
left: 5px;
|
left: 5px;
|
||||||
|
|
@ -495,18 +501,23 @@ select:active {
|
||||||
transform: translateY(-50%) scale(1);
|
transform: translateY(-50%) scale(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.noVNC_button_div {
|
||||||
|
display: block;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
/* General button style */
|
/* General button style */
|
||||||
.noVNC_button {
|
.noVNC_button {
|
||||||
display: block;
|
display: inline;
|
||||||
padding: 4px 4px;
|
padding: 4px 4px;
|
||||||
margin: 10px 0;
|
margin: 10px 0;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
border:1px solid rgba(255, 255, 255, 0.2);
|
|
||||||
border-radius: 6px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.noVNC_button.noVNC_selected {
|
.noVNC_button.noVNC_selected {
|
||||||
border-color: rgba(0, 0, 0, 0.8);
|
border-color: rgba(0, 0, 0, 0.8);
|
||||||
background: rgba(0, 0, 0, 0.5);
|
background: rgba(153, 151, 157, 0.68);
|
||||||
}
|
}
|
||||||
.noVNC_button:disabled {
|
.noVNC_button:disabled {
|
||||||
opacity: 0.4;
|
opacity: 0.4;
|
||||||
|
|
@ -523,10 +534,10 @@ select:active {
|
||||||
:root:not(.noVNC_touch) .noVNC_button.noVNC_selected:hover,
|
:root:not(.noVNC_touch) .noVNC_button.noVNC_selected:hover,
|
||||||
.noVNC_button.noVNC_selected:focus {
|
.noVNC_button.noVNC_selected:focus {
|
||||||
border-color: rgba(0, 0, 0, 0.4);
|
border-color: rgba(0, 0, 0, 0.4);
|
||||||
background: rgba(0, 0, 0, 0.2);
|
background: rgba(153, 151, 157, 0.68);
|
||||||
}
|
}
|
||||||
:root:not(.noVNC_touch) .noVNC_button:hover,
|
:root:not(.noVNC_touch) .noVNC_button_div:hover,
|
||||||
.noVNC_button:focus {
|
.noVNC_button_div:focus {
|
||||||
background: rgba(255, 255, 255, 0.2);
|
background: rgba(255, 255, 255, 0.2);
|
||||||
}
|
}
|
||||||
.noVNC_button.noVNC_hidden {
|
.noVNC_button.noVNC_hidden {
|
||||||
|
|
@ -539,6 +550,7 @@ select:active {
|
||||||
|
|
||||||
transition: 0.5s ease-in-out;
|
transition: 0.5s ease-in-out;
|
||||||
|
|
||||||
|
width: 300px;
|
||||||
max-height: 100vh; /* Chrome is buggy with 100% */
|
max-height: 100vh; /* Chrome is buggy with 100% */
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|
@ -548,11 +560,9 @@ select:active {
|
||||||
|
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
|
|
||||||
background: #fff;
|
background: rgb(9 9 0 / 0.77);
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
color: #000;
|
color: #000;
|
||||||
border: 2px solid #E0E0E0;
|
|
||||||
box-shadow: 6px 6px 0px rgba(0, 0, 0, 0.5);
|
|
||||||
}
|
}
|
||||||
.noVNC_panel.noVNC_open {
|
.noVNC_panel.noVNC_open {
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
|
|
@ -578,10 +588,11 @@ select:active {
|
||||||
.noVNC_panel label {
|
.noVNC_panel label {
|
||||||
display: block;
|
display: block;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
color:white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.noVNC_panel .noVNC_heading {
|
.noVNC_panel .noVNC_heading {
|
||||||
background-color: rgb(110, 132, 163);
|
background-color: rgb(54,58,64);
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
/* Compensate for padding in image */
|
/* Compensate for padding in image */
|
||||||
|
|
@ -602,12 +613,16 @@ select:active {
|
||||||
/* Expanders */
|
/* Expanders */
|
||||||
.noVNC_expander {
|
.noVNC_expander {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
color:white;
|
||||||
|
|
||||||
}
|
}
|
||||||
.noVNC_expander::before {
|
.noVNC_expander::before {
|
||||||
content: url("../images/expander.svg");
|
content: url("../images/expander.svg");
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
transition: 0.2s ease-in-out;
|
transition: 0.2s ease-in-out;
|
||||||
|
-webkit-filter: invert(.75); /* safari 6.0 - 9.0 */
|
||||||
|
filter: invert(.75);
|
||||||
}
|
}
|
||||||
.noVNC_expander.noVNC_open::before {
|
.noVNC_expander.noVNC_open::before {
|
||||||
transform: rotateZ(90deg);
|
transform: rotateZ(90deg);
|
||||||
|
|
@ -630,6 +645,14 @@ select:active {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:root:not(.noVNC_disconnected) .noVNC_hide_on_connect {
|
||||||
|
display: none
|
||||||
|
}
|
||||||
|
|
||||||
|
:root:not(.noVNC_connected) .noVNC_hide_on_disconnect {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
:root:not(.noVNC_connected) #noVNC_view_drag_button {
|
:root:not(.noVNC_connected) #noVNC_view_drag_button {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
@ -687,6 +710,7 @@ select:active {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
|
color:white;
|
||||||
}
|
}
|
||||||
#noVNC_setting_port {
|
#noVNC_setting_port {
|
||||||
width: 80px;
|
width: 80px;
|
||||||
|
|
@ -776,81 +800,6 @@ select:active {
|
||||||
content: url("../images/warning.svg") " ";
|
content: url("../images/warning.svg") " ";
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ----------------------------------------
|
|
||||||
* Connect Dialog
|
|
||||||
* ----------------------------------------
|
|
||||||
*/
|
|
||||||
|
|
||||||
#noVNC_connect_dlg {
|
|
||||||
transition: 0.5s ease-in-out;
|
|
||||||
|
|
||||||
transform: scale(0, 0);
|
|
||||||
visibility: hidden;
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
#noVNC_connect_dlg.noVNC_open {
|
|
||||||
transform: scale(1, 1);
|
|
||||||
visibility: visible;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
#noVNC_connect_dlg .noVNC_logo {
|
|
||||||
transition: 0.5s ease-in-out;
|
|
||||||
padding: 10px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
|
|
||||||
font-size: 80px;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
border-radius: 5px;
|
|
||||||
}
|
|
||||||
@media (max-width: 440px) {
|
|
||||||
#noVNC_connect_dlg {
|
|
||||||
max-width: calc(100vw - 100px);
|
|
||||||
}
|
|
||||||
#noVNC_connect_dlg .noVNC_logo {
|
|
||||||
font-size: calc(25vw - 30px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#noVNC_connect_button {
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
/*
|
|
||||||
padding: 10px;
|
|
||||||
|
|
||||||
color: white;
|
|
||||||
background-color: rgb(110, 132, 163);
|
|
||||||
border-radius: 12px;
|
|
||||||
box-shadow: 6px 6px 0px rgba(0, 0, 0, 0.5);
|
|
||||||
*/
|
|
||||||
|
|
||||||
text-align: center;
|
|
||||||
font-size: 20px;
|
|
||||||
margin-top: 130px;
|
|
||||||
}
|
|
||||||
#noVNC_connect_button div {
|
|
||||||
margin: 2px;
|
|
||||||
padding: 5px 30px;
|
|
||||||
border: 1px solid rgb(83, 99, 122);
|
|
||||||
border-bottom-width: 2px;
|
|
||||||
border-radius: 5px;
|
|
||||||
background: linear-gradient(to top, rgb(110, 132, 163), rgb(99, 119, 147));
|
|
||||||
|
|
||||||
/* This avoids it jumping around when :active */
|
|
||||||
vertical-align: middle;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
#noVNC_connect_button div:active {
|
|
||||||
border-bottom-width: 1px;
|
|
||||||
margin-top: 3px;
|
|
||||||
}
|
|
||||||
:root:not(.noVNC_touch) #noVNC_connect_button div:hover {
|
|
||||||
background: linear-gradient(to top, rgb(110, 132, 163), rgb(105, 125, 155));
|
|
||||||
}
|
|
||||||
|
|
||||||
#noVNC_connect_button img {
|
|
||||||
vertical-align: bottom;
|
|
||||||
height: 1.3em;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ----------------------------------------
|
/* ----------------------------------------
|
||||||
* Password Dialog
|
* Password Dialog
|
||||||
|
|
@ -930,21 +879,19 @@ select:active {
|
||||||
#noVNC_container {
|
#noVNC_container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background-color: rgb(74, 144, 217, 0.5);
|
background-image: url('../images/splash.jpg')
|
||||||
border-bottom-right-radius: 800px 600px;
|
|
||||||
/*border-top-left-radius: 800px 600px;*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#noVNC_keyboardinput {
|
#noVNC_keyboardinput {
|
||||||
width: 1px;
|
width: 0px;
|
||||||
height: 1px;
|
height: 0px;
|
||||||
background-color: #fff;
|
background-color: #fff0;
|
||||||
color: #fff;
|
color: rgba(5, 5, 5, 0);
|
||||||
border: 0;
|
border: 0;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: -40px;
|
left: 35%;
|
||||||
|
top: 40%;
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
ime-mode: disabled;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*Default noVNC logo.*/
|
/*Default noVNC logo.*/
|
||||||
|
|
@ -962,6 +909,10 @@ select:active {
|
||||||
font-family: 'Orbitron', 'OrbitronTTF', sans-serif;
|
font-family: 'Orbitron', 'OrbitronTTF', sans-serif;
|
||||||
line-height:90%;
|
line-height:90%;
|
||||||
text-shadow: 0.1em 0.1em 0 black;
|
text-shadow: 0.1em 0.1em 0 black;
|
||||||
|
margin-bottom: 0px;
|
||||||
|
}
|
||||||
|
.noVNC_logo img {
|
||||||
|
width: 45%
|
||||||
}
|
}
|
||||||
.noVNC_logo span{
|
.noVNC_logo span{
|
||||||
color:green;
|
color:green;
|
||||||
|
|
@ -1021,6 +972,11 @@ body {
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#noVNC_keyboard_control .noVNC_selected {
|
||||||
|
background-color:rgb(15, 36, 153);
|
||||||
|
border: 6px rgb(15, 36, 153) solid;
|
||||||
|
}
|
||||||
|
|
||||||
.keyboard-controls .button.ctrl {
|
.keyboard-controls .button.ctrl {
|
||||||
background-image: url("../images/ctrl.svg");
|
background-image: url("../images/ctrl.svg");
|
||||||
background-size: contain;
|
background-size: contain;
|
||||||
|
|
@ -1134,3 +1090,75 @@ body {
|
||||||
font-size: 90px;
|
font-size: 90px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ----------------------------------------
|
||||||
|
* Slider Check boxes
|
||||||
|
* ----------------------------------------
|
||||||
|
*/
|
||||||
|
/* The switch - the box around the slider */
|
||||||
|
.switch {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
width: 30px;
|
||||||
|
height: 16px;
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide default HTML checkbox */
|
||||||
|
.switch input {
|
||||||
|
opacity: 0;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The slider */
|
||||||
|
.slider {
|
||||||
|
position: absolute;
|
||||||
|
cursor: pointer;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: #ccc;
|
||||||
|
-webkit-transition: .4s;
|
||||||
|
transition: .4s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider:before {
|
||||||
|
position: absolute;
|
||||||
|
content: "";
|
||||||
|
height: 14px;
|
||||||
|
width: 14px;
|
||||||
|
left: 4px;
|
||||||
|
bottom: 1px;
|
||||||
|
background-color: white;
|
||||||
|
-webkit-transition: .4s;
|
||||||
|
transition: .4s;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:checked + .slider {
|
||||||
|
background-color: #2196F3;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:focus + .slider {
|
||||||
|
box-shadow: 0 0 1px #2196F3;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:checked + .slider:before {
|
||||||
|
-webkit-transform: translateX(26px);
|
||||||
|
-ms-transform: translateX(26px);
|
||||||
|
transform: translateX(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Rounded sliders */
|
||||||
|
.slider.round {
|
||||||
|
border-radius: 34px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider.round:before {
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider-label {
|
||||||
|
padding-left: 26px;
|
||||||
|
}
|
||||||
|
|
|
||||||
534
app/ui.js
534
app/ui.js
|
|
@ -34,7 +34,7 @@ import "core-js/stable";
|
||||||
import "regenerator-runtime/runtime";
|
import "regenerator-runtime/runtime";
|
||||||
import * as Log from '../core/util/logging.js';
|
import * as Log from '../core/util/logging.js';
|
||||||
import _, { l10n } from './localization.js';
|
import _, { l10n } from './localization.js';
|
||||||
import { isTouchDevice, isSafari, hasScrollbarGutter, dragThreshold, supportsBinaryClipboard, isFirefox, isWindows, isIOS }
|
import { isTouchDevice, isSafari, hasScrollbarGutter, dragThreshold, supportsBinaryClipboard, isFirefox, isWindows, isIOS, supportsPointerLock }
|
||||||
from '../core/util/browser.js';
|
from '../core/util/browser.js';
|
||||||
import { setCapture, getPointerEvent } from '../core/util/events.js';
|
import { setCapture, getPointerEvent } from '../core/util/events.js';
|
||||||
import KeyTable from "../core/input/keysym.js";
|
import KeyTable from "../core/input/keysym.js";
|
||||||
|
|
@ -47,6 +47,7 @@ const PAGE_TITLE = "KasmVNC";
|
||||||
|
|
||||||
var delta = 500;
|
var delta = 500;
|
||||||
var lastKeypressTime = 0;
|
var lastKeypressTime = 0;
|
||||||
|
var lastKeypressCode = -1;
|
||||||
var currentEventCount = -1;
|
var currentEventCount = -1;
|
||||||
var idleCounter = 0;
|
var idleCounter = 0;
|
||||||
|
|
||||||
|
|
@ -65,8 +66,6 @@ const UI = {
|
||||||
controlbarMouseDownClientY: 0,
|
controlbarMouseDownClientY: 0,
|
||||||
controlbarMouseDownOffsetY: 0,
|
controlbarMouseDownOffsetY: 0,
|
||||||
|
|
||||||
lastKeyboardinput: null,
|
|
||||||
defaultKeyboardinputLen: 100,
|
|
||||||
needToCheckClipboardChange: false,
|
needToCheckClipboardChange: false,
|
||||||
|
|
||||||
inhibitReconnect: true,
|
inhibitReconnect: true,
|
||||||
|
|
@ -129,16 +128,13 @@ const UI = {
|
||||||
UI.addControlbarHandlers();
|
UI.addControlbarHandlers();
|
||||||
UI.addTouchSpecificHandlers();
|
UI.addTouchSpecificHandlers();
|
||||||
UI.addExtraKeysHandlers();
|
UI.addExtraKeysHandlers();
|
||||||
|
UI.addGamingHandlers();
|
||||||
UI.addMachineHandlers();
|
UI.addMachineHandlers();
|
||||||
UI.addConnectionControlHandlers();
|
UI.addConnectionControlHandlers();
|
||||||
UI.addClipboardHandlers();
|
UI.addClipboardHandlers();
|
||||||
UI.addSettingsHandlers();
|
UI.addSettingsHandlers();
|
||||||
document.getElementById("noVNC_status")
|
document.getElementById("noVNC_status")
|
||||||
.addEventListener('click', UI.hideStatus);
|
.addEventListener('click', UI.hideStatus);
|
||||||
|
|
||||||
// Bootstrap fallback input handler
|
|
||||||
UI.keyboardinputReset();
|
|
||||||
|
|
||||||
UI.openControlbar();
|
UI.openControlbar();
|
||||||
|
|
||||||
UI.updateVisualState('init');
|
UI.updateVisualState('init');
|
||||||
|
|
@ -151,8 +147,6 @@ const UI = {
|
||||||
UI.connect();
|
UI.connect();
|
||||||
} else {
|
} else {
|
||||||
autoconnect = false;
|
autoconnect = false;
|
||||||
// Show the connect panel on first load unless autoconnecting
|
|
||||||
UI.openConnectPanel();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
window.parent.postMessage({
|
window.parent.postMessage({
|
||||||
|
|
@ -172,6 +166,12 @@ const UI = {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
window.addEventListener("beforeunload", (e) => {
|
||||||
|
if (UI.rfb) {
|
||||||
|
UI.disconnect();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return Promise.resolve(UI.rfb);
|
return Promise.resolve(UI.rfb);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -183,10 +183,9 @@ const UI = {
|
||||||
document.documentElement.mozRequestFullScreen ||
|
document.documentElement.mozRequestFullScreen ||
|
||||||
document.documentElement.webkitRequestFullscreen ||
|
document.documentElement.webkitRequestFullscreen ||
|
||||||
document.body.msRequestFullscreen)) {
|
document.body.msRequestFullscreen)) {
|
||||||
document.getElementById('noVNC_fullscreen_button')
|
UI.showControlInput("noVNC_fullscreen_button")
|
||||||
.classList.remove("noVNC_hidden");
|
UI.addFullscreenHandlers();
|
||||||
UI.addFullscreenHandlers();
|
}
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
initSettings() {
|
initSettings() {
|
||||||
|
|
@ -245,6 +244,9 @@ const UI = {
|
||||||
UI.initSetting('prefer_local_cursor', true);
|
UI.initSetting('prefer_local_cursor', true);
|
||||||
UI.initSetting('toggle_control_panel', false);
|
UI.initSetting('toggle_control_panel', false);
|
||||||
UI.initSetting('enable_perf_stats', false);
|
UI.initSetting('enable_perf_stats', false);
|
||||||
|
UI.initSetting('virtual_keyboard_visible', false);
|
||||||
|
UI.initSetting('enable_ime', false)
|
||||||
|
UI.toggleKeyboardControls();
|
||||||
|
|
||||||
if (WebUtil.isInsideKasmVDI()) {
|
if (WebUtil.isInsideKasmVDI()) {
|
||||||
UI.initSetting('clipboard_up', false);
|
UI.initSetting('clipboard_up', false);
|
||||||
|
|
@ -350,8 +352,7 @@ const UI = {
|
||||||
document.getElementById("noVNC_control_bar")
|
document.getElementById("noVNC_control_bar")
|
||||||
.addEventListener('keydown', UI.keepControlbar);
|
.addEventListener('keydown', UI.keepControlbar);
|
||||||
|
|
||||||
document.getElementById("noVNC_view_drag_button")
|
UI.addClickHandle('noVNC_view_drag_button', UI.toggleViewDrag);
|
||||||
.addEventListener('click', UI.toggleViewDrag);
|
|
||||||
|
|
||||||
document.getElementById("noVNC_control_bar_handle")
|
document.getElementById("noVNC_control_bar_handle")
|
||||||
.addEventListener('mousedown', UI.controlbarHandleMouseDown);
|
.addEventListener('mousedown', UI.controlbarHandleMouseDown);
|
||||||
|
|
@ -371,12 +372,6 @@ const UI = {
|
||||||
addTouchSpecificHandlers() {
|
addTouchSpecificHandlers() {
|
||||||
document.getElementById("noVNC_keyboard_button")
|
document.getElementById("noVNC_keyboard_button")
|
||||||
.addEventListener('click', UI.toggleVirtualKeyboard);
|
.addEventListener('click', UI.toggleVirtualKeyboard);
|
||||||
|
|
||||||
UI.touchKeyboard = new Keyboard(document.getElementById('noVNC_keyboardinput'));
|
|
||||||
UI.touchKeyboard.onkeyevent = UI.keyEvent;
|
|
||||||
UI.touchKeyboard.grab();
|
|
||||||
document.getElementById("noVNC_keyboardinput")
|
|
||||||
.addEventListener('input', UI.keyInput);
|
|
||||||
document.getElementById("noVNC_keyboardinput")
|
document.getElementById("noVNC_keyboardinput")
|
||||||
.addEventListener('focus', UI.onfocusVirtualKeyboard);
|
.addEventListener('focus', UI.onfocusVirtualKeyboard);
|
||||||
document.getElementById("noVNC_keyboardinput")
|
document.getElementById("noVNC_keyboardinput")
|
||||||
|
|
@ -410,8 +405,8 @@ const UI = {
|
||||||
},
|
},
|
||||||
|
|
||||||
addExtraKeysHandlers() {
|
addExtraKeysHandlers() {
|
||||||
document.getElementById("noVNC_toggle_extra_keys_button")
|
UI.addClickHandle('noVNC_toggle_extra_keys_button', UI.toggleExtraKeys);
|
||||||
.addEventListener('click', UI.toggleExtraKeys);
|
|
||||||
document.getElementById("noVNC_toggle_ctrl_button")
|
document.getElementById("noVNC_toggle_ctrl_button")
|
||||||
.addEventListener('click', UI.toggleCtrl);
|
.addEventListener('click', UI.toggleCtrl);
|
||||||
document.getElementById("noVNC_toggle_windows_button")
|
document.getElementById("noVNC_toggle_windows_button")
|
||||||
|
|
@ -426,20 +421,27 @@ const UI = {
|
||||||
.addEventListener('click', UI.sendCtrlAltDel);
|
.addEventListener('click', UI.sendCtrlAltDel);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
addGamingHandlers() {
|
||||||
|
UI.addClickHandle('noVNC_game_mode_button', UI.toggleRelativePointer);
|
||||||
|
document
|
||||||
|
.getElementById("noVNC_setting_pointer_lock")
|
||||||
|
.addEventListener("click", UI.togglePointerLock);
|
||||||
|
},
|
||||||
|
|
||||||
addMachineHandlers() {
|
addMachineHandlers() {
|
||||||
|
UI.addClickHandle('noVNC_power_button', UI.togglePowerPanel);
|
||||||
|
|
||||||
document.getElementById("noVNC_shutdown_button")
|
document.getElementById("noVNC_shutdown_button")
|
||||||
.addEventListener('click', () => UI.rfb.machineShutdown());
|
.addEventListener('click', () => UI.rfb.machineShutdown());
|
||||||
document.getElementById("noVNC_reboot_button")
|
document.getElementById("noVNC_reboot_button")
|
||||||
.addEventListener('click', () => UI.rfb.machineReboot());
|
.addEventListener('click', () => UI.rfb.machineReboot());
|
||||||
document.getElementById("noVNC_reset_button")
|
document.getElementById("noVNC_reset_button")
|
||||||
.addEventListener('click', () => UI.rfb.machineReset());
|
.addEventListener('click', () => UI.rfb.machineReset());
|
||||||
document.getElementById("noVNC_power_button")
|
|
||||||
.addEventListener('click', UI.togglePowerPanel);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
addConnectionControlHandlers() {
|
addConnectionControlHandlers() {
|
||||||
document.getElementById("noVNC_disconnect_button")
|
UI.addClickHandle('noVNC_disconnect_button', UI.disconnect);
|
||||||
.addEventListener('click', UI.disconnect);
|
|
||||||
var connect_btn_el = document.getElementById("noVNC_connect_button");
|
var connect_btn_el = document.getElementById("noVNC_connect_button");
|
||||||
if (typeof(connect_btn_el) != 'undefined' && connect_btn_el != null)
|
if (typeof(connect_btn_el) != 'undefined' && connect_btn_el != null)
|
||||||
{
|
{
|
||||||
|
|
@ -453,8 +455,8 @@ const UI = {
|
||||||
},
|
},
|
||||||
|
|
||||||
addClipboardHandlers() {
|
addClipboardHandlers() {
|
||||||
document.getElementById("noVNC_clipboard_button")
|
UI.addClickHandle('noVNC_clipboard_button', UI.toggleClipboardPanel);
|
||||||
.addEventListener('click', UI.toggleClipboardPanel);
|
|
||||||
document.getElementById("noVNC_clipboard_text")
|
document.getElementById("noVNC_clipboard_text")
|
||||||
.addEventListener('change', UI.clipboardSend);
|
.addEventListener('change', UI.clipboardSend);
|
||||||
document.getElementById("noVNC_clipboard_clear_button")
|
document.getElementById("noVNC_clipboard_clear_button")
|
||||||
|
|
@ -474,8 +476,7 @@ const UI = {
|
||||||
},
|
},
|
||||||
|
|
||||||
addSettingsHandlers() {
|
addSettingsHandlers() {
|
||||||
document.getElementById("noVNC_settings_button")
|
UI.addClickHandle('noVNC_settings_button', UI.toggleSettingsPanel);
|
||||||
.addEventListener('click', UI.toggleSettingsPanel);
|
|
||||||
|
|
||||||
document.getElementById("noVNC_setting_enable_perf_stats").addEventListener('click', UI.showStats);
|
document.getElementById("noVNC_setting_enable_perf_stats").addEventListener('click', UI.showStats);
|
||||||
|
|
||||||
|
|
@ -536,11 +537,15 @@ const UI = {
|
||||||
UI.addSettingChangeHandler('clipboard_seamless');
|
UI.addSettingChangeHandler('clipboard_seamless');
|
||||||
UI.addSettingChangeHandler('clipboard_up');
|
UI.addSettingChangeHandler('clipboard_up');
|
||||||
UI.addSettingChangeHandler('clipboard_down');
|
UI.addSettingChangeHandler('clipboard_down');
|
||||||
|
UI.addSettingChangeHandler('toggle_control_panel');
|
||||||
|
UI.addSettingChangeHandler('virtual_keyboard_visible');
|
||||||
|
UI.addSettingChangeHandler('virtual_keyboard_visible', UI.toggleKeyboardControls);
|
||||||
|
UI.addSettingChangeHandler('enable_ime');
|
||||||
|
UI.addSettingChangeHandler('enable_ime', UI.toggleIMEMode);
|
||||||
},
|
},
|
||||||
|
|
||||||
addFullscreenHandlers() {
|
addFullscreenHandlers() {
|
||||||
document.getElementById("noVNC_fullscreen_button")
|
UI.addClickHandle('noVNC_fullscreen_button', UI.toggleFullscreen);
|
||||||
.addEventListener('click', UI.toggleFullscreen);
|
|
||||||
|
|
||||||
window.addEventListener('fullscreenchange', UI.updateFullscreenButton);
|
window.addEventListener('fullscreenchange', UI.updateFullscreenButton);
|
||||||
window.addEventListener('mozfullscreenchange', UI.updateFullscreenButton);
|
window.addEventListener('mozfullscreenchange', UI.updateFullscreenButton);
|
||||||
|
|
@ -553,6 +558,20 @@ const UI = {
|
||||||
* ==============
|
* ==============
|
||||||
* VISUAL
|
* VISUAL
|
||||||
* ------v------*/
|
* ------v------*/
|
||||||
|
// Ignore clicks that are propogated from child elements in sub panels
|
||||||
|
isControlPanelItemClick(e) {
|
||||||
|
if (!(e && e.target && e.target.classList && e.target.parentNode &&
|
||||||
|
(
|
||||||
|
e.target.classList.contains('noVNC_button') && e.target.parentNode.id !== 'noVNC_modifiers' ||
|
||||||
|
e.target.classList.contains('noVNC_button_div') ||
|
||||||
|
e.target.classList.contains('noVNC_heading')
|
||||||
|
)
|
||||||
|
)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
// Disable/enable controls depending on connection state
|
// Disable/enable controls depending on connection state
|
||||||
updateVisualState(state) {
|
updateVisualState(state) {
|
||||||
|
|
@ -561,6 +580,7 @@ const UI = {
|
||||||
document.documentElement.classList.remove("noVNC_connected");
|
document.documentElement.classList.remove("noVNC_connected");
|
||||||
document.documentElement.classList.remove("noVNC_disconnecting");
|
document.documentElement.classList.remove("noVNC_disconnecting");
|
||||||
document.documentElement.classList.remove("noVNC_reconnecting");
|
document.documentElement.classList.remove("noVNC_reconnecting");
|
||||||
|
document.documentElement.classList.remove("noVNC_disconnected");
|
||||||
|
|
||||||
const transitionElem = document.getElementById("noVNC_transition_text");
|
const transitionElem = document.getElementById("noVNC_transition_text");
|
||||||
if (WebUtil.isInsideKasmVDI())
|
if (WebUtil.isInsideKasmVDI())
|
||||||
|
|
@ -583,6 +603,7 @@ const UI = {
|
||||||
document.documentElement.classList.add("noVNC_disconnecting");
|
document.documentElement.classList.add("noVNC_disconnecting");
|
||||||
break;
|
break;
|
||||||
case 'disconnected':
|
case 'disconnected':
|
||||||
|
document.documentElement.classList.add("noVNC_disconnected");
|
||||||
break;
|
break;
|
||||||
case 'reconnecting':
|
case 'reconnecting':
|
||||||
transitionElem.textContent = _("Reconnecting...");
|
transitionElem.textContent = _("Reconnecting...");
|
||||||
|
|
@ -616,6 +637,7 @@ const UI = {
|
||||||
UI.updatePowerButton();
|
UI.updatePowerButton();
|
||||||
UI.keepControlbar();
|
UI.keepControlbar();
|
||||||
}
|
}
|
||||||
|
//UI.updatePointerLockButton();
|
||||||
|
|
||||||
// State change closes dialogs as they may not be relevant
|
// State change closes dialogs as they may not be relevant
|
||||||
// anymore
|
// anymore
|
||||||
|
|
@ -642,7 +664,12 @@ const UI = {
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
showStatus(text, statusType, time) {
|
showStatus(text, statusType, time, kasm = false) {
|
||||||
|
// If inside the full Kasm CDI framework, don't show messages unless explicitly told to
|
||||||
|
if (WebUtil.isInsideKasmVDI() && !kasm) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const statusElem = document.getElementById('noVNC_status');
|
const statusElem = document.getElementById('noVNC_status');
|
||||||
|
|
||||||
if (typeof statusType === 'undefined') {
|
if (typeof statusType === 'undefined') {
|
||||||
|
|
@ -737,7 +764,9 @@ const UI = {
|
||||||
UI.closeAllPanels();
|
UI.closeAllPanels();
|
||||||
document.getElementById('noVNC_control_bar')
|
document.getElementById('noVNC_control_bar')
|
||||||
.classList.remove("noVNC_open");
|
.classList.remove("noVNC_open");
|
||||||
UI.rfb.focus();
|
if (UI.rfb) {
|
||||||
|
UI.rfb.focus();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleControlbar() {
|
toggleControlbar() {
|
||||||
|
|
@ -910,6 +939,48 @@ const UI = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
addClickHandle(domElementName, funcToCall) {
|
||||||
|
/* Add click handler, will attach to parent if appropriate */
|
||||||
|
var control = document.getElementById(domElementName);
|
||||||
|
if (control.parentNode.classList.contains('noVNC_button_div')) {
|
||||||
|
control.parentNode.addEventListener('click', funcToCall);
|
||||||
|
} else {
|
||||||
|
control.addEventListener('click', funcToCall);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
showControlInput(name) {
|
||||||
|
var control = document.getElementById(name);
|
||||||
|
/*var control_label = document.getElementById(name + '_label');
|
||||||
|
if (control) {
|
||||||
|
control.classList.remove("noVNC_hidden");
|
||||||
|
}
|
||||||
|
if (control_label) {
|
||||||
|
control_label.classList.remove("noVNC_hidden");
|
||||||
|
} */
|
||||||
|
if (control.parentNode.classList.contains('noVNC_button_div')) {
|
||||||
|
control.parentNode.classList.remove("noVNC_hidden")
|
||||||
|
} else {
|
||||||
|
control.classList.remove("noVNC_hidden")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
hideControlInput(name) {
|
||||||
|
var control = document.getElementById(name);
|
||||||
|
/*var control_label = document.getElementById(name + '_label');
|
||||||
|
if (control) {
|
||||||
|
control.classList.add("noVNC_hidden");
|
||||||
|
}
|
||||||
|
if (control_label) {
|
||||||
|
control_label.classList.add("noVNC_hidden");
|
||||||
|
}*/
|
||||||
|
if (control.parentNode.classList.contains('noVNC_button_div')) {
|
||||||
|
control.parentNode.classList.add("noVNC_hidden")
|
||||||
|
} else {
|
||||||
|
control.classList.add("noVNC_hidden")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
/* ------^-------
|
/* ------^-------
|
||||||
* /VISUAL
|
* /VISUAL
|
||||||
* ==============
|
* ==============
|
||||||
|
|
@ -1076,7 +1147,11 @@ const UI = {
|
||||||
.classList.remove("noVNC_selected");
|
.classList.remove("noVNC_selected");
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleSettingsPanel() {
|
toggleSettingsPanel(e) {
|
||||||
|
if (!UI.isControlPanelItemClick(e)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (document.getElementById('noVNC_settings')
|
if (document.getElementById('noVNC_settings')
|
||||||
.classList.contains("noVNC_open")) {
|
.classList.contains("noVNC_open")) {
|
||||||
UI.closeSettingsPanel();
|
UI.closeSettingsPanel();
|
||||||
|
|
@ -1108,7 +1183,11 @@ const UI = {
|
||||||
.classList.remove("noVNC_selected");
|
.classList.remove("noVNC_selected");
|
||||||
},
|
},
|
||||||
|
|
||||||
togglePowerPanel() {
|
togglePowerPanel(e) {
|
||||||
|
if (!UI.isControlPanelItemClick(e)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (document.getElementById('noVNC_power')
|
if (document.getElementById('noVNC_power')
|
||||||
.classList.contains("noVNC_open")) {
|
.classList.contains("noVNC_open")) {
|
||||||
UI.closePowerPanel();
|
UI.closePowerPanel();
|
||||||
|
|
@ -1122,11 +1201,9 @@ const UI = {
|
||||||
if (UI.connected &&
|
if (UI.connected &&
|
||||||
UI.rfb.capabilities.power &&
|
UI.rfb.capabilities.power &&
|
||||||
!UI.rfb.viewOnly) {
|
!UI.rfb.viewOnly) {
|
||||||
document.getElementById('noVNC_power_button')
|
UI.showControlInput('noVNC_power_button')
|
||||||
.classList.remove("noVNC_hidden");
|
|
||||||
} else {
|
} else {
|
||||||
document.getElementById('noVNC_power_button')
|
UI.hideControlInput('noVNC_power_button');
|
||||||
.classList.add("noVNC_hidden");
|
|
||||||
// Close power panel if open
|
// Close power panel if open
|
||||||
UI.closePowerPanel();
|
UI.closePowerPanel();
|
||||||
}
|
}
|
||||||
|
|
@ -1155,7 +1232,11 @@ const UI = {
|
||||||
.classList.remove("noVNC_selected");
|
.classList.remove("noVNC_selected");
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleClipboardPanel() {
|
toggleClipboardPanel(e) {
|
||||||
|
if (!UI.isControlPanelItemClick(e)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (document.getElementById('noVNC_clipboard')
|
if (document.getElementById('noVNC_clipboard')
|
||||||
.classList.contains("noVNC_open")) {
|
.classList.contains("noVNC_open")) {
|
||||||
UI.closeClipboardPanel();
|
UI.closeClipboardPanel();
|
||||||
|
|
@ -1245,16 +1326,6 @@ const UI = {
|
||||||
* CONNECTION
|
* CONNECTION
|
||||||
* ------v------*/
|
* ------v------*/
|
||||||
|
|
||||||
openConnectPanel() {
|
|
||||||
document.getElementById('noVNC_connect_dlg')
|
|
||||||
.classList.add("noVNC_open");
|
|
||||||
},
|
|
||||||
|
|
||||||
closeConnectPanel() {
|
|
||||||
document.getElementById('noVNC_connect_dlg')
|
|
||||||
.classList.remove("noVNC_open");
|
|
||||||
},
|
|
||||||
|
|
||||||
connect(event, password) {
|
connect(event, password) {
|
||||||
|
|
||||||
// Ignore when rfb already exists
|
// Ignore when rfb already exists
|
||||||
|
|
@ -1283,8 +1354,6 @@ const UI = {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
UI.closeConnectPanel();
|
|
||||||
|
|
||||||
UI.updateVisualState('connecting');
|
UI.updateVisualState('connecting');
|
||||||
|
|
||||||
let url;
|
let url;
|
||||||
|
|
@ -1297,7 +1366,9 @@ const UI = {
|
||||||
}
|
}
|
||||||
url += '/' + path;
|
url += '/' + path;
|
||||||
|
|
||||||
UI.rfb = new RFB(document.getElementById('noVNC_container'), url,
|
UI.rfb = new RFB(document.getElementById('noVNC_container'),
|
||||||
|
document.getElementById('noVNC_keyboardinput'),
|
||||||
|
url,
|
||||||
{ shared: UI.getSetting('shared'),
|
{ shared: UI.getSetting('shared'),
|
||||||
repeaterID: UI.getSetting('repeaterID'),
|
repeaterID: UI.getSetting('repeaterID'),
|
||||||
credentials: { password: password } });
|
credentials: { password: password } });
|
||||||
|
|
@ -1310,6 +1381,8 @@ const UI = {
|
||||||
UI.rfb.addEventListener("bottleneck_stats", UI.bottleneckStatsRecieve);
|
UI.rfb.addEventListener("bottleneck_stats", UI.bottleneckStatsRecieve);
|
||||||
UI.rfb.addEventListener("bell", UI.bell);
|
UI.rfb.addEventListener("bell", UI.bell);
|
||||||
UI.rfb.addEventListener("desktopname", UI.updateDesktopName);
|
UI.rfb.addEventListener("desktopname", UI.updateDesktopName);
|
||||||
|
UI.rfb.addEventListener("inputlock", UI.inputLockChanged);
|
||||||
|
UI.rfb.addEventListener("inputlockerror", UI.inputLockError);
|
||||||
UI.rfb.translateShortcuts = UI.getSetting('translate_shortcuts');
|
UI.rfb.translateShortcuts = UI.getSetting('translate_shortcuts');
|
||||||
UI.rfb.clipViewport = UI.getSetting('view_clip');
|
UI.rfb.clipViewport = UI.getSetting('view_clip');
|
||||||
UI.rfb.scaleViewport = UI.getSetting('resize') === 'scale';
|
UI.rfb.scaleViewport = UI.getSetting('resize') === 'scale';
|
||||||
|
|
@ -1330,11 +1403,13 @@ const UI = {
|
||||||
UI.rfb.compressionLevel = parseInt(UI.getSetting('compression'));
|
UI.rfb.compressionLevel = parseInt(UI.getSetting('compression'));
|
||||||
UI.rfb.showDotCursor = UI.getSetting('show_dot');
|
UI.rfb.showDotCursor = UI.getSetting('show_dot');
|
||||||
UI.rfb.idleDisconnect = UI.getSetting('idle_disconnect');
|
UI.rfb.idleDisconnect = UI.getSetting('idle_disconnect');
|
||||||
|
UI.rfb.pointerRelative = UI.getSetting('pointer_relative');
|
||||||
UI.rfb.videoQuality = parseInt(UI.getSetting('video_quality'));
|
UI.rfb.videoQuality = parseInt(UI.getSetting('video_quality'));
|
||||||
UI.rfb.antiAliasing = UI.getSetting('anti_aliasing');
|
UI.rfb.antiAliasing = UI.getSetting('anti_aliasing');
|
||||||
UI.rfb.clipboardUp = UI.getSetting('clipboard_up');
|
UI.rfb.clipboardUp = UI.getSetting('clipboard_up');
|
||||||
UI.rfb.clipboardDown = UI.getSetting('clipboard_down');
|
UI.rfb.clipboardDown = UI.getSetting('clipboard_down');
|
||||||
UI.rfb.clipboardSeamless = UI.getSetting('clipboard_seamless');
|
UI.rfb.clipboardSeamless = UI.getSetting('clipboard_seamless');
|
||||||
|
UI.rfb.keyboard.enableIME = UI.getSetting('enable_ime');
|
||||||
UI.rfb.clipboardBinary = supportsBinaryClipboard() && UI.rfb.clipboardSeamless;
|
UI.rfb.clipboardBinary = supportsBinaryClipboard() && UI.rfb.clipboardSeamless;
|
||||||
|
|
||||||
//Only explicitly request permission to clipboard on browsers that support binary clipboard access
|
//Only explicitly request permission to clipboard on browsers that support binary clipboard access
|
||||||
|
|
@ -1368,7 +1443,6 @@ const UI = {
|
||||||
}
|
}
|
||||||
UI.rfb.addEventListener("disconnect", UI.disconnectedRx);
|
UI.rfb.addEventListener("disconnect", UI.disconnectedRx);
|
||||||
document.getElementById('noVNC_control_bar_anchor').setAttribute('style', 'display: none');
|
document.getElementById('noVNC_control_bar_anchor').setAttribute('style', 'display: none');
|
||||||
document.getElementById('noVNC_connect_dlg').innerHTML = '';
|
|
||||||
|
|
||||||
//keep alive for websocket connection to stay open, since we may not control reverse proxies
|
//keep alive for websocket connection to stay open, since we may not control reverse proxies
|
||||||
//send a keep alive within a window that we control
|
//send a keep alive within a window that we control
|
||||||
|
|
@ -1396,23 +1470,23 @@ const UI = {
|
||||||
document.getElementById('noVNC_status').style.visibility = "visible";
|
document.getElementById('noVNC_status').style.visibility = "visible";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send an event to the parent document (kasm app) to toggle the control panel when ctl is double clicked
|
//key events for KasmVNC control
|
||||||
if (UI.getSetting('toggle_control_panel', false)) {
|
document.addEventListener('keyup', function (event) {
|
||||||
|
if (event.ctrlKey && event.shiftKey) {
|
||||||
document.addEventListener('keyup', function (event) {
|
switch(event.keyCode) {
|
||||||
// CTRL and the various implementations of the mac command key
|
case 49:
|
||||||
if ([17, 224, 91, 93].indexOf(event.keyCode) > -1) {
|
UI.toggleNav();
|
||||||
var thisKeypressTime = new Date();
|
break;
|
||||||
|
case 50:
|
||||||
if (thisKeypressTime - lastKeypressTime <= delta) {
|
UI.toggleRelativePointer();
|
||||||
UI.toggleNav();
|
break;
|
||||||
thisKeypressTime = 0;
|
case 51:
|
||||||
|
UI.togglePointerLock();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
lastKeypressTime = thisKeypressTime;
|
}, true);
|
||||||
}
|
|
||||||
}, true);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
disconnect() {
|
disconnect() {
|
||||||
|
|
@ -1449,7 +1523,6 @@ const UI = {
|
||||||
UI.updateVisualState('disconnected');
|
UI.updateVisualState('disconnected');
|
||||||
|
|
||||||
UI.openControlbar();
|
UI.openControlbar();
|
||||||
UI.openConnectPanel();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
connectFinished(e) {
|
connectFinished(e) {
|
||||||
|
|
@ -1503,7 +1576,6 @@ const UI = {
|
||||||
document.title = PAGE_TITLE;
|
document.title = PAGE_TITLE;
|
||||||
|
|
||||||
UI.openControlbar();
|
UI.openControlbar();
|
||||||
UI.openConnectPanel();
|
|
||||||
|
|
||||||
if (UI.forceReconnect) {
|
if (UI.forceReconnect) {
|
||||||
UI.forceReconnect = false;
|
UI.forceReconnect = false;
|
||||||
|
|
@ -1525,18 +1597,19 @@ const UI = {
|
||||||
UI.showStatus(msg, 'error');
|
UI.showStatus(msg, 'error');
|
||||||
},
|
},
|
||||||
|
|
||||||
/*
|
//send message to parent window
|
||||||
Menu.js Additions
|
sendMessage(name, value) {
|
||||||
*/
|
if (WebUtil.isInsideKasmVDI()) {
|
||||||
receiveMessage(event) {
|
parent.postMessage({ action: name, value: value }, '*' );
|
||||||
//TODO: UNCOMMENT FOR PRODUCTION
|
}
|
||||||
//if (event.origin !== "https://kasmweb.com")
|
},
|
||||||
// return;
|
|
||||||
|
|
||||||
|
//receive message from parent window
|
||||||
|
receiveMessage(event) {
|
||||||
if (event.data && event.data.action) {
|
if (event.data && event.data.action) {
|
||||||
switch (event.data.action) {
|
switch (event.data.action) {
|
||||||
case 'clipboardsnd':
|
case 'clipboardsnd':
|
||||||
if (UI.rfb.clipboardUp) {
|
if (UI.rfb && UI.rfb.clipboardUp) {
|
||||||
UI.rfb.clipboardPasteFrom(event.data.value);
|
UI.rfb.clipboardPasteFrom(event.data.value);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
@ -1544,6 +1617,50 @@ const UI = {
|
||||||
UI.forceSetting('video_quality', parseInt(event.data.value), false);
|
UI.forceSetting('video_quality', parseInt(event.data.value), false);
|
||||||
UI.updateQuality();
|
UI.updateQuality();
|
||||||
break;
|
break;
|
||||||
|
case 'enable_game_mode':
|
||||||
|
if (UI.rfb && !UI.rfb.pointerRelative) {
|
||||||
|
UI.toggleRelativePointer();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'disable_game_mode':
|
||||||
|
if (UI.rfb && UI.rfb.pointerRelative) {
|
||||||
|
UI.toggleRelativePointer();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'enable_pointer_lock':
|
||||||
|
if (UI.rfb && !UI.rfb.pointerLock) {
|
||||||
|
UI.togglePointerLock();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'disable_pointer_lock':
|
||||||
|
if (UI.rfb && UI.rfb.pointerLock) {
|
||||||
|
UI.togglePointerLock();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'show_keyboard_controls':
|
||||||
|
if (!UI.getSetting('virtual_keyboard_visible')) {
|
||||||
|
UI.forceSetting('virtual_keyboard_visible', true, false);
|
||||||
|
UI.showKeyboardControls();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'hide_keyboard_controls':
|
||||||
|
if (UI.getSetting('virtual_keyboard_visible')) {
|
||||||
|
UI.forceSetting('virtual_keyboard_visible', true, false);
|
||||||
|
UI.hideKeyboardControls();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'enable_ime_mode':
|
||||||
|
if (!UI.getSetting('enable_ime')) {
|
||||||
|
UI.forceSetting('enable_ime', true, false);
|
||||||
|
UI.toggleIMEMode();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'disable_ime_mode':
|
||||||
|
if (UI.getSetting('enable_ime')) {
|
||||||
|
UI.forceSetting('enable_ime', false, false);
|
||||||
|
UI.toggleIMEMode();
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -1553,7 +1670,15 @@ const UI = {
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleNav(){
|
toggleNav(){
|
||||||
parent.postMessage({ action: 'togglenav', value: null}, '*' );
|
if (WebUtil.isInsideKasmVDI()) {
|
||||||
|
parent.postMessage({ action: 'togglenav', value: null}, '*' );
|
||||||
|
} else {
|
||||||
|
UI.toggleControlbar();
|
||||||
|
UI.keepControlbar();
|
||||||
|
UI.activateControlbar();
|
||||||
|
UI.controlbarGrabbed = false;
|
||||||
|
UI.showControlbarHint(false);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
clipboardRx(event) {
|
clipboardRx(event) {
|
||||||
|
|
@ -1656,6 +1781,7 @@ const UI = {
|
||||||
document.getElementById('noVNC_fullscreen_button')
|
document.getElementById('noVNC_fullscreen_button')
|
||||||
.classList.remove("noVNC_selected");
|
.classList.remove("noVNC_selected");
|
||||||
}
|
}
|
||||||
|
UI.updatePointerLockButton();
|
||||||
},
|
},
|
||||||
|
|
||||||
/* ------^-------
|
/* ------^-------
|
||||||
|
|
@ -1708,6 +1834,68 @@ const UI = {
|
||||||
UI.updateViewDrag();
|
UI.updateViewDrag();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/* ------^-------
|
||||||
|
* /VIEW CLIPPING
|
||||||
|
* ==============
|
||||||
|
* POINTER LOCK
|
||||||
|
* ------v------*/
|
||||||
|
|
||||||
|
updatePointerLockButton() {
|
||||||
|
// Only show the button if the pointer lock API is properly supported
|
||||||
|
// AND in fullscreen.
|
||||||
|
if (
|
||||||
|
UI.connected &&
|
||||||
|
(document.pointerLockElement !== undefined ||
|
||||||
|
document.mozPointerLockElement !== undefined)
|
||||||
|
) {
|
||||||
|
UI.showControlInput("noVNC_setting_pointer_lock");
|
||||||
|
UI.showControlInput("noVNC_game_mode_button");
|
||||||
|
} else {
|
||||||
|
UI.hideControlInput("noVNC_setting_pointer_lock");
|
||||||
|
UI.hideControlInput("noVNC_game_mode_button");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
togglePointerLock() {
|
||||||
|
if (!supportsPointerLock()) {
|
||||||
|
UI.showStatus('Your browser does not support pointer lock.', 'info', 1500, true);
|
||||||
|
//force pointer lock in UI to false and disable control
|
||||||
|
UI.forceSetting('pointer_lock', false, true);
|
||||||
|
} else {
|
||||||
|
UI.rfb.pointerLock = !UI.rfb.pointerLock;
|
||||||
|
if (UI.getSetting('pointer_lock') !== UI.rfb.pointerLock) {
|
||||||
|
UI.forceSetting('pointer_lock', UI.rfb.pointerLock, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
toggleRelativePointer(event=null, forcedToggleValue=null) {
|
||||||
|
if (!supportsPointerLock()) {
|
||||||
|
UI.showStatus('Your browser does not support pointer lock.', 'info', 1500, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var togglePosition = !UI.rfb.pointerRelative;
|
||||||
|
|
||||||
|
if (UI.rfb.pointerLock !== togglePosition) {
|
||||||
|
UI.rfb.pointerLock = togglePosition;
|
||||||
|
}
|
||||||
|
if (UI.rfb.pointerRelative !== togglePosition) {
|
||||||
|
UI.rfb.pointerRelative = togglePosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (togglePosition) {
|
||||||
|
document.getElementById('noVNC_game_mode_button').classList.add("noVNC_selected");
|
||||||
|
} else {
|
||||||
|
document.getElementById('noVNC_game_mode_button').classList.remove("noVNC_selected");
|
||||||
|
UI.forceSetting('pointer_lock', false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
UI.sendMessage('enable_game_mode', togglePosition);
|
||||||
|
UI.sendMessage('enable_pointer_lock', togglePosition);
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
/* ------^-------
|
/* ------^-------
|
||||||
* /VIEW CLIPPING
|
* /VIEW CLIPPING
|
||||||
* ==============
|
* ==============
|
||||||
|
|
@ -1739,9 +1927,9 @@ const UI = {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (UI.rfb.clipViewport) {
|
if (UI.rfb.clipViewport) {
|
||||||
viewDragButton.classList.remove("noVNC_hidden");
|
UI.showControlInput('noVNC_view_drag_button');
|
||||||
} else {
|
} else {
|
||||||
viewDragButton.classList.add("noVNC_hidden");
|
UI.hideControlInput('noVNC_view_drag_button');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -1881,18 +2069,41 @@ const UI = {
|
||||||
UI.rfb.translateShortcuts = UI.getSetting('translate_shortcuts');
|
UI.rfb.translateShortcuts = UI.getSetting('translate_shortcuts');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
toggleKeyboardControls() {
|
||||||
|
if (UI.getSetting('virtual_keyboard_visible')) {
|
||||||
|
UI.showKeyboardControls();
|
||||||
|
} else {
|
||||||
|
UI.hideKeyboardControls();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
toggleIMEMode() {
|
||||||
|
if (UI.rfb) {
|
||||||
|
if (UI.getSetting('enable_ime')) {
|
||||||
|
UI.rfb.keyboard.enableIME = true;
|
||||||
|
} else {
|
||||||
|
UI.rfb.keyboard.enableIME = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
showKeyboardControls() {
|
showKeyboardControls() {
|
||||||
document.querySelector(".keyboard-controls").classList.add("is-visible");
|
document.getElementById('noVNC_keyboard_control').classList.add("is-visible");
|
||||||
},
|
},
|
||||||
|
|
||||||
hideKeyboardControls() {
|
hideKeyboardControls() {
|
||||||
document.querySelector(".keyboard-controls").classList.remove("is-visible");
|
document.getElementById('noVNC_keyboard_control').classList.remove("is-visible");
|
||||||
},
|
},
|
||||||
|
|
||||||
showVirtualKeyboard() {
|
showVirtualKeyboard() {
|
||||||
const input = document.getElementById('noVNC_keyboardinput');
|
const input = document.getElementById('noVNC_keyboardinput');
|
||||||
|
|
||||||
if (document.activeElement == input) return;
|
if (document.activeElement == input || !UI.rfb) return;
|
||||||
|
|
||||||
|
if (UI.getSetting('virtual_keyboard_visible')) {
|
||||||
|
document.getElementById('noVNC_keyboard_control_handle')
|
||||||
|
.classList.add("noVNC_selected");
|
||||||
|
}
|
||||||
|
|
||||||
input.focus();
|
input.focus();
|
||||||
|
|
||||||
|
|
@ -1916,7 +2127,12 @@ const UI = {
|
||||||
hideVirtualKeyboard() {
|
hideVirtualKeyboard() {
|
||||||
const input = document.getElementById('noVNC_keyboardinput');
|
const input = document.getElementById('noVNC_keyboardinput');
|
||||||
|
|
||||||
if (document.activeElement != input) return;
|
if (document.activeElement != input || !UI.rfb) return;
|
||||||
|
|
||||||
|
if (UI.getSetting('virtual_keyboard_visible')) {
|
||||||
|
document.getElementById('noVNC_keyboard_control_handle')
|
||||||
|
.classList.remove("noVNC_selected");
|
||||||
|
}
|
||||||
|
|
||||||
input.blur();
|
input.blur();
|
||||||
},
|
},
|
||||||
|
|
@ -1941,6 +2157,12 @@ const UI = {
|
||||||
onblurVirtualKeyboard(event) {
|
onblurVirtualKeyboard(event) {
|
||||||
document.getElementById('noVNC_keyboard_button')
|
document.getElementById('noVNC_keyboard_button')
|
||||||
.classList.remove("noVNC_selected");
|
.classList.remove("noVNC_selected");
|
||||||
|
|
||||||
|
if (UI.getSetting('virtual_keyboard_visible')) {
|
||||||
|
document.getElementById('noVNC_keyboard_control_handle')
|
||||||
|
.classList.remove("noVNC_selected");
|
||||||
|
}
|
||||||
|
|
||||||
if (UI.rfb) {
|
if (UI.rfb) {
|
||||||
UI.rfb.focusOnClick = true;
|
UI.rfb.focusOnClick = true;
|
||||||
}
|
}
|
||||||
|
|
@ -1974,83 +2196,6 @@ const UI = {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
},
|
},
|
||||||
|
|
||||||
keyboardinputReset() {
|
|
||||||
const kbi = document.getElementById('noVNC_keyboardinput');
|
|
||||||
kbi.value = new Array(UI.defaultKeyboardinputLen).join("_");
|
|
||||||
UI.lastKeyboardinput = kbi.value;
|
|
||||||
},
|
|
||||||
|
|
||||||
keyEvent(keysym, code, down) {
|
|
||||||
if (!UI.rfb) return;
|
|
||||||
|
|
||||||
UI.rfb.sendKey(keysym, code, down);
|
|
||||||
},
|
|
||||||
|
|
||||||
// When normal keyboard events are left uncought, use the input events from
|
|
||||||
// the keyboardinput element instead and generate the corresponding key events.
|
|
||||||
// This code is required since some browsers on Android are inconsistent in
|
|
||||||
// sending keyCodes in the normal keyboard events when using on screen keyboards.
|
|
||||||
keyInput(event) {
|
|
||||||
|
|
||||||
if (!UI.rfb) return;
|
|
||||||
|
|
||||||
const newValue = event.target.value;
|
|
||||||
|
|
||||||
if (!UI.lastKeyboardinput) {
|
|
||||||
UI.keyboardinputReset();
|
|
||||||
}
|
|
||||||
const oldValue = UI.lastKeyboardinput;
|
|
||||||
|
|
||||||
let newLen;
|
|
||||||
try {
|
|
||||||
// Try to check caret position since whitespace at the end
|
|
||||||
// will not be considered by value.length in some browsers
|
|
||||||
newLen = Math.max(event.target.selectionStart, newValue.length);
|
|
||||||
} catch (err) {
|
|
||||||
// selectionStart is undefined in Google Chrome
|
|
||||||
newLen = newValue.length;
|
|
||||||
}
|
|
||||||
const oldLen = oldValue.length;
|
|
||||||
|
|
||||||
let inputs = newLen - oldLen;
|
|
||||||
let backspaces = inputs < 0 ? -inputs : 0;
|
|
||||||
|
|
||||||
// Compare the old string with the new to account for
|
|
||||||
// text-corrections or other input that modify existing text
|
|
||||||
for (let i = 0; i < Math.min(oldLen, newLen); i++) {
|
|
||||||
if (newValue.charAt(i) != oldValue.charAt(i)) {
|
|
||||||
inputs = newLen - i;
|
|
||||||
backspaces = oldLen - i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send the key events
|
|
||||||
for (let i = 0; i < backspaces; i++) {
|
|
||||||
UI.rfb.sendKey(KeyTable.XK_BackSpace, "Backspace");
|
|
||||||
}
|
|
||||||
for (let i = newLen - inputs; i < newLen; i++) {
|
|
||||||
UI.rfb.sendKey(keysyms.lookup(newValue.charCodeAt(i)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Control the text content length in the keyboardinput element
|
|
||||||
if (newLen > 2 * UI.defaultKeyboardinputLen) {
|
|
||||||
UI.keyboardinputReset();
|
|
||||||
} else if (newLen < 1) {
|
|
||||||
// There always have to be some text in the keyboardinput
|
|
||||||
// element with which backspace can interact.
|
|
||||||
UI.keyboardinputReset();
|
|
||||||
// This sometimes causes the keyboard to disappear for a second
|
|
||||||
// but it is required for the android keyboard to recognize that
|
|
||||||
// text has been added to the field
|
|
||||||
event.target.blur();
|
|
||||||
// This has to be ran outside of the input handler in order to work
|
|
||||||
setTimeout(event.target.focus.bind(event.target), 0);
|
|
||||||
} else {
|
|
||||||
UI.lastKeyboardinput = newValue;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/* ------^-------
|
/* ------^-------
|
||||||
* /KEYBOARD
|
* /KEYBOARD
|
||||||
* ==============
|
* ==============
|
||||||
|
|
@ -2082,7 +2227,11 @@ const UI = {
|
||||||
.classList.remove("noVNC_selected");
|
.classList.remove("noVNC_selected");
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleExtraKeys() {
|
toggleExtraKeys(e) {
|
||||||
|
if (!UI.isControlPanelItemClick(e)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (document.getElementById('noVNC_modifiers').classList.contains("noVNC_open")) {
|
if (document.getElementById('noVNC_modifiers').classList.contains("noVNC_open")) {
|
||||||
UI.closeExtraKeys();
|
UI.closeExtraKeys();
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -2176,19 +2325,15 @@ const UI = {
|
||||||
|
|
||||||
// Hide input related buttons in view only mode
|
// Hide input related buttons in view only mode
|
||||||
if (UI.rfb.viewOnly) {
|
if (UI.rfb.viewOnly) {
|
||||||
document.getElementById('noVNC_keyboard_button')
|
UI.hideControlInput("noVNC_keyboard_button");
|
||||||
.classList.add('noVNC_hidden');
|
UI.hideControlInput("noVNC_toggle_extra_keys_button");
|
||||||
document.getElementById('noVNC_toggle_extra_keys_button')
|
UI.hideControlInput("noVNC_clipboard_button");
|
||||||
.classList.add('noVNC_hidden');
|
UI.hideControlInput("noVNC_game_mode_button");
|
||||||
document.getElementById('noVNC_clipboard_button')
|
|
||||||
.classList.add('noVNC_hidden');
|
|
||||||
} else {
|
} else {
|
||||||
document.getElementById('noVNC_keyboard_button')
|
UI.showControlInput("noVNC_keyboard_button");
|
||||||
.classList.remove('noVNC_hidden');
|
UI.showControlInput("noVNC_toggle_extra_keys_button");
|
||||||
document.getElementById('noVNC_toggle_extra_keys_button')
|
UI.showControlInput("noVNC_clipboard_button");
|
||||||
.classList.remove('noVNC_hidden');
|
UI.showControlInput("noVNC_game_mode_button");
|
||||||
document.getElementById('noVNC_clipboard_button')
|
|
||||||
.classList.remove('noVNC_hidden');
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -2207,6 +2352,39 @@ const UI = {
|
||||||
document.title = e.detail.name + " - " + PAGE_TITLE;
|
document.title = e.detail.name + " - " + PAGE_TITLE;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
inputLockChanged(e) {
|
||||||
|
var pointer_lock_el = document.getElementById("noVNC_setting_pointer_lock");
|
||||||
|
var pointer_rel_el = document.getElementById("noVNC_game_mode_button");
|
||||||
|
|
||||||
|
if (e.detail.pointer) {
|
||||||
|
pointer_lock_el.checked = true;
|
||||||
|
UI.sendMessage('enable_pointer_lock', true);
|
||||||
|
UI.closeControlbar();
|
||||||
|
UI.showStatus('Press Esc Key to Exit Pointer Lock Mode', 'warn', 5000, true);
|
||||||
|
} else {
|
||||||
|
//If in game mode
|
||||||
|
if (UI.rfb.pointerRelative) {
|
||||||
|
UI.showStatus('Game Mode paused, click on screen to resume Game Mode.', 'warn', 5000, true);
|
||||||
|
} else {
|
||||||
|
UI.forceSetting('pointer_lock', false, false);
|
||||||
|
document.getElementById('noVNC_game_mode_button')
|
||||||
|
.classList.remove("noVNC_selected");
|
||||||
|
UI.sendMessage('enable_pointer_lock', false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
inputLockError(e) {
|
||||||
|
UI.showStatus('Unable to enter pointer lock mode.', 'warn', 5000, true);
|
||||||
|
UI.rfb.pointerRelative = false;
|
||||||
|
|
||||||
|
document.getElementById('noVNC_game_mode_button').classList.remove("noVNC_selected");
|
||||||
|
UI.forceSetting('pointer_lock', false, false);
|
||||||
|
|
||||||
|
UI.sendMessage('enable_game_mode', false);
|
||||||
|
UI.sendMessage('enable_pointer_lock', false);
|
||||||
|
},
|
||||||
|
|
||||||
bell(e) {
|
bell(e) {
|
||||||
if (WebUtil.getConfigVar('bell', 'on') === 'on') {
|
if (WebUtil.getConfigVar('bell', 'on') === 'on') {
|
||||||
const promise = document.getElementById('noVNC_bell').play();
|
const promise = document.getElementById('noVNC_bell').play();
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,7 @@ export const encodings = {
|
||||||
pseudoEncodingVideoOutTimeLevel100: -1887,
|
pseudoEncodingVideoOutTimeLevel100: -1887,
|
||||||
|
|
||||||
pseudoEncodingVMwareCursor: 0x574d5664,
|
pseudoEncodingVMwareCursor: 0x574d5664,
|
||||||
|
pseudoEncodingVMwareCursorPosition: 0x574d5666,
|
||||||
pseudoEncodingExtendedClipboard: 0xc0a1e5ce
|
pseudoEncodingExtendedClipboard: 0xc0a1e5ce
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
* KasmVNC: HTML5 VNC client
|
||||||
|
* Copyright (C) 2022 Kasm Technologies Inc
|
||||||
|
* Licensed under MPL 2.0 or any later version (see LICENSE.txt)
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Keys that could be interaction with IME input
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default {
|
||||||
|
0x30: 'Digit0',
|
||||||
|
0x31: 'Digit1',
|
||||||
|
0x32: 'Digit2',
|
||||||
|
0x33: 'Digit3',
|
||||||
|
0x34: 'Digit4',
|
||||||
|
0x35: 'Digit5',
|
||||||
|
0x36: 'Digit6',
|
||||||
|
0x37: 'Digit7',
|
||||||
|
0x38: 'Digit8',
|
||||||
|
0x39: 'Digit9',
|
||||||
|
0x60: 'Numpad0',
|
||||||
|
0x61: 'Numpad1',
|
||||||
|
0x62: 'Numpad2',
|
||||||
|
0x63: 'Numpad3',
|
||||||
|
0x64: 'Numpad4',
|
||||||
|
0x65: 'Numpad5',
|
||||||
|
0x66: 'Numpad6',
|
||||||
|
0x67: 'Numpad7',
|
||||||
|
0x68: 'Numpad8',
|
||||||
|
0x69: 'Numpad9'
|
||||||
|
};
|
||||||
|
|
@ -8,16 +8,20 @@ import * as Log from '../util/logging.js';
|
||||||
import { stopEvent } from '../util/events.js';
|
import { stopEvent } from '../util/events.js';
|
||||||
import * as KeyboardUtil from "./util.js";
|
import * as KeyboardUtil from "./util.js";
|
||||||
import KeyTable from "./keysym.js";
|
import KeyTable from "./keysym.js";
|
||||||
|
import keysyms from "./keysymdef.js";
|
||||||
|
import imekeys from "./imekeys.js";
|
||||||
import * as browser from "../util/browser.js";
|
import * as browser from "../util/browser.js";
|
||||||
import UI from '../../app/ui.js';
|
import UI from '../../app/ui.js';
|
||||||
|
import { isChromiumBased } from '../util/browser.js';
|
||||||
|
|
||||||
//
|
//
|
||||||
// Keyboard event handler
|
// Keyboard event handler
|
||||||
//
|
//
|
||||||
|
|
||||||
export default class Keyboard {
|
export default class Keyboard {
|
||||||
constructor(target) {
|
constructor(screenInput, touchInput) {
|
||||||
this._target = target || null;
|
this._screenInput = screenInput;
|
||||||
|
this._touchInput = touchInput;
|
||||||
|
|
||||||
this._keyDownList = {}; // List of depressed keys
|
this._keyDownList = {}; // List of depressed keys
|
||||||
// (even if they are happy)
|
// (even if they are happy)
|
||||||
|
|
@ -28,11 +32,28 @@ export default class Keyboard {
|
||||||
'keyup': this._handleKeyUp.bind(this),
|
'keyup': this._handleKeyUp.bind(this),
|
||||||
'keydown': this._handleKeyDown.bind(this),
|
'keydown': this._handleKeyDown.bind(this),
|
||||||
'blur': this._allKeysUp.bind(this),
|
'blur': this._allKeysUp.bind(this),
|
||||||
|
'compositionstart' : this._handleCompositionStart.bind(this),
|
||||||
|
'compositionend' : this._handleCompositionEnd.bind(this),
|
||||||
|
'input' : this._handleInput.bind(this)
|
||||||
};
|
};
|
||||||
|
|
||||||
// ===== EVENT HANDLERS =====
|
// ===== EVENT HANDLERS =====
|
||||||
|
|
||||||
this.onkeyevent = () => {}; // Handler for key press/release
|
this.onkeyevent = () => {}; // Handler for key press/release
|
||||||
|
|
||||||
|
this._enableIME = false;
|
||||||
|
this._imeHold = false;
|
||||||
|
this._imeInProgress = false;
|
||||||
|
this._lastKeyboardInput = null;
|
||||||
|
this._defaultKeyboardInputLen = 100;
|
||||||
|
this._keyboardInputReset();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== PUBLIC METHODS =====
|
||||||
|
|
||||||
|
get enableIME() { return this._enableIME; }
|
||||||
|
set enableIME(val) {
|
||||||
|
this._enableIME = val;
|
||||||
|
this.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== PRIVATE METHODS =====
|
// ===== PRIVATE METHODS =====
|
||||||
|
|
@ -95,10 +116,135 @@ export default class Keyboard {
|
||||||
return 'Unidentified';
|
return 'Unidentified';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_handleCompositionStart(e) {
|
||||||
|
Log.Debug("composition started");
|
||||||
|
if (this._enableIME) {
|
||||||
|
this._imeHold = true;
|
||||||
|
this._imeInProgress = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_handleCompositionEnd(e) {
|
||||||
|
Log.Debug("Composition ended");
|
||||||
|
if (this._enableIME) { this._imeInProgress = false; }
|
||||||
|
if (isChromiumBased()) {
|
||||||
|
this._imeHold = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_handleInput(e) {
|
||||||
|
//input event occurs only when keyup keydown events don't prevent default
|
||||||
|
//IME events will make this happen, for example
|
||||||
|
//IME changes can back out old characters and replace, thus send differential if IME
|
||||||
|
//otherwise send new characters
|
||||||
|
if (this._enableIME && this._imeHold) {
|
||||||
|
Log.Debug("IME input change, sending differential");
|
||||||
|
if (!this._imeInProgress) {
|
||||||
|
this._imeHold = false; //Firefox fires compisitionend before last input change
|
||||||
|
}
|
||||||
|
|
||||||
|
const oldValue = this._lastKeyboardInput;
|
||||||
|
const newValue = e.target.value;
|
||||||
|
let diff_start = 0;
|
||||||
|
|
||||||
|
//find position where difference starts
|
||||||
|
for (let i = 0; i < Math.min(oldValue.length, newValue.length); i++) {
|
||||||
|
if (newValue.charAt(i) != oldValue.charAt(i)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
diff_start++;
|
||||||
|
}
|
||||||
|
|
||||||
|
//send backspaces if needed
|
||||||
|
for (let bs = oldValue.length - diff_start; bs > 0; bs--) {
|
||||||
|
this._sendKeyEvent(KeyTable.XK_BackSpace, "Backspace", true);
|
||||||
|
this._sendKeyEvent(KeyTable.XK_BackSpace, "Backspace", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
//send new keys
|
||||||
|
for (let i = diff_start; i < newValue.length; i++) {
|
||||||
|
this._sendKeyEvent(keysyms.lookup(newValue.charCodeAt(i)), 'Unidentified', true);
|
||||||
|
this._sendKeyEvent(keysyms.lookup(newValue.charCodeAt(i)), 'Unidentified', false);
|
||||||
|
}
|
||||||
|
this._lastKeyboardInput = newValue;
|
||||||
|
} else {
|
||||||
|
Log.Debug("Non-IME input change, sending new characters");
|
||||||
|
const newValue = e.target.value;
|
||||||
|
|
||||||
|
if (!this._lastKeyboardInput) {
|
||||||
|
this._keyboardInputReset();
|
||||||
|
}
|
||||||
|
|
||||||
|
const oldValue = this._lastKeyboardInput;
|
||||||
|
let newLen;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Try to check caret position since whitespace at the end
|
||||||
|
// will not be considered by value.length in some browsers
|
||||||
|
newLen = Math.max(e.target.selectionStart, newValue.length);
|
||||||
|
} catch (err) {
|
||||||
|
// selectionStart is undefined in Google Chrome
|
||||||
|
newLen = newValue.length;
|
||||||
|
}
|
||||||
|
const oldLen = oldValue.length;
|
||||||
|
|
||||||
|
let inputs = newLen - oldLen;
|
||||||
|
let backspaces = inputs < 0 ? -inputs : 0;
|
||||||
|
|
||||||
|
// Compare the old string with the new to account for
|
||||||
|
// text-corrections or other input that modify existing text
|
||||||
|
for (let i = 0; i < Math.min(oldLen, newLen); i++) {
|
||||||
|
if (newValue.charAt(i) != oldValue.charAt(i)) {
|
||||||
|
inputs = newLen - i;
|
||||||
|
backspaces = oldLen - i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send the key events
|
||||||
|
for (let i = 0; i < backspaces; i++) {
|
||||||
|
this._sendKeyEvent(KeyTable.XK_BackSpace, "Backspace", true);
|
||||||
|
this._sendKeyEvent(KeyTable.XK_BackSpace, "Backspace", false);
|
||||||
|
}
|
||||||
|
for (let i = newLen - inputs; i < newLen; i++) {
|
||||||
|
this._sendKeyEvent(keysyms.lookup(newValue.charCodeAt(i)), 'Unidentified', true);
|
||||||
|
this._sendKeyEvent(keysyms.lookup(newValue.charCodeAt(i)), 'Unidentified', false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Control the text content length in the keyboardinput element
|
||||||
|
if (newLen > 2 * this._defaultKeyboardInputLen) {
|
||||||
|
this._keyboardInputReset();
|
||||||
|
} else if (newLen < 1) {
|
||||||
|
// There always have to be some text in the keyboardinput
|
||||||
|
// element with which backspace can interact.
|
||||||
|
this._keyboardInputReset();
|
||||||
|
// This sometimes causes the keyboard to disappear for a second
|
||||||
|
// but it is required for the android keyboard to recognize that
|
||||||
|
// text has been added to the field
|
||||||
|
e.target.blur();
|
||||||
|
// This has to be ran outside of the input handler in order to work
|
||||||
|
setTimeout(e.target.focus.bind(e.target), 0);
|
||||||
|
} else {
|
||||||
|
this._lastKeyboardInput = newValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_keyboardInputReset() {
|
||||||
|
this._touchInput.value = new Array(this._defaultKeyboardInputLen).join("_");
|
||||||
|
this._lastKeyboardInput = this._touchInput.value;
|
||||||
|
}
|
||||||
|
|
||||||
_handleKeyDown(e) {
|
_handleKeyDown(e) {
|
||||||
const code = this._getKeyCode(e);
|
const code = this._getKeyCode(e);
|
||||||
let keysym = KeyboardUtil.getKeysym(e);
|
let keysym = KeyboardUtil.getKeysym(e);
|
||||||
|
|
||||||
|
if (this._isIMEInteraction(e)) {
|
||||||
|
//skip event if IME related
|
||||||
|
Log.Debug("Skipping keydown, IME interaction, code: " + code + " keysym: " + keysym + " keycode: " + e.keyCode);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Windows doesn't have a proper AltGr, but handles it using
|
// Windows doesn't have a proper AltGr, but handles it using
|
||||||
// fake Ctrl+Alt. However the remote end might not be Windows,
|
// fake Ctrl+Alt. However the remote end might not be Windows,
|
||||||
// so we need to merge those in to a single AltGr event. We
|
// so we need to merge those in to a single AltGr event. We
|
||||||
|
|
@ -220,10 +366,15 @@ export default class Keyboard {
|
||||||
}
|
}
|
||||||
|
|
||||||
_handleKeyUp(e) {
|
_handleKeyUp(e) {
|
||||||
stopEvent(e);
|
|
||||||
|
|
||||||
const code = this._getKeyCode(e);
|
const code = this._getKeyCode(e);
|
||||||
|
|
||||||
|
if (this._isIMEInteraction(e)) {
|
||||||
|
//skip IME related events
|
||||||
|
Log.Debug("Skipping keyup, IME interaction, code: " + code + " keycode: " + e.keyCode);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
stopEvent(e);
|
||||||
|
|
||||||
// We can't get a release in the middle of an AltGr sequence, so
|
// We can't get a release in the middle of an AltGr sequence, so
|
||||||
// abort that detection
|
// abort that detection
|
||||||
if (this._altGrArmed) {
|
if (this._altGrArmed) {
|
||||||
|
|
@ -271,13 +422,56 @@ export default class Keyboard {
|
||||||
Log.Debug("<< Keyboard.allKeysUp");
|
Log.Debug("<< Keyboard.allKeysUp");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_isIMEInteraction(e) {
|
||||||
|
//input must come from touchinput (textarea) and ime must be enabled
|
||||||
|
if (e.target != this._touchInput || !this._enableIME) { return false; }
|
||||||
|
|
||||||
|
//keyCode of 229 is IME composition
|
||||||
|
if (e.keyCode == 229) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//unfortunately, IME interactions can come through as events
|
||||||
|
//generally safe to ignore and let them come in as "input" events instead
|
||||||
|
//we can't do that with none character keys though
|
||||||
|
//Firefox does not seem to fire key events for IME interaction but Chrome does
|
||||||
|
//TODO: potentially skip this for Firefox browsers, needs more testing with different IME types
|
||||||
|
if (e.keyCode in imekeys) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// ===== PUBLIC METHODS =====
|
// ===== PUBLIC METHODS =====
|
||||||
|
|
||||||
|
focus() {
|
||||||
|
if (this._enableIME) {
|
||||||
|
this._touchInput.focus();
|
||||||
|
} else {
|
||||||
|
this._screenInput.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
blur() {
|
||||||
|
if (this._enableIME) {
|
||||||
|
this._touchInput.blur();
|
||||||
|
} else {
|
||||||
|
this._screenInput.blur();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
grab() {
|
grab() {
|
||||||
//Log.Debug(">> Keyboard.grab");
|
//Log.Debug(">> Keyboard.grab");
|
||||||
|
|
||||||
this._target.addEventListener('keydown', this._eventHandlers.keydown);
|
this._screenInput.addEventListener('keydown', this._eventHandlers.keydown);
|
||||||
this._target.addEventListener('keyup', this._eventHandlers.keyup);
|
this._screenInput.addEventListener('keyup', this._eventHandlers.keyup);
|
||||||
|
|
||||||
|
this._touchInput.addEventListener('keydown', this._eventHandlers.keydown);
|
||||||
|
this._touchInput.addEventListener('keyup', this._eventHandlers.keyup);
|
||||||
|
this._touchInput.addEventListener('compositionstart', this._eventHandlers.compositionstart);
|
||||||
|
this._touchInput.addEventListener('compositionend', this._eventHandlers.compositionend);
|
||||||
|
this._touchInput.addEventListener('input', this._eventHandlers.input);
|
||||||
|
|
||||||
// Release (key up) if window loses focus
|
// Release (key up) if window loses focus
|
||||||
window.addEventListener('blur', this._eventHandlers.blur);
|
window.addEventListener('blur', this._eventHandlers.blur);
|
||||||
|
|
@ -288,8 +482,15 @@ export default class Keyboard {
|
||||||
ungrab() {
|
ungrab() {
|
||||||
//Log.Debug(">> Keyboard.ungrab");
|
//Log.Debug(">> Keyboard.ungrab");
|
||||||
|
|
||||||
this._target.removeEventListener('keydown', this._eventHandlers.keydown);
|
this._screenInput.removeEventListener('keydown', this._eventHandlers.keydown);
|
||||||
this._target.removeEventListener('keyup', this._eventHandlers.keyup);
|
this._screenInput.removeEventListener('keyup', this._eventHandlers.keyup);
|
||||||
|
|
||||||
|
this._touchInput.removeEventListener('keydown', this._eventHandlers.keydown);
|
||||||
|
this._touchInput.removeEventListener('keyup', this._eventHandlers.keyup);
|
||||||
|
this._touchInput.removeEventListener('compositionstart', this._eventHandlers.compositionstart);
|
||||||
|
this._touchInput.removeEventListener('compositionend', this._eventHandlers.compositionend);
|
||||||
|
this._touchInput.removeEventListener('input', this._eventHandlers.input);
|
||||||
|
|
||||||
window.removeEventListener('blur', this._eventHandlers.blur);
|
window.removeEventListener('blur', this._eventHandlers.blur);
|
||||||
|
|
||||||
// Release (key up) all keys that are in a down state
|
// Release (key up) all keys that are in a down state
|
||||||
|
|
|
||||||
184
core/rfb.js
184
core/rfb.js
|
|
@ -34,6 +34,7 @@ import HextileDecoder from "./decoders/hextile.js";
|
||||||
import TightDecoder from "./decoders/tight.js";
|
import TightDecoder from "./decoders/tight.js";
|
||||||
import TightPNGDecoder from "./decoders/tightpng.js";
|
import TightPNGDecoder from "./decoders/tightpng.js";
|
||||||
import UDPDecoder from './decoders/udp.js';
|
import UDPDecoder from './decoders/udp.js';
|
||||||
|
import { toSignedRelative16bit } from './util/int.js';
|
||||||
|
|
||||||
// How many seconds to wait for a disconnect to finish
|
// How many seconds to wait for a disconnect to finish
|
||||||
const DISCONNECT_TIMEOUT = 3;
|
const DISCONNECT_TIMEOUT = 3;
|
||||||
|
|
@ -71,7 +72,7 @@ const extendedClipboardActionNotify = 1 << 27;
|
||||||
const extendedClipboardActionProvide = 1 << 28;
|
const extendedClipboardActionProvide = 1 << 28;
|
||||||
|
|
||||||
export default class RFB extends EventTargetMixin {
|
export default class RFB extends EventTargetMixin {
|
||||||
constructor(target, urlOrChannel, options) {
|
constructor(target, touchInput, urlOrChannel, options) {
|
||||||
if (!target) {
|
if (!target) {
|
||||||
throw new Error("Must specify target");
|
throw new Error("Must specify target");
|
||||||
}
|
}
|
||||||
|
|
@ -172,6 +173,9 @@ export default class RFB extends EventTargetMixin {
|
||||||
this._mousePos = {};
|
this._mousePos = {};
|
||||||
this._mouseButtonMask = 0;
|
this._mouseButtonMask = 0;
|
||||||
this._mouseLastMoveTime = 0;
|
this._mouseLastMoveTime = 0;
|
||||||
|
this._pointerLock = false;
|
||||||
|
this._pointerLockPos = { x: 0, y: 0 };
|
||||||
|
this._pointerRelativeEnabled = false;
|
||||||
this._mouseLastPinchAndZoomTime = 0;
|
this._mouseLastPinchAndZoomTime = 0;
|
||||||
this._viewportDragging = false;
|
this._viewportDragging = false;
|
||||||
this._viewportDragPos = {};
|
this._viewportDragPos = {};
|
||||||
|
|
@ -191,6 +195,8 @@ export default class RFB extends EventTargetMixin {
|
||||||
focusCanvas: this._focusCanvas.bind(this),
|
focusCanvas: this._focusCanvas.bind(this),
|
||||||
windowResize: this._windowResize.bind(this),
|
windowResize: this._windowResize.bind(this),
|
||||||
handleMouse: this._handleMouse.bind(this),
|
handleMouse: this._handleMouse.bind(this),
|
||||||
|
handlePointerLockChange: this._handlePointerLockChange.bind(this),
|
||||||
|
handlePointerLockError: this._handlePointerLockError.bind(this),
|
||||||
handleWheel: this._handleWheel.bind(this),
|
handleWheel: this._handleWheel.bind(this),
|
||||||
handleGesture: this._handleGesture.bind(this),
|
handleGesture: this._handleGesture.bind(this),
|
||||||
};
|
};
|
||||||
|
|
@ -247,7 +253,7 @@ export default class RFB extends EventTargetMixin {
|
||||||
}
|
}
|
||||||
this._display.onflush = this._onFlush.bind(this);
|
this._display.onflush = this._onFlush.bind(this);
|
||||||
|
|
||||||
this._keyboard = new Keyboard(this._canvas);
|
this._keyboard = new Keyboard(this._canvas, touchInput);
|
||||||
this._keyboard.onkeyevent = this._handleKeyEvent.bind(this);
|
this._keyboard.onkeyevent = this._handleKeyEvent.bind(this);
|
||||||
|
|
||||||
this._gestures = new GestureHandler();
|
this._gestures = new GestureHandler();
|
||||||
|
|
@ -336,6 +342,45 @@ export default class RFB extends EventTargetMixin {
|
||||||
|
|
||||||
// ===== PROPERTIES =====
|
// ===== PROPERTIES =====
|
||||||
|
|
||||||
|
get pointerLock() { return this._pointerLock; }
|
||||||
|
set pointerLock(value) {
|
||||||
|
if (!this._pointerLock) {
|
||||||
|
if (this._canvas.requestPointerLock) {
|
||||||
|
this._canvas.requestPointerLock();
|
||||||
|
this._pointerLockChanging = true;
|
||||||
|
} else if (this._canvas.mozRequestPointerLock) {
|
||||||
|
this._canvas.mozRequestPointerLock();
|
||||||
|
this._pointerLockChanging = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (window.document.exitPointerLock) {
|
||||||
|
window.document.exitPointerLock();
|
||||||
|
this._pointerLockChanging = true;
|
||||||
|
} else if (window.document.mozExitPointerLock) {
|
||||||
|
window.document.mozExitPointerLock();
|
||||||
|
this._pointerLockChanging = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get pointerRelative() { return this._pointerRelativeEnabled; }
|
||||||
|
set pointerRelative(value)
|
||||||
|
{
|
||||||
|
this._pointerRelativeEnabled = value;
|
||||||
|
if (value) {
|
||||||
|
let max_w = ((this._display.scale === 1) ? this._fbWidth : (this._fbWidth * this._display.scale));
|
||||||
|
let max_h = ((this._display.scale === 1) ? this._fbHeight : (this._fbHeight * this._display.scale));
|
||||||
|
this._pointerLockPos.x = Math.floor(max_w / 2);
|
||||||
|
this._pointerLockPos.y = Math.floor(max_h / 2);
|
||||||
|
|
||||||
|
// reset the cursor position to center
|
||||||
|
this._mousePos = { x: this._pointerLockPos.x , y: this._pointerLockPos.y };
|
||||||
|
this._cursor.move(this._pointerLockPos.x, this._pointerLockPos.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get keyboard() { return this._keyboard; }
|
||||||
|
|
||||||
get clipboardBinary() { return this._clipboardMode; }
|
get clipboardBinary() { return this._clipboardMode; }
|
||||||
set clipboardBinary(val) { this._clipboardMode = val; }
|
set clipboardBinary(val) { this._clipboardMode = val; }
|
||||||
|
|
||||||
|
|
@ -748,11 +793,11 @@ export default class RFB extends EventTargetMixin {
|
||||||
}
|
}
|
||||||
|
|
||||||
focus() {
|
focus() {
|
||||||
this._canvas.focus();
|
this._keyboard.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
blur() {
|
blur() {
|
||||||
this._canvas.blur();
|
this._keyboard.blur();
|
||||||
}
|
}
|
||||||
|
|
||||||
clipboardPasteFrom(text) {
|
clipboardPasteFrom(text) {
|
||||||
|
|
@ -914,6 +959,15 @@ export default class RFB extends EventTargetMixin {
|
||||||
// reason so we have to explicitly block it
|
// reason so we have to explicitly block it
|
||||||
this._canvas.addEventListener('contextmenu', this._eventHandlers.handleMouse);
|
this._canvas.addEventListener('contextmenu', this._eventHandlers.handleMouse);
|
||||||
|
|
||||||
|
// Pointer Lock listeners need to be installed in document instead of the canvas.
|
||||||
|
if (document.onpointerlockchange !== undefined) {
|
||||||
|
document.addEventListener('pointerlockchange', this._eventHandlers.handlePointerLockChange, false);
|
||||||
|
document.addEventListener('pointerlockerror', this._eventHandlers.handlePointerLockError, false);
|
||||||
|
} else if (document.onmozpointerlockchange !== undefined) {
|
||||||
|
document.addEventListener('mozpointerlockchange', this._eventHandlers.handlePointerLockChange, false);
|
||||||
|
document.addEventListener('mozpointerlockerror', this._eventHandlers.handlePointerLockError, false);
|
||||||
|
}
|
||||||
|
|
||||||
// Wheel events
|
// Wheel events
|
||||||
this._canvas.addEventListener("wheel", this._eventHandlers.handleWheel);
|
this._canvas.addEventListener("wheel", this._eventHandlers.handleWheel);
|
||||||
|
|
||||||
|
|
@ -1036,6 +1090,13 @@ export default class RFB extends EventTargetMixin {
|
||||||
this._canvas.removeEventListener('mousemove', this._eventHandlers.handleMouse);
|
this._canvas.removeEventListener('mousemove', this._eventHandlers.handleMouse);
|
||||||
this._canvas.removeEventListener('click', this._eventHandlers.handleMouse);
|
this._canvas.removeEventListener('click', this._eventHandlers.handleMouse);
|
||||||
this._canvas.removeEventListener('contextmenu', this._eventHandlers.handleMouse);
|
this._canvas.removeEventListener('contextmenu', this._eventHandlers.handleMouse);
|
||||||
|
if (document.onpointerlockchange !== undefined) {
|
||||||
|
document.removeEventListener('pointerlockchange', this._eventHandlers.handlePointerLockChange);
|
||||||
|
document.removeEventListener('pointerlockerror', this._eventHandlers.handlePointerLockError);
|
||||||
|
} else if (document.onmozpointerlockchange !== undefined) {
|
||||||
|
document.removeEventListener('mozpointerlockchange', this._eventHandlers.handlePointerLockChange);
|
||||||
|
document.removeEventListener('mozpointerlockerror', this._eventHandlers.handlePointerLockError);
|
||||||
|
}
|
||||||
this._canvas.removeEventListener("mousedown", this._eventHandlers.focusCanvas);
|
this._canvas.removeEventListener("mousedown", this._eventHandlers.focusCanvas);
|
||||||
this._canvas.removeEventListener("touchstart", this._eventHandlers.focusCanvas);
|
this._canvas.removeEventListener("touchstart", this._eventHandlers.focusCanvas);
|
||||||
window.removeEventListener('resize', this._eventHandlers.windowResize);
|
window.removeEventListener('resize', this._eventHandlers.windowResize);
|
||||||
|
|
@ -1074,11 +1135,18 @@ export default class RFB extends EventTargetMixin {
|
||||||
value: null
|
value: null
|
||||||
}, "*");
|
}, "*");
|
||||||
|
|
||||||
|
// Re-enable pointerLock if relative cursor is enabled
|
||||||
|
// pointerLock must come from user initiated event
|
||||||
|
if (!this._pointerLock && this._pointerRelativeEnabled) {
|
||||||
|
this.pointerLock = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.focusOnClick) {
|
if (!this.focusOnClick) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.focus();
|
this.focus();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_setDesktopName(name) {
|
_setDesktopName(name) {
|
||||||
|
|
@ -1409,8 +1477,34 @@ export default class RFB extends EventTargetMixin {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let pos = clientToElement(ev.clientX, ev.clientY,
|
let pos;
|
||||||
|
if (this._pointerLock && !this._pointerRelativeEnabled) {
|
||||||
|
let max_w = ((this._display.scale === 1) ? this._fbWidth : (this._fbWidth * this._display.scale));
|
||||||
|
let max_h = ((this._display.scale === 1) ? this._fbHeight : (this._fbHeight * this._display.scale));
|
||||||
|
pos = {
|
||||||
|
x: this._mousePos.x + ev.movementX,
|
||||||
|
y: this._mousePos.y + ev.movementY,
|
||||||
|
};
|
||||||
|
if (pos.x < 0) {
|
||||||
|
pos.x = 0;
|
||||||
|
} else if (pos.x > max_w) {
|
||||||
|
pos.x = max_w;
|
||||||
|
}
|
||||||
|
if (pos.y < 0) {
|
||||||
|
pos.y = 0;
|
||||||
|
} else if (pos.y > max_h) {
|
||||||
|
pos.y = max_h;
|
||||||
|
}
|
||||||
|
this._cursor.move(pos.x, pos.y);
|
||||||
|
} else if (this._pointerLock && this._pointerRelativeEnabled) {
|
||||||
|
pos = {
|
||||||
|
x: this._mousePos.x + ev.movementX,
|
||||||
|
y: this._mousePos.y + ev.movementY,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
pos = clientToElement(ev.clientX, ev.clientY,
|
||||||
this._canvas);
|
this._canvas);
|
||||||
|
}
|
||||||
|
|
||||||
switch (ev.type) {
|
switch (ev.type) {
|
||||||
case 'mousedown':
|
case 'mousedown':
|
||||||
|
|
@ -1526,12 +1620,54 @@ export default class RFB extends EventTargetMixin {
|
||||||
this._mouseLastMoveTime = Date.now();
|
this._mouseLastMoveTime = Date.now();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_handlePointerLockChange(env) {
|
||||||
|
if (
|
||||||
|
document.pointerLockElement === this._canvas ||
|
||||||
|
document.mozPointerLockElement === this._canvas
|
||||||
|
) {
|
||||||
|
this._pointerLock = true;
|
||||||
|
this._cursor.setEmulateCursor(true);
|
||||||
|
} else {
|
||||||
|
this._pointerLock = false;
|
||||||
|
this._cursor.setEmulateCursor(false);
|
||||||
|
}
|
||||||
|
this.dispatchEvent(new CustomEvent(
|
||||||
|
"inputlock",
|
||||||
|
{ detail: { pointer: this._pointerLock }, }));
|
||||||
|
}
|
||||||
|
|
||||||
|
_handlePointerLockError() {
|
||||||
|
this._pointerLockChanging = false;
|
||||||
|
this.dispatchEvent(new CustomEvent(
|
||||||
|
"inputlockerror",
|
||||||
|
{ detail: { pointer: this._pointerLock }, }));
|
||||||
|
}
|
||||||
|
|
||||||
_sendMouse(x, y, mask) {
|
_sendMouse(x, y, mask) {
|
||||||
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),
|
if (this._pointerLock && this._pointerRelativeEnabled) {
|
||||||
|
|
||||||
|
// Use releative cursor position
|
||||||
|
var rel_16_x = toSignedRelative16bit(x - this._pointerLockPos.x);
|
||||||
|
var rel_16_y = toSignedRelative16bit(y - this._pointerLockPos.y);
|
||||||
|
|
||||||
|
//console.log("new_pos x" + x + ", y" + y);
|
||||||
|
//console.log("lock x " + this._pointerLockPos.x + ", y " + this._pointerLockPos.y);
|
||||||
|
//console.log("rel x " + rel_16_x + ", y " + rel_16_y);
|
||||||
|
|
||||||
|
RFB.messages.pointerEvent(this._sock, rel_16_x,
|
||||||
|
rel_16_y, mask);
|
||||||
|
|
||||||
|
// reset the cursor position to center
|
||||||
|
this._mousePos = { x: this._pointerLockPos.x , y: this._pointerLockPos.y };
|
||||||
|
this._cursor.move(this._pointerLockPos.x, this._pointerLockPos.y);
|
||||||
|
} else {
|
||||||
|
RFB.messages.pointerEvent(this._sock, this._display.absX(x),
|
||||||
this._display.absY(y), mask);
|
this._display.absY(y), mask);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_sendScroll(x, y, dX, dY) {
|
_sendScroll(x, y, dX, dY) {
|
||||||
|
|
@ -2352,16 +2488,16 @@ export default class RFB extends EventTargetMixin {
|
||||||
encs.push(encodings.pseudoEncodingVideoScalingLevel0 + this.videoScaling);
|
encs.push(encodings.pseudoEncodingVideoScalingLevel0 + this.videoScaling);
|
||||||
encs.push(encodings.pseudoEncodingFrameRateLevel10 + this.frameRate - 10);
|
encs.push(encodings.pseudoEncodingFrameRateLevel10 + this.frameRate - 10);
|
||||||
encs.push(encodings.pseudoEncodingMaxVideoResolution);
|
encs.push(encodings.pseudoEncodingMaxVideoResolution);
|
||||||
// preferBandwidth choses preset settings. Since we expose all the settings, lets not pass this
|
|
||||||
|
// preferBandwidth choses preset settings. Since we expose all the settings, lets not pass this
|
||||||
if (this.preferBandwidth) // must be last - server processes in reverse order
|
if (this.preferBandwidth) // must be last - server processes in reverse order
|
||||||
encs.push(encodings.pseudoEncodingPreferBandwidth);
|
encs.push(encodings.pseudoEncodingPreferBandwidth);
|
||||||
|
|
||||||
if (supportsCursorURIs && this._fbDepth == 24) {
|
if (this._fbDepth == 24) {
|
||||||
if (this.preferLocalCursor || !isTouchDevice) {
|
encs.push(encodings.pseudoEncodingVMwareCursor);
|
||||||
encs.push(encodings.pseudoEncodingVMwareCursor);
|
encs.push(encodings.pseudoEncodingCursor);
|
||||||
encs.push(encodings.pseudoEncodingCursor);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
encs.push(encodings.pseudoEncodingVMwareCursorPosition);
|
||||||
|
|
||||||
RFB.messages.clientEncodings(this._sock, encs);
|
RFB.messages.clientEncodings(this._sock, encs);
|
||||||
}
|
}
|
||||||
|
|
@ -2595,6 +2731,10 @@ export default class RFB extends EventTargetMixin {
|
||||||
|
|
||||||
|
|
||||||
for (let i = 0; i < num; i++) {
|
for (let i = 0; i < num; i++) {
|
||||||
|
if (this._sock.rQwait("Binary Clipboard op id", 4, buffByteLen)) { return false; }
|
||||||
|
buffByteLen += 4;
|
||||||
|
let clipid = this._sock.rQshift32();
|
||||||
|
|
||||||
if (this._sock.rQwait("Binary Clipboard mimelen", 1, buffByteLen)) { return false; }
|
if (this._sock.rQwait("Binary Clipboard mimelen", 1, buffByteLen)) { return false; }
|
||||||
buffByteLen++;
|
buffByteLen++;
|
||||||
let mimelen = this._sock.rQshift8();
|
let mimelen = this._sock.rQshift8();
|
||||||
|
|
@ -2635,9 +2775,9 @@ export default class RFB extends EventTargetMixin {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.clipboardBinary) { continue; }
|
Log.Info("Processed binary clipboard (ID: " + clipid + ") of MIME " + mime + " of length " + len);
|
||||||
|
|
||||||
Log.Info("Processed binary clipboard of MIME " + mime + " of length " + len);
|
if (!this.clipboardBinary) { continue; }
|
||||||
|
|
||||||
clipItemData[mime] = new Blob([data], { type: mime });
|
clipItemData[mime] = new Blob([data], { type: mime });
|
||||||
break;
|
break;
|
||||||
|
|
@ -2987,6 +3127,9 @@ export default class RFB extends EventTargetMixin {
|
||||||
case encodings.pseudoEncodingVMwareCursor:
|
case encodings.pseudoEncodingVMwareCursor:
|
||||||
return this._handleVMwareCursor();
|
return this._handleVMwareCursor();
|
||||||
|
|
||||||
|
case encodings.pseudoEncodingVMwareCursorPosition:
|
||||||
|
return this._handleVMwareCursorPosition();
|
||||||
|
|
||||||
case encodings.pseudoEncodingCursor:
|
case encodings.pseudoEncodingCursor:
|
||||||
return this._handleCursor();
|
return this._handleCursor();
|
||||||
|
|
||||||
|
|
@ -3125,6 +3268,19 @@ export default class RFB extends EventTargetMixin {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_handleVMwareCursorPosition() {
|
||||||
|
const x = this._FBU.x;
|
||||||
|
const y = this._FBU.y;
|
||||||
|
|
||||||
|
if (this._pointerLock) {
|
||||||
|
// Only attempt to match the server's pointer position if we are in
|
||||||
|
// pointer lock mode.
|
||||||
|
this._mousePos = { x: x, y: y };
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
_handleCursor() {
|
_handleCursor() {
|
||||||
const hotx = this._FBU.x; // hotspot-x
|
const hotx = this._FBU.x; // hotspot-x
|
||||||
const hoty = this._FBU.y; // hotspot-y
|
const hoty = this._FBU.y; // hotspot-y
|
||||||
|
|
|
||||||
|
|
@ -97,6 +97,51 @@ export function isSafari() {
|
||||||
navigator.userAgent.indexOf('Chrome') === -1);
|
navigator.userAgent.indexOf('Chrome') === -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns IE version number if IE or older Edge browser
|
||||||
|
export function isIE() {
|
||||||
|
var ua = window.navigator.userAgent;
|
||||||
|
|
||||||
|
// Test values; Uncomment to check result &
|
||||||
|
|
||||||
|
// IE 10
|
||||||
|
// ua = 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; Trident/6.0)';
|
||||||
|
|
||||||
|
// IE 11
|
||||||
|
// ua = 'Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko';
|
||||||
|
|
||||||
|
// Edge 12 (Spartan)
|
||||||
|
// ua = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71 Safari/537.36 Edge/12.0';
|
||||||
|
|
||||||
|
// Edge 13
|
||||||
|
// ua = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Safari/537.36 Edge/13.10586';
|
||||||
|
|
||||||
|
var msie = ua.indexOf('MSIE ');
|
||||||
|
var ie_ver = false;
|
||||||
|
if (msie > 0) {
|
||||||
|
// IE 10 or older => return version number
|
||||||
|
ie_ver = parseInt(ua.substring(msie + 5, ua.indexOf('.', msie)), 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
var trident = ua.indexOf('Trident/');
|
||||||
|
if (trident > 0) {
|
||||||
|
// IE 11 => return version number
|
||||||
|
var rv = ua.indexOf('rv:');
|
||||||
|
ie_ver = parseInt(ua.substring(rv + 3, ua.indexOf('.', rv)), 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
var edge = ua.indexOf('Edge/');
|
||||||
|
if (edge > 0) {
|
||||||
|
// Edge (IE 12+) => return version number
|
||||||
|
ie_ver = parseInt(ua.substring(edge + 5, ua.indexOf('.', edge)), 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ie_ver;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isChromiumBased() {
|
||||||
|
return (!!window.chrome);
|
||||||
|
}
|
||||||
|
|
||||||
export function isFirefox() {
|
export function isFirefox() {
|
||||||
return navigator && !!(/firefox/i).exec(navigator.userAgent);
|
return navigator && !!(/firefox/i).exec(navigator.userAgent);
|
||||||
}
|
}
|
||||||
|
|
@ -107,3 +152,10 @@ export function supportsBinaryClipboard() {
|
||||||
return (navigator.clipboard && typeof navigator.clipboard.read === "function");
|
return (navigator.clipboard && typeof navigator.clipboard.read === "function");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function supportsPointerLock() {
|
||||||
|
//Older versions of edge do support browser lock, but seems to not behave as expected
|
||||||
|
//Disable on browsers that don't fully support or work as expected
|
||||||
|
if (isIOS() || isIE()) { return false; }
|
||||||
|
return (document.exitPointerLock);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,21 +6,19 @@
|
||||||
|
|
||||||
import { supportsCursorURIs, isTouchDevice } from './browser.js';
|
import { supportsCursorURIs, isTouchDevice } from './browser.js';
|
||||||
|
|
||||||
const useFallback = !supportsCursorURIs || isTouchDevice;
|
const needsFallback = !supportsCursorURIs || isTouchDevice;
|
||||||
|
|
||||||
export default class Cursor {
|
export default class Cursor {
|
||||||
constructor() {
|
constructor() {
|
||||||
this._target = null;
|
this._target = null;
|
||||||
|
|
||||||
this._canvas = document.createElement('canvas');
|
this._canvas = document.createElement('canvas');
|
||||||
|
this._canvas.style.position = 'fixed';
|
||||||
if (useFallback) {
|
this._canvas.style.zIndex = '65535';
|
||||||
this._canvas.style.position = 'fixed';
|
this._canvas.style.pointerEvents = 'none';
|
||||||
this._canvas.style.zIndex = '65535';
|
// Can't use "display" because of Firefox bug #1445997
|
||||||
this._canvas.style.pointerEvents = 'none';
|
this._canvas.style.visibility = 'hidden';
|
||||||
// Can't use "display" because of Firefox bug #1445997
|
this._useFallback = needsFallback;
|
||||||
this._canvas.style.visibility = 'hidden';
|
|
||||||
}
|
|
||||||
|
|
||||||
this._position = { x: 0, y: 0 };
|
this._position = { x: 0, y: 0 };
|
||||||
this._hotSpot = { x: 0, y: 0 };
|
this._hotSpot = { x: 0, y: 0 };
|
||||||
|
|
@ -40,9 +38,15 @@ export default class Cursor {
|
||||||
|
|
||||||
this._target = target;
|
this._target = target;
|
||||||
|
|
||||||
if (useFallback) {
|
|
||||||
document.body.appendChild(this._canvas);
|
|
||||||
|
|
||||||
|
document.body.appendChild(this._canvas);
|
||||||
|
|
||||||
|
if (needsFallback) {
|
||||||
|
// Only add the event listeners if this will be responsible for
|
||||||
|
// rendering the cursor all the time. Otherwise, the cursor will
|
||||||
|
// only be rendered then the forced emulation is turned on, and
|
||||||
|
// that doesn't require this class to be adjusting the cursor
|
||||||
|
// position.
|
||||||
const options = { capture: true, passive: true };
|
const options = { capture: true, passive: true };
|
||||||
this._target.addEventListener('mouseover', this._eventHandlers.mouseover, options);
|
this._target.addEventListener('mouseover', this._eventHandlers.mouseover, options);
|
||||||
this._target.addEventListener('mouseleave', this._eventHandlers.mouseleave, options);
|
this._target.addEventListener('mouseleave', this._eventHandlers.mouseleave, options);
|
||||||
|
|
@ -58,16 +62,16 @@ export default class Cursor {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (useFallback) {
|
if (needsFallback) {
|
||||||
const options = { capture: true, passive: true };
|
const options = { capture: true, passive: true };
|
||||||
this._target.removeEventListener('mouseover', this._eventHandlers.mouseover, options);
|
this._target.removeEventListener('mouseover', this._eventHandlers.mouseover, options);
|
||||||
this._target.removeEventListener('mouseleave', this._eventHandlers.mouseleave, options);
|
this._target.removeEventListener('mouseleave', this._eventHandlers.mouseleave, options);
|
||||||
this._target.removeEventListener('mousemove', this._eventHandlers.mousemove, options);
|
this._target.removeEventListener('mousemove', this._eventHandlers.mousemove, options);
|
||||||
this._target.removeEventListener('mouseup', this._eventHandlers.mouseup, options);
|
this._target.removeEventListener('mouseup', this._eventHandlers.mouseup, options);
|
||||||
|
|
||||||
document.body.removeChild(this._canvas);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
document.body.removeChild(this._canvas);
|
||||||
|
|
||||||
this._target = null;
|
this._target = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -91,9 +95,10 @@ export default class Cursor {
|
||||||
ctx.clearRect(0, 0, w, h);
|
ctx.clearRect(0, 0, w, h);
|
||||||
ctx.putImageData(img, 0, 0);
|
ctx.putImageData(img, 0, 0);
|
||||||
|
|
||||||
if (useFallback) {
|
if (this._useFallback || needsFallback) {
|
||||||
this._updatePosition();
|
this._updatePosition();
|
||||||
} else {
|
}
|
||||||
|
if (!needsFallback) {
|
||||||
let url = this._canvas.toDataURL();
|
let url = this._canvas.toDataURL();
|
||||||
this._target.style.cursor = 'url(' + url + ')' + hotx + ' ' + hoty + ', default';
|
this._target.style.cursor = 'url(' + url + ')' + hotx + ' ' + hoty + ', default';
|
||||||
}
|
}
|
||||||
|
|
@ -112,7 +117,7 @@ export default class Cursor {
|
||||||
// Mouse events might be emulated, this allows
|
// Mouse events might be emulated, this allows
|
||||||
// moving the cursor in such cases
|
// moving the cursor in such cases
|
||||||
move(clientX, clientY) {
|
move(clientX, clientY) {
|
||||||
if (!useFallback) {
|
if (!this._useFallback) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// clientX/clientY are relative the _visual viewport_,
|
// clientX/clientY are relative the _visual viewport_,
|
||||||
|
|
@ -130,6 +135,22 @@ export default class Cursor {
|
||||||
this._updateVisibility(target);
|
this._updateVisibility(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Force the use of cursor emulation. This is needed when the pointer lock
|
||||||
|
// is in use, since the browser will not render the cursor.
|
||||||
|
setEmulateCursor(emulate) {
|
||||||
|
if (needsFallback) {
|
||||||
|
// We need to use the fallback all the time, so we shouldn't update
|
||||||
|
// the fallback flag.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._useFallback = emulate;
|
||||||
|
if (this._useFallback) {
|
||||||
|
this._showCursor();
|
||||||
|
} else {
|
||||||
|
this._hideCursor();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_handleMouseOver(event) {
|
_handleMouseOver(event) {
|
||||||
// This event could be because we're entering the target, or
|
// This event could be because we're entering the target, or
|
||||||
// moving around amongst its sub elements. Let the move handler
|
// moving around amongst its sub elements. Let the move handler
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,36 @@ export function toSigned32bit(toConvert) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Fast hashing function with low entropy, not for security uses.
|
* Converts a signed 32bit integer to a signed 16bit int
|
||||||
|
* Uses second most significant bit to represent it is relative
|
||||||
*/
|
*/
|
||||||
|
export function toSignedRelative16bit(toConvert) {
|
||||||
|
// TODO: move these so they are not computed with every func call
|
||||||
|
var negmask16 = 1 << 15;
|
||||||
|
var negmask32 = 1 << 31;
|
||||||
|
var relmask16 = 1 << 14;
|
||||||
|
|
||||||
|
var converted16 = toConvert | 0;
|
||||||
|
|
||||||
|
// number is negative
|
||||||
|
if ((toConvert & negmask32) != 0) {
|
||||||
|
// clear the 32bit negative bit
|
||||||
|
// not neccessary because the last 16bits will get dropped anyway
|
||||||
|
converted16 *= -1;
|
||||||
|
|
||||||
|
// set the 16bit negative bit
|
||||||
|
converted16 |= negmask16;
|
||||||
|
// set the relative bit
|
||||||
|
converted16 |= relmask16;
|
||||||
|
} else {
|
||||||
|
// set the relative bit
|
||||||
|
converted16 |= relmask16;
|
||||||
|
}
|
||||||
|
|
||||||
|
return converted16;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fast hashing function with low entropy */
|
||||||
export function hashUInt8Array(data) {
|
export function hashUInt8Array(data) {
|
||||||
let h;
|
let h;
|
||||||
for (let i = 0; i < data.length; i++) {
|
for (let i = 0; i < data.length; i++) {
|
||||||
|
|
|
||||||
38
docs/API.md
38
docs/API.md
|
|
@ -113,6 +113,10 @@ protocol stream.
|
||||||
- The `capabilities` event is fired when `RFB.capabilities` is
|
- The `capabilities` event is fired when `RFB.capabilities` is
|
||||||
updated.
|
updated.
|
||||||
|
|
||||||
|
[`inputlock`](#inputlock)
|
||||||
|
- The `inputlock` event is fired when an input lock is acquired (or released)
|
||||||
|
by the canvas.
|
||||||
|
|
||||||
### Methods
|
### Methods
|
||||||
|
|
||||||
[`RFB.disconnect()`](#rfbdisconnect)
|
[`RFB.disconnect()`](#rfbdisconnect)
|
||||||
|
|
@ -146,6 +150,10 @@ protocol stream.
|
||||||
[`RFB.clipboardPasteFrom()`](#rfbclipboardPasteFrom)
|
[`RFB.clipboardPasteFrom()`](#rfbclipboardPasteFrom)
|
||||||
- Send clipboard contents to server.
|
- Send clipboard contents to server.
|
||||||
|
|
||||||
|
[`inputlock`](#inputlock)
|
||||||
|
- The `inputlock` event is fired when an input lock is acquired (or released)
|
||||||
|
by the canvas.
|
||||||
|
|
||||||
### Details
|
### Details
|
||||||
|
|
||||||
#### RFB()
|
#### RFB()
|
||||||
|
|
@ -262,6 +270,15 @@ The `capabilities` event is fired whenever an entry is added or removed
|
||||||
from `RFB.capabilities`. The `detail` property is an `Object` with the
|
from `RFB.capabilities`. The `detail` property is an `Object` with the
|
||||||
property `capabilities` containing the new value of `RFB.capabilities`.
|
property `capabilities` containing the new value of `RFB.capabilities`.
|
||||||
|
|
||||||
|
#### inputlock
|
||||||
|
|
||||||
|
The `inputlock` event is fired after a request to acquire an input lock or
|
||||||
|
whenever the state of the canvas' input lock has changed, the latter typically
|
||||||
|
occurs because the lock was released by the user pressing the ESC key or
|
||||||
|
performing a browser-specific gesture. The `detail` property is an `Object`
|
||||||
|
with the property `pointer` containing whether the Pointer Lock is currently
|
||||||
|
held or not.
|
||||||
|
|
||||||
#### RFB.disconnect()
|
#### RFB.disconnect()
|
||||||
|
|
||||||
The `RFB.disconnect()` method is used to disconnect from the currently
|
The `RFB.disconnect()` method is used to disconnect from the currently
|
||||||
|
|
@ -383,3 +400,24 @@ to the remote server.
|
||||||
|
|
||||||
**`text`**
|
**`text`**
|
||||||
- A `DOMString` specifying the clipboard data to send.
|
- A `DOMString` specifying the clipboard data to send.
|
||||||
|
|
||||||
|
#### RFB.requestInputLock()
|
||||||
|
|
||||||
|
The `RFB.requestInputLock()` method is used to request that the RFB canvas hold
|
||||||
|
an input lock. An `inputlock` event will be fired with the result of the
|
||||||
|
acquisition of the requested locks.
|
||||||
|
|
||||||
|
##### Syntax
|
||||||
|
|
||||||
|
RFB.requestInputLock( { pointer: true } );
|
||||||
|
|
||||||
|
###### Parameters
|
||||||
|
|
||||||
|
**`pointer`**
|
||||||
|
- Requests to acquire a [Pointer
|
||||||
|
Lock](https://developer.mozilla.org/en-US/docs/Web/API/Pointer_Lock_API),
|
||||||
|
which hides the local mouse cursor and provides relative motion events.
|
||||||
|
This must be called directly from an event handler where a user has
|
||||||
|
directly interacted with an element through an [engagement
|
||||||
|
gesture](https://w3c.github.io/pointerlock/#dfn-engagement-gesture) (e.g. a
|
||||||
|
click or touch event) for the browser to allow this.
|
||||||
|
|
@ -2610,6 +2610,27 @@ describe('Remote Frame Buffer Protocol Client', function () {
|
||||||
client._canvas.dispatchEvent(ev);
|
client._canvas.dispatchEvent(ev);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function supportsSendMouseMovementEvent() {
|
||||||
|
// Some browsers (like Safari) support the movementX /
|
||||||
|
// movementY properties of MouseEvent, but do not allow creation
|
||||||
|
// of non-trusted events with those properties.
|
||||||
|
let ev;
|
||||||
|
|
||||||
|
ev = new MouseEvent('mousemove',
|
||||||
|
{ 'movementX': 100,
|
||||||
|
'movementY': 100 });
|
||||||
|
return ev.movementX === 100 && ev.movementY === 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendMouseMovementEvent(dx, dy) {
|
||||||
|
let ev;
|
||||||
|
|
||||||
|
ev = new MouseEvent('mousemove',
|
||||||
|
{ 'movementX': dx,
|
||||||
|
'movementY': dy });
|
||||||
|
client._canvas.dispatchEvent(ev);
|
||||||
|
}
|
||||||
|
|
||||||
function sendMouseButtonEvent(x, y, down, button) {
|
function sendMouseButtonEvent(x, y, down, button) {
|
||||||
let pos = elementToClient(x, y);
|
let pos = elementToClient(x, y);
|
||||||
let ev;
|
let ev;
|
||||||
|
|
@ -2723,6 +2744,62 @@ describe('Remote Frame Buffer Protocol Client', function () {
|
||||||
50, 70, 0x0);
|
50, 70, 0x0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should ignore remote cursor position updates', function () {
|
||||||
|
if (!supportsSendMouseMovementEvent()) {
|
||||||
|
this.skip();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Simple VMware Cursor Position FBU message with pointer coordinates
|
||||||
|
// (50, 50).
|
||||||
|
const incoming = [ 0x00, 0x00, 0x00, 0x01, 0x00, 0x32, 0x00, 0x32,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x57, 0x4d, 0x56, 0x66 ];
|
||||||
|
client._resize(100, 100);
|
||||||
|
|
||||||
|
const cursorSpy = sinon.spy(client, '_handleVMwareCursorPosition');
|
||||||
|
client._sock._websocket._receiveData(new Uint8Array(incoming));
|
||||||
|
expect(cursorSpy).to.have.been.calledOnceWith();
|
||||||
|
cursorSpy.restore();
|
||||||
|
|
||||||
|
expect(client._mousePos).to.deep.equal({ });
|
||||||
|
sendMouseMoveEvent(10, 10);
|
||||||
|
clock.tick(100);
|
||||||
|
expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
|
||||||
|
10, 10, 0x0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle remote mouse position updates in pointer lock mode', function () {
|
||||||
|
if (!supportsSendMouseMovementEvent()) {
|
||||||
|
this.skip();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Simple VMware Cursor Position FBU message with pointer coordinates
|
||||||
|
// (50, 50).
|
||||||
|
const incoming = [ 0x00, 0x00, 0x00, 0x01, 0x00, 0x32, 0x00, 0x32,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x57, 0x4d, 0x56, 0x66 ];
|
||||||
|
client._resize(100, 100);
|
||||||
|
|
||||||
|
const spy = sinon.spy();
|
||||||
|
client.addEventListener("inputlock", spy);
|
||||||
|
let stub = sinon.stub(document, 'pointerLockElement');
|
||||||
|
stub.get(function () { return client._canvas; });
|
||||||
|
client._handlePointerLockChange();
|
||||||
|
stub.restore();
|
||||||
|
client._sock._websocket._receiveData(new Uint8Array([0x02, 0x02]));
|
||||||
|
expect(spy).to.have.been.calledOnce;
|
||||||
|
expect(spy.args[0][0].detail.pointer).to.be.true;
|
||||||
|
|
||||||
|
const cursorSpy = sinon.spy(client, '_handleVMwareCursorPosition');
|
||||||
|
client._sock._websocket._receiveData(new Uint8Array(incoming));
|
||||||
|
expect(cursorSpy).to.have.been.calledOnceWith();
|
||||||
|
cursorSpy.restore();
|
||||||
|
|
||||||
|
expect(client._mousePos).to.deep.equal({ x: 50, y: 50 });
|
||||||
|
sendMouseMovementEvent(10, 10);
|
||||||
|
clock.tick(100);
|
||||||
|
expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
|
||||||
|
60, 60, 0x0);
|
||||||
|
});
|
||||||
|
|
||||||
describe('Event Aggregation', function () {
|
describe('Event Aggregation', function () {
|
||||||
it('should send a single pointer event on mouse movement', function () {
|
it('should send a single pointer event on mouse movement', function () {
|
||||||
sendMouseMoveEvent(50, 70);
|
sendMouseMoveEvent(50, 70);
|
||||||
|
|
|
||||||
707
vnc.html
707
vnc.html
|
|
@ -86,300 +86,434 @@
|
||||||
Loading statistics...
|
Loading statistics...
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="noVNC_vcenter">
|
|
||||||
<div id="noVNC_modifiers" class="noVNC_panel">
|
|
||||||
<input type="image" alt="Keyboard" src="app/images/keyboard.svg"
|
|
||||||
id="noVNC_keyboard_button" class="noVNC_button" title="Show Keyboard">
|
|
||||||
<input type="image" alt="Ctrl" src="app/images/ctrl.svg"
|
|
||||||
id="noVNC_toggle_ctrl_button" class="noVNC_button"
|
|
||||||
title="Toggle Ctrl">
|
|
||||||
<input type="image" alt="Alt" src="app/images/alt.svg"
|
|
||||||
id="noVNC_toggle_alt_button" class="noVNC_button"
|
|
||||||
title="Toggle Alt">
|
|
||||||
<input type="image" alt="Windows" src="app/images/windows.svg"
|
|
||||||
id="noVNC_toggle_windows_button" class="noVNC_button"
|
|
||||||
title="Toggle Windows">
|
|
||||||
<input type="image" alt="Tab" src="app/images/tab.svg"
|
|
||||||
id="noVNC_send_tab_button" class="noVNC_button"
|
|
||||||
title="Send Tab">
|
|
||||||
<input type="image" alt="Esc" src="app/images/esc.svg"
|
|
||||||
id="noVNC_send_esc_button" class="noVNC_button"
|
|
||||||
title="Send Escape">
|
|
||||||
<input type="image" alt="Ctrl+Alt+Del" src="app/images/ctrlaltdel.svg"
|
|
||||||
id="noVNC_send_ctrl_alt_del_button" class="noVNC_button"
|
|
||||||
title="Send Ctrl-Alt-Del">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- noVNC Control Bar -->
|
<!-- noVNC Control Bar -->
|
||||||
<div id="noVNC_control_bar_anchor" class="noVNC_vcenter">
|
<div id="noVNC_control_bar_anchor" class="noVNC_vcenter">
|
||||||
|
|
||||||
<div id="noVNC_control_bar">
|
<div id="noVNC_control_bar">
|
||||||
<div id="noVNC_control_bar_handle" title="Hide/Show the control bar"><div></div></div>
|
<div id="noVNC_control_bar_handle" title="Hide/Show the control bar">
|
||||||
|
<div></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="noVNC_scroll">
|
<div class="noVNC_scroll">
|
||||||
|
|
||||||
<h1 class="noVNC_logo"><img src="app/images/icons/368_kasm_logo_only_24x24.png" /></h1>
|
<h1 class="noVNC_logo"><img src="app/images/icons/kasm_logo.png" /></h1>
|
||||||
|
|
||||||
<!-- Drag/Pan the viewport -->
|
<!-- Drag/Pan the viewport -->
|
||||||
<input type="image" alt="Drag" src="app/images/drag.svg"
|
<div class="noVNC_button_div noVNC_hidden noVNC_hide_on_disconnect" >
|
||||||
id="noVNC_view_drag_button" class="noVNC_button noVNC_hidden"
|
<input type="image" alt="Drag" src="app/images/drag.svg"
|
||||||
title="Move/Drag Viewport">
|
id="noVNC_view_drag_button" class="noVNC_button"
|
||||||
|
title="Move/Drag Viewport">
|
||||||
<!--noVNC Touch Device only buttons-->
|
Drag Viewport
|
||||||
<!-- Extra manual keys -->
|
|
||||||
<input type="image" alt="Extra keys" src="app/images/toggleextrakeys.svg"
|
|
||||||
id="noVNC_toggle_extra_keys_button" class="noVNC_button"
|
|
||||||
title="Show Extra Keys">
|
|
||||||
|
|
||||||
<!-- Shutdown/Reboot -->
|
|
||||||
<input type="image" alt="Shutdown/Reboot" src="app/images/power.svg"
|
|
||||||
id="noVNC_power_button" class="noVNC_button"
|
|
||||||
title="Shutdown/Reboot...">
|
|
||||||
<div class="noVNC_vcenter">
|
|
||||||
<div id="noVNC_power" class="noVNC_panel">
|
|
||||||
<div class="noVNC_heading">
|
|
||||||
<img alt="" src="app/images/power.svg"> Power
|
|
||||||
</div>
|
</div>
|
||||||
<input type="button" id="noVNC_shutdown_button" value="Shutdown">
|
|
||||||
<input type="button" id="noVNC_reboot_button" value="Reboot">
|
|
||||||
<input type="button" id="noVNC_reset_button" value="Reset">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Clipboard -->
|
<!--noVNC Touch Device only buttons-->
|
||||||
<input type="image" alt="Clipboard" src="app/images/clipboard.svg"
|
<!-- Extra manual keys -->
|
||||||
id="noVNC_clipboard_button" class="noVNC_button"
|
<div class="noVNC_button_div noVNC_hide_on_disconnect" >
|
||||||
title="Clipboard">
|
<input type="image" alt="Extra keys" src="app/images/toggleextrakeys.svg"
|
||||||
<div class="noVNC_vcenter">
|
id="noVNC_toggle_extra_keys_button" class="noVNC_button"
|
||||||
<div id="noVNC_clipboard" class="noVNC_panel">
|
title="Show Extra Keys">
|
||||||
<div class="noVNC_heading">
|
<div class="noVNC_vcenter">
|
||||||
<img alt="" src="app/images/clipboard.svg"> Clipboard
|
<div id="noVNC_modifiers" class="noVNC_panel">
|
||||||
|
<input type="image" alt="Keyboard" src="app/images/keyboard.svg"
|
||||||
|
id="noVNC_keyboard_button" class="noVNC_button" title="Show Keyboard">
|
||||||
|
<input type="image" alt="Ctrl" src="app/images/ctrl.svg"
|
||||||
|
id="noVNC_toggle_ctrl_button" class="noVNC_button"
|
||||||
|
title="Toggle Ctrl">
|
||||||
|
<input type="image" alt="Alt" src="app/images/alt.svg"
|
||||||
|
id="noVNC_toggle_alt_button" class="noVNC_button"
|
||||||
|
title="Toggle Alt">
|
||||||
|
<input type="image" alt="Windows" src="app/images/windows.svg"
|
||||||
|
id="noVNC_toggle_windows_button" class="noVNC_button"
|
||||||
|
title="Toggle Windows">
|
||||||
|
<input type="image" alt="Tab" src="app/images/tab.svg"
|
||||||
|
id="noVNC_send_tab_button" class="noVNC_button"
|
||||||
|
title="Send Tab">
|
||||||
|
<input type="image" alt="Esc" src="app/images/esc.svg"
|
||||||
|
id="noVNC_send_esc_button" class="noVNC_button"
|
||||||
|
title="Send Escape">
|
||||||
|
<input type="image" alt="Ctrl+Alt+Del" src="app/images/ctrlaltdel.svg"
|
||||||
|
id="noVNC_send_ctrl_alt_del_button" class="noVNC_button"
|
||||||
|
title="Send Ctrl-Alt-Del">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
Keys
|
||||||
</div>
|
</div>
|
||||||
<textarea id="noVNC_clipboard_text" rows=5></textarea>
|
|
||||||
<br>
|
|
||||||
<input id="noVNC_clipboard_clear_button" type="button"
|
|
||||||
value="Clear" class="noVNC_submit">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Toggle fullscreen -->
|
<!-- Shutdown/Reboot -->
|
||||||
<input type="image" alt="Fullscreen" src="app/images/fullscreen.svg"
|
<div class="noVNC_button_div noVNC_hide_on_disconnect" >
|
||||||
id="noVNC_fullscreen_button" class="noVNC_button noVNC_hidden"
|
<input type="image" alt="Shutdown/Reboot" src="app/images/power.svg"
|
||||||
title="Fullscreen">
|
id="noVNC_power_button" class="noVNC_button"
|
||||||
|
title="Shutdown/Reboot...">
|
||||||
|
|
||||||
<!-- Settings -->
|
<div class="noVNC_vcenter">
|
||||||
<input type="image" alt="Settings" src="app/images/settings.svg"
|
<div id="noVNC_power" class="noVNC_panel">
|
||||||
id="noVNC_settings_button" class="noVNC_button"
|
<div class="noVNC_heading">
|
||||||
title="Settings">
|
<img alt="" src="app/images/power.svg"> Power
|
||||||
<div class="noVNC_vcenter">
|
</div>
|
||||||
<div id="noVNC_settings" class="noVNC_panel">
|
<input type="button" id="noVNC_shutdown_button" value="Shutdown">
|
||||||
<ul>
|
<input type="button" id="noVNC_reboot_button" value="Reboot">
|
||||||
<li class="noVNC_heading">
|
<input type="button" id="noVNC_reset_button" value="Reset">
|
||||||
<img alt="" src="app/images/settings.svg"> Settings
|
</div>
|
||||||
</li>
|
</div>
|
||||||
<li>
|
Power
|
||||||
<label><input id="noVNC_setting_shared" type="checkbox"> Shared Mode</label>
|
</div>
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<label><input id="noVNC_setting_view_only" type="checkbox" /> View Only</label></li>
|
|
||||||
<li>
|
|
||||||
<label><input id="noVNC_setting_clipboard_up" type="checkbox" /> Clipboard Up</label></li>
|
|
||||||
<li>
|
|
||||||
<label><input id="noVNC_setting_clipboard_down" type="checkbox" /> Clipboard Down</label></li>
|
|
||||||
<li>
|
|
||||||
<label><input id="noVNC_setting_clipboard_seamless" type="checkbox" /> Clipboard Seamless</label></li>
|
|
||||||
<li>
|
|
||||||
<label><input id="noVNC_setting_prefer_local_cursor" type="checkbox" /> Prefer Local Cursor</label>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<label>
|
|
||||||
<input id="noVNC_setting_translate_shortcuts" type="checkbox" />Translate keyboard shurtcuts
|
|
||||||
</label>
|
|
||||||
</li>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<label><input id="noVNC_setting_enable_webp" type="checkbox" /> Enable WebP Compression</label></li>
|
|
||||||
<li>
|
|
||||||
<label><input id="noVNC_setting_enable_perf_stats" type="checkbox" /> Enable Performance Stats</label></li>
|
|
||||||
<li>
|
|
||||||
<label><input id="noVNC_setting_toggle_control_panel" type="checkbox" /> Toggle Control Panel via Keystrokes</label></li>
|
|
||||||
<li>
|
|
||||||
<label for="noVNC_setting_idle_disconnect">Idle Timeout:</label>
|
|
||||||
<select id="noVNC_setting_idle_disconnect" name="vncIdleDisconnect">
|
|
||||||
<option value=20>20</option>
|
|
||||||
</select>
|
|
||||||
</li>
|
|
||||||
<li><hr></li>
|
|
||||||
<li>
|
|
||||||
<label><input id="noVNC_setting_view_clip" type="checkbox"> Clip to Window</label>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<label for="noVNC_setting_resize">Scaling Mode:</label>
|
|
||||||
<select id="noVNC_setting_resize" name="vncResize">
|
|
||||||
<option value="off">None</option>
|
|
||||||
<option value="scale">Local Scaling</option>
|
|
||||||
<option value="remote">Remote Resizing</option>
|
|
||||||
</select>
|
|
||||||
</li>
|
|
||||||
<li><hr></li>
|
|
||||||
<li>
|
|
||||||
<div class="noVNC_expander">Stream Quality</div>
|
|
||||||
<div><ul>
|
|
||||||
<li>
|
|
||||||
<label for="noVNC_setting_video_quality">Preset Modes:</label>
|
|
||||||
<select id="noVNC_setting_video_quality" name="vncVideoQuality">
|
|
||||||
<option value=0>Static</option>
|
|
||||||
<option value=1>Low</option>
|
|
||||||
<option value=2>Medium</option>
|
|
||||||
<option value=3>High</option>
|
|
||||||
<option value=4>Extreme</option>
|
|
||||||
<option value=10>Custom</option>
|
|
||||||
</select>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<label for="noVNC_setting_anti_aliasing">Anti-Aliasing:</label>
|
|
||||||
<select id="noVNC_setting_anti_aliasing" name="vncAntiAliasing">
|
|
||||||
<option value=0>Auto Dynamic</option>
|
|
||||||
<option value=1>On</option>
|
|
||||||
<option value=2>Off</option>
|
|
||||||
</select>
|
|
||||||
</li>
|
|
||||||
<li style="display: none;">
|
|
||||||
<label for="noVNC_setting_quality">Quality:</label>
|
|
||||||
<input id="noVNC_setting_quality" type="range" min="0" max="9" value="6">
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li>
|
<!-- Clipboard -->
|
||||||
<label for="noVNC_setting_dynamic_quality_min">Dynamic Quality Min:</label>
|
<div class="noVNC_button_div noVNC_hide_on_disconnect" >
|
||||||
<input id="noVNC_setting_dynamic_quality_min" type="range" min="0" max="9" value="3" onchange="noVNC_setting_dynamic_quality_min_output.value=value">
|
<input type="image" alt="Clipboard" src="app/images/clipboard.svg"
|
||||||
<output id="noVNC_setting_dynamic_quality_min_output">3</output>
|
id="noVNC_clipboard_button" class="noVNC_button"
|
||||||
</li>
|
title="Clipboard">
|
||||||
<li>
|
Clipboard
|
||||||
<label for="noVNC_setting_dynamic_quality_max">Dynamic Quality Max:</label>
|
<div class="noVNC_vcenter">
|
||||||
<input id="noVNC_setting_dynamic_quality_max" type="range" min="0" max="9" value="9" onchange="noVNC_setting_dynamic_quality_max_output.value=value">
|
<div id="noVNC_clipboard" class="noVNC_panel">
|
||||||
<output id="noVNC_setting_dynamic_quality_max_output">9</output>
|
<div class="noVNC_heading">
|
||||||
</li>
|
<img alt="" src="app/images/clipboard.svg"> Clipboard
|
||||||
<li>
|
</div>
|
||||||
<label for="noVNC_setting_treat_lossless">Treat Lossless:</label>
|
<textarea id="noVNC_clipboard_text" rows=5></textarea>
|
||||||
<input id="noVNC_setting_treat_lossless" type="range" min="0" max="9" value="7" onchange="noVNC_setting_treat_lossless_output.value=value">
|
<br>
|
||||||
<output id="noVNC_setting_treat_lossless_output">7</output>
|
<input id="noVNC_clipboard_clear_button" type="button"
|
||||||
</li>
|
value="Clear" class="noVNC_submit">
|
||||||
<li>
|
</div>
|
||||||
<label for="noVNC_setting_framerate">Frame Rate:</label>
|
</div>
|
||||||
<input id="noVNC_setting_framerate" type="number" min="1" max="120" value="30">
|
</div>
|
||||||
</li>
|
|
||||||
<li>
|
<!-- Toggle fullscreen -->
|
||||||
<label for="noVNC_setting_jpeg_video_quality">Video JPEG Quality:</label>
|
<div class="noVNC_button_div noVNC_hidden" >
|
||||||
<input id="noVNC_setting_jpeg_video_quality" type="range" min="0" max="9" value="5" onchange="noVNC_setting_jpeg_video_quality_output.value=value">
|
<input type="image" alt="Fullscreen" src="app/images/fullscreen.svg"
|
||||||
<output id="noVNC_setting_jpeg_video_quality_output">5</output>
|
id="noVNC_fullscreen_button" class="noVNC_button"
|
||||||
</li>
|
title="Fullscreen">
|
||||||
<li>
|
Fullscreen
|
||||||
<label for="noVNC_setting_webp_video_quality">Video WEBP Quality:</label>
|
</div>
|
||||||
<input id="noVNC_setting_webp_video_quality" type="range" min="0" max="9" value="5" onchange="noVNC_setting_webp_video_quality_output.value=value">
|
|
||||||
<output id="noVNC_setting_webp_video_quality_output">5</output>
|
<!-- Toggle game mode -->
|
||||||
</li>
|
<div class="noVNC_button_div noVNC_hidden noVNC_hide_on_disconnect" >
|
||||||
<li>
|
<input type="image" alt="Game Mode" src="app/images/gamepad.png"
|
||||||
<label for="noVNC_setting_video_area">Video Area:</label>
|
id="noVNC_game_mode_button" class="noVNC_button"
|
||||||
<input id="noVNC_setting_video_area" type="range" min="0" max="100" value="65" onchange="noVNC_setting_video_area_output.value=value">
|
title="Game Pointer Mode">
|
||||||
<output id="noVNC_setting_video_area_output">65</output>
|
Game Cursor Mode
|
||||||
</li>
|
</div>
|
||||||
<li>
|
|
||||||
<label for="noVNC_setting_video_time">Video Time:</label>
|
<!-- Settings -->
|
||||||
<input id="noVNC_setting_video_time" type="range" min="0" max="60" value="5" onchange="noVNC_setting_video_time_output.value=value">
|
<div class="noVNC_button_div" >
|
||||||
<output id="noVNC_setting_video_time_output">5</output>
|
<input type="image" alt="Settings" src="app/images/settings.svg"
|
||||||
</li>
|
id="noVNC_settings_button" class="noVNC_button"
|
||||||
<li>
|
title="Settings">
|
||||||
<label for="noVNC_setting_video_out_time">Video Out Time:</label>
|
<div class="noVNC_vcenter">
|
||||||
<input id="noVNC_setting_video_out_time" type="range" min="0" max="60" value="3" onchange="noVNC_setting_video_out_time_output.value=value">
|
<div id="noVNC_settings" class="noVNC_panel">
|
||||||
<output id="noVNC_setting_video_out_time_output">3</output>
|
<ul>
|
||||||
</li>
|
<li class="noVNC_heading">
|
||||||
<li>
|
<img alt="" src="app/images/settings.svg"> Settings
|
||||||
<label for="noVNC_setting_video_scaling">Scaling Mode:</label>
|
</li>
|
||||||
<select id="noVNC_setting_video_scaling" name="vncVideoModeScaling">
|
<li>
|
||||||
<option value="0">Nearest</option>
|
<label class="switch"><input id="noVNC_setting_shared" type="checkbox">
|
||||||
<option value="1">Bilinear</option>
|
<span class="slider round"></span>
|
||||||
<option value="2">Progressive Bilinear</option>
|
<span class="slider-label">Shared Mode</span>
|
||||||
</select>
|
</label>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<label for="noVNC_setting_max_video_resolution_x">Video Mode Width:</label>
|
<label class="switch"><input id="noVNC_setting_view_only" type="checkbox" />
|
||||||
<input id="noVNC_setting_max_video_resolution_x" type="number" min="100" max="3840" value="960">
|
<span class="slider round"></span>
|
||||||
</li>
|
<span class="slider-label">View Only</span>
|
||||||
<li>
|
</label>
|
||||||
<label for="noVNC_setting_max_video_resolution_y">Video Mode Height:</label>
|
</li>
|
||||||
<input id="noVNC_setting_max_video_resolution_y" type="number" min="100" max="2160" value="540">
|
<li>
|
||||||
</li>
|
<label class="switch"><input id="noVNC_setting_clipboard_up" type="checkbox" />
|
||||||
</ul></div>
|
<span class="slider round"></span>
|
||||||
</li>
|
<span class="slider-label">Clipboard Up</span>
|
||||||
<li><hr></li>
|
</label>
|
||||||
<li>
|
</li>
|
||||||
<div class="noVNC_expander">Advanced</div>
|
<li>
|
||||||
<div><ul>
|
<label class="switch"><input id="noVNC_setting_clipboard_down" type="checkbox" />
|
||||||
<li>
|
<span class="slider round"></span>
|
||||||
<label for="noVNC_setting_compression">Compression level:</label>
|
<span class="slider-label">Clipboard Down</span>
|
||||||
<input id="noVNC_setting_compression" type="range" min="0" max="9" value="2">
|
</label>
|
||||||
</li>
|
</li>
|
||||||
<li><hr></li>
|
<li>
|
||||||
<li>
|
<label class="switch"><input id="noVNC_setting_clipboard_seamless" type="checkbox" />
|
||||||
<label for="noVNC_setting_repeaterID">Repeater ID:</label>
|
<span class="slider round"></span>
|
||||||
<input id="noVNC_setting_repeaterID" type="text" value="">
|
<span class="slider-label">Clipboard Seamless</span>
|
||||||
</li>
|
</label>
|
||||||
<li>
|
</li>
|
||||||
<div class="noVNC_expander">WebSocket</div>
|
<li>
|
||||||
<div><ul>
|
<label class="switch"><input id="noVNC_setting_prefer_local_cursor" type="checkbox" />
|
||||||
<li>
|
<span class="slider round"></span>
|
||||||
<label><input id="noVNC_setting_encrypt" type="checkbox"> Encrypt</label>
|
<span class="slider-label">Prefer Local Cursor</span>
|
||||||
</li>
|
</label>
|
||||||
<li>
|
</li>
|
||||||
<label for="noVNC_setting_host">Host:</label>
|
<li>
|
||||||
<input id="noVNC_setting_host">
|
<label class="switch">
|
||||||
</li>
|
<input id="noVNC_setting_translate_shortcuts" type="checkbox" />
|
||||||
<li>
|
<span class="slider round"></span>
|
||||||
<label for="noVNC_setting_port">Port:</label>
|
<span class="slider-label">Translate keyboard shurtcuts</span>
|
||||||
<input id="noVNC_setting_port" type="number">
|
</label>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<label for="noVNC_setting_path">Path:</label>
|
<label class="switch"><input id="noVNC_setting_enable_webp" type="checkbox" />
|
||||||
<input id="noVNC_setting_path" type="text" value="websockify">
|
<span class="slider round"></span>
|
||||||
</li>
|
<span class="slider-label">Enable WebP Compression</span>
|
||||||
</ul></div>
|
</label>
|
||||||
</li>
|
</li>
|
||||||
<li><hr></li>
|
<li>
|
||||||
<li>
|
<label class="switch">
|
||||||
<label><input id="noVNC_setting_reconnect" type="checkbox"> Automatic Reconnect</label>
|
<input id="noVNC_setting_enable_perf_stats" type="checkbox" />
|
||||||
</li>
|
<span class="slider round"></span>
|
||||||
<li>
|
<span class="slider-label">Enable Performance Stats</span>
|
||||||
<label for="noVNC_setting_reconnect_delay">Reconnect Delay (ms):</label>
|
</label>
|
||||||
<input id="noVNC_setting_reconnect_delay" type="number">
|
</li>
|
||||||
</li>
|
<li>
|
||||||
<li><hr></li>
|
<label class="switch">
|
||||||
<li>
|
<input type="checkbox" id="noVNC_setting_pointer_lock" />
|
||||||
<label><input id="noVNC_setting_show_dot" type="checkbox"> Show Dot when No Cursor</label>
|
<span class="slider round"></span>
|
||||||
</li>
|
<span class="slider-label">Enable Pointer Lock</span>
|
||||||
<li><hr></li>
|
</label>
|
||||||
<!-- Logging selection dropdown -->
|
</li>
|
||||||
<li>
|
<label class="switch"><input id="noVNC_setting_enable_ime" type="checkbox" />
|
||||||
<label>Logging:
|
<span class="slider round"></span>
|
||||||
<select id="noVNC_setting_logging" name="vncLogging">
|
<span class="slider-label">IME Input Mode</span>
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<label class="switch"><input id="noVNC_setting_virtual_keyboard_visible" type="checkbox" />
|
||||||
|
<span class="slider round"></span>
|
||||||
|
<span class="slider-label">Show Virtual Keyboard Control</span>
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<label class="switch"><input id="noVNC_setting_toggle_control_panel" type="checkbox" />
|
||||||
|
<span class="slider round"></span>
|
||||||
|
<span class="slider-label">Toggle Control Panel via Keystrokes</span>
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<label for="noVNC_setting_idle_disconnect">Idle Timeout:</label>
|
||||||
|
<select id="noVNC_setting_idle_disconnect" name="vncIdleDisconnect">
|
||||||
|
<option value=10>10</option>
|
||||||
|
<option value=20>20</option>
|
||||||
|
<option value=30>30</option>
|
||||||
|
<option value=60>60</option>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</li>
|
||||||
</li>
|
<li><hr></li>
|
||||||
</ul></div>
|
<li>
|
||||||
</li>
|
<label class="switch">
|
||||||
<li class="noVNC_version_separator"><hr></li>
|
<input id="noVNC_setting_view_clip" type="checkbox">
|
||||||
<li class="noVNC_version_wrapper">
|
<span class="slider round"></span>
|
||||||
<span>Version:</span>
|
<span class="slider-label">Clip to Window</span>
|
||||||
<span class="noVNC_version"></span>
|
</label>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
<li>
|
||||||
</div>
|
<label for="noVNC_setting_resize">Scaling Mode:</label>
|
||||||
</div>
|
<select id="noVNC_setting_resize" name="vncResize">
|
||||||
|
<option value="off">None</option>
|
||||||
|
<option value="scale">Local Scaling</option>
|
||||||
|
<option value="remote">Remote Resizing</option>
|
||||||
|
</select>
|
||||||
|
</li>
|
||||||
|
<li><hr></li>
|
||||||
|
<li>
|
||||||
|
<div class="noVNC_expander">Keyboard Shortcuts</div>
|
||||||
|
<div>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<label class="switch">
|
||||||
|
<input id="noVNC_setting_toggle_control_panel" type="checkbox" />
|
||||||
|
<span class="slider round"></span>
|
||||||
|
<span class="slider-label">Enable KasmVNC Keyboard Shortcuts</span>
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
<li>Ctrl+Shift+</li>
|
||||||
|
<li>1 - Toggle Control Panel</li>
|
||||||
|
<li>2 - Toggle Game Pointer Mode</li>
|
||||||
|
<li>3 - Toggle Pointer Lock</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li><hr></li>
|
||||||
|
<li>
|
||||||
|
<div class="noVNC_expander">Stream Quality</div>
|
||||||
|
<div>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<label for="noVNC_setting_video_quality">Preset Modes:</label>
|
||||||
|
<select id="noVNC_setting_video_quality" name="vncVideoQuality">
|
||||||
|
<option value=0>Static</option>
|
||||||
|
<option value=1>Low</option>
|
||||||
|
<option value=2>Medium</option>
|
||||||
|
<option value=3>High</option>
|
||||||
|
<option value=4>Extreme</option>
|
||||||
|
<option value=10>Custom</option>
|
||||||
|
</select>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<label for="noVNC_setting_anti_aliasing">Anti-Aliasing:</label>
|
||||||
|
<select id="noVNC_setting_anti_aliasing" name="vncAntiAliasing">
|
||||||
|
<option value=0>Auto Dynamic</option>
|
||||||
|
<option value=1>On</option>
|
||||||
|
<option value=2>Off</option>
|
||||||
|
</select>
|
||||||
|
</li>
|
||||||
|
<li style="display: none;">
|
||||||
|
<label for="noVNC_setting_quality">Quality:</label>
|
||||||
|
<input id="noVNC_setting_quality" type="range" min="0" max="9" value="6">
|
||||||
|
</li>
|
||||||
|
|
||||||
<!-- Connection Controls -->
|
<li>
|
||||||
<input type="image" alt="Disconnect" src="app/images/disconnect.svg"
|
<label for="noVNC_setting_dynamic_quality_min">Dynamic Quality Min:</label>
|
||||||
id="noVNC_disconnect_button" class="noVNC_button"
|
<input id="noVNC_setting_dynamic_quality_min" type="range" min="0" max="9" value="3" onchange="noVNC_setting_dynamic_quality_min_output.value=value">
|
||||||
title="Disconnect">
|
<output id="noVNC_setting_dynamic_quality_min_output">3</output>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<label for="noVNC_setting_dynamic_quality_max">Dynamic Quality Max:</label>
|
||||||
|
<input id="noVNC_setting_dynamic_quality_max" type="range" min="0" max="9" value="9" onchange="noVNC_setting_dynamic_quality_max_output.value=value">
|
||||||
|
<output id="noVNC_setting_dynamic_quality_max_output">9</output>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<label for="noVNC_setting_treat_lossless">Treat Lossless:</label>
|
||||||
|
<input id="noVNC_setting_treat_lossless" type="range" min="0" max="9" value="7" onchange="noVNC_setting_treat_lossless_output.value=value">
|
||||||
|
<output id="noVNC_setting_treat_lossless_output">7</output>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<label for="noVNC_setting_framerate">Frame Rate:</label>
|
||||||
|
<input id="noVNC_setting_framerate" type="number" min="1" max="120" value="30">
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<label for="noVNC_setting_jpeg_video_quality">Video JPEG Quality:</label>
|
||||||
|
<input id="noVNC_setting_jpeg_video_quality" type="range" min="0" max="9" value="5" onchange="noVNC_setting_jpeg_video_quality_output.value=value">
|
||||||
|
<output id="noVNC_setting_jpeg_video_quality_output">5</output>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<label for="noVNC_setting_webp_video_quality">Video WEBP Quality:</label>
|
||||||
|
<input id="noVNC_setting_webp_video_quality" type="range" min="0" max="9" value="5" onchange="noVNC_setting_webp_video_quality_output.value=value">
|
||||||
|
<output id="noVNC_setting_webp_video_quality_output">5</output>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<label for="noVNC_setting_video_area">Video Area:</label>
|
||||||
|
<input id="noVNC_setting_video_area" type="range" min="0" max="100" value="65" onchange="noVNC_setting_video_area_output.value=value">
|
||||||
|
<output id="noVNC_setting_video_area_output">65</output>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<label for="noVNC_setting_video_time">Video Time:</label>
|
||||||
|
<input id="noVNC_setting_video_time" type="range" min="0" max="60" value="5" onchange="noVNC_setting_video_time_output.value=value">
|
||||||
|
<output id="noVNC_setting_video_time_output">5</output>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<label for="noVNC_setting_video_out_time">Video Out Time:</label>
|
||||||
|
<input id="noVNC_setting_video_out_time" type="range" min="0" max="60" value="3" onchange="noVNC_setting_video_out_time_output.value=value">
|
||||||
|
<output id="noVNC_setting_video_out_time_output">3</output>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<label for="noVNC_setting_video_scaling">Scaling Mode:</label>
|
||||||
|
<select id="noVNC_setting_video_scaling" name="vncVideoModeScaling">
|
||||||
|
<option value="0">Nearest</option>
|
||||||
|
<option value="1">Bilinear</option>
|
||||||
|
<option value="2">Progressive Bilinear</option>
|
||||||
|
</select>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<label for="noVNC_setting_max_video_resolution_x">Video Mode Width:</label>
|
||||||
|
<input id="noVNC_setting_max_video_resolution_x" type="number" min="100" max="3840" value="960">
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<label for="noVNC_setting_max_video_resolution_y">Video Mode Height:</label>
|
||||||
|
<input id="noVNC_setting_max_video_resolution_y" type="number" min="100" max="2160" value="540">
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li><hr></li>
|
||||||
|
<li>
|
||||||
|
<div class="noVNC_expander">Advanced</div>
|
||||||
|
<div>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<label for="noVNC_setting_compression">Compression level:</label>
|
||||||
|
<input id="noVNC_setting_compression" type="range" min="0" max="9" value="2">
|
||||||
|
</li>
|
||||||
|
<li><hr></li>
|
||||||
|
<li>
|
||||||
|
<label for="noVNC_setting_repeaterID">Repeater ID:</label>
|
||||||
|
<input id="noVNC_setting_repeaterID" type="text" value="">
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<div class="noVNC_expander">WebSocket</div>
|
||||||
|
<div>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<label class="switch">
|
||||||
|
<input id="noVNC_setting_encrypt" type="checkbox">
|
||||||
|
<span class="slider round"></span>
|
||||||
|
<span class="slider-label">Encrypt</span>
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<label for="noVNC_setting_host">Host:</label>
|
||||||
|
<input id="noVNC_setting_host">
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<label for="noVNC_setting_port">Port:</label>
|
||||||
|
<input id="noVNC_setting_port" type="number">
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<label for="noVNC_setting_path">Path:</label>
|
||||||
|
<input id="noVNC_setting_path" type="text" value="websockify">
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li><hr></li>
|
||||||
|
<li>
|
||||||
|
<label class="switch">
|
||||||
|
<input id="noVNC_setting_reconnect" type="checkbox">
|
||||||
|
<span class="slider-label">Automatic Reconnect</span>
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<label for="noVNC_setting_reconnect_delay">Reconnect Delay (ms):</label>
|
||||||
|
<input id="noVNC_setting_reconnect_delay" type="number">
|
||||||
|
</li>
|
||||||
|
<li><hr></li>
|
||||||
|
<li>
|
||||||
|
<label class="switch"><input id="noVNC_setting_show_dot" type="checkbox">
|
||||||
|
<span class="slider round"></span>
|
||||||
|
<span class="slider-label">Show Dot when No Cursor</span>
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
<li><hr></li>
|
||||||
|
<!-- Logging selection dropdown -->
|
||||||
|
<li>
|
||||||
|
<label>Logging:
|
||||||
|
<select id="noVNC_setting_logging" name="vncLogging">
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="noVNC_version_separator"><hr></li>
|
||||||
|
<li class="noVNC_version_wrapper">
|
||||||
|
<span>Version:</span>
|
||||||
|
<span class="noVNC_version"></span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
Settings
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Connection Controls -->
|
||||||
|
<div class="noVNC_button_div noVNC_hide_on_disconnect" >
|
||||||
|
<input type="image" alt="Disconnect" src="app/images/disconnect.svg"
|
||||||
|
id="noVNC_disconnect_button" class="noVNC_button"
|
||||||
|
title="Disconnect">
|
||||||
|
Disconnect
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Connection Controls -->
|
||||||
|
<div class="noVNC_button_div noVNC_hide_on_connect" id="noVNC_connect_button" >
|
||||||
|
<input type="image" alt="Connect" src="app/images/connect.svg"
|
||||||
|
class="noVNC_button"
|
||||||
|
title="Connect">
|
||||||
|
Connect
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -389,15 +523,16 @@
|
||||||
</div> <!-- End of noVNC_control_bar -->
|
</div> <!-- End of noVNC_control_bar -->
|
||||||
|
|
||||||
<!-- Status Dialog -->
|
<!-- Status Dialog -->
|
||||||
<div style="visibility: hidden" id="noVNC_status"></div>
|
<div id="noVNC_status"></div>
|
||||||
|
|
||||||
<!-- Connect button -->
|
<!-- Connect button -->
|
||||||
<div class="noVNC_center">
|
<div class="noVNC_center">
|
||||||
<div id="noVNC_connect_dlg">
|
<div id="noVNC_connect_dlg">
|
||||||
<!--div class="noVNC_logo" translate="no"><span>no</span>VNC</div-->
|
<!--div id="noVNC_connect_button">
|
||||||
<div id="noVNC_connect_button"><div>
|
<div>
|
||||||
<img alt="" src="app/images/connect.svg"> Connect
|
<img alt="" src="app/images/connect.svg"> Connect
|
||||||
</div></div>
|
</div>
|
||||||
|
</div-->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -444,7 +579,7 @@
|
||||||
<source src="app/sounds/bell.mp3" type="audio/mpeg">
|
<source src="app/sounds/bell.mp3" type="audio/mpeg">
|
||||||
</audio>
|
</audio>
|
||||||
|
|
||||||
<div class="keyboard-controls">
|
<div id="noVNC_keyboard_control" class="keyboard-controls">
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
<div class="button ctrl"></div>
|
<div class="button ctrl"></div>
|
||||||
<div class="button alt"></div>
|
<div class="button alt"></div>
|
||||||
|
|
@ -454,7 +589,7 @@
|
||||||
<div class="button ctrlaltdel"></div>
|
<div class="button ctrlaltdel"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="button keyboard handle"></div>
|
<div id="noVNC_keyboard_control_handle" class="button keyboard handle"></div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
182
vnc_lite.html
182
vnc_lite.html
|
|
@ -1,182 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
|
|
||||||
<!--
|
|
||||||
noVNC example: lightweight example using minimal UI and features
|
|
||||||
|
|
||||||
This is a self-contained file which doesn't import WebUtil or external CSS.
|
|
||||||
|
|
||||||
Copyright (C) 2019 The noVNC Authors
|
|
||||||
noVNC is licensed under the MPL 2.0 (see LICENSE.txt)
|
|
||||||
This file is licensed under the 2-Clause BSD license (see LICENSE.txt).
|
|
||||||
|
|
||||||
Connect parameters are provided in query string:
|
|
||||||
http://example.com/?host=HOST&port=PORT&scale=true
|
|
||||||
-->
|
|
||||||
<title>KasmVNC</title>
|
|
||||||
|
|
||||||
<meta charset="utf-8">
|
|
||||||
|
|
||||||
<style>
|
|
||||||
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
background-color: dimgrey;
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
html {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
#top_bar {
|
|
||||||
background-color: #6e84a3;
|
|
||||||
color: white;
|
|
||||||
font: bold 12px Helvetica;
|
|
||||||
padding: 6px 5px 4px 5px;
|
|
||||||
border-bottom: 1px outset;
|
|
||||||
}
|
|
||||||
#status {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
#sendCtrlAltDelButton {
|
|
||||||
position: fixed;
|
|
||||||
top: 0px;
|
|
||||||
right: 0px;
|
|
||||||
border: 1px outset;
|
|
||||||
padding: 5px 5px 4px 5px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
#screen {
|
|
||||||
flex: 1; /* fill remaining space */
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<script type="module" crossorigin="anonymous">
|
|
||||||
// RFB holds the API to connect and communicate with a VNC server
|
|
||||||
import RFB from './core/rfb.js';
|
|
||||||
|
|
||||||
let rfb;
|
|
||||||
let desktopName;
|
|
||||||
|
|
||||||
// When this function is called we have
|
|
||||||
// successfully connected to a server
|
|
||||||
function connectedToServer(e) {
|
|
||||||
status("Connected to " + desktopName);
|
|
||||||
}
|
|
||||||
|
|
||||||
// This function is called when we are disconnected
|
|
||||||
function disconnectedFromServer(e) {
|
|
||||||
if (e.detail.clean) {
|
|
||||||
status("Disconnected");
|
|
||||||
} else {
|
|
||||||
status("Something went wrong, connection is closed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// When this function is called, the server requires
|
|
||||||
// credentials to authenticate
|
|
||||||
function credentialsAreRequired(e) {
|
|
||||||
const password = prompt("Password Required:");
|
|
||||||
rfb.sendCredentials({ password: password });
|
|
||||||
}
|
|
||||||
|
|
||||||
// When this function is called we have received
|
|
||||||
// a desktop name from the server
|
|
||||||
function updateDesktopName(e) {
|
|
||||||
desktopName = e.detail.name;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Since most operating systems will catch Ctrl+Alt+Del
|
|
||||||
// before they get a chance to be intercepted by the browser,
|
|
||||||
// we provide a way to emulate this key sequence.
|
|
||||||
function sendCtrlAltDel() {
|
|
||||||
rfb.sendCtrlAltDel();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show a status text in the top bar
|
|
||||||
function status(text) {
|
|
||||||
document.getElementById('status').textContent = text;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This function extracts the value of one variable from the
|
|
||||||
// query string. If the variable isn't defined in the URL
|
|
||||||
// it returns the default value instead.
|
|
||||||
function readQueryVariable(name, defaultValue) {
|
|
||||||
// A URL with a query parameter can look like this:
|
|
||||||
// https://www.example.com?myqueryparam=myvalue
|
|
||||||
//
|
|
||||||
// Note that we use location.href instead of location.search
|
|
||||||
// because Firefox < 53 has a bug w.r.t location.search
|
|
||||||
const re = new RegExp('.*[?&]' + name + '=([^&#]*)'),
|
|
||||||
match = document.location.href.match(re);
|
|
||||||
|
|
||||||
if (match) {
|
|
||||||
// We have to decode the URL since want the cleartext value
|
|
||||||
return decodeURIComponent(match[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return defaultValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
document.getElementById('sendCtrlAltDelButton')
|
|
||||||
.onclick = sendCtrlAltDel;
|
|
||||||
|
|
||||||
// Read parameters specified in the URL query string
|
|
||||||
// By default, use the host and port of server that served this file
|
|
||||||
const host = readQueryVariable('host', window.location.hostname);
|
|
||||||
let port = readQueryVariable('port', window.location.port);
|
|
||||||
const password = readQueryVariable('password');
|
|
||||||
const path = readQueryVariable('path', 'websockify');
|
|
||||||
|
|
||||||
// | | | | | |
|
|
||||||
// | | | Connect | | |
|
|
||||||
// v v v v v v
|
|
||||||
|
|
||||||
status("Connecting");
|
|
||||||
|
|
||||||
// Build the websocket URL used to connect
|
|
||||||
let url;
|
|
||||||
if (window.location.protocol === "https:") {
|
|
||||||
url = 'wss';
|
|
||||||
} else {
|
|
||||||
url = 'ws';
|
|
||||||
}
|
|
||||||
url += '://' + host;
|
|
||||||
if(port) {
|
|
||||||
url += ':' + port;
|
|
||||||
}
|
|
||||||
url += '/' + path;
|
|
||||||
|
|
||||||
// Creating a new RFB object will start a new connection
|
|
||||||
rfb = new RFB(document.getElementById('screen'), url,
|
|
||||||
{ credentials: { password: password } });
|
|
||||||
|
|
||||||
// Add listeners to important events from the RFB module
|
|
||||||
rfb.addEventListener("connect", connectedToServer);
|
|
||||||
rfb.addEventListener("disconnect", disconnectedFromServer);
|
|
||||||
rfb.addEventListener("credentialsrequired", credentialsAreRequired);
|
|
||||||
rfb.addEventListener("desktopname", updateDesktopName);
|
|
||||||
|
|
||||||
// Set parameters that can be changed on an active connection
|
|
||||||
rfb.viewOnly = readQueryVariable('view_only', false);
|
|
||||||
rfb.scaleViewport = readQueryVariable('scale', false);
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div id="top_bar">
|
|
||||||
<div id="status">Loading</div>
|
|
||||||
<div id="sendCtrlAltDelButton">Send CtrlAltDel</div>
|
|
||||||
</div>
|
|
||||||
<div id="screen">
|
|
||||||
<!-- This is where the remote screen will appear -->
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
Loading…
Reference in New Issue