summaryrefslogtreecommitdiffstats
path: root/webclients/novnc/app/ui.js
diff options
context:
space:
mode:
Diffstat (limited to 'webclients/novnc/app/ui.js')
-rw-r--r--webclients/novnc/app/ui.js1669
1 files changed, 1669 insertions, 0 deletions
diff --git a/webclients/novnc/app/ui.js b/webclients/novnc/app/ui.js
new file mode 100644
index 0000000..2218d24
--- /dev/null
+++ b/webclients/novnc/app/ui.js
@@ -0,0 +1,1669 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2012 Joel Martin
+ * Copyright (C) 2016 Samuel Mannehed for Cendio AB
+ * Copyright (C) 2016 Pierre Ossman for Cendio AB
+ * 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 } 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 Display from "../core/display.js";
+import * as WebUtil from "./webutil.js";
+
+var UI = {
+
+ connected: false,
+ desktopName: "",
+
+ statusTimeout: null,
+ hideKeyboardTimeout: null,
+ idleControlbarTimeout: null,
+ closeControlbarTimeout: null,
+
+ controlbarGrabbed: false,
+ controlbarDrag: false,
+ controlbarMouseDownClientY: 0,
+ controlbarMouseDownOffsetY: 0,
+
+ isSafari: false,
+ lastKeyboardinput: null,
+ defaultKeyboardinputLen: 100,
+
+ inhibit_reconnect: true,
+ reconnect_callback: null,
+ reconnect_password: null,
+
+ prime: function(callback) {
+ if (document.readyState === "interactive" || document.readyState === "complete") {
+ UI.load(callback);
+ } else {
+ document.addEventListener('DOMContentLoaded', UI.load.bind(UI, callback));
+ }
+ },
+
+ // Setup rfb object, load settings from browser storage, then call
+ // UI.init to setup the UI/menus
+ load: function(callback) {
+ WebUtil.initSettings(UI.start, callback);
+ },
+
+ // Render default UI and initialize settings menu
+ start: function(callback) {
+
+ // Setup global variables first
+ UI.isSafari = (navigator.userAgent.indexOf('Safari') !== -1 &&
+ navigator.userAgent.indexOf('Chrome') === -1);
+
+ UI.initSettings();
+
+ // Translate the DOM
+ l10n.translateDOM();
+
+ // Adapt the interface for touch screen devices
+ if (isTouchDevice) {
+ document.documentElement.classList.add("noVNC_touch");
+ // Remove the address bar
+ setTimeout(function() { 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");
+
+ var autoconnect = WebUtil.getConfigVar('autoconnect', false);
+ if (autoconnect === 'true' || autoconnect == '1') {
+ autoconnect = true;
+ UI.connect();
+ } else {
+ autoconnect = false;
+ // Show the connect panel on first load unless autoconnecting
+ UI.openConnectPanel();
+ }
+
+ if (typeof callback === "function") {
+ callback(UI.rfb);
+ }
+ },
+
+ initFullscreen: function() {
+ // Only show the button if fullscreen is properly supported
+ // * Safari doesn't support alphanumerical input while in fullscreen
+ if (!UI.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: function() {
+ var i;
+
+ // Logging selection dropdown
+ var llevels = ['error', 'warn', 'info', 'debug'];
+ for (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
+ var 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('shared', true);
+ UI.initSetting('view_only', 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: function() {
+ var labels = document.getElementsByTagName('LABEL');
+ for (var i = 0; i < labels.length; i++) {
+ var htmlFor = labels[i].htmlFor;
+ if (htmlFor != '') {
+ var elem = document.getElementById(htmlFor);
+ if (elem) elem.label = labels[i];
+ } else {
+ // If 'for' isn't set, use the first input element child
+ var children = labels[i].children;
+ for (var j = 0; j < children.length; j++) {
+ if (children[j].form !== undefined) {
+ children[j].label = labels[i];
+ break;
+ }
+ }
+ }
+ }
+ },
+
+/* ------^-------
+* /INIT
+* ==============
+* EVENT HANDLERS
+* ------v------*/
+
+ addControlbarHandlers: function() {
+ 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);
+
+ var exps = document.getElementsByClassName("noVNC_expander");
+ for (var i = 0;i < exps.length;i++) {
+ exps[i].addEventListener('click', UI.toggleExpander);
+ }
+ },
+
+ addTouchSpecificHandlers: function() {
+ document.getElementById("noVNC_mouse_button0")
+ .addEventListener('click', function () { UI.setMouseButton(1); });
+ document.getElementById("noVNC_mouse_button1")
+ .addEventListener('click', function () { UI.setMouseButton(2); });
+ document.getElementById("noVNC_mouse_button2")
+ .addEventListener('click', function () { UI.setMouseButton(4); });
+ document.getElementById("noVNC_mouse_button4")
+ .addEventListener('click', function () { UI.setMouseButton(0); });
+ document.getElementById("noVNC_keyboard_button")
+ .addEventListener('click', UI.toggleVirtualKeyboard);
+
+ UI.touchKeyboard = new Keyboard(document.getElementById('noVNC_keyboardinput'));
+ UI.touchKeyboard.onkeyevent = UI.keyEvent;
+ UI.touchKeyboard.grab();
+ document.getElementById("noVNC_keyboardinput")
+ .addEventListener('input', UI.keyInput);
+ document.getElementById("noVNC_keyboardinput")
+ .addEventListener('focus', UI.onfocusVirtualKeyboard);
+ document.getElementById("noVNC_keyboardinput")
+ .addEventListener('blur', UI.onblurVirtualKeyboard);
+ document.getElementById("noVNC_keyboardinput")
+ .addEventListener('submit', function () { return 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: function() {
+ 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_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: function() {
+ document.getElementById("noVNC_shutdown_button")
+ .addEventListener('click', function() { UI.rfb.machineShutdown(); });
+ document.getElementById("noVNC_reboot_button")
+ .addEventListener('click', function() { UI.rfb.machineReboot(); });
+ document.getElementById("noVNC_reset_button")
+ .addEventListener('click', function() { UI.rfb.machineReset(); });
+ document.getElementById("noVNC_power_button")
+ .addEventListener('click', UI.togglePowerPanel);
+ },
+
+ addConnectionControlHandlers: function() {
+ document.getElementById("noVNC_disconnect_button")
+ .addEventListener('click', UI.disconnect);
+ document.getElementById("noVNC_connect_button")
+ .addEventListener('click', UI.connect);
+ document.getElementById("noVNC_cancel_reconnect_button")
+ .addEventListener('click', UI.cancelReconnect);
+
+ document.getElementById("noVNC_password_button")
+ .addEventListener('click', UI.setPassword);
+ },
+
+ addClipboardHandlers: function() {
+ 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: function(name, changeFunc) {
+ var settingElem = document.getElementById("noVNC_setting_" + name);
+ if (changeFunc === undefined) {
+ changeFunc = function () { UI.saveSetting(name); };
+ }
+ settingElem.addEventListener('change', changeFunc);
+ },
+
+ addSettingsHandlers: function() {
+ document.getElementById("noVNC_settings_button")
+ .addEventListener('click', UI.toggleSettingsPanel);
+
+ UI.addSettingChangeHandler('encrypt');
+ UI.addSettingChangeHandler('resize');
+ UI.addSettingChangeHandler('resize', UI.enableDisableViewClip);
+ UI.addSettingChangeHandler('resize', UI.applyResizeMode);
+ 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('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: function() {
+ 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: function(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");
+
+ let transition_elem = document.getElementById("noVNC_transition_text");
+ switch (state) {
+ case 'init':
+ break;
+ case 'connecting':
+ transition_elem.textContent = _("Connecting...");
+ document.documentElement.classList.add("noVNC_connecting");
+ break;
+ case 'connected':
+ document.documentElement.classList.add("noVNC_connected");
+ break;
+ case 'disconnecting':
+ transition_elem.textContent = _("Disconnecting...");
+ document.documentElement.classList.add("noVNC_disconnecting");
+ break;
+ case 'disconnected':
+ break;
+ case 'reconnecting':
+ transition_elem.textContent = _("Reconnecting...");
+ document.documentElement.classList.add("noVNC_reconnecting");
+ break;
+ default:
+ Log.Error("Invalid visual state: " + state);
+ UI.showStatus(_("Internal error"), 'error');
+ return;
+ }
+
+ UI.enableDisableViewClip();
+
+ if (UI.connected) {
+ UI.disableSetting('encrypt');
+ UI.disableSetting('shared');
+ UI.disableSetting('host');
+ UI.disableSetting('port');
+ UI.disableSetting('path');
+ UI.disableSetting('repeaterID');
+ UI.setMouseButton(1);
+
+ // Hide the controlbar after 2 seconds
+ UI.closeControlbarTimeout = setTimeout(UI.closeControlbar, 2000);
+ } else {
+ UI.enableSetting('encrypt');
+ UI.enableSetting('shared');
+ UI.enableSetting('host');
+ UI.enableSetting('port');
+ UI.enableSetting('path');
+ UI.enableSetting('repeaterID');
+ UI.updatePowerButton();
+ UI.keepControlbar();
+ }
+
+ // State change disables viewport dragging.
+ // It is enabled (toggled) by direct click on the button
+ UI.setViewDrag(false);
+
+ // State change also closes the password dialog
+ document.getElementById('noVNC_password_dlg')
+ .classList.remove('noVNC_open');
+ },
+
+ showStatus: function(text, status_type, time) {
+ var statusElem = document.getElementById('noVNC_status');
+
+ clearTimeout(UI.statusTimeout);
+
+ if (typeof status_type === 'undefined') {
+ status_type = 'normal';
+ }
+
+ // Don't overwrite more severe visible statuses and never
+ // errors. Only shows the first error.
+ let visible_status_type = 'none';
+ if (statusElem.classList.contains("noVNC_open")) {
+ if (statusElem.classList.contains("noVNC_status_error")) {
+ visible_status_type = 'error';
+ } else if (statusElem.classList.contains("noVNC_status_warn")) {
+ visible_status_type = 'warn';
+ } else {
+ visible_status_type = 'normal';
+ }
+ }
+ if (visible_status_type === 'error' ||
+ (visible_status_type === 'warn' && status_type === 'normal')) {
+ return;
+ }
+
+ switch (status_type) {
+ case 'error':
+ statusElem.classList.remove("noVNC_status_warn");
+ statusElem.classList.remove("noVNC_status_normal");
+ statusElem.classList.add("noVNC_status_error");
+ break;
+ case 'warning':
+ case 'warn':
+ statusElem.classList.remove("noVNC_status_error");
+ statusElem.classList.remove("noVNC_status_normal");
+ statusElem.classList.add("noVNC_status_warn");
+ break;
+ case 'normal':
+ case 'info':
+ default:
+ statusElem.classList.remove("noVNC_status_error");
+ statusElem.classList.remove("noVNC_status_warn");
+ statusElem.classList.add("noVNC_status_normal");
+ break;
+ }
+
+ statusElem.textContent = text;
+ statusElem.classList.add("noVNC_open");
+
+ // If no time was specified, show the status for 1.5 seconds
+ if (typeof time === 'undefined') {
+ time = 1500;
+ }
+
+ // Error messages do not timeout
+ if (status_type !== 'error') {
+ UI.statusTimeout = window.setTimeout(UI.hideStatus, time);
+ }
+ },
+
+ hideStatus: function() {
+ clearTimeout(UI.statusTimeout);
+ document.getElementById('noVNC_status').classList.remove("noVNC_open");
+ },
+
+ activateControlbar: function(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: function() {
+ document.getElementById('noVNC_control_bar_anchor')
+ .classList.add("noVNC_idle");
+ },
+
+ keepControlbar: function() {
+ clearTimeout(UI.closeControlbarTimeout);
+ },
+
+ openControlbar: function() {
+ document.getElementById('noVNC_control_bar')
+ .classList.add("noVNC_open");
+ },
+
+ closeControlbar: function() {
+ UI.closeAllPanels();
+ document.getElementById('noVNC_control_bar')
+ .classList.remove("noVNC_open");
+ },
+
+ toggleControlbar: function() {
+ if (document.getElementById('noVNC_control_bar')
+ .classList.contains("noVNC_open")) {
+ UI.closeControlbar();
+ } else {
+ UI.openControlbar();
+ }
+ },
+
+ toggleControlbarSide: function () {
+ // Temporarily disable animation to avoid weird movement
+ var bar = document.getElementById('noVNC_control_bar');
+ bar.style.transitionDuration = '0s';
+ bar.addEventListener('transitionend', function () { this.style.transitionDuration = ""; });
+
+ var 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: function (show) {
+ var hint = document.getElementById('noVNC_control_bar_hint');
+ if (show) {
+ hint.classList.add("noVNC_active");
+ } else {
+ hint.classList.remove("noVNC_active");
+ }
+ },
+
+ dragControlbarHandle: function (e) {
+ if (!UI.controlbarGrabbed) return;
+
+ var ptr = getPointerEvent(e);
+
+ var 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) {
+ // The goal is to trigger on a certain physical width, the
+ // devicePixelRatio brings us a bit closer but is not optimal.
+ var dragThreshold = 10 * (window.devicePixelRatio || 1);
+ var dragDistance = Math.abs(ptr.clientY - UI.controlbarMouseDownClientY);
+
+ if (dragDistance < dragThreshold) return;
+
+ UI.controlbarDrag = true;
+ }
+
+ var 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: function (viewportRelativeY) {
+ var handle = document.getElementById("noVNC_control_bar_handle");
+ var handleHeight = handle.getBoundingClientRect().height;
+ var controlbarBounds = document.getElementById("noVNC_control_bar")
+ .getBoundingClientRect();
+ var margin = 10;
+
+ // These heights need to be non-zero for the below logic to work
+ if (handleHeight === 0 || controlbarBounds.height === 0) {
+ return;
+ }
+
+ var 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
+ var parentRelativeY = newY - controlbarBounds.top;
+ handle.style.transform = "translateY(" + parentRelativeY + "px)";
+ },
+
+ updateControlbarHandle: function () {
+ // Since the control bar is fixed on the viewport and not the page,
+ // the move function expects coordinates relative the the viewport.
+ var handle = document.getElementById("noVNC_control_bar_handle");
+ var handleBounds = handle.getBoundingClientRect();
+ UI.moveControlbarHandle(handleBounds.top);
+ },
+
+ controlbarHandleMouseUp: function(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: function(e) {
+ if ((e.type == "mousedown") && (e.button != 0)) return;
+
+ var ptr = getPointerEvent(e);
+
+ var handle = document.getElementById("noVNC_control_bar_handle");
+ var 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: function(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: function(name, defVal) {
+ // Check Query string followed by cookie
+ var val = WebUtil.getConfigVar(name);
+ if (val === null) {
+ val = WebUtil.readSetting(name, defVal);
+ }
+ UI.updateSetting(name, val);
+ return val;
+ },
+
+ // Update cookie and form control setting. If value is not set, then
+ // updates from control to current cookie setting.
+ updateSetting: function(name, value) {
+
+ // Save the cookie for this session
+ if (typeof value !== 'undefined') {
+ WebUtil.writeSetting(name, value);
+ }
+
+ // Update the settings control
+ value = UI.getSetting(name);
+
+ var ctrl = document.getElementById('noVNC_setting_' + name);
+ if (ctrl.type === 'checkbox') {
+ ctrl.checked = value;
+
+ } else if (typeof ctrl.options !== 'undefined') {
+ for (var i = 0; i < ctrl.options.length; i += 1) {
+ if (ctrl.options[i].value === value) {
+ ctrl.selectedIndex = i;
+ break;
+ }
+ }
+ } else {
+ /*Weird IE9 error leads to 'null' appearring
+ in textboxes instead of ''.*/
+ if (value === null) {
+ value = "";
+ }
+ ctrl.value = value;
+ }
+ },
+
+ // Save control setting to cookie
+ saveSetting: function(name) {
+ var val, ctrl = document.getElementById('noVNC_setting_' + name);
+ 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: function(name) {
+ var ctrl = document.getElementById('noVNC_setting_' + name);
+ var 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: function(name) {
+ var ctrl = document.getElementById('noVNC_setting_' + name);
+ ctrl.disabled = true;
+ ctrl.label.classList.add('noVNC_disabled');
+ },
+
+ enableSetting: function(name) {
+ var ctrl = document.getElementById('noVNC_setting_' + name);
+ ctrl.disabled = false;
+ ctrl.label.classList.remove('noVNC_disabled');
+ },
+
+/* ------^-------
+ * /SETTINGS
+ * ==============
+ * PANELS
+ * ------v------*/
+
+ closeAllPanels: function() {
+ UI.closeSettingsPanel();
+ UI.closePowerPanel();
+ UI.closeClipboardPanel();
+ UI.closeExtraKeys();
+ },
+
+/* ------^-------
+ * /PANELS
+ * ==============
+ * SETTINGS (panel)
+ * ------v------*/
+
+ openSettingsPanel: function() {
+ UI.closeAllPanels();
+ UI.openControlbar();
+
+ // Refresh UI elements from saved cookies
+ UI.updateSetting('encrypt');
+ UI.updateSetting('view_clip');
+ UI.updateSetting('resize');
+ UI.updateSetting('shared');
+ UI.updateSetting('view_only');
+ UI.updateSetting('path');
+ UI.updateSetting('repeaterID');
+ UI.updateSetting('logging');
+ UI.updateSetting('reconnect');
+ UI.updateSetting('reconnect_delay');
+
+ document.getElementById('noVNC_settings')
+ .classList.add("noVNC_open");
+ document.getElementById('noVNC_settings_button')
+ .classList.add("noVNC_selected");
+ },
+
+ closeSettingsPanel: function() {
+ document.getElementById('noVNC_settings')
+ .classList.remove("noVNC_open");
+ document.getElementById('noVNC_settings_button')
+ .classList.remove("noVNC_selected");
+ },
+
+ toggleSettingsPanel: function() {
+ if (document.getElementById('noVNC_settings')
+ .classList.contains("noVNC_open")) {
+ UI.closeSettingsPanel();
+ } else {
+ UI.openSettingsPanel();
+ }
+ },
+
+/* ------^-------
+ * /SETTINGS
+ * ==============
+ * POWER
+ * ------v------*/
+
+ openPowerPanel: function() {
+ UI.closeAllPanels();
+ UI.openControlbar();
+
+ document.getElementById('noVNC_power')
+ .classList.add("noVNC_open");
+ document.getElementById('noVNC_power_button')
+ .classList.add("noVNC_selected");
+ },
+
+ closePowerPanel: function() {
+ document.getElementById('noVNC_power')
+ .classList.remove("noVNC_open");
+ document.getElementById('noVNC_power_button')
+ .classList.remove("noVNC_selected");
+ },
+
+ togglePowerPanel: function() {
+ if (document.getElementById('noVNC_power')
+ .classList.contains("noVNC_open")) {
+ UI.closePowerPanel();
+ } else {
+ UI.openPowerPanel();
+ }
+ },
+
+ // Disable/enable power button
+ updatePowerButton: function() {
+ 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: function() {
+ UI.closeAllPanels();
+ UI.openControlbar();
+
+ document.getElementById('noVNC_clipboard')
+ .classList.add("noVNC_open");
+ document.getElementById('noVNC_clipboard_button')
+ .classList.add("noVNC_selected");
+ },
+
+ closeClipboardPanel: function() {
+ document.getElementById('noVNC_clipboard')
+ .classList.remove("noVNC_open");
+ document.getElementById('noVNC_clipboard_button')
+ .classList.remove("noVNC_selected");
+ },
+
+ toggleClipboardPanel: function() {
+ if (document.getElementById('noVNC_clipboard')
+ .classList.contains("noVNC_open")) {
+ UI.closeClipboardPanel();
+ } else {
+ UI.openClipboardPanel();
+ }
+ },
+
+ clipboardReceive: function(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: function() {
+ document.getElementById('noVNC_clipboard_text').value = "";
+ UI.rfb.clipboardPasteFrom("");
+ },
+
+ clipboardSend: function() {
+ var 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: function() {
+ document.getElementById('noVNC_connect_dlg')
+ .classList.add("noVNC_open");
+ },
+
+ closeConnectPanel: function() {
+ document.getElementById('noVNC_connect_dlg')
+ .classList.remove("noVNC_open");
+ },
+
+ connect: function(event, password) {
+
+ // Ignore when rfb already exists
+ if (typeof UI.rfb !== 'undefined') {
+ return;
+ }
+
+ var host = UI.getSetting('host');
+ var port = UI.getSetting('port');
+ var path = UI.getSetting('path');
+
+ if (typeof password === 'undefined') {
+ password = WebUtil.getConfigVar('password');
+ UI.reconnect_password = password;
+ }
+
+ if (password === null) {
+ password = undefined;
+ }
+
+ UI.hideStatus();
+
+ if (!host) {
+ Log.Error("Can't connect when host is: " + host);
+ UI.showStatus(_("Must set host"), 'error');
+ return;
+ }
+
+ UI.closeAllPanels();
+ UI.closeConnectPanel();
+
+ UI.updateVisualState('connecting');
+
+ var 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", function () { 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.updateViewOnly(); // requires UI.rfb
+ },
+
+ disconnect: function() {
+ UI.closeAllPanels();
+ UI.rfb.disconnect();
+
+ UI.connected = false;
+
+ // Disable automatic reconnecting
+ UI.inhibit_reconnect = true;
+
+ UI.updateVisualState('disconnecting');
+
+ // Don't display the connection settings until we're actually disconnected
+ },
+
+ reconnect: function() {
+ UI.reconnect_callback = null;
+
+ // if reconnect has been disabled in the meantime, do nothing.
+ if (UI.inhibit_reconnect) {
+ return;
+ }
+
+ UI.connect(null, UI.reconnect_password);
+ },
+
+ cancelReconnect: function() {
+ if (UI.reconnect_callback !== null) {
+ clearTimeout(UI.reconnect_callback);
+ UI.reconnect_callback = null;
+ }
+
+ UI.updateVisualState('disconnected');
+
+ UI.openControlbar();
+ UI.openConnectPanel();
+ },
+
+ connectFinished: function (e) {
+ UI.connected = true;
+ UI.inhibit_reconnect = false;
+
+ let msg;
+ if (UI.getSetting('encrypt')) {
+ msg = _("Connected (encrypted) to ") + UI.desktopName;
+ } else {
+ msg = _("Connected (unencrypted) to ") + UI.desktopName;
+ }
+ UI.showStatus(msg);
+ UI.updateVisualState('connected');
+
+ // Do this last because it can only be used on rendered elements
+ UI.rfb.focus();
+ },
+
+ disconnectFinished: function (e) {
+ let wasConnected = UI.connected;
+
+ // This variable is ideally set when disconnection starts, but
+ // when the disconnection isn't clean or if it is initiated by
+ // the server, we need to do it here as well since
+ // UI.disconnect() won't be used in those cases.
+ UI.connected = false;
+
+ UI.rfb = undefined;
+
+ if (!e.detail.clean) {
+ UI.updateVisualState('disconnected');
+ if (wasConnected) {
+ UI.showStatus(_("Something went wrong, connection is closed"),
+ 'error');
+ } else {
+ UI.showStatus(_("Failed to connect to server"), 'error');
+ }
+ } else if (UI.getSetting('reconnect', false) === true && !UI.inhibit_reconnect) {
+ UI.updateVisualState('reconnecting');
+
+ var delay = parseInt(UI.getSetting('reconnect_delay'));
+ UI.reconnect_callback = setTimeout(UI.reconnect, delay);
+ return;
+ } else {
+ UI.updateVisualState('disconnected');
+ UI.showStatus(_("Disconnected"), 'normal');
+ }
+
+ UI.openControlbar();
+ UI.openConnectPanel();
+ },
+
+ securityFailed: function (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: function(e) {
+ // FIXME: handle more types
+ document.getElementById('noVNC_password_dlg')
+ .classList.add('noVNC_open');
+
+ setTimeout(function () {
+ document.getElementById('noVNC_password_input').focus();
+ }, 100);
+
+ Log.Warn("Server asked for a password");
+ UI.showStatus(_("Password is required"), "warning");
+ },
+
+ setPassword: function(e) {
+ // Prevent actually submitting the form
+ e.preventDefault();
+
+ var inputElem = document.getElementById('noVNC_password_input');
+ var password = inputElem.value;
+ // Clear the input after reading the password
+ inputElem.value = "";
+ UI.rfb.sendCredentials({ password: password });
+ UI.reconnect_password = password;
+ document.getElementById('noVNC_password_dlg')
+ .classList.remove('noVNC_open');
+ },
+
+/* ------^-------
+ * /PASSWORD
+ * ==============
+ * FULLSCREEN
+ * ------v------*/
+
+ toggleFullscreen: function() {
+ 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.enableDisableViewClip();
+ UI.updateFullscreenButton();
+ },
+
+ updateFullscreenButton: function() {
+ 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: function() {
+ if (!UI.rfb) return;
+
+ UI.rfb.scaleViewport = UI.getSetting('resize') === 'scale';
+ UI.rfb.resizeSession = UI.getSetting('resize') === 'remote';
+ },
+
+/* ------^-------
+ * /RESIZE
+ * ==============
+ * VIEW CLIPPING
+ * ------v------*/
+
+ // Update parameters that depend on the viewport clip setting
+ updateViewClip: function() {
+ if (!UI.rfb) return;
+
+ var cur_clip = UI.rfb.clipViewport;
+ var new_clip = UI.getSetting('view_clip');
+
+ if (isTouchDevice) {
+ // Touch devices usually have shit scrollbars
+ new_clip = true;
+ }
+
+ if (cur_clip !== new_clip) {
+ UI.rfb.clipViewport = new_clip;
+ }
+
+ // Changing the viewport may change the state of
+ // the dragging button
+ UI.updateViewDrag();
+ },
+
+ // Handle special cases where viewport clipping is forced on/off or locked
+ enableDisableViewClip: function() {
+ var resizeSetting = UI.getSetting('resize');
+ // Disable clipping if we are scaling, connected or on touch
+ if (resizeSetting === 'scale' ||
+ isTouchDevice) {
+ UI.disableSetting('view_clip');
+ } else {
+ UI.enableSetting('view_clip');
+ }
+ },
+
+/* ------^-------
+ * /VIEW CLIPPING
+ * ==============
+ * VIEWDRAG
+ * ------v------*/
+
+ toggleViewDrag: function() {
+ if (!UI.rfb) return;
+
+ var drag = UI.rfb.dragViewport;
+ UI.setViewDrag(!drag);
+ },
+
+ // Set the view drag mode which moves the viewport on mouse drags
+ setViewDrag: function(drag) {
+ if (!UI.rfb) return;
+
+ UI.rfb.dragViewport = drag;
+
+ UI.updateViewDrag();
+ },
+
+ updateViewDrag: function() {
+ if (!UI.connected) return;
+
+ var 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");
+ }
+
+ // Different behaviour for touch vs non-touch
+ // The button is disabled instead of hidden on touch devices
+ if (isTouchDevice) {
+ viewDragButton.classList.remove("noVNC_hidden");
+
+ if (UI.rfb.clipViewport) {
+ viewDragButton.disabled = false;
+ } else {
+ viewDragButton.disabled = true;
+ }
+ } else {
+ viewDragButton.disabled = false;
+
+ if (UI.rfb.clipViewport) {
+ viewDragButton.classList.remove("noVNC_hidden");
+ } else {
+ viewDragButton.classList.add("noVNC_hidden");
+ }
+ }
+ },
+
+/* ------^-------
+ * /VIEWDRAG
+ * ==============
+ * KEYBOARD
+ * ------v------*/
+
+ showVirtualKeyboard: function() {
+ if (!isTouchDevice) return;
+
+ var input = document.getElementById('noVNC_keyboardinput');
+
+ if (document.activeElement == input) return;
+
+ input.focus();
+
+ try {
+ var l = input.value.length;
+ // Move the caret to the end
+ input.setSelectionRange(l, l);
+ } catch (err) {} // setSelectionRange is undefined in Google Chrome
+ },
+
+ hideVirtualKeyboard: function() {
+ if (!isTouchDevice) return;
+
+ var input = document.getElementById('noVNC_keyboardinput');
+
+ if (document.activeElement != input) return;
+
+ input.blur();
+ },
+
+ toggleVirtualKeyboard: function () {
+ if (document.getElementById('noVNC_keyboard_button')
+ .classList.contains("noVNC_selected")) {
+ UI.hideVirtualKeyboard();
+ } else {
+ UI.showVirtualKeyboard();
+ }
+ },
+
+ onfocusVirtualKeyboard: function(event) {
+ document.getElementById('noVNC_keyboard_button')
+ .classList.add("noVNC_selected");
+ if (UI.rfb) {
+ UI.rfb.focusOnClick = false;
+ }
+ },
+
+ onblurVirtualKeyboard: function(event) {
+ document.getElementById('noVNC_keyboard_button')
+ .classList.remove("noVNC_selected");
+ if (UI.rfb) {
+ UI.rfb.focusOnClick = true;
+ }
+ },
+
+ keepVirtualKeyboard: function(event) {
+ var 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: function() {
+ var kbi = document.getElementById('noVNC_keyboardinput');
+ kbi.value = new Array(UI.defaultKeyboardinputLen).join("_");
+ UI.lastKeyboardinput = kbi.value;
+ },
+
+ keyEvent: function (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: function(event) {
+
+ if (!UI.rfb) return;
+
+ var newValue = event.target.value;
+
+ if (!UI.lastKeyboardinput) {
+ UI.keyboardinputReset();
+ }
+ var oldValue = UI.lastKeyboardinput;
+
+ var 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;
+ }
+ var oldLen = oldValue.length;
+
+ var backspaces;
+ var inputs = newLen - oldLen;
+ if (inputs < 0) {
+ backspaces = -inputs;
+ } else {
+ backspaces = 0;
+ }
+
+ // Compare the old string with the new to account for
+ // text-corrections or other input that modify existing text
+ var i;
+ for (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 (i = 0; i < backspaces; i++) {
+ UI.rfb.sendKey(KeyTable.XK_BackSpace, "Backspace");
+ }
+ for (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: function() {
+ UI.closeAllPanels();
+ UI.openControlbar();
+
+ document.getElementById('noVNC_modifiers')
+ .classList.add("noVNC_open");
+ document.getElementById('noVNC_toggle_extra_keys_button')
+ .classList.add("noVNC_selected");
+ },
+
+ closeExtraKeys: function() {
+ document.getElementById('noVNC_modifiers')
+ .classList.remove("noVNC_open");
+ document.getElementById('noVNC_toggle_extra_keys_button')
+ .classList.remove("noVNC_selected");
+ },
+
+ toggleExtraKeys: function() {
+ if(document.getElementById('noVNC_modifiers')
+ .classList.contains("noVNC_open")) {
+ UI.closeExtraKeys();
+ } else {
+ UI.openExtraKeys();
+ }
+ },
+
+ sendEsc: function() {
+ UI.rfb.sendKey(KeyTable.XK_Escape, "Escape");
+ },
+
+ sendTab: function() {
+ UI.rfb.sendKey(KeyTable.XK_Tab);
+ },
+
+ toggleCtrl: function() {
+ var btn = document.getElementById('noVNC_toggle_ctrl_button');
+ if (btn.classList.contains("noVNC_selected")) {
+ UI.rfb.sendKey(KeyTable.XK_Control_L, "ControlLeft", false);
+ btn.classList.remove("noVNC_selected");
+ } else {
+ UI.rfb.sendKey(KeyTable.XK_Control_L, "ControlLeft", true);
+ btn.classList.add("noVNC_selected");
+ }
+ },
+
+ toggleAlt: function() {
+ var btn = document.getElementById('noVNC_toggle_alt_button');
+ if (btn.classList.contains("noVNC_selected")) {
+ UI.rfb.sendKey(KeyTable.XK_Alt_L, "AltLeft", false);
+ btn.classList.remove("noVNC_selected");
+ } else {
+ UI.rfb.sendKey(KeyTable.XK_Alt_L, "AltLeft", true);
+ btn.classList.add("noVNC_selected");
+ }
+ },
+
+ sendCtrlAltDel: function() {
+ UI.rfb.sendCtrlAltDel();
+ },
+
+/* ------^-------
+ * /EXTRA KEYS
+ * ==============
+ * MISC
+ * ------v------*/
+
+ setMouseButton: function(num) {
+ var view_only = UI.rfb.viewOnly;
+ if (UI.rfb && !view_only) {
+ UI.rfb.touchButton = num;
+ }
+
+ var blist = [0, 1,2,4];
+ for (var b = 0; b < blist.length; b++) {
+ var button = document.getElementById('noVNC_mouse_button' +
+ blist[b]);
+ if (blist[b] === num && !view_only) {
+ button.classList.remove("noVNC_hidden");
+ } else {
+ button.classList.add("noVNC_hidden");
+ }
+ }
+ },
+
+ updateViewOnly: function() {
+ 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');
+ } else {
+ document.getElementById('noVNC_keyboard_button')
+ .classList.remove('noVNC_hidden');
+ document.getElementById('noVNC_toggle_extra_keys_button')
+ .classList.remove('noVNC_hidden');
+ }
+ UI.setMouseButton(1); //has it's own logic for hiding/showing
+ },
+
+ updateLogging: function() {
+ WebUtil.init_logging(UI.getSetting('logging'));
+ },
+
+ updateDesktopName: function(e) {
+ UI.desktopName = e.detail.name;
+ // Display the desktop name in the document title
+ document.title = e.detail.name + " - noVNC";
+ },
+
+ bell: function(e) {
+ if (WebUtil.getConfigVar('bell', 'on') === 'on') {
+ var promise = document.getElementById('noVNC_bell').play();
+ // The standards disagree on the return value here
+ if (promise) {
+ promise.catch(function(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: function(selectbox, text, value) {
+ var optn = document.createElement("OPTION");
+ optn.text = text;
+ optn.value = value;
+ selectbox.options.add(optn);
+ },
+
+/* ------^-------
+ * /MISC
+ * ==============
+ */
+};
+
+// Set up translations
+var LINGUAS = ["de", "el", "es", "nl", "pl", "sv", "tr", "zh"];
+l10n.setup(LINGUAS);
+if (l10n.language !== "en" && l10n.dictionary === undefined) {
+ WebUtil.fetchJSON('app/locale/' + l10n.language + '.json', function (translations) {
+ l10n.dictionary = translations;
+
+ // wait for translations to load before loading the UI
+ UI.prime();
+ }, function (err) {
+ Log.Error("Failed to load translations: " + err);
+ UI.prime();
+ });
+} else {
+ UI.prime();
+}
+
+export default UI;