| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795 |
- /*
- * 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.
- */
- import * as Log from '../core/util/logging.js';
- import _, { l10n } from './localization.js';
- import { isTouchDevice, isMac, isIOS, isAndroid, isChromeOS, isSafari,
- hasScrollbarGutter, 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";
- const PAGE_TITLE = "noVNC";
- // String validation
- function isAlphaNumeric(str) { return (str.match(/^[A-Za-z0-9]+$/) != null); };
- function isSafeString(str) { return ((typeof str == 'string') && (str.indexOf('<') == -1) && (str.indexOf('>') == -1) && (str.indexOf('&') == -1) && (str.indexOf('"') == -1) && (str.indexOf('\'') == -1) && (str.indexOf('+') == -1) && (str.indexOf('(') == -1) && (str.indexOf(')') == -1) && (str.indexOf('#') == -1) && (str.indexOf('%') == -1)) };
- // Parse URL arguments, only keep safe values
- function parseUriArgs() {
- var href = window.document.location.href;
- if (href.endsWith('#')) { href = href.substring(0, href.length - 1); }
- var name, r = {}, parsedUri = href.split(/[\?&|\=]/);
- parsedUri.splice(0, 1);
- for (x in parsedUri) {
- switch (x % 2) {
- case 0: { name = decodeURIComponent(parsedUri[x]); break; }
- case 1: {
- r[name] = decodeURIComponent(parsedUri[x]);
- if (!isSafeString(r[name])) { delete r[name]; } else { var x = parseInt(r[name]); if (x == r[name]) { r[name] = x; } }
- break;
- } default: { break; }
- }
- }
- return r;
- }
- var urlargs = parseUriArgs();
- 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,
- inhibitReconnect: true,
- reconnectCallback: null,
- reconnectPassword: null,
- prime() {
- return WebUtil.initSettings().then(() => {
- if (document.readyState === "interactive" || document.readyState === "complete") {
- return UI.start();
- }
- return new Promise((resolve, reject) => {
- document.addEventListener('DOMContentLoaded', () => UI.start().then(resolve).catch(reject));
- });
- });
- },
- // Render default UI and initialize settings menu
- start() {
- UI.initSettings();
- // Translate the DOM
- l10n.translateDOM();
- // We rely on modern APIs which might not be available in an
- // insecure context
- if (!window.isSecureContext) {
- // FIXME: This gets hidden when connecting
- UI.showStatus(_("Running without HTTPS is not recommended, crashes or other issues are likely."), 'error');
- }
- // Try to fetch version number
- fetch('./package.json')
- .then((response) => {
- if (!response.ok) {
- throw Error("" + response.status + " " + response.statusText);
- }
- return response.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) {
- // 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();
- }
- return Promise.resolve(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('resize', 'off');
- UI.initSetting('quality', 6);
- UI.initSetting('compression', 2);
- 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.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_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_approve_server_button")
- .addEventListener('click', UI.approveServer);
- document.getElementById("noVNC_reject_server_button")
- .addEventListener('click', UI.rejectServer);
- document.getElementById("noVNC_credentials_button")
- .addEventListener('click', UI.setCredentials);
- },
- addClipboardHandlers() {
- document.getElementById("noVNC_clipboard_button")
- .addEventListener('click', UI.toggleClipboardPanel);
- document.getElementById("noVNC_clipboard_text")
- .addEventListener('change', UI.clipboardSend);
- },
- // 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);
- UI.addSettingChangeHandler('encrypt');
- UI.addSettingChangeHandler('resize');
- UI.addSettingChangeHandler('resize', UI.applyResizeMode);
- UI.addSettingChangeHandler('resize', UI.updateViewClip);
- UI.addSettingChangeHandler('quality');
- UI.addSettingChangeHandler('quality', UI.updateQuality);
- UI.addSettingChangeHandler('compression');
- UI.addSettingChangeHandler('compression', UI.updateCompression);
- 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');
- },
- 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 transitionElem = document.getElementById("noVNC_transition_text");
- switch (state) {
- case 'init':
- break;
- case 'connecting':
- transitionElem.textContent = _("Connecting...");
- document.documentElement.classList.add("noVNC_connecting");
- break;
- case 'connected':
- document.documentElement.classList.add("noVNC_connected");
- break;
- case 'disconnecting':
- transitionElem.textContent = _("Disconnecting...");
- document.documentElement.classList.add("noVNC_disconnecting");
- break;
- case 'disconnected':
- break;
- case 'reconnecting':
- transitionElem.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');
- // 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 dialogs as they may not be relevant
- // anymore
- UI.closeAllPanels();
- document.getElementById('noVNC_verify_server_dlg')
- .classList.remove('noVNC_open');
- document.getElementById('noVNC_credentials_dlg')
- .classList.remove('noVNC_open');
- },
- showStatus(text, statusType, time) {
- const statusElem = document.getElementById('noVNC_status');
- if (typeof statusType === 'undefined') {
- statusType = 'normal';
- }
- // Don't overwrite more severe visible statuses and never
- // errors. Only shows the first error.
- if (statusElem.classList.contains("noVNC_open")) {
- if (statusElem.classList.contains("noVNC_status_error")) {
- return;
- }
- if (statusElem.classList.contains("noVNC_status_warn") &&
- statusType === 'normal') {
- return;
- }
- }
- clearTimeout(UI.statusTimeout);
- switch (statusType) {
- 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 (statusType !== '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() {
- // Don't fade if a child of the control bar has focus
- if (document.getElementById('noVNC_control_bar')
- .contains(document.activeElement) && document.hasFocus()) {
- UI.activateControlbar();
- return;
- }
- 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");
- UI.rfb.focus();
- },
- 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;
- // The user has "followed" hint, let's hide it until the next drag
- UI.showControlbarHint(false, false);
- },
- showControlbarHint(show, animate=true) {
- const hint = document.getElementById('noVNC_control_bar_hint');
- if (animate) {
- hint.classList.remove("noVNC_notransition");
- } else {
- hint.classList.add("noVNC_notransition");
- }
- 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 {
- 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('quality');
- UI.updateSetting('compression');
- 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();
- }
- },
- clipboardReceive(e) {
- Log.Debug(">> UI.clipboardReceive: " + e.detail.text.substr(0, 40) + "...");
- document.getElementById('noVNC_clipboard_text').value = e.detail.text;
- Log.Debug("<< UI.clipboardReceive");
- },
- 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");
- },
- /* ------^-------
- * /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.reconnectPassword = 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.closeConnectPanel();
- UI.updateVisualState('connecting');
- try {
- UI.rfb = new RFB(document.getElementById('noVNC_container'), urlargs.ws,
- { shared: UI.getSetting('shared'),
- repeaterID: UI.getSetting('repeaterID'),
- credentials: { password: password } });
- } catch (exc) {
- Log.Error("Failed to connect to server: " + exc);
- UI.updateVisualState('disconnected');
- UI.showStatus(_("Failed to connect to server: ") + exc, 'error');
- return;
- }
- UI.rfb.addEventListener("connect", UI.connectFinished);
- UI.rfb.addEventListener("disconnect", UI.disconnectFinished);
- UI.rfb.addEventListener("serververification", UI.serverVerify);
- UI.rfb.addEventListener("credentialsrequired", UI.credentials);
- UI.rfb.addEventListener("securityfailure", UI.securityFailed);
- UI.rfb.addEventListener("clippingviewport", UI.updateViewDrag);
- UI.rfb.addEventListener("capabilities", UI.updatePowerButton);
- UI.rfb.addEventListener("clipboard", UI.clipboardReceive);
- 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.qualityLevel = parseInt(UI.getSetting('quality'));
- UI.rfb.compressionLevel = parseInt(UI.getSetting('compression'));
- UI.rfb.showDotCursor = UI.getSetting('show_dot');
- UI.updateViewOnly(); // requires UI.rfb
- },
- disconnect() {
- UI.rfb.disconnect();
- UI.connected = false;
- // Disable automatic reconnecting
- UI.inhibitReconnect = true;
- UI.updateVisualState('disconnecting');
- // Don't display the connection settings until we're actually disconnected
- },
- reconnect() {
- UI.reconnectCallback = null;
- // if reconnect has been disabled in the meantime, do nothing.
- if (UI.inhibitReconnect) {
- return;
- }
- UI.connect(null, UI.reconnectPassword);
- },
- cancelReconnect() {
- if (UI.reconnectCallback !== null) {
- clearTimeout(UI.reconnectCallback);
- UI.reconnectCallback = null;
- }
- UI.updateVisualState('disconnected');
- UI.openControlbar();
- UI.openConnectPanel();
- },
- connectFinished(e) {
- UI.connected = true;
- UI.inhibitReconnect = false;
- let msg;
- if (UI.getSetting('encrypt')) {
- msg = _("Connected (encrypted) to ") + UI.desktopName;
- } else {
- msg = _("Connected (unencrypted) to ") + UI.desktopName;
- }
- UI.showStatus(msg);
- 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');
- }
- }
- // If reconnecting is allowed process it now
- if (UI.getSetting('reconnect', false) === true && !UI.inhibitReconnect) {
- UI.updateVisualState('reconnecting');
- const delay = parseInt(UI.getSetting('reconnect_delay'));
- UI.reconnectCallback = setTimeout(UI.reconnect, delay);
- return;
- } else {
- UI.updateVisualState('disconnected');
- UI.showStatus(_("Disconnected"), 'normal');
- }
- document.title = PAGE_TITLE;
- 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');
- },
- /* ------^-------
- * /CONNECTION
- * ==============
- * SERVER VERIFY
- * ------v------*/
- async serverVerify(e) {
- const type = e.detail.type;
- if (type === 'RSA') {
- const publickey = e.detail.publickey;
- let fingerprint = await window.crypto.subtle.digest("SHA-1", publickey);
- // The same fingerprint format as RealVNC
- fingerprint = Array.from(new Uint8Array(fingerprint).slice(0, 8)).map(
- x => x.toString(16).padStart(2, '0')).join('-');
- document.getElementById('noVNC_verify_server_dlg').classList.add('noVNC_open');
- document.getElementById('noVNC_fingerprint').innerHTML = fingerprint;
- }
- },
- approveServer(e) {
- e.preventDefault();
- document.getElementById('noVNC_verify_server_dlg').classList.remove('noVNC_open');
- UI.rfb.approveServer();
- },
- rejectServer(e) {
- e.preventDefault();
- document.getElementById('noVNC_verify_server_dlg').classList.remove('noVNC_open');
- UI.disconnect();
- },
- /* ------^-------
- * /SERVER VERIFY
- * ==============
- * PASSWORD
- * ------v------*/
- credentials(e) {
- // FIXME: handle more types
- document.getElementById("noVNC_username_block").classList.remove("noVNC_hidden");
- document.getElementById("noVNC_password_block").classList.remove("noVNC_hidden");
- let inputFocus = "none";
- if (e.detail.types.indexOf("username") === -1) {
- document.getElementById("noVNC_username_block").classList.add("noVNC_hidden");
- } else {
- inputFocus = inputFocus === "none" ? "noVNC_username_input" : inputFocus;
- }
- if (e.detail.types.indexOf("password") === -1) {
- document.getElementById("noVNC_password_block").classList.add("noVNC_hidden");
- } else {
- inputFocus = inputFocus === "none" ? "noVNC_password_input" : inputFocus;
- }
- document.getElementById('noVNC_credentials_dlg')
- .classList.add('noVNC_open');
- setTimeout(() => document
- .getElementById(inputFocus).focus(), 100);
- Log.Warn("Server asked for credentials");
- UI.showStatus(_("Credentials are required"), "warning");
- },
- setCredentials(e) {
- // Prevent actually submitting the form
- e.preventDefault();
- let inputElemUsername = document.getElementById('noVNC_username_input');
- const username = inputElemUsername.value;
- let inputElemPassword = document.getElementById('noVNC_password_input');
- const password = inputElemPassword.value;
- // Clear the input after reading the password
- inputElemPassword.value = "";
- UI.rfb.sendCredentials({ username: username, password: password });
- UI.reconnectPassword = password;
- document.getElementById('noVNC_credentials_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';
- },
- /* ------^-------
- * /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';
- // Some platforms have overlay scrollbars that are difficult
- // to use in our case, which means we have to force panning
- // FIXME: Working scrollbars can still be annoying to use with
- // touch, so we should ideally be able to have both
- // panning and scrollbars at the same time
- let brokenScrollbars = false;
- if (!hasScrollbarGutter) {
- if (isIOS() || isAndroid() || isMac() || isChromeOS()) {
- brokenScrollbars = true;
- }
- }
- if (scaling) {
- // Can't be clipping if viewport is scaled to fit
- UI.forceSetting('view_clip', false);
- UI.rfb.clipViewport = false;
- } else if (brokenScrollbars) {
- 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.clippingViewport) &&
- 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");
- }
- viewDragButton.disabled = !UI.rfb.clippingViewport;
- },
- /* ------^-------
- * /VIEWDRAG
- * ==============
- * QUALITY
- * ------v------*/
- updateQuality() {
- if (!UI.rfb) return;
- UI.rfb.qualityLevel = parseInt(UI.getSetting('quality'));
- },
- /* ------^-------
- * /QUALITY
- * ==============
- * COMPRESSION
- * ------v------*/
- updateCompression() {
- if (!UI.rfb) return;
- UI.rfb.compressionLevel = parseInt(UI.getSetting('compression'));
- },
- /* ------^-------
- * /COMPRESSION
- * ==============
- * 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');
- // 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.sendKey(KeyTable.XK_Escape, "Escape");
- },
- sendTab() {
- UI.sendKey(KeyTable.XK_Tab, "Tab");
- },
- toggleCtrl() {
- const btn = document.getElementById('noVNC_toggle_ctrl_button');
- if (btn.classList.contains("noVNC_selected")) {
- UI.sendKey(KeyTable.XK_Control_L, "ControlLeft", false);
- btn.classList.remove("noVNC_selected");
- } else {
- UI.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.sendKey(KeyTable.XK_Super_L, "MetaLeft", false);
- btn.classList.remove("noVNC_selected");
- } else {
- UI.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.sendKey(KeyTable.XK_Alt_L, "AltLeft", false);
- btn.classList.remove("noVNC_selected");
- } else {
- UI.sendKey(KeyTable.XK_Alt_L, "AltLeft", true);
- btn.classList.add("noVNC_selected");
- }
- },
- sendCtrlAltDel() {
- UI.rfb.sendCtrlAltDel();
- // See below
- UI.rfb.focus();
- UI.idleControlbar();
- },
- sendKey(keysym, code, down) {
- UI.rfb.sendKey(keysym, code, down);
- // Move focus to the screen in order to be able to use the
- // keyboard right after these extra keys.
- // The exception is when a virtual keyboard is used, because
- // if we focus the screen the virtual keyboard would be closed.
- // In this case we focus our special virtual keyboard input
- // element instead.
- if (document.getElementById('noVNC_keyboard_button')
- .classList.contains("noVNC_selected")) {
- document.getElementById('noVNC_keyboardinput').focus();
- } else {
- UI.rfb.focus();
- }
- // fade out the controlbar to highlight that
- // the focus has been moved to the screen
- UI.idleControlbar();
- },
- /* ------^-------
- * /EXTRA KEYS
- * ==============
- * MISC
- * ------v------*/
- 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_clipboard_button')
- .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_clipboard_button')
- .classList.remove('noVNC_hidden');
- }
- },
- updateShowDotCursor() {
- if (!UI.rfb) return;
- UI.rfb.showDotCursor = UI.getSetting('show_dot');
- },
- updateLogging() {
- WebUtil.initLogging(UI.getSetting('logging'));
- },
- updateDesktopName(e) {
- UI.desktopName = e.detail.name;
- // Display the desktop name in the document title
- document.title = e.detail.name + " - " + PAGE_TITLE;
- },
- 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", "fr", "it", "ja", "ko", "nl", "pl", "pt_BR", "ru", "sv", "tr", "zh_CN", "zh_TW"];
- l10n.setup(LINGUAS, "app/locale/")
- .catch(err => Log.Error("Failed to load translations: " + err))
- .then(UI.prime);
- export default UI;
|