/*
* noVNC: HTML5 VNC client
- * Copyright (C) 2018 The noVNC Authors
+ * 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, isIOS, isAndroid, dragThreshold }
+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 RFB from "../core/rfb.js";
import * as WebUtil from "./webutil.js";
+const PAGE_TITLE = "noVNC";
+
const UI = {
connected: false,
// Translate the DOM
l10n.translateDOM();
+ WebUtil.fetchJSON('./package.json')
+ .then((packageInfo) => {
+ Array.from(document.getElementsByClassName('noVNC_version')).forEach(el => el.innerText = packageInfo.version);
+ })
+ .catch((err) => {
+ Log.Error("Couldn't fetch package.json: " + err);
+ Array.from(document.getElementsByClassName('noVNC_version_wrapper'))
+ .concat(Array.from(document.getElementsByClassName('noVNC_version_separator')))
+ .forEach(el => el.style.display = 'none');
+ });
+
// Adapt the interface for touch screen devices
if (isTouchDevice) {
document.documentElement.classList.add("noVNC_touch");
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);
document.getElementById("noVNC_cancel_reconnect_button")
.addEventListener('click', UI.cancelReconnect);
- document.getElementById("noVNC_password_button")
- .addEventListener('click', UI.setPassword);
+ document.getElementById("noVNC_credentials_button")
+ .addEventListener('click', UI.setCredentials);
},
addClipboardHandlers() {
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');
}
// State change closes the password dialog
- document.getElementById('noVNC_password_dlg')
+ document.getElementById('noVNC_credentials_dlg')
.classList.remove('noVNC_open');
},
showStatus(text, status_type, time) {
const statusElem = document.getElementById('noVNC_status');
- clearTimeout(UI.statusTimeout);
-
if (typeof status_type === 'undefined') {
status_type = 'normal';
}
// Don't overwrite more severe visible statuses and never
// errors. Only shows the first error.
- let visible_status_type = 'none';
if (statusElem.classList.contains("noVNC_open")) {
if (statusElem.classList.contains("noVNC_status_error")) {
- visible_status_type = 'error';
- } else if (statusElem.classList.contains("noVNC_status_warn")) {
- visible_status_type = 'warn';
- } else {
- visible_status_type = 'normal';
+ return;
+ }
+ if (statusElem.classList.contains("noVNC_status_warn") &&
+ status_type === 'normal') {
+ return;
}
- }
- if (visible_status_type === 'error' ||
- (visible_status_type === 'warn' && status_type === 'normal')) {
- return;
}
+ clearTimeout(UI.statusTimeout);
+
switch (status_type) {
case 'error':
statusElem.classList.remove("noVNC_status_warn");
},
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");
},
UI.closeAllPanels();
document.getElementById('noVNC_control_bar')
.classList.remove("noVNC_open");
+ UI.rfb.focus();
},
toggleControlbar() {
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.rfb = new RFB(document.getElementById('noVNC_container'), url,
{ shared: UI.getSetting('shared'),
- showDotCursor: UI.getSetting('show_dot'),
repeaterID: UI.getSetting('repeaterID'),
credentials: { password: password } });
UI.rfb.addEventListener("connect", UI.connectFinished);
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
},
UI.showStatus(_("Disconnected"), 'normal');
}
+ document.title = PAGE_TITLE;
+
UI.openControlbar();
UI.openConnectPanel();
},
credentials(e) {
// FIXME: handle more types
- document.getElementById('noVNC_password_dlg')
+
+ 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('noVNC_password_input').focus(), 100);
+ .getElementById(inputFocus).focus(), 100);
- Log.Warn("Server asked for a password");
- UI.showStatus(_("Password is required"), "warning");
+ Log.Warn("Server asked for credentials");
+ UI.showStatus(_("Credentials are required"), "warning");
},
- setPassword(e) {
+ setCredentials(e) {
// Prevent actually submitting the form
e.preventDefault();
- const inputElem = document.getElementById('noVNC_password_input');
- const password = inputElem.value;
+ 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
- inputElem.value = "";
- UI.rfb.sendCredentials({ password: password });
+ inputElemPassword.value = "";
+
+ UI.rfb.sendCredentials({ username: username, password: password });
UI.reconnect_password = password;
- document.getElementById('noVNC_password_dlg')
+ document.getElementById('noVNC_credentials_dlg')
.classList.remove('noVNC_open');
},
* ------v------*/
toggleFullscreen() {
- // Determine the document using fullscreen. This may either be
- // just the "document", but if using the viewer in an iframe, you need
- // to use the parent window's "document" instead.
- let doc = document;
- if (window.self !== window.top) {
- doc = window.parent.document;
- }
- // Check the fullscreen status using the correct document as
- // a reference. The same document is then used to exit fullscreen.
- if (doc.fullscreenElement || // alternative standard method
- doc.mozFullScreenElement || // currently working methods
- doc.webkitFullscreenElement ||
- doc.msFullscreenElement) {
- if (doc.exitFullscreen) {
- doc.exitFullscreen();
- } else if (doc.mozCancelFullScreen) {
- doc.mozCancelFullScreen();
- } else if (doc.webkitExitFullscreen) {
- doc.webkitExitFullscreen();
- } else if (doc.msExitFullscreen) {
- doc.msExitFullscreen();
+ 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 {
- // To activate fullscreen, we need to find the correct document
- // element, which is usually "document.documentElement". But when
- // using the viewer in an iframe, this is actually the iframe
- // element itself in the parent document.
- let doc_el = document.documentElement;
- if (window.self !== window.top) {
- // Seek out the correct iframe from the parent document.
- let iframes = window.parent.document
- .getElementsByTagName('iframe');
- for (let i in iframes) {
- let content_doc = null;
- try {
- content_doc = iframes[i].contentDocument;
- } catch (err) {
- // I may not be permitted to read the contentDocument
- // of the iframe, but then it can't be me, so ignore.
- }
- if (content_doc === document) {
- doc_el = iframes[i];
- // To use .body.msRequestFullscreen in IE, we need to
- // set the document element accordingly.
- // Note that the <iframe> must have the attribute
- // "allowfullscreen" set for IE to allow the feature.
- doc = iframes[i].contentDocument;
- break;
- }
- }
- }
- // Activate fullscreen. All except MS use the document element for
- // this. The behaviour of body.msRequestFullscreen is untested.
- if (doc_el.requestFullscreen) {
- doc_el.requestFullscreen();
- } else if (doc_el.mozRequestFullScreen) {
- doc_el.mozRequestFullScreen();
- } else if (doc_el.webkitRequestFullscreen) {
- doc_el.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
- } else if (doc.body.msRequestFullscreen) {
- doc.body.msRequestFullscreen();
+ 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();
// Can't be clipping if viewport is scaled to fit
UI.forceSetting('view_clip', false);
UI.rfb.clipViewport = false;
- } else if (isIOS() || isAndroid()) {
- // iOS and Android usually have shit scrollbars
+ } 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 {
viewDragButton.classList.remove("noVNC_selected");
}
- // Different behaviour for touch vs non-touch
- // The button is disabled instead of hidden on touch devices
- if (isTouchDevice) {
+ if (UI.rfb.clipViewport) {
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");
- }
+ 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------*/
},
sendEsc() {
- UI.rfb.sendKey(KeyTable.XK_Escape, "Escape");
+ UI.sendKey(KeyTable.XK_Escape, "Escape");
},
sendTab() {
- UI.rfb.sendKey(KeyTable.XK_Tab);
+ UI.sendKey(KeyTable.XK_Tab, "Tab");
},
toggleCtrl() {
const btn = document.getElementById('noVNC_toggle_ctrl_button');
if (btn.classList.contains("noVNC_selected")) {
- UI.rfb.sendKey(KeyTable.XK_Control_L, "ControlLeft", false);
+ UI.sendKey(KeyTable.XK_Control_L, "ControlLeft", false);
btn.classList.remove("noVNC_selected");
} else {
- UI.rfb.sendKey(KeyTable.XK_Control_L, "ControlLeft", true);
+ 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.rfb.sendKey(KeyTable.XK_Super_L, "MetaLeft", false);
+ UI.sendKey(KeyTable.XK_Super_L, "MetaLeft", false);
btn.classList.remove("noVNC_selected");
} else {
- UI.rfb.sendKey(KeyTable.XK_Super_L, "MetaLeft", true);
+ 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.rfb.sendKey(KeyTable.XK_Alt_L, "AltLeft", false);
+ UI.sendKey(KeyTable.XK_Alt_L, "AltLeft", false);
btn.classList.remove("noVNC_selected");
} else {
- UI.rfb.sendKey(KeyTable.XK_Alt_L, "AltLeft", true);
+ 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();
},
/* ------^-------
.classList.add('noVNC_hidden');
document.getElementById('noVNC_mouse_button' + UI.rfb.touchButton)
.classList.add('noVNC_hidden');
+ document.getElementById('noVNC_clipboard_button')
+ .classList.add('noVNC_hidden');
} else {
document.getElementById('noVNC_keyboard_button')
.classList.remove('noVNC_hidden');
.classList.remove('noVNC_hidden');
document.getElementById('noVNC_mouse_button' + UI.rfb.touchButton)
.classList.remove('noVNC_hidden');
+ document.getElementById('noVNC_clipboard_button')
+ .classList.remove('noVNC_hidden');
}
},
updateDesktopName(e) {
UI.desktopName = e.detail.name;
// Display the desktop name in the document title
- document.title = e.detail.name + " - noVNC";
+ document.title = e.detail.name + " - " + PAGE_TITLE;
},
bell(e) {
};
// Set up translations
-const LINGUAS = ["cs", "de", "el", "es", "ko", "nl", "pl", "ru", "sv", "tr", "zh_CN", "zh_TW"];
+const LINGUAS = ["cs", "de", "el", "es", "ja", "ko", "nl", "pl", "ru", "sv", "tr", "zh_CN", "zh_TW"];
l10n.setup(LINGUAS);
if (l10n.language === "en" || l10n.dictionary !== undefined) {
UI.prime();