mirror of
https://github.com/kasmtech/KasmVNC.git
synced 2024-12-02 13:03:17 +01:00
6acf3c9e62
Toggling the enabled state is a remnant from an earlier version of the code where we could determine if the the session is actually clipped, and not just that the setting is enabled. Right now we only change things based on the setting, so let's completely hide the button when clipping is disabled.
2032 lines
69 KiB
JavaScript
2032 lines
69 KiB
JavaScript
/*
|
|
* noVNC: HTML5 VNC client
|
|
* Copyright (C) 2019 The noVNC Authors
|
|
* Licensed under MPL 2.0 (see LICENSE.txt)
|
|
*
|
|
* See README.md for usage and integration instructions.
|
|
*/
|
|
window._noVNC_has_module_support = true;
|
|
window.addEventListener("load", function() {
|
|
if (window._noVNC_has_module_support) return;
|
|
var loader = document.createElement("script");
|
|
loader.src = "vendor/browser-es-module-loader/dist/browser-es-module-loader.js";
|
|
document.head.appendChild(loader);
|
|
});
|
|
window.addEventListener("load", function() {
|
|
var connect_btn_el = document.getElementById("noVNC_connect_button");
|
|
if (typeof(connect_btn_el) != 'undefined' && connect_btn_el != null)
|
|
{
|
|
connect_btn_el.click();
|
|
}
|
|
});
|
|
|
|
import * as Log from '../core/util/logging.js';
|
|
import _, { l10n } from './localization.js';
|
|
import { isTouchDevice, isSafari, isIOS, isAndroid, dragThreshold }
|
|
from '../core/util/browser.js';
|
|
import { setCapture, getPointerEvent } from '../core/util/events.js';
|
|
import KeyTable from "../core/input/keysym.js";
|
|
import keysyms from "../core/input/keysymdef.js";
|
|
import Keyboard from "../core/input/keyboard.js";
|
|
import RFB from "../core/rfb.js";
|
|
import * as WebUtil from "./webutil.js";
|
|
|
|
|
|
var delta = 500;
|
|
var lastKeypressTime = 0;
|
|
var currentEventCount = -1;
|
|
var idleCounter = 0;
|
|
|
|
const UI = {
|
|
|
|
connected: false,
|
|
desktopName: "",
|
|
|
|
statusTimeout: null,
|
|
hideKeyboardTimeout: null,
|
|
idleControlbarTimeout: null,
|
|
closeControlbarTimeout: null,
|
|
|
|
controlbarGrabbed: false,
|
|
controlbarDrag: false,
|
|
controlbarMouseDownClientY: 0,
|
|
controlbarMouseDownOffsetY: 0,
|
|
|
|
lastKeyboardinput: null,
|
|
defaultKeyboardinputLen: 100,
|
|
needToCheckClipboardChange: false,
|
|
|
|
inhibit_reconnect: true,
|
|
reconnect_callback: null,
|
|
reconnect_password: null,
|
|
|
|
prime(callback) {
|
|
if (document.readyState === "interactive" || document.readyState === "complete") {
|
|
UI.load(callback);
|
|
} else {
|
|
document.addEventListener('DOMContentLoaded', UI.load.bind(UI, callback));
|
|
}
|
|
},
|
|
|
|
// Setup rfb object, load settings from browser storage, then call
|
|
// UI.init to setup the UI/menus
|
|
load(callback) {
|
|
WebUtil.initSettings(UI.start, callback);
|
|
},
|
|
|
|
// Render default UI and initialize settings menu
|
|
start(callback) {
|
|
|
|
UI.initSettings();
|
|
|
|
// Translate the DOM
|
|
l10n.translateDOM();
|
|
|
|
WebUtil.fetchJSON('../package.json')
|
|
.then((packageInfo) => {
|
|
Array.from(document.getElementsByClassName('noVNC_version')).forEach(el => el.innerText = packageInfo.version);
|
|
})
|
|
.catch((err) => {
|
|
Log.Error("Couldn't fetch package.json: " + err);
|
|
Array.from(document.getElementsByClassName('noVNC_version_wrapper'))
|
|
.concat(Array.from(document.getElementsByClassName('noVNC_version_separator')))
|
|
.forEach(el => el.style.display = 'none');
|
|
});
|
|
|
|
// Adapt the interface for touch screen devices
|
|
if (isTouchDevice) {
|
|
document.documentElement.classList.add("noVNC_touch");
|
|
// Remove the address bar
|
|
setTimeout(() => window.scrollTo(0, 1), 100);
|
|
}
|
|
|
|
// Restore control bar position
|
|
if (WebUtil.readSetting('controlbar_pos') === 'right') {
|
|
UI.toggleControlbarSide();
|
|
}
|
|
|
|
UI.initFullscreen();
|
|
|
|
// Setup event handlers
|
|
UI.addControlbarHandlers();
|
|
UI.addTouchSpecificHandlers();
|
|
UI.addExtraKeysHandlers();
|
|
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');
|
|
|
|
document.documentElement.classList.remove("noVNC_loading");
|
|
|
|
let autoconnect = WebUtil.getConfigVar('autoconnect', false);
|
|
if (autoconnect === 'true' || autoconnect == '1') {
|
|
autoconnect = true;
|
|
UI.connect();
|
|
} else {
|
|
autoconnect = false;
|
|
// Show the connect panel on first load unless autoconnecting
|
|
UI.openConnectPanel();
|
|
}
|
|
|
|
if (typeof callback === "function") {
|
|
callback(UI.rfb);
|
|
}
|
|
},
|
|
|
|
initFullscreen() {
|
|
// Only show the button if fullscreen is properly supported
|
|
// * Safari doesn't support alphanumerical input while in fullscreen
|
|
if (!isSafari() &&
|
|
(document.documentElement.requestFullscreen ||
|
|
document.documentElement.mozRequestFullScreen ||
|
|
document.documentElement.webkitRequestFullscreen ||
|
|
document.body.msRequestFullscreen)) {
|
|
document.getElementById('noVNC_fullscreen_button')
|
|
.classList.remove("noVNC_hidden");
|
|
UI.addFullscreenHandlers();
|
|
}
|
|
},
|
|
|
|
initSettings() {
|
|
// Logging selection dropdown
|
|
const llevels = ['error', 'warn', 'info', 'debug'];
|
|
for (let i = 0; i < llevels.length; i += 1) {
|
|
UI.addOption(document.getElementById('noVNC_setting_logging'), llevels[i], llevels[i]);
|
|
}
|
|
|
|
// Settings with immediate effects
|
|
UI.initSetting('logging', 'warn');
|
|
UI.updateLogging();
|
|
|
|
// if port == 80 (or 443) then it won't be present and should be
|
|
// set manually
|
|
let port = window.location.port;
|
|
if (!port) {
|
|
if (window.location.protocol.substring(0, 5) == 'https') {
|
|
port = 443;
|
|
} else if (window.location.protocol.substring(0, 4) == 'http') {
|
|
port = 80;
|
|
}
|
|
}
|
|
|
|
/* Populate the controls if defaults are provided in the URL */
|
|
UI.initSetting('host', window.location.hostname);
|
|
UI.initSetting('port', port);
|
|
UI.initSetting('encrypt', (window.location.protocol === "https:"));
|
|
UI.initSetting('view_clip', false);
|
|
UI.initSetting('shared', true);
|
|
UI.initSetting('view_only', false);
|
|
UI.initSetting('show_dot', false);
|
|
UI.initSetting('path', 'websockify');
|
|
UI.initSetting('repeaterID', '');
|
|
UI.initSetting('reconnect', false);
|
|
UI.initSetting('reconnect_delay', 5000);
|
|
UI.initSetting('idle_disconnect', 20);
|
|
UI.initSetting('prefer_local_cursor', true);
|
|
UI.initSetting('toggle_control_panel', false);
|
|
UI.initSetting('enable_perf_stats', false);
|
|
|
|
if (WebUtil.isInsideKasmVDI()) {
|
|
UI.initSetting('video_quality', 1);
|
|
UI.initSetting('clipboard_up', false);
|
|
UI.initSetting('clipboard_down', false);
|
|
UI.initSetting('clipboard_seamless', false);
|
|
UI.initSetting('enable_webp', false);
|
|
UI.initSetting('resize', 'off');
|
|
} else {
|
|
UI.initSetting('video_quality', 3);
|
|
UI.initSetting('clipboard_up', true);
|
|
UI.initSetting('clipboard_down', true);
|
|
UI.initSetting('clipboard_seamless', true);
|
|
UI.initSetting('enable_webp', true);
|
|
UI.initSetting('resize', 'remote');
|
|
}
|
|
|
|
UI.setupSettingLabels();
|
|
},
|
|
// Adds a link to the label elements on the corresponding input elements
|
|
setupSettingLabels() {
|
|
const labels = document.getElementsByTagName('LABEL');
|
|
for (let i = 0; i < labels.length; i++) {
|
|
const htmlFor = labels[i].htmlFor;
|
|
if (htmlFor != '') {
|
|
const elem = document.getElementById(htmlFor);
|
|
if (elem) elem.label = labels[i];
|
|
} else {
|
|
// If 'for' isn't set, use the first input element child
|
|
const children = labels[i].children;
|
|
for (let j = 0; j < children.length; j++) {
|
|
if (children[j].form !== undefined) {
|
|
children[j].label = labels[i];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
/* ------^-------
|
|
* /INIT
|
|
* ==============
|
|
* EVENT HANDLERS
|
|
* ------v------*/
|
|
|
|
addControlbarHandlers() {
|
|
document.getElementById("noVNC_control_bar")
|
|
.addEventListener('mousemove', UI.activateControlbar);
|
|
document.getElementById("noVNC_control_bar")
|
|
.addEventListener('mouseup', UI.activateControlbar);
|
|
document.getElementById("noVNC_control_bar")
|
|
.addEventListener('mousedown', UI.activateControlbar);
|
|
document.getElementById("noVNC_control_bar")
|
|
.addEventListener('keydown', UI.activateControlbar);
|
|
|
|
document.getElementById("noVNC_control_bar")
|
|
.addEventListener('mousedown', UI.keepControlbar);
|
|
document.getElementById("noVNC_control_bar")
|
|
.addEventListener('keydown', UI.keepControlbar);
|
|
|
|
document.getElementById("noVNC_view_drag_button")
|
|
.addEventListener('click', UI.toggleViewDrag);
|
|
|
|
document.getElementById("noVNC_control_bar_handle")
|
|
.addEventListener('mousedown', UI.controlbarHandleMouseDown);
|
|
document.getElementById("noVNC_control_bar_handle")
|
|
.addEventListener('mouseup', UI.controlbarHandleMouseUp);
|
|
document.getElementById("noVNC_control_bar_handle")
|
|
.addEventListener('mousemove', UI.dragControlbarHandle);
|
|
// resize events aren't available for elements
|
|
window.addEventListener('resize', UI.updateControlbarHandle);
|
|
|
|
const exps = document.getElementsByClassName("noVNC_expander");
|
|
for (let i = 0;i < exps.length;i++) {
|
|
exps[i].addEventListener('click', UI.toggleExpander);
|
|
}
|
|
},
|
|
|
|
addTouchSpecificHandlers() {
|
|
document.getElementById("noVNC_mouse_button0")
|
|
.addEventListener('click', () => UI.setMouseButton(1));
|
|
document.getElementById("noVNC_mouse_button1")
|
|
.addEventListener('click', () => UI.setMouseButton(2));
|
|
document.getElementById("noVNC_mouse_button2")
|
|
.addEventListener('click', () => UI.setMouseButton(4));
|
|
document.getElementById("noVNC_mouse_button4")
|
|
.addEventListener('click', () => UI.setMouseButton(0));
|
|
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")
|
|
.addEventListener('blur', UI.onblurVirtualKeyboard);
|
|
document.getElementById("noVNC_keyboardinput")
|
|
.addEventListener('submit', () => false);
|
|
|
|
document.documentElement
|
|
.addEventListener('mousedown', UI.keepVirtualKeyboard, true);
|
|
|
|
document.getElementById("noVNC_control_bar")
|
|
.addEventListener('touchstart', UI.activateControlbar);
|
|
document.getElementById("noVNC_control_bar")
|
|
.addEventListener('touchmove', UI.activateControlbar);
|
|
document.getElementById("noVNC_control_bar")
|
|
.addEventListener('touchend', UI.activateControlbar);
|
|
document.getElementById("noVNC_control_bar")
|
|
.addEventListener('input', UI.activateControlbar);
|
|
|
|
document.getElementById("noVNC_control_bar")
|
|
.addEventListener('touchstart', UI.keepControlbar);
|
|
document.getElementById("noVNC_control_bar")
|
|
.addEventListener('input', UI.keepControlbar);
|
|
|
|
document.getElementById("noVNC_control_bar_handle")
|
|
.addEventListener('touchstart', UI.controlbarHandleMouseDown);
|
|
document.getElementById("noVNC_control_bar_handle")
|
|
.addEventListener('touchend', UI.controlbarHandleMouseUp);
|
|
document.getElementById("noVNC_control_bar_handle")
|
|
.addEventListener('touchmove', UI.dragControlbarHandle);
|
|
},
|
|
|
|
addExtraKeysHandlers() {
|
|
document.getElementById("noVNC_toggle_extra_keys_button")
|
|
.addEventListener('click', UI.toggleExtraKeys);
|
|
document.getElementById("noVNC_toggle_ctrl_button")
|
|
.addEventListener('click', UI.toggleCtrl);
|
|
document.getElementById("noVNC_toggle_windows_button")
|
|
.addEventListener('click', UI.toggleWindows);
|
|
document.getElementById("noVNC_toggle_alt_button")
|
|
.addEventListener('click', UI.toggleAlt);
|
|
document.getElementById("noVNC_send_tab_button")
|
|
.addEventListener('click', UI.sendTab);
|
|
document.getElementById("noVNC_send_esc_button")
|
|
.addEventListener('click', UI.sendEsc);
|
|
document.getElementById("noVNC_send_ctrl_alt_del_button")
|
|
.addEventListener('click', UI.sendCtrlAltDel);
|
|
},
|
|
|
|
addMachineHandlers() {
|
|
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);
|
|
document.getElementById("noVNC_connect_button")
|
|
.addEventListener('click', UI.connect);
|
|
document.getElementById("noVNC_cancel_reconnect_button")
|
|
.addEventListener('click', UI.cancelReconnect);
|
|
|
|
document.getElementById("noVNC_password_button")
|
|
.addEventListener('click', UI.setPassword);
|
|
},
|
|
|
|
addClipboardHandlers() {
|
|
document.getElementById("noVNC_clipboard_button")
|
|
.addEventListener('click', UI.toggleClipboardPanel);
|
|
document.getElementById("noVNC_clipboard_text")
|
|
.addEventListener('change', UI.clipboardSend);
|
|
document.getElementById("noVNC_clipboard_clear_button")
|
|
.addEventListener('click', UI.clipboardClear);
|
|
},
|
|
|
|
// Add a call to save settings when the element changes,
|
|
// unless the optional parameter changeFunc is used instead.
|
|
addSettingChangeHandler(name, changeFunc) {
|
|
const settingElem = document.getElementById("noVNC_setting_" + name);
|
|
if (changeFunc === undefined) {
|
|
changeFunc = () => UI.saveSetting(name);
|
|
}
|
|
settingElem.addEventListener('change', changeFunc);
|
|
},
|
|
|
|
addSettingsHandlers() {
|
|
document.getElementById("noVNC_settings_button")
|
|
.addEventListener('click', UI.toggleSettingsPanel);
|
|
|
|
document.getElementById("noVNC_setting_enable_perf_stats").addEventListener('click', UI.showStats);
|
|
|
|
UI.addSettingChangeHandler('encrypt');
|
|
UI.addSettingChangeHandler('resize');
|
|
UI.addSettingChangeHandler('resize', UI.applyResizeMode);
|
|
UI.addSettingChangeHandler('resize', UI.updateViewClip);
|
|
UI.addSettingChangeHandler('view_clip');
|
|
UI.addSettingChangeHandler('view_clip', UI.updateViewClip);
|
|
UI.addSettingChangeHandler('shared');
|
|
UI.addSettingChangeHandler('view_only');
|
|
UI.addSettingChangeHandler('view_only', UI.updateViewOnly);
|
|
UI.addSettingChangeHandler('show_dot');
|
|
UI.addSettingChangeHandler('show_dot', UI.updateShowDotCursor);
|
|
UI.addSettingChangeHandler('host');
|
|
UI.addSettingChangeHandler('port');
|
|
UI.addSettingChangeHandler('path');
|
|
UI.addSettingChangeHandler('repeaterID');
|
|
UI.addSettingChangeHandler('logging');
|
|
UI.addSettingChangeHandler('logging', UI.updateLogging);
|
|
UI.addSettingChangeHandler('reconnect');
|
|
UI.addSettingChangeHandler('reconnect_delay');
|
|
UI.addSettingChangeHandler('enable_webp');
|
|
UI.addSettingChangeHandler('clipboard_seamless');
|
|
UI.addSettingChangeHandler('clipboard_up');
|
|
UI.addSettingChangeHandler('clipboard_down');
|
|
},
|
|
|
|
addFullscreenHandlers() {
|
|
document.getElementById("noVNC_fullscreen_button")
|
|
.addEventListener('click', UI.toggleFullscreen);
|
|
|
|
window.addEventListener('fullscreenchange', UI.updateFullscreenButton);
|
|
window.addEventListener('mozfullscreenchange', UI.updateFullscreenButton);
|
|
window.addEventListener('webkitfullscreenchange', UI.updateFullscreenButton);
|
|
window.addEventListener('msfullscreenchange', UI.updateFullscreenButton);
|
|
},
|
|
|
|
/* ------^-------
|
|
* /EVENT HANDLERS
|
|
* ==============
|
|
* VISUAL
|
|
* ------v------*/
|
|
|
|
// Disable/enable controls depending on connection state
|
|
updateVisualState(state) {
|
|
|
|
document.documentElement.classList.remove("noVNC_connecting");
|
|
document.documentElement.classList.remove("noVNC_connected");
|
|
document.documentElement.classList.remove("noVNC_disconnecting");
|
|
document.documentElement.classList.remove("noVNC_reconnecting");
|
|
|
|
const transition_elem = document.getElementById("noVNC_transition_text");
|
|
if (WebUtil.isInsideKasmVDI())
|
|
{
|
|
parent.postMessage({ action: 'connection_state', value: state}, '*' );
|
|
}
|
|
switch (state) {
|
|
case 'init':
|
|
break;
|
|
case 'connecting':
|
|
transition_elem.textContent = _("Connecting...");
|
|
document.documentElement.classList.add("noVNC_connecting");
|
|
break;
|
|
case 'connected':
|
|
document.documentElement.classList.add("noVNC_connected");
|
|
break;
|
|
case 'disconnecting':
|
|
transition_elem.textContent = _("Disconnecting...");
|
|
document.documentElement.classList.add("noVNC_disconnecting");
|
|
break;
|
|
case 'disconnected':
|
|
break;
|
|
case 'reconnecting':
|
|
transition_elem.textContent = _("Reconnecting...");
|
|
document.documentElement.classList.add("noVNC_reconnecting");
|
|
break;
|
|
default:
|
|
Log.Error("Invalid visual state: " + state);
|
|
UI.showStatus(_("Internal error"), 'error');
|
|
return;
|
|
}
|
|
|
|
if (UI.connected) {
|
|
UI.updateViewClip();
|
|
|
|
UI.disableSetting('encrypt');
|
|
UI.disableSetting('shared');
|
|
UI.disableSetting('host');
|
|
UI.disableSetting('port');
|
|
UI.disableSetting('path');
|
|
UI.disableSetting('repeaterID');
|
|
UI.setMouseButton(1);
|
|
|
|
// Hide the controlbar after 2 seconds
|
|
UI.closeControlbarTimeout = setTimeout(UI.closeControlbar, 2000);
|
|
} else {
|
|
UI.enableSetting('encrypt');
|
|
UI.enableSetting('shared');
|
|
UI.enableSetting('host');
|
|
UI.enableSetting('port');
|
|
UI.enableSetting('path');
|
|
UI.enableSetting('repeaterID');
|
|
UI.updatePowerButton();
|
|
UI.keepControlbar();
|
|
}
|
|
|
|
// State change closes the password dialog
|
|
document.getElementById('noVNC_password_dlg')
|
|
.classList.remove('noVNC_open');
|
|
},
|
|
|
|
showStats() {
|
|
UI.saveSetting('enable_perf_stats');
|
|
|
|
let enable_stats = UI.getSetting('enable_perf_stats');
|
|
if (enable_stats === true && UI.statsInterval == undefined) {
|
|
document.getElementById("noVNC_connection_stats").style.visibility = "visible";
|
|
UI.statsInterval = setInterval(function() {
|
|
if (UI.rfb !== undefined) {
|
|
UI.rfb.requestBottleneckStats();
|
|
}
|
|
} , 5000);
|
|
} else {
|
|
document.getElementById("noVNC_connection_stats").style.visibility = "hidden";
|
|
UI.statsInterval = null;
|
|
}
|
|
|
|
},
|
|
|
|
showStatus(text, status_type, time) {
|
|
const statusElem = document.getElementById('noVNC_status');
|
|
|
|
clearTimeout(UI.statusTimeout);
|
|
|
|
if (typeof status_type === 'undefined') {
|
|
status_type = 'normal';
|
|
}
|
|
|
|
// Don't overwrite more severe visible statuses and never
|
|
// errors. Only shows the first error.
|
|
let visible_status_type = 'none';
|
|
if (statusElem.classList.contains("noVNC_open")) {
|
|
if (statusElem.classList.contains("noVNC_status_error")) {
|
|
visible_status_type = 'error';
|
|
} else if (statusElem.classList.contains("noVNC_status_warn")) {
|
|
visible_status_type = 'warn';
|
|
} else {
|
|
visible_status_type = 'normal';
|
|
}
|
|
}
|
|
if (visible_status_type === 'error' ||
|
|
(visible_status_type === 'warn' && status_type === 'normal')) {
|
|
return;
|
|
}
|
|
|
|
switch (status_type) {
|
|
case 'error':
|
|
statusElem.classList.remove("noVNC_status_warn");
|
|
statusElem.classList.remove("noVNC_status_normal");
|
|
statusElem.classList.add("noVNC_status_error");
|
|
break;
|
|
case 'warning':
|
|
case 'warn':
|
|
statusElem.classList.remove("noVNC_status_error");
|
|
statusElem.classList.remove("noVNC_status_normal");
|
|
statusElem.classList.add("noVNC_status_warn");
|
|
break;
|
|
case 'normal':
|
|
case 'info':
|
|
default:
|
|
statusElem.classList.remove("noVNC_status_error");
|
|
statusElem.classList.remove("noVNC_status_warn");
|
|
statusElem.classList.add("noVNC_status_normal");
|
|
break;
|
|
}
|
|
|
|
statusElem.textContent = text;
|
|
statusElem.classList.add("noVNC_open");
|
|
|
|
// If no time was specified, show the status for 1.5 seconds
|
|
if (typeof time === 'undefined') {
|
|
time = 1500;
|
|
}
|
|
|
|
// Error messages do not timeout
|
|
if (status_type !== 'error') {
|
|
UI.statusTimeout = window.setTimeout(UI.hideStatus, time);
|
|
}
|
|
},
|
|
|
|
hideStatus() {
|
|
clearTimeout(UI.statusTimeout);
|
|
document.getElementById('noVNC_status').classList.remove("noVNC_open");
|
|
},
|
|
|
|
activateControlbar(event) {
|
|
clearTimeout(UI.idleControlbarTimeout);
|
|
// We manipulate the anchor instead of the actual control
|
|
// bar in order to avoid creating new a stacking group
|
|
document.getElementById('noVNC_control_bar_anchor')
|
|
.classList.remove("noVNC_idle");
|
|
UI.idleControlbarTimeout = window.setTimeout(UI.idleControlbar, 2000);
|
|
},
|
|
|
|
idleControlbar() {
|
|
document.getElementById('noVNC_control_bar_anchor')
|
|
.classList.add("noVNC_idle");
|
|
},
|
|
|
|
keepControlbar() {
|
|
clearTimeout(UI.closeControlbarTimeout);
|
|
},
|
|
|
|
openControlbar() {
|
|
document.getElementById('noVNC_control_bar')
|
|
.classList.add("noVNC_open");
|
|
},
|
|
|
|
closeControlbar() {
|
|
UI.closeAllPanels();
|
|
document.getElementById('noVNC_control_bar')
|
|
.classList.remove("noVNC_open");
|
|
},
|
|
|
|
toggleControlbar() {
|
|
if (document.getElementById('noVNC_control_bar')
|
|
.classList.contains("noVNC_open")) {
|
|
UI.closeControlbar();
|
|
} else {
|
|
UI.openControlbar();
|
|
}
|
|
},
|
|
|
|
toggleControlbarSide() {
|
|
// Temporarily disable animation, if bar is displayed, to avoid weird
|
|
// movement. The transitionend-event will not fire when display=none.
|
|
const bar = document.getElementById('noVNC_control_bar');
|
|
const barDisplayStyle = window.getComputedStyle(bar).display;
|
|
if (barDisplayStyle !== 'none') {
|
|
bar.style.transitionDuration = '0s';
|
|
bar.addEventListener('transitionend', () => bar.style.transitionDuration = '');
|
|
}
|
|
|
|
const anchor = document.getElementById('noVNC_control_bar_anchor');
|
|
if (anchor.classList.contains("noVNC_right")) {
|
|
WebUtil.writeSetting('controlbar_pos', 'left');
|
|
anchor.classList.remove("noVNC_right");
|
|
} else {
|
|
WebUtil.writeSetting('controlbar_pos', 'right');
|
|
anchor.classList.add("noVNC_right");
|
|
}
|
|
|
|
// Consider this a movement of the handle
|
|
UI.controlbarDrag = true;
|
|
},
|
|
|
|
showControlbarHint(show) {
|
|
const hint = document.getElementById('noVNC_control_bar_hint');
|
|
if (show) {
|
|
hint.classList.add("noVNC_active");
|
|
} else {
|
|
hint.classList.remove("noVNC_active");
|
|
}
|
|
},
|
|
|
|
dragControlbarHandle(e) {
|
|
if (!UI.controlbarGrabbed) return;
|
|
|
|
const ptr = getPointerEvent(e);
|
|
|
|
const anchor = document.getElementById('noVNC_control_bar_anchor');
|
|
if (ptr.clientX < (window.innerWidth * 0.1)) {
|
|
if (anchor.classList.contains("noVNC_right")) {
|
|
UI.toggleControlbarSide();
|
|
}
|
|
} else if (ptr.clientX > (window.innerWidth * 0.9)) {
|
|
if (!anchor.classList.contains("noVNC_right")) {
|
|
UI.toggleControlbarSide();
|
|
}
|
|
}
|
|
|
|
if (!UI.controlbarDrag) {
|
|
const dragDistance = Math.abs(ptr.clientY - UI.controlbarMouseDownClientY);
|
|
|
|
if (dragDistance < dragThreshold) return;
|
|
|
|
UI.controlbarDrag = true;
|
|
}
|
|
|
|
const eventY = ptr.clientY - UI.controlbarMouseDownOffsetY;
|
|
|
|
UI.moveControlbarHandle(eventY);
|
|
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
UI.keepControlbar();
|
|
UI.activateControlbar();
|
|
},
|
|
|
|
// Move the handle but don't allow any position outside the bounds
|
|
moveControlbarHandle(viewportRelativeY) {
|
|
const handle = document.getElementById("noVNC_control_bar_handle");
|
|
const handleHeight = handle.getBoundingClientRect().height;
|
|
const controlbarBounds = document.getElementById("noVNC_control_bar")
|
|
.getBoundingClientRect();
|
|
const margin = 10;
|
|
|
|
// These heights need to be non-zero for the below logic to work
|
|
if (handleHeight === 0 || controlbarBounds.height === 0) {
|
|
return;
|
|
}
|
|
|
|
let newY = viewportRelativeY;
|
|
|
|
// Check if the coordinates are outside the control bar
|
|
if (newY < controlbarBounds.top + margin) {
|
|
// Force coordinates to be below the top of the control bar
|
|
newY = controlbarBounds.top + margin;
|
|
|
|
} else if (newY > controlbarBounds.top +
|
|
controlbarBounds.height - handleHeight - margin) {
|
|
// Force coordinates to be above the bottom of the control bar
|
|
newY = controlbarBounds.top +
|
|
controlbarBounds.height - handleHeight - margin;
|
|
}
|
|
|
|
// Corner case: control bar too small for stable position
|
|
if (controlbarBounds.height < (handleHeight + margin * 2)) {
|
|
newY = controlbarBounds.top +
|
|
(controlbarBounds.height - handleHeight) / 2;
|
|
}
|
|
|
|
// The transform needs coordinates that are relative to the parent
|
|
const parentRelativeY = newY - controlbarBounds.top;
|
|
handle.style.transform = "translateY(" + parentRelativeY + "px)";
|
|
},
|
|
|
|
updateControlbarHandle() {
|
|
// Since the control bar is fixed on the viewport and not the page,
|
|
// the move function expects coordinates relative the the viewport.
|
|
const handle = document.getElementById("noVNC_control_bar_handle");
|
|
const handleBounds = handle.getBoundingClientRect();
|
|
UI.moveControlbarHandle(handleBounds.top);
|
|
},
|
|
|
|
controlbarHandleMouseUp(e) {
|
|
if ((e.type == "mouseup") && (e.button != 0)) return;
|
|
|
|
// mouseup and mousedown on the same place toggles the controlbar
|
|
if (UI.controlbarGrabbed && !UI.controlbarDrag) {
|
|
UI.toggleControlbar();
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
UI.keepControlbar();
|
|
UI.activateControlbar();
|
|
}
|
|
UI.controlbarGrabbed = false;
|
|
UI.showControlbarHint(false);
|
|
},
|
|
|
|
controlbarHandleMouseDown(e) {
|
|
if ((e.type == "mousedown") && (e.button != 0)) return;
|
|
|
|
const ptr = getPointerEvent(e);
|
|
|
|
const handle = document.getElementById("noVNC_control_bar_handle");
|
|
const bounds = handle.getBoundingClientRect();
|
|
|
|
// Touch events have implicit capture
|
|
if (e.type === "mousedown") {
|
|
setCapture(handle);
|
|
}
|
|
|
|
UI.controlbarGrabbed = true;
|
|
UI.controlbarDrag = false;
|
|
|
|
UI.showControlbarHint(true);
|
|
|
|
UI.controlbarMouseDownClientY = ptr.clientY;
|
|
UI.controlbarMouseDownOffsetY = ptr.clientY - bounds.top;
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
UI.keepControlbar();
|
|
UI.activateControlbar();
|
|
},
|
|
|
|
toggleExpander(e) {
|
|
if (this.classList.contains("noVNC_open")) {
|
|
this.classList.remove("noVNC_open");
|
|
} else {
|
|
this.classList.add("noVNC_open");
|
|
}
|
|
},
|
|
|
|
/* ------^-------
|
|
* /VISUAL
|
|
* ==============
|
|
* SETTINGS
|
|
* ------v------*/
|
|
|
|
// Initial page load read/initialization of settings
|
|
initSetting(name, defVal) {
|
|
// Check Query string followed by cookie
|
|
let val = WebUtil.getConfigVar(name);
|
|
if (val === null) {
|
|
val = WebUtil.readSetting(name, defVal);
|
|
}
|
|
WebUtil.setSetting(name, val);
|
|
UI.updateSetting(name);
|
|
return val;
|
|
},
|
|
|
|
// Set the new value, update and disable form control setting
|
|
forceSetting(name, val) {
|
|
WebUtil.setSetting(name, val);
|
|
UI.updateSetting(name);
|
|
UI.disableSetting(name);
|
|
},
|
|
|
|
// Update cookie and form control setting. If value is not set, then
|
|
// updates from control to current cookie setting.
|
|
updateSetting(name) {
|
|
|
|
// Update the settings control
|
|
let value = UI.getSetting(name);
|
|
|
|
const ctrl = document.getElementById('noVNC_setting_' + name);
|
|
if (ctrl.type === 'checkbox') {
|
|
ctrl.checked = value;
|
|
|
|
} else if (typeof ctrl.options !== 'undefined') {
|
|
for (let i = 0; i < ctrl.options.length; i += 1) {
|
|
if (ctrl.options[i].value === value) {
|
|
ctrl.selectedIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
/*Weird IE9 error leads to 'null' appearring
|
|
in textboxes instead of ''.*/
|
|
if (value === null) {
|
|
value = "";
|
|
}
|
|
ctrl.value = value;
|
|
}
|
|
},
|
|
|
|
// Save control setting to cookie
|
|
saveSetting(name) {
|
|
const ctrl = document.getElementById('noVNC_setting_' + name);
|
|
let val;
|
|
if (ctrl.type === 'checkbox') {
|
|
val = ctrl.checked;
|
|
} else if (typeof ctrl.options !== 'undefined') {
|
|
val = ctrl.options[ctrl.selectedIndex].value;
|
|
} else {
|
|
val = ctrl.value;
|
|
}
|
|
WebUtil.writeSetting(name, val);
|
|
//Log.Debug("Setting saved '" + name + "=" + val + "'");
|
|
return val;
|
|
},
|
|
|
|
// Read form control compatible setting from cookie
|
|
getSetting(name) {
|
|
const ctrl = document.getElementById('noVNC_setting_' + name);
|
|
let val = WebUtil.readSetting(name);
|
|
if (typeof val !== 'undefined' && val !== null && ctrl.type === 'checkbox') {
|
|
if (val.toString().toLowerCase() in {'0': 1, 'no': 1, 'false': 1}) {
|
|
val = false;
|
|
} else {
|
|
val = true;
|
|
}
|
|
}
|
|
return val;
|
|
},
|
|
|
|
// These helpers compensate for the lack of parent-selectors and
|
|
// previous-sibling-selectors in CSS which are needed when we want to
|
|
// disable the labels that belong to disabled input elements.
|
|
disableSetting(name) {
|
|
const ctrl = document.getElementById('noVNC_setting_' + name);
|
|
ctrl.disabled = true;
|
|
ctrl.label.classList.add('noVNC_disabled');
|
|
},
|
|
|
|
enableSetting(name) {
|
|
const ctrl = document.getElementById('noVNC_setting_' + name);
|
|
ctrl.disabled = false;
|
|
ctrl.label.classList.remove('noVNC_disabled');
|
|
},
|
|
|
|
/* ------^-------
|
|
* /SETTINGS
|
|
* ==============
|
|
* PANELS
|
|
* ------v------*/
|
|
|
|
closeAllPanels() {
|
|
UI.closeSettingsPanel();
|
|
UI.closePowerPanel();
|
|
UI.closeClipboardPanel();
|
|
UI.closeExtraKeys();
|
|
},
|
|
|
|
/* ------^-------
|
|
* /PANELS
|
|
* ==============
|
|
* SETTINGS (panel)
|
|
* ------v------*/
|
|
|
|
openSettingsPanel() {
|
|
UI.closeAllPanels();
|
|
UI.openControlbar();
|
|
|
|
// Refresh UI elements from saved cookies
|
|
UI.updateSetting('encrypt');
|
|
UI.updateSetting('view_clip');
|
|
UI.updateSetting('resize');
|
|
UI.updateSetting('shared');
|
|
UI.updateSetting('view_only');
|
|
UI.updateSetting('path');
|
|
UI.updateSetting('repeaterID');
|
|
UI.updateSetting('logging');
|
|
UI.updateSetting('reconnect');
|
|
UI.updateSetting('reconnect_delay');
|
|
|
|
document.getElementById('noVNC_settings')
|
|
.classList.add("noVNC_open");
|
|
document.getElementById('noVNC_settings_button')
|
|
.classList.add("noVNC_selected");
|
|
},
|
|
|
|
closeSettingsPanel() {
|
|
document.getElementById('noVNC_settings')
|
|
.classList.remove("noVNC_open");
|
|
document.getElementById('noVNC_settings_button')
|
|
.classList.remove("noVNC_selected");
|
|
},
|
|
|
|
toggleSettingsPanel() {
|
|
if (document.getElementById('noVNC_settings')
|
|
.classList.contains("noVNC_open")) {
|
|
UI.closeSettingsPanel();
|
|
} else {
|
|
UI.openSettingsPanel();
|
|
}
|
|
},
|
|
|
|
/* ------^-------
|
|
* /SETTINGS
|
|
* ==============
|
|
* POWER
|
|
* ------v------*/
|
|
|
|
openPowerPanel() {
|
|
UI.closeAllPanels();
|
|
UI.openControlbar();
|
|
|
|
document.getElementById('noVNC_power')
|
|
.classList.add("noVNC_open");
|
|
document.getElementById('noVNC_power_button')
|
|
.classList.add("noVNC_selected");
|
|
},
|
|
|
|
closePowerPanel() {
|
|
document.getElementById('noVNC_power')
|
|
.classList.remove("noVNC_open");
|
|
document.getElementById('noVNC_power_button')
|
|
.classList.remove("noVNC_selected");
|
|
},
|
|
|
|
togglePowerPanel() {
|
|
if (document.getElementById('noVNC_power')
|
|
.classList.contains("noVNC_open")) {
|
|
UI.closePowerPanel();
|
|
} else {
|
|
UI.openPowerPanel();
|
|
}
|
|
},
|
|
|
|
// Disable/enable power button
|
|
updatePowerButton() {
|
|
if (UI.connected &&
|
|
UI.rfb.capabilities.power &&
|
|
!UI.rfb.viewOnly) {
|
|
document.getElementById('noVNC_power_button')
|
|
.classList.remove("noVNC_hidden");
|
|
} else {
|
|
document.getElementById('noVNC_power_button')
|
|
.classList.add("noVNC_hidden");
|
|
// Close power panel if open
|
|
UI.closePowerPanel();
|
|
}
|
|
},
|
|
|
|
/* ------^-------
|
|
* /POWER
|
|
* ==============
|
|
* CLIPBOARD
|
|
* ------v------*/
|
|
|
|
openClipboardPanel() {
|
|
UI.closeAllPanels();
|
|
UI.openControlbar();
|
|
|
|
document.getElementById('noVNC_clipboard')
|
|
.classList.add("noVNC_open");
|
|
document.getElementById('noVNC_clipboard_button')
|
|
.classList.add("noVNC_selected");
|
|
},
|
|
|
|
closeClipboardPanel() {
|
|
document.getElementById('noVNC_clipboard')
|
|
.classList.remove("noVNC_open");
|
|
document.getElementById('noVNC_clipboard_button')
|
|
.classList.remove("noVNC_selected");
|
|
},
|
|
|
|
toggleClipboardPanel() {
|
|
if (document.getElementById('noVNC_clipboard')
|
|
.classList.contains("noVNC_open")) {
|
|
UI.closeClipboardPanel();
|
|
} else {
|
|
UI.openClipboardPanel();
|
|
}
|
|
},
|
|
|
|
readClipboard: function readClipboard(callback) {
|
|
if (navigator.clipboard && navigator.clipboard.readText) {
|
|
navigator.clipboard.readText().then(function (text) {
|
|
return callback(text);
|
|
}).catch(function () {
|
|
return Log.Debug("Failed to read system clipboard");
|
|
});
|
|
}
|
|
},
|
|
|
|
// Copy text from NoVNC desktop to local computer
|
|
clipboardReceive(e) {
|
|
if (UI.rfb.clipboardDown && UI.rfb.clipboardSeamless ) {
|
|
var curvalue = document.getElementById('noVNC_clipboard_text').value;
|
|
if (curvalue != e.detail.text) {
|
|
Log.Debug(">> UI.clipboardReceive: " + e.detail.text.substr(0, 40) + "...");
|
|
document.getElementById('noVNC_clipboard_text').value = e.detail.text;
|
|
Log.Debug("<< UI.clipboardReceive");
|
|
if (navigator.clipboard && navigator.clipboard.writeText){
|
|
navigator.clipboard.writeText(e.detail.text)
|
|
.then(function () {
|
|
//UI.popupMessage("Selection Copied");
|
|
}, function () {
|
|
console.error("Failed to write system clipboard (trying to copy from NoVNC clipboard)")
|
|
});
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
//recieved bottleneck stats
|
|
bottleneckStatsRecieve(e) {
|
|
var obj = JSON.parse(e.detail.text);
|
|
document.getElementById("noVNC_connection_stats").innerHTML = "CPU: " + obj[0] + "/" + obj[1] + " | Network: " + obj[2] + "/" + obj[3];
|
|
console.log(e.detail.text);
|
|
},
|
|
|
|
popupMessage: function(msg, secs) {
|
|
if (!secs){
|
|
secs = 500;
|
|
}
|
|
// Quick popup to give feedback that selection was copied
|
|
setTimeout(UI.showOverlay.bind(this, msg, secs), 200);
|
|
},
|
|
|
|
// Enter and focus events come when we return to NoVNC.
|
|
// In both cases, check the local clipboard to see if it changed.
|
|
focusVNC: function() {
|
|
UI.copyFromLocalClipboard();
|
|
},
|
|
enterVNC: function() {
|
|
UI.copyFromLocalClipboard();
|
|
},
|
|
copyFromLocalClipboard: function copyFromLocalClipboard() {
|
|
if (UI.rfb && UI.rfb.clipboardUp && UI.rfb.clipboardSeamless) {
|
|
UI.readClipboard(function (text) {
|
|
var maximumBufferSize = 10000;
|
|
var clipVal = document.getElementById('noVNC_clipboard_text').value;
|
|
|
|
if (clipVal != text) {
|
|
document.getElementById('noVNC_clipboard_text').value = text; // The websocket has a maximum buffer array size
|
|
|
|
if (text.length > maximumBufferSize) {
|
|
UI.popupMessage("Clipboard contents too large. Data truncated", 2000);
|
|
UI.rfb.clipboardPasteFrom(text.slice(0, maximumBufferSize));
|
|
} else {
|
|
//UI.popupMessage("Copied from Local Clipboard");
|
|
UI.rfb.clipboardPasteFrom(text);
|
|
}
|
|
} // Reset flag to prevent checking too often
|
|
|
|
|
|
UI.needToCheckClipboardChange = false;
|
|
});
|
|
}
|
|
},
|
|
|
|
// These 3 events indicate the focus has gone outside the NoVNC.
|
|
// When outside the NoVNC, the system clipboard could change.
|
|
leaveVNC: function() {
|
|
UI.needToCheckClipboardChange = true;
|
|
},
|
|
blurVNC: function() {
|
|
UI.needToCheckClipboardChange = true;
|
|
},
|
|
focusoutVNC: function() {
|
|
UI.needToCheckClipboardChange = true;
|
|
},
|
|
|
|
// On these 2 events, check if we need to look at clipboard.
|
|
mouseMoveVNC: function() {
|
|
if ( UI.needToCheckClipboardChange ) {
|
|
UI.copyFromLocalClipboard();
|
|
}
|
|
},
|
|
mouseDownVNC: function() {
|
|
if ( UI.needToCheckClipboardChange ) {
|
|
UI.copyFromLocalClipboard();
|
|
}
|
|
},
|
|
|
|
clipboardClear() {
|
|
document.getElementById('noVNC_clipboard_text').value = "";
|
|
UI.rfb.clipboardPasteFrom("");
|
|
},
|
|
|
|
clipboardSend() {
|
|
const text = document.getElementById('noVNC_clipboard_text').value;
|
|
Log.Debug(">> UI.clipboardSend: " + text.substr(0, 40) + "...");
|
|
UI.rfb.clipboardPasteFrom(text);
|
|
Log.Debug("<< UI.clipboardSend");
|
|
},
|
|
|
|
/**
|
|
* Show the terminal overlay for a given amount of time.
|
|
*
|
|
* The terminal overlay appears in inverse video in a large font, centered
|
|
* over the terminal. You should probably keep the overlay message brief,
|
|
* since it's in a large font and you probably aren't going to check the size
|
|
* of the terminal first.
|
|
*
|
|
* @param {string} msg The text (not HTML) message to display in the overlay.
|
|
* @param {number} opt_timeout The amount of time to wait before fading out
|
|
* the overlay. Defaults to 1.5 seconds. Pass null to have the overlay
|
|
* stay up forever (or until the next overlay).
|
|
*/
|
|
showOverlay: function(msg, opt_timeout) {
|
|
if (!UI.overlayNode) {
|
|
UI.overlayNode = document.createElement('div');
|
|
UI.overlayNode.style.cssText = (
|
|
'border-radius: 15px;' +
|
|
'font-size: xx-large;' +
|
|
'opacity: 0.90;' +
|
|
'padding: 0.2em 0.5em 0.2em 0.5em;' +
|
|
'position: absolute;' +
|
|
'-webkit-user-select: none;' +
|
|
'-webkit-transition: opacity 180ms ease-in;' +
|
|
'-moz-user-select: none;' +
|
|
'-moz-transition: opacity 180ms ease-in;');
|
|
UI.overlayNode.style.color = 'rgb(16,16,16)';
|
|
UI.overlayNode.style.backgroundColor = 'rgb(240,240,240)';
|
|
UI.overlayNode.style.fontFamily = '"DejaVu Sans Mono", "Noto Sans Mono", "Everson Mono", FreeMono, Menlo, Terminal, monospace"';
|
|
UI.overlayNode.style.opacity = '0.90';
|
|
|
|
//UI.overlayNode.addEventListener('mousedown', function(e) {
|
|
// e.preventDefault();
|
|
// e.stopPropagation();
|
|
// }, true);
|
|
}
|
|
|
|
UI.overlayNode.textContent = msg;
|
|
|
|
if (!UI.overlayNode.parentNode_) {
|
|
UI.overlayNode.parentNode_ = document.getElementById('noVNC_container');
|
|
}
|
|
UI.overlayNode.parentNode_.appendChild(UI.overlayNode);
|
|
|
|
var divWidth = UI.overlayNode.parentNode_.offsetWidth;
|
|
var divHeight = UI.overlayNode.parentNode_.offsetHeight;
|
|
var overlayWidth = UI.overlayNode.offsetWidth;
|
|
var overlayHeight = UI.overlayNode.offsetHeight;
|
|
|
|
UI.overlayNode.style.top =
|
|
(divHeight - overlayHeight) / 2 + 'px';
|
|
UI.overlayNode.style.left =
|
|
(divWidth - overlayWidth) / 2 + 'px';
|
|
|
|
if (UI.overlayTimeout)
|
|
clearTimeout(UI.overlayTimeout);
|
|
|
|
if (opt_timeout === null)
|
|
opt_timeout = 500;
|
|
|
|
UI.overlayTimeout = setTimeout(function () {
|
|
UI.overlayNode.style.opacity = '0';
|
|
UI.overlayTimeout = setTimeout(function () {
|
|
return UI.hideOverlay();
|
|
}, 200);
|
|
}, opt_timeout || 1500);
|
|
},
|
|
|
|
/**
|
|
* Hide the terminal overlay immediately.
|
|
*
|
|
* Useful when we show an overlay for an event with an unknown end time.
|
|
*/
|
|
hideOverlay: function() {
|
|
if (UI.overlayTimeout)
|
|
clearTimeout(UI.overlayTimeout);
|
|
UI.overlayTimeout = null;
|
|
|
|
if (UI.overlayNode.parentNode_)
|
|
UI.overlayNode.parentNode_.removeChild(UI.overlayNode);
|
|
UI.overlayNode.style.opacity = '0.90';
|
|
},
|
|
|
|
/* ------^-------
|
|
* /CLIPBOARD
|
|
* ==============
|
|
* 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
|
|
if (typeof UI.rfb !== 'undefined') {
|
|
return;
|
|
}
|
|
|
|
const host = UI.getSetting('host');
|
|
const port = UI.getSetting('port');
|
|
const path = UI.getSetting('path');
|
|
|
|
if (typeof password === 'undefined') {
|
|
password = WebUtil.getConfigVar('password');
|
|
UI.reconnect_password = password;
|
|
}
|
|
|
|
if (password === null) {
|
|
password = undefined;
|
|
}
|
|
|
|
UI.hideStatus();
|
|
|
|
if (!host) {
|
|
Log.Error("Can't connect when host is: " + host);
|
|
UI.showStatus(_("Must set host"), 'error');
|
|
return;
|
|
}
|
|
|
|
UI.closeAllPanels();
|
|
UI.closeConnectPanel();
|
|
|
|
UI.updateVisualState('connecting');
|
|
|
|
let url;
|
|
|
|
url = UI.getSetting('encrypt') ? 'wss' : 'ws';
|
|
|
|
url += '://' + host;
|
|
if (port) {
|
|
url += ':' + port;
|
|
}
|
|
url += '/' + path;
|
|
|
|
UI.rfb = new RFB(document.getElementById('noVNC_container'), url,
|
|
{ shared: UI.getSetting('shared'),
|
|
repeaterID: UI.getSetting('repeaterID'),
|
|
credentials: { password: password } });
|
|
UI.rfb.addEventListener("connect", UI.connectFinished);
|
|
UI.rfb.addEventListener("disconnect", UI.disconnectFinished);
|
|
UI.rfb.addEventListener("credentialsrequired", UI.credentials);
|
|
UI.rfb.addEventListener("securityfailure", UI.securityFailed);
|
|
UI.rfb.addEventListener("capabilities", UI.updatePowerButton);
|
|
UI.rfb.addEventListener("clipboard", UI.clipboardReceive);
|
|
UI.rfb.addEventListener("bottleneck_stats", UI.bottleneckStatsRecieve);
|
|
|
|
document.addEventListener('mouseenter', UI.enterVNC);
|
|
document.addEventListener('mouseleave', UI.leaveVNC);
|
|
document.addEventListener('blur', UI.blurVNC);
|
|
document.addEventListener('focus', UI.focusVNC);
|
|
document.addEventListener('focusout', UI.focusoutVNC);
|
|
document.addEventListener('mousemove', UI.mouseMoveVNC);
|
|
document.addEventListener('mousedown', UI.mouseDownVNC);
|
|
|
|
UI.rfb.addEventListener("bell", UI.bell);
|
|
UI.rfb.addEventListener("desktopname", UI.updateDesktopName);
|
|
UI.rfb.clipViewport = UI.getSetting('view_clip');
|
|
UI.rfb.scaleViewport = UI.getSetting('resize') === 'scale';
|
|
UI.rfb.resizeSession = UI.getSetting('resize') === 'remote';
|
|
UI.rfb.showDotCursor = UI.getSetting('show_dot');
|
|
UI.rfb.idleDisconnect = UI.getSetting('idle_disconnect');
|
|
UI.rfb.videoQuality = UI.getSetting('video_quality');
|
|
UI.rfb.clipboardUp = UI.getSetting('clipboard_up');
|
|
UI.rfb.clipboardDown = UI.getSetting('clipboard_down');
|
|
UI.rfb.clipboardSeamless = UI.getSetting('clipboard_seamless');
|
|
// KASM-960 workaround, disable seamless on Safari
|
|
if (/^((?!chrome|android).)*safari/i.test(navigator.userAgent))
|
|
{
|
|
UI.rfb.clipboardSeamless = false;
|
|
}
|
|
UI.rfb.preferLocalCursor = UI.getSetting('prefer_local_cursor');
|
|
UI.rfb.enableWebP = UI.getSetting('enable_webp');
|
|
UI.updateViewOnly(); // requires UI.rfb
|
|
|
|
/****
|
|
* Kasm VDI specific
|
|
*****/
|
|
if (WebUtil.isInsideKasmVDI())
|
|
{
|
|
if (window.addEventListener) { // Mozilla, Netscape, Firefox
|
|
//window.addEventListener('load', WindowLoad, false);
|
|
window.addEventListener('message', UI.receiveMessage, false);
|
|
} else if (window.attachEvent) { //IE
|
|
window.attachEvent('onload', WindowLoad);
|
|
window.attachEvent('message', UI.receiveMessage);
|
|
}
|
|
if (UI.rfb.clipboardDown){
|
|
UI.rfb.addEventListener("clipboard", UI.clipboardRx);
|
|
}
|
|
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
|
|
setInterval(function() {
|
|
if (currentEventCount!=UI.rfb.sentEventsCounter) {
|
|
idleCounter=0;
|
|
currentEventCount=UI.rfb.sentEventsCounter;
|
|
} else {
|
|
idleCounter+=1;
|
|
var idleDisconnect = parseFloat(UI.rfb.idleDisconnect);
|
|
if ((idleCounter / 2) >= idleDisconnect) {
|
|
//idle for longer than the limit, disconnect
|
|
currentEventCount = -1;
|
|
idleCounter = 0;
|
|
parent.postMessage({ action: 'idle_session_timeout', value: 'Idle session timeout exceeded'}, '*' );
|
|
//UI.rfb.disconnect();
|
|
} else {
|
|
//send a keep alive
|
|
UI.rfb.sendKey(1, null, false);
|
|
currentEventCount=UI.rfb.sentEventsCounter;
|
|
}
|
|
}
|
|
}, 30000);
|
|
}
|
|
|
|
|
|
// 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;
|
|
}
|
|
|
|
lastKeypressTime = thisKeypressTime;
|
|
}
|
|
}, true);
|
|
}
|
|
|
|
},
|
|
|
|
disconnect() {
|
|
UI.closeAllPanels();
|
|
UI.rfb.disconnect();
|
|
|
|
UI.connected = false;
|
|
|
|
// Disable automatic reconnecting
|
|
UI.inhibit_reconnect = true;
|
|
|
|
UI.updateVisualState('disconnecting');
|
|
|
|
// Don't display the connection settings until we're actually disconnected
|
|
},
|
|
|
|
reconnect() {
|
|
UI.reconnect_callback = null;
|
|
|
|
// if reconnect has been disabled in the meantime, do nothing.
|
|
if (UI.inhibit_reconnect) {
|
|
return;
|
|
}
|
|
|
|
UI.connect(null, UI.reconnect_password);
|
|
},
|
|
|
|
cancelReconnect() {
|
|
if (UI.reconnect_callback !== null) {
|
|
clearTimeout(UI.reconnect_callback);
|
|
UI.reconnect_callback = null;
|
|
}
|
|
|
|
UI.updateVisualState('disconnected');
|
|
|
|
UI.openControlbar();
|
|
UI.openConnectPanel();
|
|
},
|
|
|
|
connectFinished(e) {
|
|
UI.connected = true;
|
|
UI.inhibit_reconnect = false;
|
|
|
|
let msg;
|
|
if (UI.getSetting('encrypt')) {
|
|
msg = _("Connected (encrypted) to ") + UI.desktopName;
|
|
} else {
|
|
msg = _("Connected (unencrypted) to ") + UI.desktopName;
|
|
}
|
|
UI.showStatus(msg);
|
|
UI.showStats();
|
|
UI.updateVisualState('connected');
|
|
|
|
// Do this last because it can only be used on rendered elements
|
|
UI.rfb.focus();
|
|
},
|
|
|
|
disconnectFinished(e) {
|
|
const wasConnected = UI.connected;
|
|
|
|
// This variable is ideally set when disconnection starts, but
|
|
// when the disconnection isn't clean or if it is initiated by
|
|
// the server, we need to do it here as well since
|
|
// UI.disconnect() won't be used in those cases.
|
|
UI.connected = false;
|
|
|
|
UI.rfb = undefined;
|
|
|
|
if (!e.detail.clean) {
|
|
UI.updateVisualState('disconnected');
|
|
if (wasConnected) {
|
|
UI.showStatus(_("Something went wrong, connection is closed"),
|
|
'error');
|
|
} else {
|
|
UI.showStatus(_("Failed to connect to server"), 'error');
|
|
}
|
|
} else if (UI.getSetting('reconnect', false) === true && !UI.inhibit_reconnect) {
|
|
UI.updateVisualState('reconnecting');
|
|
|
|
const delay = parseInt(UI.getSetting('reconnect_delay'));
|
|
UI.reconnect_callback = setTimeout(UI.reconnect, delay);
|
|
return;
|
|
} else {
|
|
UI.updateVisualState('disconnected');
|
|
UI.showStatus(_("Disconnected"), 'normal');
|
|
}
|
|
|
|
UI.openControlbar();
|
|
UI.openConnectPanel();
|
|
},
|
|
|
|
securityFailed(e) {
|
|
let msg = "";
|
|
// On security failures we might get a string with a reason
|
|
// directly from the server. Note that we can't control if
|
|
// this string is translated or not.
|
|
if ('reason' in e.detail) {
|
|
msg = _("New connection has been rejected with reason: ") +
|
|
e.detail.reason;
|
|
} else {
|
|
msg = _("New connection has been rejected");
|
|
}
|
|
UI.showStatus(msg, 'error');
|
|
},
|
|
|
|
/*
|
|
Menu.js Additions
|
|
*/
|
|
receiveMessage(event) {
|
|
//TODO: UNCOMMENT FOR PRODUCTION
|
|
//if (event.origin !== "https://kasmweb.com")
|
|
// return;
|
|
|
|
if (event.data && event.data.action) {
|
|
switch (event.data.action) {
|
|
case 'clipboardsnd':
|
|
if (UI.rfb.clipboardUp) {
|
|
UI.rfb.clipboardPasteFrom(event.data.value);
|
|
}
|
|
break;
|
|
case 'setvideoquality':
|
|
UI.rfb.videoQuality = event.data.value;
|
|
break;
|
|
}
|
|
}
|
|
},
|
|
|
|
disconnectedRx(event) {
|
|
parent.postMessage({ action: 'disconnectrx', value: event.detail.reason}, '*' );
|
|
},
|
|
|
|
toggleNav(){
|
|
parent.postMessage({ action: 'togglenav', value: null}, '*' );
|
|
},
|
|
|
|
clipboardRx(event) {
|
|
parent.postMessage({ action: 'clipboardrx', value: event.detail.text}, '*' ); //TODO fix star
|
|
},
|
|
|
|
/* ------^-------
|
|
* /CONNECTION
|
|
* ==============
|
|
* PASSWORD
|
|
* ------v------*/
|
|
|
|
credentials(e) {
|
|
// FIXME: handle more types
|
|
document.getElementById('noVNC_password_dlg')
|
|
.classList.add('noVNC_open');
|
|
|
|
setTimeout(() => document
|
|
.getElementById('noVNC_password_input').focus(), 100);
|
|
|
|
Log.Warn("Server asked for a password");
|
|
UI.showStatus(_("Password is required"), "warning");
|
|
},
|
|
|
|
setPassword(e) {
|
|
// Prevent actually submitting the form
|
|
e.preventDefault();
|
|
|
|
const inputElem = document.getElementById('noVNC_password_input');
|
|
const password = inputElem.value;
|
|
// Clear the input after reading the password
|
|
inputElem.value = "";
|
|
UI.rfb.sendCredentials({ password: password });
|
|
UI.reconnect_password = password;
|
|
document.getElementById('noVNC_password_dlg')
|
|
.classList.remove('noVNC_open');
|
|
},
|
|
|
|
/* ------^-------
|
|
* /PASSWORD
|
|
* ==============
|
|
* FULLSCREEN
|
|
* ------v------*/
|
|
|
|
toggleFullscreen() {
|
|
if (document.fullscreenElement || // alternative standard method
|
|
document.mozFullScreenElement || // currently working methods
|
|
document.webkitFullscreenElement ||
|
|
document.msFullscreenElement) {
|
|
if (document.exitFullscreen) {
|
|
document.exitFullscreen();
|
|
} else if (document.mozCancelFullScreen) {
|
|
document.mozCancelFullScreen();
|
|
} else if (document.webkitExitFullscreen) {
|
|
document.webkitExitFullscreen();
|
|
} else if (document.msExitFullscreen) {
|
|
document.msExitFullscreen();
|
|
}
|
|
} else {
|
|
if (document.documentElement.requestFullscreen) {
|
|
document.documentElement.requestFullscreen();
|
|
} else if (document.documentElement.mozRequestFullScreen) {
|
|
document.documentElement.mozRequestFullScreen();
|
|
} else if (document.documentElement.webkitRequestFullscreen) {
|
|
document.documentElement.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
|
|
} else if (document.body.msRequestFullscreen) {
|
|
document.body.msRequestFullscreen();
|
|
}
|
|
}
|
|
UI.updateFullscreenButton();
|
|
},
|
|
|
|
updateFullscreenButton() {
|
|
if (document.fullscreenElement || // alternative standard method
|
|
document.mozFullScreenElement || // currently working methods
|
|
document.webkitFullscreenElement ||
|
|
document.msFullscreenElement ) {
|
|
document.getElementById('noVNC_fullscreen_button')
|
|
.classList.add("noVNC_selected");
|
|
} else {
|
|
document.getElementById('noVNC_fullscreen_button')
|
|
.classList.remove("noVNC_selected");
|
|
}
|
|
},
|
|
|
|
/* ------^-------
|
|
* /FULLSCREEN
|
|
* ==============
|
|
* RESIZE
|
|
* ------v------*/
|
|
|
|
// Apply remote resizing or local scaling
|
|
applyResizeMode() {
|
|
if (!UI.rfb) return;
|
|
|
|
UI.rfb.scaleViewport = UI.getSetting('resize') === 'scale';
|
|
UI.rfb.resizeSession = UI.getSetting('resize') === 'remote';
|
|
UI.rfb.idleDisconnect = UI.getSetting('idle_disconnect');
|
|
UI.rfb.videoQuality = UI.getSetting('video_quality');
|
|
UI.rfb.enableWebP = UI.getSetting('enable_webp');
|
|
},
|
|
|
|
/* ------^-------
|
|
* /RESIZE
|
|
* ==============
|
|
* VIEW CLIPPING
|
|
* ------v------*/
|
|
|
|
// Update viewport clipping property for the connection. The normal
|
|
// case is to get the value from the setting. There are special cases
|
|
// for when the viewport is scaled or when a touch device is used.
|
|
updateViewClip() {
|
|
if (!UI.rfb) return;
|
|
|
|
const scaling = UI.getSetting('resize') === 'scale';
|
|
|
|
if (scaling) {
|
|
// Can't be clipping if viewport is scaled to fit
|
|
UI.forceSetting('view_clip', false);
|
|
UI.rfb.clipViewport = false;
|
|
} else if (isIOS() || isAndroid()) {
|
|
// iOS and Android usually have shit scrollbars
|
|
UI.forceSetting('view_clip', true);
|
|
UI.rfb.clipViewport = true;
|
|
} else {
|
|
UI.enableSetting('view_clip');
|
|
UI.rfb.clipViewport = UI.getSetting('view_clip');
|
|
}
|
|
|
|
// Changing the viewport may change the state of
|
|
// the dragging button
|
|
UI.updateViewDrag();
|
|
},
|
|
|
|
/* ------^-------
|
|
* /VIEW CLIPPING
|
|
* ==============
|
|
* VIEWDRAG
|
|
* ------v------*/
|
|
|
|
toggleViewDrag() {
|
|
if (!UI.rfb) return;
|
|
|
|
UI.rfb.dragViewport = !UI.rfb.dragViewport;
|
|
UI.updateViewDrag();
|
|
},
|
|
|
|
updateViewDrag() {
|
|
if (!UI.connected) return;
|
|
|
|
const viewDragButton = document.getElementById('noVNC_view_drag_button');
|
|
|
|
if (!UI.rfb.clipViewport && UI.rfb.dragViewport) {
|
|
// We are no longer clipping the viewport. Make sure
|
|
// viewport drag isn't active when it can't be used.
|
|
UI.rfb.dragViewport = false;
|
|
}
|
|
|
|
if (UI.rfb.dragViewport) {
|
|
viewDragButton.classList.add("noVNC_selected");
|
|
} else {
|
|
viewDragButton.classList.remove("noVNC_selected");
|
|
}
|
|
|
|
if (UI.rfb.clipViewport) {
|
|
viewDragButton.classList.remove("noVNC_hidden");
|
|
} else {
|
|
viewDragButton.classList.add("noVNC_hidden");
|
|
}
|
|
},
|
|
|
|
/* ------^-------
|
|
* /VIEWDRAG
|
|
* ==============
|
|
* KEYBOARD
|
|
* ------v------*/
|
|
|
|
showVirtualKeyboard() {
|
|
if (!isTouchDevice) return;
|
|
|
|
const input = document.getElementById('noVNC_keyboardinput');
|
|
|
|
if (document.activeElement == input) return;
|
|
|
|
input.focus();
|
|
|
|
try {
|
|
const l = input.value.length;
|
|
// Move the caret to the end
|
|
input.setSelectionRange(l, l);
|
|
} catch (err) {
|
|
// setSelectionRange is undefined in Google Chrome
|
|
}
|
|
},
|
|
|
|
hideVirtualKeyboard() {
|
|
if (!isTouchDevice) return;
|
|
|
|
const input = document.getElementById('noVNC_keyboardinput');
|
|
|
|
if (document.activeElement != input) return;
|
|
|
|
input.blur();
|
|
},
|
|
|
|
toggleVirtualKeyboard() {
|
|
if (document.getElementById('noVNC_keyboard_button')
|
|
.classList.contains("noVNC_selected")) {
|
|
UI.hideVirtualKeyboard();
|
|
} else {
|
|
UI.showVirtualKeyboard();
|
|
}
|
|
},
|
|
|
|
onfocusVirtualKeyboard(event) {
|
|
document.getElementById('noVNC_keyboard_button')
|
|
.classList.add("noVNC_selected");
|
|
if (UI.rfb) {
|
|
UI.rfb.focusOnClick = false;
|
|
}
|
|
},
|
|
|
|
onblurVirtualKeyboard(event) {
|
|
document.getElementById('noVNC_keyboard_button')
|
|
.classList.remove("noVNC_selected");
|
|
if (UI.rfb) {
|
|
UI.rfb.focusOnClick = true;
|
|
}
|
|
},
|
|
|
|
keepVirtualKeyboard(event) {
|
|
const input = document.getElementById('noVNC_keyboardinput');
|
|
if ( UI.needToCheckClipboardChange ) {
|
|
UI.copyFromLocalClipboard();
|
|
}
|
|
|
|
// Only prevent focus change if the virtual keyboard is active
|
|
if (document.activeElement != input) {
|
|
return;
|
|
}
|
|
|
|
// Only allow focus to move to other elements that need
|
|
// focus to function properly
|
|
if (event.target.form !== undefined) {
|
|
switch (event.target.type) {
|
|
case 'text':
|
|
case 'email':
|
|
case 'search':
|
|
case 'password':
|
|
case 'tel':
|
|
case 'url':
|
|
case 'textarea':
|
|
case 'select-one':
|
|
case 'select-multiple':
|
|
return;
|
|
}
|
|
}
|
|
|
|
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
|
|
* ==============
|
|
* EXTRA KEYS
|
|
* ------v------*/
|
|
|
|
openExtraKeys() {
|
|
UI.closeAllPanels();
|
|
UI.openControlbar();
|
|
|
|
document.getElementById('noVNC_modifiers')
|
|
.classList.add("noVNC_open");
|
|
document.getElementById('noVNC_toggle_extra_keys_button')
|
|
.classList.add("noVNC_selected");
|
|
},
|
|
|
|
closeExtraKeys() {
|
|
document.getElementById('noVNC_modifiers')
|
|
.classList.remove("noVNC_open");
|
|
document.getElementById('noVNC_toggle_extra_keys_button')
|
|
.classList.remove("noVNC_selected");
|
|
},
|
|
|
|
toggleExtraKeys() {
|
|
if (document.getElementById('noVNC_modifiers')
|
|
.classList.contains("noVNC_open")) {
|
|
UI.closeExtraKeys();
|
|
} else {
|
|
UI.openExtraKeys();
|
|
}
|
|
},
|
|
|
|
sendEsc() {
|
|
UI.rfb.sendKey(KeyTable.XK_Escape, "Escape");
|
|
},
|
|
|
|
sendTab() {
|
|
UI.rfb.sendKey(KeyTable.XK_Tab);
|
|
},
|
|
|
|
toggleCtrl() {
|
|
const btn = document.getElementById('noVNC_toggle_ctrl_button');
|
|
if (btn.classList.contains("noVNC_selected")) {
|
|
UI.rfb.sendKey(KeyTable.XK_Control_L, "ControlLeft", false);
|
|
btn.classList.remove("noVNC_selected");
|
|
} else {
|
|
UI.rfb.sendKey(KeyTable.XK_Control_L, "ControlLeft", true);
|
|
btn.classList.add("noVNC_selected");
|
|
}
|
|
},
|
|
|
|
toggleWindows() {
|
|
const btn = document.getElementById('noVNC_toggle_windows_button');
|
|
if (btn.classList.contains("noVNC_selected")) {
|
|
UI.rfb.sendKey(KeyTable.XK_Super_L, "MetaLeft", false);
|
|
btn.classList.remove("noVNC_selected");
|
|
} else {
|
|
UI.rfb.sendKey(KeyTable.XK_Super_L, "MetaLeft", true);
|
|
btn.classList.add("noVNC_selected");
|
|
}
|
|
},
|
|
|
|
toggleAlt() {
|
|
const btn = document.getElementById('noVNC_toggle_alt_button');
|
|
if (btn.classList.contains("noVNC_selected")) {
|
|
UI.rfb.sendKey(KeyTable.XK_Alt_L, "AltLeft", false);
|
|
btn.classList.remove("noVNC_selected");
|
|
} else {
|
|
UI.rfb.sendKey(KeyTable.XK_Alt_L, "AltLeft", true);
|
|
btn.classList.add("noVNC_selected");
|
|
}
|
|
},
|
|
|
|
sendCtrlAltDel() {
|
|
UI.rfb.sendCtrlAltDel();
|
|
},
|
|
|
|
/* ------^-------
|
|
* /EXTRA KEYS
|
|
* ==============
|
|
* MISC
|
|
* ------v------*/
|
|
|
|
setMouseButton(num) {
|
|
const view_only = UI.rfb.viewOnly;
|
|
if (UI.rfb && !view_only) {
|
|
UI.rfb.touchButton = num;
|
|
}
|
|
|
|
const blist = [0, 1, 2, 4];
|
|
for (let b = 0; b < blist.length; b++) {
|
|
const button = document.getElementById('noVNC_mouse_button' +
|
|
blist[b]);
|
|
if (blist[b] === num && !view_only) {
|
|
button.classList.remove("noVNC_hidden");
|
|
} else {
|
|
button.classList.add("noVNC_hidden");
|
|
}
|
|
}
|
|
},
|
|
|
|
updateViewOnly() {
|
|
if (!UI.rfb) return;
|
|
UI.rfb.viewOnly = UI.getSetting('view_only');
|
|
|
|
// 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_mouse_button' + UI.rfb.touchButton)
|
|
.classList.add('noVNC_hidden');
|
|
} else {
|
|
document.getElementById('noVNC_keyboard_button')
|
|
.classList.remove('noVNC_hidden');
|
|
document.getElementById('noVNC_toggle_extra_keys_button')
|
|
.classList.remove('noVNC_hidden');
|
|
document.getElementById('noVNC_mouse_button' + UI.rfb.touchButton)
|
|
.classList.remove('noVNC_hidden');
|
|
}
|
|
},
|
|
|
|
updateShowDotCursor() {
|
|
if (!UI.rfb) return;
|
|
UI.rfb.showDotCursor = UI.getSetting('show_dot');
|
|
},
|
|
|
|
updateLogging() {
|
|
WebUtil.init_logging(UI.getSetting('logging'));
|
|
},
|
|
|
|
updateDesktopName(e) {
|
|
UI.desktopName = e.detail.name;
|
|
// Display the desktop name in the document title
|
|
document.title = e.detail.name + " - noVNC";
|
|
},
|
|
|
|
bell(e) {
|
|
if (WebUtil.getConfigVar('bell', 'on') === 'on') {
|
|
const promise = document.getElementById('noVNC_bell').play();
|
|
// The standards disagree on the return value here
|
|
if (promise) {
|
|
promise.catch((e) => {
|
|
if (e.name === "NotAllowedError") {
|
|
// Ignore when the browser doesn't let us play audio.
|
|
// It is common that the browsers require audio to be
|
|
// initiated from a user action.
|
|
} else {
|
|
Log.Error("Unable to play bell: " + e);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
},
|
|
|
|
//Helper to add options to dropdown.
|
|
addOption(selectbox, text, value) {
|
|
const optn = document.createElement("OPTION");
|
|
optn.text = text;
|
|
optn.value = value;
|
|
selectbox.options.add(optn);
|
|
},
|
|
|
|
/* ------^-------
|
|
* /MISC
|
|
* ==============
|
|
*/
|
|
};
|
|
|
|
// Set up translations
|
|
const LINGUAS = ["cs", "de", "el", "es", "ko", "nl", "pl", "ru", "sv", "tr", "zh_CN", "zh_TW"];
|
|
l10n.setup(LINGUAS);
|
|
if (l10n.language !== "en" && l10n.dictionary === undefined) {
|
|
WebUtil.fetchJSON('app/locale/' + l10n.language + '.json', (translations) => {
|
|
l10n.dictionary = translations;
|
|
|
|
// wait for translations to load before loading the UI
|
|
UI.prime();
|
|
}, (err) => {
|
|
Log.Error("Failed to load translations: " + err);
|
|
UI.prime();
|
|
});
|
|
} else {
|
|
UI.prime();
|
|
}
|
|
|
|
export default UI;
|