| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715 |
- /*
- * 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, 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";
- 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();
- 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) {
- 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();
- }
- 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_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);
- 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);
- 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_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;
- },
- 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 {
- 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");
- },
- 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");
- },
- /* ------^-------
- * /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');
- 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("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');
- }
- } else 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
- * ==============
- * 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';
- if (scaling) {
- // Can't be clipping if viewport is scaled to fit
- UI.forceSetting('view_clip', false);
- UI.rfb.clipViewport = false;
- } else if (!hasScrollbarGutter) {
- // Some platforms have scrollbars that are difficult
- // to use in our case, so we always use our own panning
- 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
- * ==============
- * 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", "ja", "ko", "nl", "pl", "pt_BR", "ru", "sv", "tr", "zh_CN", "zh_TW"];
- l10n.setup(LINGUAS);
- if (l10n.language === "en" || l10n.dictionary !== undefined) {
- UI.prime();
- } else {
- fetch('app/locale/' + l10n.language + '.json')
- .then((response) => {
- if (!response.ok) {
- throw Error("" + response.status + " " + response.statusText);
- }
- return response.json();
- })
- .then((translations) => { l10n.dictionary = translations; })
- .catch(err => Log.Error("Failed to load translations: " + err))
- .then(UI.prime);
- }
- export default UI;
|