rebase
This commit is contained in:
commit
e5a62ea66b
|
|
@ -26,6 +26,12 @@
|
|||
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");
|
||||
div.classList.add('noVNC_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 {
|
||||
margin: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;
|
||||
height:100%;
|
||||
touch-action: none;
|
||||
|
|
@ -37,7 +38,7 @@ html {
|
|||
}
|
||||
|
||||
.noVNC_disabled {
|
||||
color: rgb(128, 128, 128);
|
||||
color: rgb(128, 128, 128) !important;
|
||||
}
|
||||
|
||||
/* ----------------------------------------
|
||||
|
|
@ -351,8 +352,9 @@ select:active {
|
|||
/* Edge misrenders animations wihthout this */
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
:root.noVNC_connected #noVNC_control_bar_anchor.noVNC_idle {
|
||||
opacity: 0.8;
|
||||
/* opacity: 0.8; */
|
||||
}
|
||||
#noVNC_control_bar_anchor.noVNC_right {
|
||||
left: auto;
|
||||
|
|
@ -365,12 +367,12 @@ select:active {
|
|||
|
||||
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-style: inset;
|
||||
border-color: rgb(255 255 255 / 0.6);
|
||||
}
|
||||
#noVNC_control_bar.noVNC_open {
|
||||
box-shadow: 6px 6px 0px rgba(0, 0, 0, 0.5);
|
||||
left: 0;
|
||||
}
|
||||
#noVNC_control_bar::before {
|
||||
|
|
@ -400,28 +402,31 @@ select:active {
|
|||
#noVNC_control_bar_handle {
|
||||
position: absolute;
|
||||
left: -15px;
|
||||
top: 0;
|
||||
transform: translateY(35px);
|
||||
width: calc(100% + 30px);
|
||||
height: 50px;
|
||||
z-index: -1;
|
||||
cursor: pointer;
|
||||
border-radius: 5px;
|
||||
background-color: rgb(83, 99, 122);
|
||||
background-image: url("../images/handle_bg.svg");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right;
|
||||
box-shadow: 3px 3px 0px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
#noVNC_control_bar_handle:after {
|
||||
content: "";
|
||||
transition: transform 0.5s ease-in-out;
|
||||
background: url("../images/handle.svg");
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
position: absolute;
|
||||
top: 22px; /* (50px-6px)/2 */
|
||||
right: 5px;
|
||||
width: 5px;
|
||||
height: 6px;
|
||||
right: 0px;
|
||||
width: 15px;
|
||||
height: 60px;
|
||||
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 {
|
||||
transform: translateX(1px) rotate(180deg);
|
||||
|
|
@ -431,6 +436,7 @@ select:active {
|
|||
}
|
||||
.noVNC_right #noVNC_control_bar_handle {
|
||||
background-position: left;
|
||||
|
||||
}
|
||||
.noVNC_right #noVNC_control_bar_handle:after {
|
||||
left: 5px;
|
||||
|
|
@ -495,18 +501,23 @@ select:active {
|
|||
transform: translateY(-50%) scale(1);
|
||||
}
|
||||
|
||||
.noVNC_button_div {
|
||||
display: block;
|
||||
color: #fff;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* General button style */
|
||||
.noVNC_button {
|
||||
display: block;
|
||||
display: inline;
|
||||
padding: 4px 4px;
|
||||
margin: 10px 0;
|
||||
vertical-align: middle;
|
||||
border:1px solid rgba(255, 255, 255, 0.2);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.noVNC_button.noVNC_selected {
|
||||
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 {
|
||||
opacity: 0.4;
|
||||
|
|
@ -523,10 +534,10 @@ select:active {
|
|||
:root:not(.noVNC_touch) .noVNC_button.noVNC_selected:hover,
|
||||
.noVNC_button.noVNC_selected:focus {
|
||||
border-color: rgba(0, 0, 0, 0.4);
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
background: rgba(153, 151, 157, 0.68);
|
||||
}
|
||||
:root:not(.noVNC_touch) .noVNC_button:hover,
|
||||
.noVNC_button:focus {
|
||||
:root:not(.noVNC_touch) .noVNC_button_div:hover,
|
||||
.noVNC_button_div:focus {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
.noVNC_button.noVNC_hidden {
|
||||
|
|
@ -539,6 +550,7 @@ select:active {
|
|||
|
||||
transition: 0.5s ease-in-out;
|
||||
|
||||
width: 300px;
|
||||
max-height: 100vh; /* Chrome is buggy with 100% */
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
|
|
@ -548,11 +560,9 @@ select:active {
|
|||
|
||||
padding: 15px;
|
||||
|
||||
background: #fff;
|
||||
background: rgb(9 9 0 / 0.77);
|
||||
border-radius: 10px;
|
||||
color: #000;
|
||||
border: 2px solid #E0E0E0;
|
||||
box-shadow: 6px 6px 0px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
.noVNC_panel.noVNC_open {
|
||||
visibility: visible;
|
||||
|
|
@ -578,10 +588,11 @@ select:active {
|
|||
.noVNC_panel label {
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
color:white;
|
||||
}
|
||||
|
||||
.noVNC_panel .noVNC_heading {
|
||||
background-color: rgb(110, 132, 163);
|
||||
background-color: rgb(54,58,64);
|
||||
border-radius: 5px;
|
||||
padding: 5px;
|
||||
/* Compensate for padding in image */
|
||||
|
|
@ -602,12 +613,16 @@ select:active {
|
|||
/* Expanders */
|
||||
.noVNC_expander {
|
||||
cursor: pointer;
|
||||
color:white;
|
||||
|
||||
}
|
||||
.noVNC_expander::before {
|
||||
content: url("../images/expander.svg");
|
||||
display: inline-block;
|
||||
margin-right: 5px;
|
||||
transition: 0.2s ease-in-out;
|
||||
-webkit-filter: invert(.75); /* safari 6.0 - 9.0 */
|
||||
filter: invert(.75);
|
||||
}
|
||||
.noVNC_expander.noVNC_open::before {
|
||||
transform: rotateZ(90deg);
|
||||
|
|
@ -630,6 +645,14 @@ select:active {
|
|||
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 {
|
||||
display: none;
|
||||
}
|
||||
|
|
@ -687,6 +710,7 @@ select:active {
|
|||
list-style: none;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
color:white;
|
||||
}
|
||||
#noVNC_setting_port {
|
||||
width: 80px;
|
||||
|
|
@ -776,81 +800,6 @@ select:active {
|
|||
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
|
||||
|
|
@ -930,21 +879,19 @@ select:active {
|
|||
#noVNC_container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgb(74, 144, 217, 0.5);
|
||||
border-bottom-right-radius: 800px 600px;
|
||||
/*border-top-left-radius: 800px 600px;*/
|
||||
background-image: url('../images/splash.jpg')
|
||||
}
|
||||
|
||||
#noVNC_keyboardinput {
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
background-color: #fff;
|
||||
color: #fff;
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
background-color: #fff0;
|
||||
color: rgba(5, 5, 5, 0);
|
||||
border: 0;
|
||||
position: absolute;
|
||||
left: -40px;
|
||||
left: 35%;
|
||||
top: 40%;
|
||||
z-index: -1;
|
||||
ime-mode: disabled;
|
||||
}
|
||||
|
||||
/*Default noVNC logo.*/
|
||||
|
|
@ -962,6 +909,10 @@ select:active {
|
|||
font-family: 'Orbitron', 'OrbitronTTF', sans-serif;
|
||||
line-height:90%;
|
||||
text-shadow: 0.1em 0.1em 0 black;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
.noVNC_logo img {
|
||||
width: 45%
|
||||
}
|
||||
.noVNC_logo span{
|
||||
color:green;
|
||||
|
|
@ -1021,6 +972,11 @@ body {
|
|||
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 {
|
||||
background-image: url("../images/ctrl.svg");
|
||||
background-size: contain;
|
||||
|
|
@ -1134,3 +1090,75 @@ body {
|
|||
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 * as Log from '../core/util/logging.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';
|
||||
import { setCapture, getPointerEvent } from '../core/util/events.js';
|
||||
import KeyTable from "../core/input/keysym.js";
|
||||
|
|
@ -47,6 +47,7 @@ const PAGE_TITLE = "KasmVNC";
|
|||
|
||||
var delta = 500;
|
||||
var lastKeypressTime = 0;
|
||||
var lastKeypressCode = -1;
|
||||
var currentEventCount = -1;
|
||||
var idleCounter = 0;
|
||||
|
||||
|
|
@ -65,8 +66,6 @@ const UI = {
|
|||
controlbarMouseDownClientY: 0,
|
||||
controlbarMouseDownOffsetY: 0,
|
||||
|
||||
lastKeyboardinput: null,
|
||||
defaultKeyboardinputLen: 100,
|
||||
needToCheckClipboardChange: false,
|
||||
|
||||
inhibitReconnect: true,
|
||||
|
|
@ -129,16 +128,13 @@ const UI = {
|
|||
UI.addControlbarHandlers();
|
||||
UI.addTouchSpecificHandlers();
|
||||
UI.addExtraKeysHandlers();
|
||||
UI.addGamingHandlers();
|
||||
UI.addMachineHandlers();
|
||||
UI.addConnectionControlHandlers();
|
||||
UI.addClipboardHandlers();
|
||||
UI.addSettingsHandlers();
|
||||
document.getElementById("noVNC_status")
|
||||
.addEventListener('click', UI.hideStatus);
|
||||
|
||||
// Bootstrap fallback input handler
|
||||
UI.keyboardinputReset();
|
||||
|
||||
UI.openControlbar();
|
||||
|
||||
UI.updateVisualState('init');
|
||||
|
|
@ -151,8 +147,6 @@ const UI = {
|
|||
UI.connect();
|
||||
} else {
|
||||
autoconnect = false;
|
||||
// Show the connect panel on first load unless autoconnecting
|
||||
UI.openConnectPanel();
|
||||
}
|
||||
|
||||
window.parent.postMessage({
|
||||
|
|
@ -172,6 +166,12 @@ const UI = {
|
|||
}
|
||||
});
|
||||
|
||||
window.addEventListener("beforeunload", (e) => {
|
||||
if (UI.rfb) {
|
||||
UI.disconnect();
|
||||
}
|
||||
});
|
||||
|
||||
return Promise.resolve(UI.rfb);
|
||||
},
|
||||
|
||||
|
|
@ -183,10 +183,9 @@ const UI = {
|
|||
document.documentElement.mozRequestFullScreen ||
|
||||
document.documentElement.webkitRequestFullscreen ||
|
||||
document.body.msRequestFullscreen)) {
|
||||
document.getElementById('noVNC_fullscreen_button')
|
||||
.classList.remove("noVNC_hidden");
|
||||
UI.addFullscreenHandlers();
|
||||
}
|
||||
UI.showControlInput("noVNC_fullscreen_button")
|
||||
UI.addFullscreenHandlers();
|
||||
}
|
||||
},
|
||||
|
||||
initSettings() {
|
||||
|
|
@ -245,6 +244,9 @@ const UI = {
|
|||
UI.initSetting('prefer_local_cursor', true);
|
||||
UI.initSetting('toggle_control_panel', false);
|
||||
UI.initSetting('enable_perf_stats', false);
|
||||
UI.initSetting('virtual_keyboard_visible', false);
|
||||
UI.initSetting('enable_ime', false)
|
||||
UI.toggleKeyboardControls();
|
||||
|
||||
if (WebUtil.isInsideKasmVDI()) {
|
||||
UI.initSetting('clipboard_up', false);
|
||||
|
|
@ -350,8 +352,7 @@ const UI = {
|
|||
document.getElementById("noVNC_control_bar")
|
||||
.addEventListener('keydown', UI.keepControlbar);
|
||||
|
||||
document.getElementById("noVNC_view_drag_button")
|
||||
.addEventListener('click', UI.toggleViewDrag);
|
||||
UI.addClickHandle('noVNC_view_drag_button', UI.toggleViewDrag);
|
||||
|
||||
document.getElementById("noVNC_control_bar_handle")
|
||||
.addEventListener('mousedown', UI.controlbarHandleMouseDown);
|
||||
|
|
@ -371,12 +372,6 @@ const UI = {
|
|||
addTouchSpecificHandlers() {
|
||||
document.getElementById("noVNC_keyboard_button")
|
||||
.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")
|
||||
.addEventListener('focus', UI.onfocusVirtualKeyboard);
|
||||
document.getElementById("noVNC_keyboardinput")
|
||||
|
|
@ -410,8 +405,8 @@ const UI = {
|
|||
},
|
||||
|
||||
addExtraKeysHandlers() {
|
||||
document.getElementById("noVNC_toggle_extra_keys_button")
|
||||
.addEventListener('click', UI.toggleExtraKeys);
|
||||
UI.addClickHandle('noVNC_toggle_extra_keys_button', UI.toggleExtraKeys);
|
||||
|
||||
document.getElementById("noVNC_toggle_ctrl_button")
|
||||
.addEventListener('click', UI.toggleCtrl);
|
||||
document.getElementById("noVNC_toggle_windows_button")
|
||||
|
|
@ -426,20 +421,27 @@ const UI = {
|
|||
.addEventListener('click', UI.sendCtrlAltDel);
|
||||
},
|
||||
|
||||
addGamingHandlers() {
|
||||
UI.addClickHandle('noVNC_game_mode_button', UI.toggleRelativePointer);
|
||||
document
|
||||
.getElementById("noVNC_setting_pointer_lock")
|
||||
.addEventListener("click", UI.togglePointerLock);
|
||||
},
|
||||
|
||||
addMachineHandlers() {
|
||||
UI.addClickHandle('noVNC_power_button', UI.togglePowerPanel);
|
||||
|
||||
document.getElementById("noVNC_shutdown_button")
|
||||
.addEventListener('click', () => UI.rfb.machineShutdown());
|
||||
document.getElementById("noVNC_reboot_button")
|
||||
.addEventListener('click', () => UI.rfb.machineReboot());
|
||||
document.getElementById("noVNC_reset_button")
|
||||
.addEventListener('click', () => UI.rfb.machineReset());
|
||||
document.getElementById("noVNC_power_button")
|
||||
.addEventListener('click', UI.togglePowerPanel);
|
||||
},
|
||||
|
||||
addConnectionControlHandlers() {
|
||||
document.getElementById("noVNC_disconnect_button")
|
||||
.addEventListener('click', UI.disconnect);
|
||||
UI.addClickHandle('noVNC_disconnect_button', UI.disconnect);
|
||||
|
||||
var connect_btn_el = document.getElementById("noVNC_connect_button");
|
||||
if (typeof(connect_btn_el) != 'undefined' && connect_btn_el != null)
|
||||
{
|
||||
|
|
@ -453,8 +455,8 @@ const UI = {
|
|||
},
|
||||
|
||||
addClipboardHandlers() {
|
||||
document.getElementById("noVNC_clipboard_button")
|
||||
.addEventListener('click', UI.toggleClipboardPanel);
|
||||
UI.addClickHandle('noVNC_clipboard_button', UI.toggleClipboardPanel);
|
||||
|
||||
document.getElementById("noVNC_clipboard_text")
|
||||
.addEventListener('change', UI.clipboardSend);
|
||||
document.getElementById("noVNC_clipboard_clear_button")
|
||||
|
|
@ -474,8 +476,7 @@ const UI = {
|
|||
},
|
||||
|
||||
addSettingsHandlers() {
|
||||
document.getElementById("noVNC_settings_button")
|
||||
.addEventListener('click', UI.toggleSettingsPanel);
|
||||
UI.addClickHandle('noVNC_settings_button', UI.toggleSettingsPanel);
|
||||
|
||||
document.getElementById("noVNC_setting_enable_perf_stats").addEventListener('click', UI.showStats);
|
||||
|
||||
|
|
@ -536,11 +537,15 @@ const UI = {
|
|||
UI.addSettingChangeHandler('clipboard_seamless');
|
||||
UI.addSettingChangeHandler('clipboard_up');
|
||||
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() {
|
||||
document.getElementById("noVNC_fullscreen_button")
|
||||
.addEventListener('click', UI.toggleFullscreen);
|
||||
UI.addClickHandle('noVNC_fullscreen_button', UI.toggleFullscreen);
|
||||
|
||||
window.addEventListener('fullscreenchange', UI.updateFullscreenButton);
|
||||
window.addEventListener('mozfullscreenchange', UI.updateFullscreenButton);
|
||||
|
|
@ -553,6 +558,20 @@ const UI = {
|
|||
* ==============
|
||||
* VISUAL
|
||||
* ------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
|
||||
updateVisualState(state) {
|
||||
|
|
@ -561,6 +580,7 @@ const UI = {
|
|||
document.documentElement.classList.remove("noVNC_connected");
|
||||
document.documentElement.classList.remove("noVNC_disconnecting");
|
||||
document.documentElement.classList.remove("noVNC_reconnecting");
|
||||
document.documentElement.classList.remove("noVNC_disconnected");
|
||||
|
||||
const transitionElem = document.getElementById("noVNC_transition_text");
|
||||
if (WebUtil.isInsideKasmVDI())
|
||||
|
|
@ -583,6 +603,7 @@ const UI = {
|
|||
document.documentElement.classList.add("noVNC_disconnecting");
|
||||
break;
|
||||
case 'disconnected':
|
||||
document.documentElement.classList.add("noVNC_disconnected");
|
||||
break;
|
||||
case 'reconnecting':
|
||||
transitionElem.textContent = _("Reconnecting...");
|
||||
|
|
@ -616,6 +637,7 @@ const UI = {
|
|||
UI.updatePowerButton();
|
||||
UI.keepControlbar();
|
||||
}
|
||||
//UI.updatePointerLockButton();
|
||||
|
||||
// State change closes dialogs as they may not be relevant
|
||||
// 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');
|
||||
|
||||
if (typeof statusType === 'undefined') {
|
||||
|
|
@ -737,7 +764,9 @@ const UI = {
|
|||
UI.closeAllPanels();
|
||||
document.getElementById('noVNC_control_bar')
|
||||
.classList.remove("noVNC_open");
|
||||
UI.rfb.focus();
|
||||
if (UI.rfb) {
|
||||
UI.rfb.focus();
|
||||
}
|
||||
},
|
||||
|
||||
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
|
||||
* ==============
|
||||
|
|
@ -1076,7 +1147,11 @@ const UI = {
|
|||
.classList.remove("noVNC_selected");
|
||||
},
|
||||
|
||||
toggleSettingsPanel() {
|
||||
toggleSettingsPanel(e) {
|
||||
if (!UI.isControlPanelItemClick(e)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (document.getElementById('noVNC_settings')
|
||||
.classList.contains("noVNC_open")) {
|
||||
UI.closeSettingsPanel();
|
||||
|
|
@ -1108,7 +1183,11 @@ const UI = {
|
|||
.classList.remove("noVNC_selected");
|
||||
},
|
||||
|
||||
togglePowerPanel() {
|
||||
togglePowerPanel(e) {
|
||||
if (!UI.isControlPanelItemClick(e)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (document.getElementById('noVNC_power')
|
||||
.classList.contains("noVNC_open")) {
|
||||
UI.closePowerPanel();
|
||||
|
|
@ -1122,11 +1201,9 @@ const UI = {
|
|||
if (UI.connected &&
|
||||
UI.rfb.capabilities.power &&
|
||||
!UI.rfb.viewOnly) {
|
||||
document.getElementById('noVNC_power_button')
|
||||
.classList.remove("noVNC_hidden");
|
||||
UI.showControlInput('noVNC_power_button')
|
||||
} else {
|
||||
document.getElementById('noVNC_power_button')
|
||||
.classList.add("noVNC_hidden");
|
||||
UI.hideControlInput('noVNC_power_button');
|
||||
// Close power panel if open
|
||||
UI.closePowerPanel();
|
||||
}
|
||||
|
|
@ -1155,7 +1232,11 @@ const UI = {
|
|||
.classList.remove("noVNC_selected");
|
||||
},
|
||||
|
||||
toggleClipboardPanel() {
|
||||
toggleClipboardPanel(e) {
|
||||
if (!UI.isControlPanelItemClick(e)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (document.getElementById('noVNC_clipboard')
|
||||
.classList.contains("noVNC_open")) {
|
||||
UI.closeClipboardPanel();
|
||||
|
|
@ -1245,16 +1326,6 @@ const UI = {
|
|||
* CONNECTION
|
||||
* ------v------*/
|
||||
|
||||
openConnectPanel() {
|
||||
document.getElementById('noVNC_connect_dlg')
|
||||
.classList.add("noVNC_open");
|
||||
},
|
||||
|
||||
closeConnectPanel() {
|
||||
document.getElementById('noVNC_connect_dlg')
|
||||
.classList.remove("noVNC_open");
|
||||
},
|
||||
|
||||
connect(event, password) {
|
||||
|
||||
// Ignore when rfb already exists
|
||||
|
|
@ -1283,8 +1354,6 @@ const UI = {
|
|||
return;
|
||||
}
|
||||
|
||||
UI.closeConnectPanel();
|
||||
|
||||
UI.updateVisualState('connecting');
|
||||
|
||||
let url;
|
||||
|
|
@ -1297,7 +1366,9 @@ const UI = {
|
|||
}
|
||||
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'),
|
||||
repeaterID: UI.getSetting('repeaterID'),
|
||||
credentials: { password: password } });
|
||||
|
|
@ -1310,6 +1381,8 @@ const UI = {
|
|||
UI.rfb.addEventListener("bottleneck_stats", UI.bottleneckStatsRecieve);
|
||||
UI.rfb.addEventListener("bell", UI.bell);
|
||||
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.clipViewport = UI.getSetting('view_clip');
|
||||
UI.rfb.scaleViewport = UI.getSetting('resize') === 'scale';
|
||||
|
|
@ -1330,11 +1403,13 @@ const UI = {
|
|||
UI.rfb.compressionLevel = parseInt(UI.getSetting('compression'));
|
||||
UI.rfb.showDotCursor = UI.getSetting('show_dot');
|
||||
UI.rfb.idleDisconnect = UI.getSetting('idle_disconnect');
|
||||
UI.rfb.pointerRelative = UI.getSetting('pointer_relative');
|
||||
UI.rfb.videoQuality = parseInt(UI.getSetting('video_quality'));
|
||||
UI.rfb.antiAliasing = UI.getSetting('anti_aliasing');
|
||||
UI.rfb.clipboardUp = UI.getSetting('clipboard_up');
|
||||
UI.rfb.clipboardDown = UI.getSetting('clipboard_down');
|
||||
UI.rfb.clipboardSeamless = UI.getSetting('clipboard_seamless');
|
||||
UI.rfb.keyboard.enableIME = UI.getSetting('enable_ime');
|
||||
UI.rfb.clipboardBinary = supportsBinaryClipboard() && UI.rfb.clipboardSeamless;
|
||||
|
||||
//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);
|
||||
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
|
||||
//send a keep alive within a window that we control
|
||||
|
|
@ -1396,23 +1470,23 @@ const UI = {
|
|||
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
|
||||
if (UI.getSetting('toggle_control_panel', false)) {
|
||||
|
||||
document.addEventListener('keyup', function (event) {
|
||||
// CTRL and the various implementations of the mac command key
|
||||
if ([17, 224, 91, 93].indexOf(event.keyCode) > -1) {
|
||||
var thisKeypressTime = new Date();
|
||||
|
||||
if (thisKeypressTime - lastKeypressTime <= delta) {
|
||||
UI.toggleNav();
|
||||
thisKeypressTime = 0;
|
||||
//key events for KasmVNC control
|
||||
document.addEventListener('keyup', function (event) {
|
||||
if (event.ctrlKey && event.shiftKey) {
|
||||
switch(event.keyCode) {
|
||||
case 49:
|
||||
UI.toggleNav();
|
||||
break;
|
||||
case 50:
|
||||
UI.toggleRelativePointer();
|
||||
break;
|
||||
case 51:
|
||||
UI.togglePointerLock();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
lastKeypressTime = thisKeypressTime;
|
||||
}
|
||||
}, true);
|
||||
}
|
||||
}, true);
|
||||
},
|
||||
|
||||
disconnect() {
|
||||
|
|
@ -1449,7 +1523,6 @@ const UI = {
|
|||
UI.updateVisualState('disconnected');
|
||||
|
||||
UI.openControlbar();
|
||||
UI.openConnectPanel();
|
||||
},
|
||||
|
||||
connectFinished(e) {
|
||||
|
|
@ -1503,7 +1576,6 @@ const UI = {
|
|||
document.title = PAGE_TITLE;
|
||||
|
||||
UI.openControlbar();
|
||||
UI.openConnectPanel();
|
||||
|
||||
if (UI.forceReconnect) {
|
||||
UI.forceReconnect = false;
|
||||
|
|
@ -1525,18 +1597,19 @@ const UI = {
|
|||
UI.showStatus(msg, 'error');
|
||||
},
|
||||
|
||||
/*
|
||||
Menu.js Additions
|
||||
*/
|
||||
receiveMessage(event) {
|
||||
//TODO: UNCOMMENT FOR PRODUCTION
|
||||
//if (event.origin !== "https://kasmweb.com")
|
||||
// return;
|
||||
//send message to parent window
|
||||
sendMessage(name, value) {
|
||||
if (WebUtil.isInsideKasmVDI()) {
|
||||
parent.postMessage({ action: name, value: value }, '*' );
|
||||
}
|
||||
},
|
||||
|
||||
//receive message from parent window
|
||||
receiveMessage(event) {
|
||||
if (event.data && event.data.action) {
|
||||
switch (event.data.action) {
|
||||
case 'clipboardsnd':
|
||||
if (UI.rfb.clipboardUp) {
|
||||
if (UI.rfb && UI.rfb.clipboardUp) {
|
||||
UI.rfb.clipboardPasteFrom(event.data.value);
|
||||
}
|
||||
break;
|
||||
|
|
@ -1544,6 +1617,50 @@ const UI = {
|
|||
UI.forceSetting('video_quality', parseInt(event.data.value), false);
|
||||
UI.updateQuality();
|
||||
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(){
|
||||
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) {
|
||||
|
|
@ -1656,6 +1781,7 @@ const UI = {
|
|||
document.getElementById('noVNC_fullscreen_button')
|
||||
.classList.remove("noVNC_selected");
|
||||
}
|
||||
UI.updatePointerLockButton();
|
||||
},
|
||||
|
||||
/* ------^-------
|
||||
|
|
@ -1708,6 +1834,68 @@ const UI = {
|
|||
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
|
||||
* ==============
|
||||
|
|
@ -1739,9 +1927,9 @@ const UI = {
|
|||
}
|
||||
|
||||
if (UI.rfb.clipViewport) {
|
||||
viewDragButton.classList.remove("noVNC_hidden");
|
||||
UI.showControlInput('noVNC_view_drag_button');
|
||||
} 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');
|
||||
},
|
||||
|
||||
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() {
|
||||
document.querySelector(".keyboard-controls").classList.add("is-visible");
|
||||
document.getElementById('noVNC_keyboard_control').classList.add("is-visible");
|
||||
},
|
||||
|
||||
hideKeyboardControls() {
|
||||
document.querySelector(".keyboard-controls").classList.remove("is-visible");
|
||||
document.getElementById('noVNC_keyboard_control').classList.remove("is-visible");
|
||||
},
|
||||
|
||||
showVirtualKeyboard() {
|
||||
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();
|
||||
|
||||
|
|
@ -1916,7 +2127,12 @@ const UI = {
|
|||
hideVirtualKeyboard() {
|
||||
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();
|
||||
},
|
||||
|
|
@ -1941,6 +2157,12 @@ const UI = {
|
|||
onblurVirtualKeyboard(event) {
|
||||
document.getElementById('noVNC_keyboard_button')
|
||||
.classList.remove("noVNC_selected");
|
||||
|
||||
if (UI.getSetting('virtual_keyboard_visible')) {
|
||||
document.getElementById('noVNC_keyboard_control_handle')
|
||||
.classList.remove("noVNC_selected");
|
||||
}
|
||||
|
||||
if (UI.rfb) {
|
||||
UI.rfb.focusOnClick = true;
|
||||
}
|
||||
|
|
@ -1974,83 +2196,6 @@ const UI = {
|
|||
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
|
||||
* ==============
|
||||
|
|
@ -2082,7 +2227,11 @@ const UI = {
|
|||
.classList.remove("noVNC_selected");
|
||||
},
|
||||
|
||||
toggleExtraKeys() {
|
||||
toggleExtraKeys(e) {
|
||||
if (!UI.isControlPanelItemClick(e)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (document.getElementById('noVNC_modifiers').classList.contains("noVNC_open")) {
|
||||
UI.closeExtraKeys();
|
||||
} else {
|
||||
|
|
@ -2176,19 +2325,15 @@ const UI = {
|
|||
|
||||
// Hide input related buttons in view only mode
|
||||
if (UI.rfb.viewOnly) {
|
||||
document.getElementById('noVNC_keyboard_button')
|
||||
.classList.add('noVNC_hidden');
|
||||
document.getElementById('noVNC_toggle_extra_keys_button')
|
||||
.classList.add('noVNC_hidden');
|
||||
document.getElementById('noVNC_clipboard_button')
|
||||
.classList.add('noVNC_hidden');
|
||||
UI.hideControlInput("noVNC_keyboard_button");
|
||||
UI.hideControlInput("noVNC_toggle_extra_keys_button");
|
||||
UI.hideControlInput("noVNC_clipboard_button");
|
||||
UI.hideControlInput("noVNC_game_mode_button");
|
||||
} else {
|
||||
document.getElementById('noVNC_keyboard_button')
|
||||
.classList.remove('noVNC_hidden');
|
||||
document.getElementById('noVNC_toggle_extra_keys_button')
|
||||
.classList.remove('noVNC_hidden');
|
||||
document.getElementById('noVNC_clipboard_button')
|
||||
.classList.remove('noVNC_hidden');
|
||||
UI.showControlInput("noVNC_keyboard_button");
|
||||
UI.showControlInput("noVNC_toggle_extra_keys_button");
|
||||
UI.showControlInput("noVNC_clipboard_button");
|
||||
UI.showControlInput("noVNC_game_mode_button");
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -2207,6 +2352,39 @@ const UI = {
|
|||
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) {
|
||||
if (WebUtil.getConfigVar('bell', 'on') === 'on') {
|
||||
const promise = document.getElementById('noVNC_bell').play();
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ export const encodings = {
|
|||
pseudoEncodingVideoOutTimeLevel100: -1887,
|
||||
|
||||
pseudoEncodingVMwareCursor: 0x574d5664,
|
||||
pseudoEncodingVMwareCursorPosition: 0x574d5666,
|
||||
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 * as KeyboardUtil from "./util.js";
|
||||
import KeyTable from "./keysym.js";
|
||||
import keysyms from "./keysymdef.js";
|
||||
import imekeys from "./imekeys.js";
|
||||
import * as browser from "../util/browser.js";
|
||||
import UI from '../../app/ui.js';
|
||||
import { isChromiumBased } from '../util/browser.js';
|
||||
|
||||
//
|
||||
// Keyboard event handler
|
||||
//
|
||||
|
||||
export default class Keyboard {
|
||||
constructor(target) {
|
||||
this._target = target || null;
|
||||
constructor(screenInput, touchInput) {
|
||||
this._screenInput = screenInput;
|
||||
this._touchInput = touchInput;
|
||||
|
||||
this._keyDownList = {}; // List of depressed keys
|
||||
// (even if they are happy)
|
||||
|
|
@ -28,11 +32,28 @@ export default class Keyboard {
|
|||
'keyup': this._handleKeyUp.bind(this),
|
||||
'keydown': this._handleKeyDown.bind(this),
|
||||
'blur': this._allKeysUp.bind(this),
|
||||
'compositionstart' : this._handleCompositionStart.bind(this),
|
||||
'compositionend' : this._handleCompositionEnd.bind(this),
|
||||
'input' : this._handleInput.bind(this)
|
||||
};
|
||||
|
||||
// ===== EVENT HANDLERS =====
|
||||
|
||||
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 =====
|
||||
|
|
@ -95,10 +116,135 @@ export default class Keyboard {
|
|||
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) {
|
||||
const code = this._getKeyCode(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
|
||||
// fake Ctrl+Alt. However the remote end might not be Windows,
|
||||
// so we need to merge those in to a single AltGr event. We
|
||||
|
|
@ -220,10 +366,15 @@ export default class Keyboard {
|
|||
}
|
||||
|
||||
_handleKeyUp(e) {
|
||||
stopEvent(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
|
||||
// abort that detection
|
||||
if (this._altGrArmed) {
|
||||
|
|
@ -271,13 +422,56 @@ export default class Keyboard {
|
|||
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 =====
|
||||
|
||||
focus() {
|
||||
if (this._enableIME) {
|
||||
this._touchInput.focus();
|
||||
} else {
|
||||
this._screenInput.focus();
|
||||
}
|
||||
}
|
||||
|
||||
blur() {
|
||||
if (this._enableIME) {
|
||||
this._touchInput.blur();
|
||||
} else {
|
||||
this._screenInput.blur();
|
||||
}
|
||||
}
|
||||
|
||||
grab() {
|
||||
//Log.Debug(">> Keyboard.grab");
|
||||
|
||||
this._target.addEventListener('keydown', this._eventHandlers.keydown);
|
||||
this._target.addEventListener('keyup', this._eventHandlers.keyup);
|
||||
this._screenInput.addEventListener('keydown', this._eventHandlers.keydown);
|
||||
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
|
||||
window.addEventListener('blur', this._eventHandlers.blur);
|
||||
|
|
@ -288,8 +482,15 @@ export default class Keyboard {
|
|||
ungrab() {
|
||||
//Log.Debug(">> Keyboard.ungrab");
|
||||
|
||||
this._target.removeEventListener('keydown', this._eventHandlers.keydown);
|
||||
this._target.removeEventListener('keyup', this._eventHandlers.keyup);
|
||||
this._screenInput.removeEventListener('keydown', this._eventHandlers.keydown);
|
||||
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);
|
||||
|
||||
// Release (key up) all keys that are in a down state
|
||||
|
|
|
|||
188
core/rfb.js
188
core/rfb.js
|
|
@ -34,6 +34,7 @@ import HextileDecoder from "./decoders/hextile.js";
|
|||
import TightDecoder from "./decoders/tight.js";
|
||||
import TightPNGDecoder from "./decoders/tightpng.js";
|
||||
import UDPDecoder from './decoders/udp.js';
|
||||
import { toSignedRelative16bit } from './util/int.js';
|
||||
|
||||
// How many seconds to wait for a disconnect to finish
|
||||
const DISCONNECT_TIMEOUT = 3;
|
||||
|
|
@ -43,7 +44,7 @@ var _videoQuality = 2;
|
|||
var _enableWebP = false;
|
||||
|
||||
// Minimum wait (ms) between two mouse moves
|
||||
const MOUSE_MOVE_DELAY = 17;
|
||||
const MOUSE_MOVE_DELAY = 17;
|
||||
|
||||
// Wheel thresholds
|
||||
let WHEEL_LINE_HEIGHT = 19; // Pixels for one line step (on Windows)
|
||||
|
|
@ -71,7 +72,7 @@ const extendedClipboardActionNotify = 1 << 27;
|
|||
const extendedClipboardActionProvide = 1 << 28;
|
||||
|
||||
export default class RFB extends EventTargetMixin {
|
||||
constructor(target, urlOrChannel, options) {
|
||||
constructor(target, touchInput, urlOrChannel, options) {
|
||||
if (!target) {
|
||||
throw new Error("Must specify target");
|
||||
}
|
||||
|
|
@ -172,6 +173,9 @@ export default class RFB extends EventTargetMixin {
|
|||
this._mousePos = {};
|
||||
this._mouseButtonMask = 0;
|
||||
this._mouseLastMoveTime = 0;
|
||||
this._pointerLock = false;
|
||||
this._pointerLockPos = { x: 0, y: 0 };
|
||||
this._pointerRelativeEnabled = false;
|
||||
this._mouseLastPinchAndZoomTime = 0;
|
||||
this._viewportDragging = false;
|
||||
this._viewportDragPos = {};
|
||||
|
|
@ -191,6 +195,8 @@ export default class RFB extends EventTargetMixin {
|
|||
focusCanvas: this._focusCanvas.bind(this),
|
||||
windowResize: this._windowResize.bind(this),
|
||||
handleMouse: this._handleMouse.bind(this),
|
||||
handlePointerLockChange: this._handlePointerLockChange.bind(this),
|
||||
handlePointerLockError: this._handlePointerLockError.bind(this),
|
||||
handleWheel: this._handleWheel.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._keyboard = new Keyboard(this._canvas);
|
||||
this._keyboard = new Keyboard(this._canvas, touchInput);
|
||||
this._keyboard.onkeyevent = this._handleKeyEvent.bind(this);
|
||||
|
||||
this._gestures = new GestureHandler();
|
||||
|
|
@ -336,6 +342,45 @@ export default class RFB extends EventTargetMixin {
|
|||
|
||||
// ===== 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; }
|
||||
set clipboardBinary(val) { this._clipboardMode = val; }
|
||||
|
||||
|
|
@ -748,11 +793,11 @@ export default class RFB extends EventTargetMixin {
|
|||
}
|
||||
|
||||
focus() {
|
||||
this._canvas.focus();
|
||||
this._keyboard.focus();
|
||||
}
|
||||
|
||||
blur() {
|
||||
this._canvas.blur();
|
||||
this._keyboard.blur();
|
||||
}
|
||||
|
||||
clipboardPasteFrom(text) {
|
||||
|
|
@ -914,6 +959,15 @@ export default class RFB extends EventTargetMixin {
|
|||
// reason so we have to explicitly block it
|
||||
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
|
||||
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('click', 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("touchstart", this._eventHandlers.focusCanvas);
|
||||
window.removeEventListener('resize', this._eventHandlers.windowResize);
|
||||
|
|
@ -1074,11 +1135,18 @@ export default class RFB extends EventTargetMixin {
|
|||
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) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.focus();
|
||||
|
||||
}
|
||||
|
||||
_setDesktopName(name) {
|
||||
|
|
@ -1409,8 +1477,34 @@ export default class RFB extends EventTargetMixin {
|
|||
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);
|
||||
}
|
||||
|
||||
switch (ev.type) {
|
||||
case 'mousedown':
|
||||
|
|
@ -1526,12 +1620,54 @@ export default class RFB extends EventTargetMixin {
|
|||
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) {
|
||||
if (this._rfbConnectionState !== 'connected') { return; }
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
_sendScroll(x, y, dX, dY) {
|
||||
|
|
@ -2352,16 +2488,16 @@ export default class RFB extends EventTargetMixin {
|
|||
encs.push(encodings.pseudoEncodingVideoScalingLevel0 + this.videoScaling);
|
||||
encs.push(encodings.pseudoEncodingFrameRateLevel10 + this.frameRate - 10);
|
||||
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
|
||||
encs.push(encodings.pseudoEncodingPreferBandwidth);
|
||||
|
||||
if (supportsCursorURIs && this._fbDepth == 24) {
|
||||
if (this.preferLocalCursor || !isTouchDevice) {
|
||||
encs.push(encodings.pseudoEncodingVMwareCursor);
|
||||
encs.push(encodings.pseudoEncodingCursor);
|
||||
}
|
||||
if (this._fbDepth == 24) {
|
||||
encs.push(encodings.pseudoEncodingVMwareCursor);
|
||||
encs.push(encodings.pseudoEncodingCursor);
|
||||
}
|
||||
encs.push(encodings.pseudoEncodingVMwareCursorPosition);
|
||||
|
||||
RFB.messages.clientEncodings(this._sock, encs);
|
||||
}
|
||||
|
|
@ -2595,6 +2731,10 @@ export default class RFB extends EventTargetMixin {
|
|||
|
||||
|
||||
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; }
|
||||
buffByteLen++;
|
||||
let mimelen = this._sock.rQshift8();
|
||||
|
|
@ -2635,10 +2775,10 @@ export default class RFB extends EventTargetMixin {
|
|||
);
|
||||
}
|
||||
|
||||
if (!this.clipboardBinary) { continue; }
|
||||
Log.Info("Processed binary clipboard (ID: " + clipid + ") of MIME " + mime + " of length " + len);
|
||||
|
||||
if (!this.clipboardBinary) { continue; }
|
||||
|
||||
Log.Info("Processed binary clipboard of MIME " + mime + " of length " + len);
|
||||
|
||||
clipItemData[mime] = new Blob([data], { type: mime });
|
||||
break;
|
||||
default:
|
||||
|
|
@ -2987,6 +3127,9 @@ export default class RFB extends EventTargetMixin {
|
|||
case encodings.pseudoEncodingVMwareCursor:
|
||||
return this._handleVMwareCursor();
|
||||
|
||||
case encodings.pseudoEncodingVMwareCursorPosition:
|
||||
return this._handleVMwareCursorPosition();
|
||||
|
||||
case encodings.pseudoEncodingCursor:
|
||||
return this._handleCursor();
|
||||
|
||||
|
|
@ -3125,6 +3268,19 @@ export default class RFB extends EventTargetMixin {
|
|||
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() {
|
||||
const hotx = this._FBU.x; // hotspot-x
|
||||
const hoty = this._FBU.y; // hotspot-y
|
||||
|
|
|
|||
|
|
@ -97,6 +97,51 @@ export function isSafari() {
|
|||
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() {
|
||||
return navigator && !!(/firefox/i).exec(navigator.userAgent);
|
||||
}
|
||||
|
|
@ -107,3 +152,10 @@ export function supportsBinaryClipboard() {
|
|||
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';
|
||||
|
||||
const useFallback = !supportsCursorURIs || isTouchDevice;
|
||||
const needsFallback = !supportsCursorURIs || isTouchDevice;
|
||||
|
||||
export default class Cursor {
|
||||
constructor() {
|
||||
this._target = null;
|
||||
|
||||
this._canvas = document.createElement('canvas');
|
||||
|
||||
if (useFallback) {
|
||||
this._canvas.style.position = 'fixed';
|
||||
this._canvas.style.zIndex = '65535';
|
||||
this._canvas.style.pointerEvents = 'none';
|
||||
// Can't use "display" because of Firefox bug #1445997
|
||||
this._canvas.style.visibility = 'hidden';
|
||||
}
|
||||
this._canvas.style.position = 'fixed';
|
||||
this._canvas.style.zIndex = '65535';
|
||||
this._canvas.style.pointerEvents = 'none';
|
||||
// Can't use "display" because of Firefox bug #1445997
|
||||
this._canvas.style.visibility = 'hidden';
|
||||
this._useFallback = needsFallback;
|
||||
|
||||
this._position = { x: 0, y: 0 };
|
||||
this._hotSpot = { x: 0, y: 0 };
|
||||
|
|
@ -40,9 +38,15 @@ export default class Cursor {
|
|||
|
||||
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 };
|
||||
this._target.addEventListener('mouseover', this._eventHandlers.mouseover, options);
|
||||
this._target.addEventListener('mouseleave', this._eventHandlers.mouseleave, options);
|
||||
|
|
@ -58,16 +62,16 @@ export default class Cursor {
|
|||
return;
|
||||
}
|
||||
|
||||
if (useFallback) {
|
||||
if (needsFallback) {
|
||||
const options = { capture: true, passive: true };
|
||||
this._target.removeEventListener('mouseover', this._eventHandlers.mouseover, options);
|
||||
this._target.removeEventListener('mouseleave', this._eventHandlers.mouseleave, options);
|
||||
this._target.removeEventListener('mousemove', this._eventHandlers.mousemove, options);
|
||||
this._target.removeEventListener('mouseup', this._eventHandlers.mouseup, options);
|
||||
|
||||
document.body.removeChild(this._canvas);
|
||||
}
|
||||
|
||||
document.body.removeChild(this._canvas);
|
||||
|
||||
this._target = null;
|
||||
}
|
||||
|
||||
|
|
@ -91,9 +95,10 @@ export default class Cursor {
|
|||
ctx.clearRect(0, 0, w, h);
|
||||
ctx.putImageData(img, 0, 0);
|
||||
|
||||
if (useFallback) {
|
||||
if (this._useFallback || needsFallback) {
|
||||
this._updatePosition();
|
||||
} else {
|
||||
}
|
||||
if (!needsFallback) {
|
||||
let url = this._canvas.toDataURL();
|
||||
this._target.style.cursor = 'url(' + url + ')' + hotx + ' ' + hoty + ', default';
|
||||
}
|
||||
|
|
@ -112,7 +117,7 @@ export default class Cursor {
|
|||
// Mouse events might be emulated, this allows
|
||||
// moving the cursor in such cases
|
||||
move(clientX, clientY) {
|
||||
if (!useFallback) {
|
||||
if (!this._useFallback) {
|
||||
return;
|
||||
}
|
||||
// clientX/clientY are relative the _visual viewport_,
|
||||
|
|
@ -130,6 +135,22 @@ export default class Cursor {
|
|||
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) {
|
||||
// This event could be because we're entering the target, or
|
||||
// moving around amongst its sub elements. Let the move handler
|
||||
|
|
|
|||
|
|
@ -15,12 +15,40 @@ 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) {
|
||||
let h;
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
h = Math.imul(31, h) + data[i] | 0;
|
||||
}
|
||||
return h;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
38
docs/API.md
38
docs/API.md
|
|
@ -113,6 +113,10 @@ protocol stream.
|
|||
- The `capabilities` event is fired when `RFB.capabilities` is
|
||||
updated.
|
||||
|
||||
[`inputlock`](#inputlock)
|
||||
- The `inputlock` event is fired when an input lock is acquired (or released)
|
||||
by the canvas.
|
||||
|
||||
### Methods
|
||||
|
||||
[`RFB.disconnect()`](#rfbdisconnect)
|
||||
|
|
@ -146,6 +150,10 @@ protocol stream.
|
|||
[`RFB.clipboardPasteFrom()`](#rfbclipboardPasteFrom)
|
||||
- Send clipboard contents to server.
|
||||
|
||||
[`inputlock`](#inputlock)
|
||||
- The `inputlock` event is fired when an input lock is acquired (or released)
|
||||
by the canvas.
|
||||
|
||||
### Details
|
||||
|
||||
#### 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
|
||||
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()
|
||||
|
||||
The `RFB.disconnect()` method is used to disconnect from the currently
|
||||
|
|
@ -383,3 +400,24 @@ to the remote server.
|
|||
|
||||
**`text`**
|
||||
- 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);
|
||||
}
|
||||
|
||||
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) {
|
||||
let pos = elementToClient(x, y);
|
||||
let ev;
|
||||
|
|
@ -2723,6 +2744,62 @@ describe('Remote Frame Buffer Protocol Client', function () {
|
|||
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 () {
|
||||
it('should send a single pointer event on mouse movement', function () {
|
||||
sendMouseMoveEvent(50, 70);
|
||||
|
|
|
|||
709
vnc.html
709
vnc.html
|
|
@ -86,300 +86,434 @@
|
|||
Loading statistics...
|
||||
</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 -->
|
||||
<div id="noVNC_control_bar_anchor" class="noVNC_vcenter">
|
||||
|
||||
<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">
|
||||
|
||||
<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 -->
|
||||
<input type="image" alt="Drag" src="app/images/drag.svg"
|
||||
id="noVNC_view_drag_button" class="noVNC_button noVNC_hidden"
|
||||
title="Move/Drag Viewport">
|
||||
|
||||
<!--noVNC Touch Device only buttons-->
|
||||
<!-- 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
|
||||
<!-- Drag/Pan the viewport -->
|
||||
<div class="noVNC_button_div noVNC_hidden noVNC_hide_on_disconnect" >
|
||||
<input type="image" alt="Drag" src="app/images/drag.svg"
|
||||
id="noVNC_view_drag_button" class="noVNC_button"
|
||||
title="Move/Drag Viewport">
|
||||
Drag Viewport
|
||||
</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 -->
|
||||
<input type="image" alt="Clipboard" src="app/images/clipboard.svg"
|
||||
id="noVNC_clipboard_button" class="noVNC_button"
|
||||
title="Clipboard">
|
||||
<div class="noVNC_vcenter">
|
||||
<div id="noVNC_clipboard" class="noVNC_panel">
|
||||
<div class="noVNC_heading">
|
||||
<img alt="" src="app/images/clipboard.svg"> Clipboard
|
||||
<!--noVNC Touch Device only buttons-->
|
||||
<!-- Extra manual keys -->
|
||||
<div class="noVNC_button_div noVNC_hide_on_disconnect" >
|
||||
<input type="image" alt="Extra keys" src="app/images/toggleextrakeys.svg"
|
||||
id="noVNC_toggle_extra_keys_button" class="noVNC_button"
|
||||
title="Show Extra Keys">
|
||||
<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>
|
||||
Keys
|
||||
</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 -->
|
||||
<input type="image" alt="Fullscreen" src="app/images/fullscreen.svg"
|
||||
id="noVNC_fullscreen_button" class="noVNC_button noVNC_hidden"
|
||||
title="Fullscreen">
|
||||
<!-- Shutdown/Reboot -->
|
||||
<div class="noVNC_button_div noVNC_hide_on_disconnect" >
|
||||
<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>
|
||||
<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>
|
||||
Power
|
||||
</div>
|
||||
|
||||
<!-- Settings -->
|
||||
<input type="image" alt="Settings" src="app/images/settings.svg"
|
||||
id="noVNC_settings_button" class="noVNC_button"
|
||||
title="Settings">
|
||||
<div class="noVNC_vcenter">
|
||||
<div id="noVNC_settings" class="noVNC_panel">
|
||||
<ul>
|
||||
<li class="noVNC_heading">
|
||||
<img alt="" src="app/images/settings.svg"> Settings
|
||||
</li>
|
||||
<li>
|
||||
<label><input id="noVNC_setting_shared" type="checkbox"> Shared Mode</label>
|
||||
</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>
|
||||
<label for="noVNC_setting_dynamic_quality_min">Dynamic Quality Min:</label>
|
||||
<input id="noVNC_setting_dynamic_quality_min" type="range" min="0" max="9" value="3" onchange="noVNC_setting_dynamic_quality_min_output.value=value">
|
||||
<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><input id="noVNC_setting_encrypt" type="checkbox"> Encrypt</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><input id="noVNC_setting_reconnect" type="checkbox"> Automatic Reconnect</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><input id="noVNC_setting_show_dot" type="checkbox"> Show Dot when No Cursor</label>
|
||||
</li>
|
||||
<li><hr></li>
|
||||
<!-- Logging selection dropdown -->
|
||||
<li>
|
||||
<label>Logging:
|
||||
<select id="noVNC_setting_logging" name="vncLogging">
|
||||
<!-- Clipboard -->
|
||||
<div class="noVNC_button_div noVNC_hide_on_disconnect" >
|
||||
<input type="image" alt="Clipboard" src="app/images/clipboard.svg"
|
||||
id="noVNC_clipboard_button" class="noVNC_button"
|
||||
title="Clipboard">
|
||||
Clipboard
|
||||
<div class="noVNC_vcenter">
|
||||
<div id="noVNC_clipboard" class="noVNC_panel">
|
||||
<div class="noVNC_heading">
|
||||
<img alt="" src="app/images/clipboard.svg"> Clipboard
|
||||
</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>
|
||||
</div>
|
||||
|
||||
<!-- Toggle fullscreen -->
|
||||
<div class="noVNC_button_div noVNC_hidden" >
|
||||
<input type="image" alt="Fullscreen" src="app/images/fullscreen.svg"
|
||||
id="noVNC_fullscreen_button" class="noVNC_button"
|
||||
title="Fullscreen">
|
||||
Fullscreen
|
||||
</div>
|
||||
|
||||
<!-- Toggle game mode -->
|
||||
<div class="noVNC_button_div noVNC_hidden noVNC_hide_on_disconnect" >
|
||||
<input type="image" alt="Game Mode" src="app/images/gamepad.png"
|
||||
id="noVNC_game_mode_button" class="noVNC_button"
|
||||
title="Game Pointer Mode">
|
||||
Game Cursor Mode
|
||||
</div>
|
||||
|
||||
<!-- Settings -->
|
||||
<div class="noVNC_button_div" >
|
||||
<input type="image" alt="Settings" src="app/images/settings.svg"
|
||||
id="noVNC_settings_button" class="noVNC_button"
|
||||
title="Settings">
|
||||
<div class="noVNC_vcenter">
|
||||
<div id="noVNC_settings" class="noVNC_panel">
|
||||
<ul>
|
||||
<li class="noVNC_heading">
|
||||
<img alt="" src="app/images/settings.svg"> Settings
|
||||
</li>
|
||||
<li>
|
||||
<label class="switch"><input id="noVNC_setting_shared" type="checkbox">
|
||||
<span class="slider round"></span>
|
||||
<span class="slider-label">Shared Mode</span>
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<label class="switch"><input id="noVNC_setting_view_only" type="checkbox" />
|
||||
<span class="slider round"></span>
|
||||
<span class="slider-label">View Only</span>
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<label class="switch"><input id="noVNC_setting_clipboard_up" type="checkbox" />
|
||||
<span class="slider round"></span>
|
||||
<span class="slider-label">Clipboard Up</span>
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<label class="switch"><input id="noVNC_setting_clipboard_down" type="checkbox" />
|
||||
<span class="slider round"></span>
|
||||
<span class="slider-label">Clipboard Down</span>
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<label class="switch"><input id="noVNC_setting_clipboard_seamless" type="checkbox" />
|
||||
<span class="slider round"></span>
|
||||
<span class="slider-label">Clipboard Seamless</span>
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<label class="switch"><input id="noVNC_setting_prefer_local_cursor" type="checkbox" />
|
||||
<span class="slider round"></span>
|
||||
<span class="slider-label">Prefer Local Cursor</span>
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<label class="switch">
|
||||
<input id="noVNC_setting_translate_shortcuts" type="checkbox" />
|
||||
<span class="slider round"></span>
|
||||
<span class="slider-label">Translate keyboard shurtcuts</span>
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<label class="switch"><input id="noVNC_setting_enable_webp" type="checkbox" />
|
||||
<span class="slider round"></span>
|
||||
<span class="slider-label">Enable WebP Compression</span>
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<label class="switch">
|
||||
<input id="noVNC_setting_enable_perf_stats" type="checkbox" />
|
||||
<span class="slider round"></span>
|
||||
<span class="slider-label">Enable Performance Stats</span>
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<label class="switch">
|
||||
<input type="checkbox" id="noVNC_setting_pointer_lock" />
|
||||
<span class="slider round"></span>
|
||||
<span class="slider-label">Enable Pointer Lock</span>
|
||||
</label>
|
||||
</li>
|
||||
<label class="switch"><input id="noVNC_setting_enable_ime" type="checkbox" />
|
||||
<span class="slider round"></span>
|
||||
<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>
|
||||
</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>
|
||||
</li>
|
||||
<li><hr></li>
|
||||
<li>
|
||||
<label class="switch">
|
||||
<input id="noVNC_setting_view_clip" type="checkbox">
|
||||
<span class="slider round"></span>
|
||||
<span class="slider-label">Clip to Window</span>
|
||||
</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">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>
|
||||
|
||||
<li>
|
||||
<label for="noVNC_setting_dynamic_quality_min">Dynamic Quality Min:</label>
|
||||
<input id="noVNC_setting_dynamic_quality_min" type="range" min="0" max="9" value="3" onchange="noVNC_setting_dynamic_quality_min_output.value=value">
|
||||
<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 -->
|
||||
<input type="image" alt="Disconnect" src="app/images/disconnect.svg"
|
||||
id="noVNC_disconnect_button" class="noVNC_button"
|
||||
title="Disconnect">
|
||||
<!-- 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>
|
||||
|
|
@ -389,15 +523,16 @@
|
|||
</div> <!-- End of noVNC_control_bar -->
|
||||
|
||||
<!-- Status Dialog -->
|
||||
<div style="visibility: hidden" id="noVNC_status"></div>
|
||||
<div id="noVNC_status"></div>
|
||||
|
||||
<!-- Connect button -->
|
||||
<div class="noVNC_center">
|
||||
<div id="noVNC_connect_dlg">
|
||||
<!--div class="noVNC_logo" translate="no"><span>no</span>VNC</div-->
|
||||
<div id="noVNC_connect_button"><div>
|
||||
<img alt="" src="app/images/connect.svg"> Connect
|
||||
</div></div>
|
||||
<!--div id="noVNC_connect_button">
|
||||
<div>
|
||||
<img alt="" src="app/images/connect.svg"> Connect
|
||||
</div>
|
||||
</div-->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -444,7 +579,7 @@
|
|||
<source src="app/sounds/bell.mp3" type="audio/mpeg">
|
||||
</audio>
|
||||
|
||||
<div class="keyboard-controls">
|
||||
<div id="noVNC_keyboard_control" class="keyboard-controls">
|
||||
<div class="buttons">
|
||||
<div class="button ctrl"></div>
|
||||
<div class="button alt"></div>
|
||||
|
|
@ -454,7 +589,7 @@
|
|||
<div class="button ctrlaltdel"></div>
|
||||
</div>
|
||||
|
||||
<div class="button keyboard handle"></div>
|
||||
<div id="noVNC_keyboard_control_handle" class="button keyboard handle"></div>
|
||||
</div>
|
||||
</body>
|
||||
</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