Merge pull request #1 from kasmtech/Merge_KasmVNC

Merge kasm vnc
This commit is contained in:
Kasm 2021-04-04 14:37:54 -04:00 committed by GitHub
commit 41daa6042c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
45 changed files with 1504 additions and 108 deletions

View File

@ -16,7 +16,7 @@
(function _scope() {
"use strict";
// Fallback for all uncought errors
// Fallback for all uncaught errors
function handleError(event, err) {
try {
const msg = document.getElementById('noVNC_fallback_errormsg');
@ -55,7 +55,7 @@
document.getElementById('noVNC_fallback_error')
.classList.add("noVNC_open");
} catch (exc) {
document.write("noVNC encountered an error.");
document.write("Kasm encountered an error.");
}
// Don't return true since this would prevent the error
// from being printed to the browser console.

BIN
app/images/download.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

@ -1,40 +1,40 @@
ICONS := \
novnc-16x16.png \
novnc-24x24.png \
novnc-32x32.png \
novnc-48x48.png \
novnc-64x64.png
368_kasm_logo_only_16x16.png \
368_kasm_logo_only_24x24.png \
368_kasm_logo_only_32x32.png \
368_kasm_logo_only_48x48.png \
368_kasm_logo_only_64x64.png
ANDROID_LAUNCHER := \
novnc-48x48.png \
novnc-72x72.png \
novnc-96x96.png \
novnc-144x144.png \
novnc-192x192.png
368_kasm_logo_only_48x48.png \
368_kasm_logo_only_-72x72.png \
368_kasm_logo_only_96x96.png \
368_kasm_logo_only_144x144.png \
368_kasm_logo_only_192x192.png
IPHONE_LAUNCHER := \
novnc-60x60.png \
novnc-120x120.png
368_kasm_logo_only_60x60.png \
368_kasm_logo_only_120x120.png
IPAD_LAUNCHER := \
novnc-76x76.png \
novnc-152x152.png
368_kasm_logo_only_76x76.png \
368_kasm_logo_only_152x152.png
ALL_ICONS := $(ICONS) $(ANDROID_LAUNCHER) $(IPHONE_LAUNCHER) $(IPAD_LAUNCHER)
all: $(ALL_ICONS)
novnc-16x16.png: novnc-icon-sm.svg
368_kasm_logo_only_16x16.png: kasm-icon-sm.svg
convert -density 90 \
-background transparent "$<" "$@"
novnc-24x24.png: novnc-icon-sm.svg
368_kasm_logo_only_24x24.png: kasm-icon-sm.svg
convert -density 135 \
-background transparent "$<" "$@"
novnc-32x32.png: novnc-icon-sm.svg
368_kasm_logo_only_32x32.png: kasm-icon-sm.svg
convert -density 180 \
-background transparent "$<" "$@"
novnc-%.png: novnc-icon.svg
368_kasm_logo_only_%.png: kasm-icon.svg
convert -density $$[`echo $* | cut -d x -f 1` * 90 / 48] \
-background transparent "$<" "$@"

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
app/images/refresh.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 B

View File

@ -12,7 +12,7 @@
"New connection has been rejected with reason: ": "Nové připojení bylo odmítnuto s odůvodněním: ",
"New connection has been rejected": "Nové připojení bylo odmítnuto",
"Password is required": "Je vyžadováno heslo",
"noVNC encountered an error:": "noVNC narazilo na chybu:",
"Kasm encountered an error:": "Kasm narazilo na chybu:",
"Hide/Show the control bar": "Skrýt/zobrazit ovládací panel",
"Move/Drag Viewport": "Přesunout/přetáhnout výřez",
"viewport drag": "přesun výřezu",

View File

@ -11,7 +11,7 @@
"New connection has been rejected with reason: ": "Verbindung wurde aus folgendem Grund abgelehnt: ",
"New connection has been rejected": "Verbindung wurde abgelehnt",
"Password is required": "Passwort ist erforderlich",
"noVNC encountered an error:": "Ein Fehler ist aufgetreten:",
"Kasm encountered an error:": "Ein Fehler ist aufgetreten:",
"Hide/Show the control bar": "Kontrollleiste verstecken/anzeigen",
"Move/Drag Viewport": "Ansichtsfenster verschieben/ziehen",
"viewport drag": "Ansichtsfenster ziehen",

View File

@ -11,7 +11,7 @@
"New connection has been rejected with reason: ": "Η νέα σύνδεση απορρίφθηκε διότι: ",
"New connection has been rejected": "Η νέα σύνδεση απορρίφθηκε ",
"Password is required": "Απαιτείται ο κωδικός πρόσβασης",
"noVNC encountered an error:": "το noVNC αντιμετώπισε ένα σφάλμα:",
"Kasm encountered an error:": "το Kasm αντιμετώπισε ένα σφάλμα:",
"Hide/Show the control bar": "Απόκρυψη/Εμφάνιση γραμμής ελέγχου",
"Move/Drag Viewport": "Μετακίνηση/Σύρσιμο Θεατού πεδίου",
"viewport drag": "σύρσιμο θεατού πεδίου",

View File

@ -8,7 +8,7 @@
"Reconnecting...": "Reconectando...",
"Password is required": "Contraseña es obligatoria",
"Disconnect timeout": "Tiempo de desconexión agotado",
"noVNC encountered an error:": "noVNC ha encontrado un error:",
"Kasm encountered an error:": "Kasm ha encontrado un error:",
"Hide/Show the control bar": "Ocultar/Mostrar la barra de control",
"Move/Drag Viewport": "Mover/Arrastrar la ventana",
"viewport drag": "Arrastrar la ventana",

View File

@ -12,7 +12,7 @@
"New connection has been rejected with reason: ": "新規接続は次の理由で拒否されました: ",
"New connection has been rejected": "新規接続は拒否されました",
"Credentials are required": "資格情報が必要です",
"noVNC encountered an error:": "noVNC でエラーが発生しました:",
"Kasm encountered an error:": "Kasm でエラーが発生しました:",
"Hide/Show the control bar": "コントロールバーを隠す/表示する",
"Drag": "ドラッグ",
"Move/Drag Viewport": "ビューポートを移動/ドラッグ",

View File

@ -12,7 +12,7 @@
"New connection has been rejected with reason: ": "새 연결이 다음 이유로 거부되었습니다:",
"New connection has been rejected": "새 연결이 거부되었습니다.",
"Password is required": "비밀번호가 필요합니다.",
"noVNC encountered an error:": "noVNC에 오류가 발생했습니다:",
"Kasm encountered an error:": "Kasm에 오류가 발생했습니다:",
"Hide/Show the control bar": "컨트롤 바 숨기기/보이기",
"Move/Drag Viewport": "움직이기/드래그 뷰포트",
"viewport drag": "뷰포트 드래그",

View File

@ -12,7 +12,7 @@
"New connection has been rejected with reason: ": "Nieuwe verbinding is geweigerd omwille van de volgende reden: ",
"New connection has been rejected": "Nieuwe verbinding is geweigerd",
"Password is required": "Wachtwoord is vereist",
"noVNC encountered an error:": "noVNC heeft een fout bemerkt:",
"Kasm encountered an error:": "Kasm heeft een fout bemerkt:",
"Hide/Show the control bar": "Verberg/Toon de bedieningsbalk",
"Move/Drag Viewport": "Verplaats/Versleep Kijkvenster",
"viewport drag": "kijkvenster slepen",

View File

@ -11,7 +11,7 @@
"New connection has been rejected with reason: ": "Nowe połączenie zostało odrzucone z powodu: ",
"New connection has been rejected": "Nowe połączenie zostało odrzucone",
"Password is required": "Hasło jest wymagane",
"noVNC encountered an error:": "noVNC napotkało błąd:",
"Kasm encountered an error:": "Kasm napotkało błąd:",
"Hide/Show the control bar": "Pokaż/Ukryj pasek ustawień",
"Move/Drag Viewport": "Ruszaj/Przeciągaj Viewport",
"viewport drag": "przeciągnij viewport",

View File

@ -12,7 +12,7 @@
"New connection has been rejected with reason: ": "A nova conexão foi rejeitada pelo motivo: ",
"New connection has been rejected": "A nova conexão foi rejeitada",
"Credentials are required": "Credenciais são obrigatórias",
"noVNC encountered an error:": "O noVNC encontrou um erro:",
"Kasm encountered an error:": "O Kasm encontrou um erro:",
"Hide/Show the control bar": "Esconder/mostrar a barra de controles",
"Drag": "Arrastar",
"Move/Drag Viewport": "Mover/arrastar a janela",

View File

@ -12,7 +12,7 @@
"New connection has been rejected with reason: ": "Подключиться не удалось: ",
"New connection has been rejected": "Подключиться не удалось",
"Password is required": "Требуется пароль",
"noVNC encountered an error:": "Ошибка noVNC: ",
"Kasm encountered an error:": "Ошибка Kasm: ",
"Hide/Show the control bar": "Скрыть/Показать контрольную панель",
"Move/Drag Viewport": "Переместить окно",
"viewport drag": "Переместить окно",

View File

@ -12,7 +12,7 @@
"New connection has been rejected with reason: ": "Ny anslutning har blivit nekad med följande skäl: ",
"New connection has been rejected": "Ny anslutning har blivit nekad",
"Credentials are required": "Användaruppgifter krävs",
"noVNC encountered an error:": "noVNC stötte på ett problem:",
"Kasm encountered an error:": "Kasm stötte på ett problem:",
"Hide/Show the control bar": "Göm/Visa kontrollbaren",
"Drag": "Dra",
"Move/Drag Viewport": "Flytta/Dra Vyn",

View File

@ -11,7 +11,7 @@
"New connection has been rejected with reason: ": "Bağlantı aşağıdaki nedenlerden dolayı reddedildi: ",
"New connection has been rejected": "Bağlantı reddedildi",
"Password is required": "Şifre gerekli",
"noVNC encountered an error:": "Bir hata oluştu:",
"Kasm encountered an error:": "Bir hata oluştu:",
"Hide/Show the control bar": "Denetim masasını Gizle/Göster",
"Move/Drag Viewport": "Görünümü Taşı/Sürükle",
"viewport drag": "Görüntü penceresini sürükle",

View File

@ -12,7 +12,7 @@
"New connection has been rejected with reason: ": "连接被拒绝,原因:",
"New connection has been rejected": "连接被拒绝",
"Password is required": "请提供密码",
"noVNC encountered an error:": "noVNC 遇到一个错误:",
"Kasm encountered an error:": "Kasm 遇到一个错误:",
"Hide/Show the control bar": "显示/隐藏控制栏",
"Move/Drag Viewport": "拖放显示范围",
"viewport drag": "显示范围拖放",

View File

@ -12,7 +12,7 @@
"New connection has been rejected with reason: ": "連線被拒絕,原因:",
"New connection has been rejected": "連線被拒絕",
"Password is required": "請提供密碼",
"noVNC encountered an error:": "noVNC 遇到一個錯誤:",
"Kasm encountered an error:": "Kasm 遇到一個錯誤:",
"Hide/Show the control bar": "顯示/隱藏控制列",
"Move/Drag Viewport": "拖放顯示範圍",
"viewport drag": "顯示範圍拖放",

View File

@ -23,10 +23,7 @@ body {
margin:0;
padding:0;
font-family: Helvetica;
/*Background image with light grey curve.*/
background-color:#494949;
background-repeat:no-repeat;
background-position:right bottom;
background: white url('../images/icons/kasm_logo.png') no-repeat fixed center;
height:100%;
touch-action: none;
}
@ -55,7 +52,7 @@ html {
width: 10px;
height: 10px;
border-radius: 2px;
box-shadow: -60px 10px 0 rgba(255, 255, 255, 0);
box-shadow: -60px 10px 0 white;
animation: noVNC_spinner 1.0s linear infinite;
}
.noVNC_spinner::before {
@ -73,9 +70,9 @@ html {
animation-delay: 0.1s;
}
@keyframes noVNC_spinner {
0% { box-shadow: -60px 10px 0 rgba(255, 255, 255, 0); width: 20px; }
25% { box-shadow: 20px 10px 0 rgba(255, 255, 255, 1); width: 10px; }
50% { box-shadow: 60px 10px 0 rgba(255, 255, 255, 0); width: 10px; }
0% { box-shadow: -60px 10px 0 rgba(0, 135, 200, 0); width: 20px; }
25% { box-shadow: 20px 10px 0 rgba(0, 135, 200, 1); width: 10px; }
50% { box-shadow: 60px 10px 0 rgba(0, 135, 200, 0); width: 10px; }
}
/* ----------------------------------------
@ -290,9 +287,8 @@ select:active {
font-weight: bold;
color: #fff;
border-radius: 10px;
box-shadow: 6px 6px 0px rgba(0, 0, 0, 0.5);
background: rgba(200,55,55,0.8);
background: rgba(33, 130, 177, 0.8);
}
#noVNC_fallback_error.noVNC_open > div {
transform: translateY(0);
@ -329,6 +325,20 @@ select:active {
overflow: auto;
}
/* ----------------------------------------
* Connection Stats
* ----------------------------------------
*/
#noVNC_connection_stats {
top: 0;
left: auto;
right: 0;
position: fixed;
background: #9fa5a2d4;
color: #00ffa2d4;
visibility: hidden;
}
/* ----------------------------------------
* Control Bar
* ----------------------------------------
@ -358,7 +368,7 @@ select:active {
transition: 0.5s ease-in-out;
background-color: rgb(110, 132, 163);
background-color: rgb(80, 89, 101);
border-radius: 0 10px 10px 0;
}
@ -620,6 +630,7 @@ select:active {
#noVNC_control_bar .noVNC_logo {
font-size: 13px;
text-align: center;
}
:root:not(.noVNC_connected) #noVNC_view_drag_button {
@ -806,16 +817,18 @@ select:active {
#noVNC_connect_button {
cursor: pointer;
/*
padding: 10px;
color: white;
background-color: rgb(110, 132, 163);
border-radius: 12px;
box-shadow: 6px 6px 0px rgba(0, 0, 0, 0.5);
*/
text-align: center;
font-size: 20px;
box-shadow: 6px 6px 0px rgba(0, 0, 0, 0.5);
margin-top: 130px;
}
#noVNC_connect_button div {
margin: 2px;
@ -827,6 +840,7 @@ select:active {
/* This avoids it jumping around when :active */
vertical-align: middle;
color: white;
}
#noVNC_connect_button div:active {
border-bottom-width: 1px;
@ -879,14 +893,20 @@ select:active {
bottom: 0;
right: 0;
color: white;
background: rgba(0, 0, 0, 0.5);
color: #0084C2;
background: white url('../images/icons/kasm_logo.png') no-repeat fixed center;
z-index: 50;
/*display: flex;*/
align-items: center;
justify-content: center;
flex-direction: column;
-webkit-transition: opacity 1s ease-in-out;
-moz-transition: opacity 1s ease-in-out;
-ms-transition: opacity 1s ease-in-out;
-o-transition: opacity 1s ease-in-out;
opacity: 1;
}
:root.noVNC_loading #noVNC_transition,
:root.noVNC_connecting #noVNC_transition,
@ -899,13 +919,14 @@ select:active {
}
#noVNC_transition_text {
font-size: 1.5em;
margin-top: 125px;
}
/* Main container */
#noVNC_container {
width: 100%;
height: 100%;
background-color: #313131;
background-color: rgb(74, 144, 217, 0.5);
border-bottom-right-radius: 800px 600px;
/*border-top-left-radius: 800px 600px;*/
}

6
app/styles/bootstrap.min.css vendored Normal file

File diff suppressed because one or more lines are too long

297
app/ui.js
View File

@ -5,6 +5,20 @@
*
* See README.md for usage and integration instructions.
*/
window._noVNC_has_module_support = true;
window.addEventListener("load", function() {
if (window._noVNC_has_module_support) return;
var loader = document.createElement("script");
loader.src = "vendor/browser-es-module-loader/dist/browser-es-module-loader.js";
document.head.appendChild(loader);
});
window.addEventListener("load", function() {
var connect_btn_el = document.getElementById("noVNC_connect_button");
if (typeof(connect_btn_el) != 'undefined' && connect_btn_el != null)
{
connect_btn_el.click();
}
});
import * as Log from '../core/util/logging.js';
import _, { l10n } from './localization.js';
@ -17,7 +31,12 @@ import Keyboard from "../core/input/keyboard.js";
import RFB from "../core/rfb.js";
import * as WebUtil from "./webutil.js";
const PAGE_TITLE = "noVNC";
const PAGE_TITLE = "KasmVNC";
var delta = 500;
var lastKeypressTime = 0;
var currentEventCount = -1;
var idleCounter = 0;
const UI = {
@ -36,6 +55,7 @@ const UI = {
lastKeyboardinput: null,
defaultKeyboardinputLen: 100,
needToCheckClipboardChange: false,
inhibitReconnect: true,
reconnectCallback: null,
@ -166,7 +186,7 @@ const UI = {
UI.initSetting('port', port);
UI.initSetting('encrypt', (window.location.protocol === "https:"));
UI.initSetting('view_clip', false);
UI.initSetting('resize', 'off');
/* UI.initSetting('resize', 'off'); */
UI.initSetting('quality', 6);
UI.initSetting('compression', 2);
UI.initSetting('shared', true);
@ -176,6 +196,26 @@ const UI = {
UI.initSetting('repeaterID', '');
UI.initSetting('reconnect', false);
UI.initSetting('reconnect_delay', 5000);
UI.initSetting('idle_disconnect', 20);
UI.initSetting('prefer_local_cursor', true);
UI.initSetting('toggle_control_panel', false);
UI.initSetting('enable_perf_stats', false);
if (WebUtil.isInsideKasmVDI()) {
UI.initSetting('video_quality', 1);
UI.initSetting('clipboard_up', false);
UI.initSetting('clipboard_down', false);
UI.initSetting('clipboard_seamless', false);
UI.initSetting('enable_webp', false);
UI.initSetting('resize', 'off');
} else {
UI.initSetting('video_quality', 3);
UI.initSetting('clipboard_up', true);
UI.initSetting('clipboard_down', true);
UI.initSetting('clipboard_seamless', true);
UI.initSetting('enable_webp', true);
UI.initSetting('resize', 'remote');
}
UI.setupSettingLabels();
},
@ -311,8 +351,11 @@ const UI = {
addConnectionControlHandlers() {
document.getElementById("noVNC_disconnect_button")
.addEventListener('click', UI.disconnect);
document.getElementById("noVNC_connect_button")
.addEventListener('click', UI.connect);
var connect_btn_el = document.getElementById("noVNC_connect_button");
if (typeof(connect_btn_el) != 'undefined' && connect_btn_el != null)
{
connect_btn_el.addEventListener('click', UI.connect);
}
document.getElementById("noVNC_cancel_reconnect_button")
.addEventListener('click', UI.cancelReconnect);
@ -343,6 +386,8 @@ const UI = {
document.getElementById("noVNC_settings_button")
.addEventListener('click', UI.toggleSettingsPanel);
document.getElementById("noVNC_setting_enable_perf_stats").addEventListener('click', UI.showStats);
UI.addSettingChangeHandler('encrypt');
UI.addSettingChangeHandler('resize');
UI.addSettingChangeHandler('resize', UI.applyResizeMode);
@ -366,6 +411,10 @@ const UI = {
UI.addSettingChangeHandler('logging', UI.updateLogging);
UI.addSettingChangeHandler('reconnect');
UI.addSettingChangeHandler('reconnect_delay');
UI.addSettingChangeHandler('enable_webp');
UI.addSettingChangeHandler('clipboard_seamless');
UI.addSettingChangeHandler('clipboard_up');
UI.addSettingChangeHandler('clipboard_down');
},
addFullscreenHandlers() {
@ -393,6 +442,11 @@ const UI = {
document.documentElement.classList.remove("noVNC_reconnecting");
const transitionElem = document.getElementById("noVNC_transition_text");
if (WebUtil.isInsideKasmVDI())
{
parent.postMessage({ action: 'connection_state', value: state}, '*' );
}
switch (state) {
case 'init':
break;
@ -449,6 +503,24 @@ const UI = {
.classList.remove('noVNC_open');
},
showStats() {
UI.saveSetting('enable_perf_stats');
let enable_stats = UI.getSetting('enable_perf_stats');
if (enable_stats === true && UI.statsInterval == undefined) {
document.getElementById("noVNC_connection_stats").style.visibility = "visible";
UI.statsInterval = setInterval(function() {
if (UI.rfb !== undefined) {
UI.rfb.requestBottleneckStats();
}
} , 5000);
} else {
document.getElementById("noVNC_connection_stats").style.visibility = "hidden";
UI.statsInterval = null;
}
},
showStatus(text, statusType, time) {
const statusElem = document.getElementById('noVNC_status');
@ -948,10 +1020,104 @@ const UI = {
}
},
readClipboard: function readClipboard(callback) {
if (navigator.clipboard && navigator.clipboard.readText) {
navigator.clipboard.readText().then(function (text) {
return callback(text);
}).catch(function () {
return Log.Debug("Failed to read system clipboard");
});
}
},
clipboardReceive(e) {
if (UI.rfb.clipboardDown && UI.rfb.clipboardSeamless ) {
var curvalue = document.getElementById('noVNC_clipboard_text').value;
if (curvalue != e.detail.text) {
Log.Debug(">> UI.clipboardReceive: " + e.detail.text.substr(0, 40) + "...");
document.getElementById('noVNC_clipboard_text').value = e.detail.text;
Log.Debug("<< UI.clipboardReceive");
if (navigator.clipboard && navigator.clipboard.writeText){
navigator.clipboard.writeText(e.detail.text)
.then(function () {
//UI.popupMessage("Selection Copied");
}, function () {
console.error("Failed to write system clipboard (trying to copy from NoVNC clipboard)")
});
}
}
}
},
//recieved bottleneck stats
bottleneckStatsRecieve(e) {
var obj = JSON.parse(e.detail.text);
document.getElementById("noVNC_connection_stats").innerHTML = "CPU: " + obj[0] + "/" + obj[1] + " | Network: " + obj[2] + "/" + obj[3];
console.log(e.detail.text);
},
popupMessage: function(msg, secs) {
if (!secs){
secs = 500;
}
// Quick popup to give feedback that selection was copied
setTimeout(UI.showOverlay.bind(this, msg, secs), 200);
},
// Enter and focus events come when we return to NoVNC.
// In both cases, check the local clipboard to see if it changed.
focusVNC: function() {
UI.copyFromLocalClipboard();
},
enterVNC: function() {
UI.copyFromLocalClipboard();
},
copyFromLocalClipboard: function copyFromLocalClipboard() {
if (UI.rfb && UI.rfb.clipboardUp && UI.rfb.clipboardSeamless) {
UI.readClipboard(function (text) {
var maximumBufferSize = 10000;
var clipVal = document.getElementById('noVNC_clipboard_text').value;
if (clipVal != text) {
document.getElementById('noVNC_clipboard_text').value = text; // The websocket has a maximum buffer array size
if (text.length > maximumBufferSize) {
UI.popupMessage("Clipboard contents too large. Data truncated", 2000);
UI.rfb.clipboardPasteFrom(text.slice(0, maximumBufferSize));
} else {
//UI.popupMessage("Copied from Local Clipboard");
UI.rfb.clipboardPasteFrom(text);
}
} // Reset flag to prevent checking too often
UI.needToCheckClipboardChange = false;
});
}
},
// These 3 events indicate the focus has gone outside the NoVNC.
// When outside the NoVNC, the system clipboard could change.
leaveVNC: function() {
UI.needToCheckClipboardChange = true;
},
blurVNC: function() {
UI.needToCheckClipboardChange = true;
},
focusoutVNC: function() {
UI.needToCheckClipboardChange = true;
},
// On these 2 events, check if we need to look at clipboard.
mouseMoveVNC: function() {
if ( UI.needToCheckClipboardChange ) {
UI.copyFromLocalClipboard();
}
},
mouseDownVNC: function() {
if ( UI.needToCheckClipboardChange ) {
UI.copyFromLocalClipboard();
}
},
clipboardClear() {
@ -1034,6 +1200,14 @@ const UI = {
UI.rfb.addEventListener("securityfailure", UI.securityFailed);
UI.rfb.addEventListener("capabilities", UI.updatePowerButton);
UI.rfb.addEventListener("clipboard", UI.clipboardReceive);
UI.rfb.addEventListener("bottleneck_stats", UI.bottleneckStatsRecieve);
document.addEventListener('mouseenter', UI.enterVNC);
document.addEventListener('mouseleave', UI.leaveVNC);
document.addEventListener('blur', UI.blurVNC);
document.addEventListener('focus', UI.focusVNC);
document.addEventListener('focusout', UI.focusoutVNC);
document.addEventListener('mousemove', UI.mouseMoveVNC);
document.addEventListener('mousedown', UI.mouseDownVNC);
UI.rfb.addEventListener("bell", UI.bell);
UI.rfb.addEventListener("desktopname", UI.updateDesktopName);
UI.rfb.clipViewport = UI.getSetting('view_clip');
@ -1042,8 +1216,80 @@ const UI = {
UI.rfb.qualityLevel = parseInt(UI.getSetting('quality'));
UI.rfb.compressionLevel = parseInt(UI.getSetting('compression'));
UI.rfb.showDotCursor = UI.getSetting('show_dot');
UI.rfb.idleDisconnect = UI.getSetting('idle_disconnect');
UI.rfb.videoQuality = UI.getSetting('video_quality');
UI.rfb.clipboardUp = UI.getSetting('clipboard_up');
UI.rfb.clipboardDown = UI.getSetting('clipboard_down');
UI.rfb.clipboardSeamless = UI.getSetting('clipboard_seamless');
// KASM-960 workaround, disable seamless on Safari
if (/^((?!chrome|android).)*safari/i.test(navigator.userAgent))
{
UI.rfb.clipboardSeamless = false;
}
UI.rfb.preferLocalCursor = UI.getSetting('prefer_local_cursor');
UI.rfb.enableWebP = UI.getSetting('enable_webp');
UI.updateViewOnly(); // requires UI.rfb
/****
* Kasm VDI specific
*****/
if (WebUtil.isInsideKasmVDI())
{
if (window.addEventListener) { // Mozilla, Netscape, Firefox
//window.addEventListener('load', WindowLoad, false);
window.addEventListener('message', UI.receiveMessage, false);
} else if (window.attachEvent) { //IE
window.attachEvent('onload', WindowLoad);
window.attachEvent('message', UI.receiveMessage);
}
if (UI.rfb.clipboardDown){
UI.rfb.addEventListener("clipboard", UI.clipboardRx);
}
UI.rfb.addEventListener("disconnect", UI.disconnectedRx);
document.getElementById('noVNC_control_bar_anchor').setAttribute('style', 'display: none');
document.getElementById('noVNC_connect_dlg').innerHTML = '';
//keep alive for websocket connection to stay open, since we may not control reverse proxies
//send a keep alive within a window that we control
setInterval(function() {
if (currentEventCount!=UI.rfb.sentEventsCounter) {
idleCounter=0;
currentEventCount=UI.rfb.sentEventsCounter;
} else {
idleCounter+=1;
var idleDisconnect = parseFloat(UI.rfb.idleDisconnect);
if ((idleCounter / 2) >= idleDisconnect) {
//idle for longer than the limit, disconnect
currentEventCount = -1;
idleCounter = 0;
parent.postMessage({ action: 'idle_session_timeout', value: 'Idle session timeout exceeded'}, '*' );
//UI.rfb.disconnect();
} else {
//send a keep alive
UI.rfb.sendKey(1, null, false);
currentEventCount=UI.rfb.sentEventsCounter;
}
}
}, 30000);
}
// Send an event to the parent document (kasm app) to toggle the control panel when ctl is double clicked
if (UI.getSetting('toggle_control_panel', false)) {
document.addEventListener('keyup', function (event) {
// CTRL and the various implementations of the mac command key
if ([17, 224, 91, 93].indexOf(event.keyCode) > -1) {
var thisKeypressTime = new Date();
if (thisKeypressTime - lastKeypressTime <= delta) {
UI.toggleNav();
thisKeypressTime = 0;
}
lastKeypressTime = thisKeypressTime;
}
}, true);
}
},
disconnect() {
@ -1093,6 +1339,7 @@ const UI = {
msg = _("Connected (unencrypted) to ") + UI.desktopName;
}
UI.showStatus(msg);
UI.showStats();
UI.updateVisualState('connected');
// Do this last because it can only be used on rendered elements
@ -1149,6 +1396,40 @@ const UI = {
UI.showStatus(msg, 'error');
},
/*
Menu.js Additions
*/
receiveMessage(event) {
//TODO: UNCOMMENT FOR PRODUCTION
//if (event.origin !== "https://kasmweb.com")
// return;
if (event.data && event.data.action) {
switch (event.data.action) {
case 'clipboardsnd':
if (UI.rfb.clipboardUp) {
UI.rfb.clipboardPasteFrom(event.data.value);
}
break;
case 'setvideoquality':
UI.rfb.videoQuality = event.data.value;
break;
}
}
},
disconnectedRx(event) {
parent.postMessage({ action: 'disconnectrx', value: event.detail.reason}, '*' );
},
toggleNav(){
parent.postMessage({ action: 'togglenav', value: null}, '*' );
},
clipboardRx(event) {
parent.postMessage({ action: 'clipboardrx', value: event.detail.text}, '*' ); //TODO fix star
},
/* ------^-------
* /CONNECTION
* ==============
@ -1259,6 +1540,9 @@ const UI = {
UI.rfb.scaleViewport = UI.getSetting('resize') === 'scale';
UI.rfb.resizeSession = UI.getSetting('resize') === 'remote';
UI.rfb.idleDisconnect = UI.getSetting('idle_disconnect');
UI.rfb.videoQuality = UI.getSetting('video_quality');
UI.rfb.enableWebP = UI.getSetting('enable_webp');
},
/* ------^-------
@ -1416,6 +1700,9 @@ const UI = {
keepVirtualKeyboard(event) {
const input = document.getElementById('noVNC_keyboardinput');
if ( UI.needToCheckClipboardChange ) {
UI.copyFromLocalClipboard();
}
// Only prevent focus change if the virtual keyboard is active
if (document.activeElement != input) {

View File

@ -175,3 +175,13 @@ export function eraseSetting(name) {
localStorage.removeItem(name);
}
}
//Are we running inside the Kasm VDI Framework
export function isInsideKasmVDI() {
//TODO: We should use a more explicit way to detect we are running inside KasmVDI
try {
return window.self !== window.top;
} catch (e) {
return true;
}
}

View File

@ -59,6 +59,9 @@ export default class TightDecoder {
} else if ((this._ctl & 0x08) == 0) {
ret = this._basicRect(this._ctl, x, y, width, height,
sock, display, depth);
} else if (this._ctl === 0x0B) {
ret = this._webpRect(x, y, width, height,
sock, display, depth);
} else {
throw new Error("Illegal tight compression received (ctl: " +
this._ctl + ")");
@ -97,6 +100,17 @@ export default class TightDecoder {
return true;
}
_webpRect(x, y, width, height, sock, display, depth) {
let data = this._readData(sock);
if (data === null) {
return false;
}
display.imageRect(x, y, width, height, "image/webp", data);
return true;
}
_pngRect(x, y, width, height, sock, display, depth) {
throw new Error("PNG received in standard Tight rect");
}

View File

@ -27,6 +27,32 @@ export const encodings = {
pseudoEncodingContinuousUpdates: -313,
pseudoEncodingCompressLevel9: -247,
pseudoEncodingCompressLevel0: -256,
pseudoEncodingWEBP: -1024,
pseudoEncodingJpegVideoQualityLevel0: -1023,
pseudoEncodingJpegVideoQualityLevel9: -1014,
pseudoEncodingWebpVideoQualityLevel0: -1013,
pseudoEncodingWebpVideoQualityLevel9: -1004,
pseudoEncodingTreatLosslessLevel0: -1003,
pseudoEncodingTreatLosslessLevel10: -993,
pseudoEncodingPreferBandwidth: -992,
pseudoEncodingDynamicQualityMinLevel0: -991,
pseudoEncodingDynamicQualityMinLevel9: -982,
pseudoEncodingDynamicQualityMaxLevel0: -981,
pseudoEncodingDynamicQualityMaxLevel9: -972,
pseudoEncodingVideoAreaLevel1: -971,
pseudoEncodingVideoAreaLevel100: -871,
pseudoEncodingVideoTimeLevel0: -870,
pseudoEncodingVideoTimeLevel100: -770,
pseudoEncodingFrameRateLevel10: -2048,
pseudoEncodingFrameRateLevel60: -1998,
pseudoEncodingMaxVideoResolution: -1997,
pseudoEncodingVideoScalingLevel0: -1996,
pseudoEncodingVideoScalingLevel9: -1987,
pseudoEncodingVideoOutTimeLevel1: -1986,
pseudoEncodingVideoOutTimeLevel100: -1887,
pseudoEncodingVMwareCursor: 0x574d5664,
pseudoEncodingExtendedClipboard: 0xc0a1e5ce
};

View File

@ -37,6 +37,9 @@ import TightPNGDecoder from "./decoders/tightpng.js";
const DISCONNECT_TIMEOUT = 3;
const DEFAULT_BACKGROUND = 'rgb(40, 40, 40)';
var _videoQuality = 2;
var _enableWebP = false;
// Minimum wait (ms) between two mouse moves
const MOUSE_MOVE_DELAY = 17;
@ -91,7 +94,7 @@ export default class RFB extends EventTargetMixin {
this._rfbCredentials = options.credentials || {};
this._shared = 'shared' in options ? !!options.shared : true;
this._repeaterID = options.repeaterID || '';
this._wsProtocols = options.wsProtocols || [];
this._wsProtocols = options.wsProtocols || ['binary'];
// Internal state
this._rfbConnectionState = '';
@ -124,6 +127,21 @@ export default class RFB extends EventTargetMixin {
this._qemuExtKeyEventSupported = false;
// kasm defaults
this._jpegVideoQuality = 5;
this._webpVideoQuality = 5;
this._treatLossless = 7;
this._preferBandwidth = true;
this._dynamicQualityMin = 3;
this._dynamicQualityMax = 9;
this._videoArea = 65;
this._videoTime = 5;
this._videoOutTime = 3;
this._videoScaling = 2;
this._frameRate = 30;
this._maxVideoResolutionX = 960;
this._maxVideoResolutionY = 540;
this._clipboardText = null;
this._clipboardServerCapabilitiesActions = {};
this._clipboardServerCapabilitiesFormats = {};
@ -293,8 +311,11 @@ export default class RFB extends EventTargetMixin {
// ===== PROPERTIES =====
this.dragViewport = false;
this.focusOnClick = true;
this.sentEventsCounter = 0;
this._viewOnly = false;
this._clipViewport = false;
@ -313,6 +334,51 @@ export default class RFB extends EventTargetMixin {
// ===== PROPERTIES =====
get videoQuality() { return this._videoQuality; }
set videoQuality(quality) { this._videoQuality = quality; }
get enableWebP() { return this._enableWebP; }
set enableWebP(enabled) { this._enableWebP = enabled; }
get jpegVideoQuality() { return this._jpegVideoQuality; }
set jpegVideoQuality(val) { this._jpegVideoQuality = val; }
get webpVideoQuality() { return this._webpVideoQuality; }
set webpVideoQuality(val) { this._webpVideoQuality = val; }
get treatLossless() { return this._treatLossless; }
set treatLossless(val) { this._treatLossless = val; }
get preferBandwidth() { return this._preferBandwidth; }
set preferBandwidth(val) { this._preferBandwidth = val; }
get dynamicQualityMin() { return this._dynamicQualityMin; }
set dynamicQualityMin(val) { this._dynamicQualityMin = val; }
get dynamicQualityMax() { return this._dynamicQualityMax; }
set dynamicQualityMax(val) { this._dynamicQualityMax = val; }
get videoArea() { return this._videoArea; }
set videoArea(val) { this._videoArea = val; }
get videoTime() { return this._videoTime; }
set videoTime(val) { this._videoTime = val; }
get videoOutTime() { return this._videoOutTime; }
set videoOutTime(val) { this._videoOutTime = val; }
get videoScaling() { return this._videoScaling; }
set videoScaling(val) { this._videoScaling = val; }
get frameRate() { return this._frameRate; }
set frameRate(val) { this._frameRate = val; }
get maxVideoResolutionX() { return this._maxVideoResolutionX; }
set maxVideoResolutionX(val) { this._maxVideoResolutionX = val; }
get maxVideoResolutionY() { return this._maxVideoResolutionY; }
set maxVideoResolutionY(val) { this._maxVideoResolutionY = val; }
get viewOnly() { return this._viewOnly; }
set viewOnly(viewOnly) {
this._viewOnly = viewOnly;
@ -357,6 +423,7 @@ export default class RFB extends EventTargetMixin {
this._resizeSession = resize;
if (resize) {
this._requestRemoteResize();
this.scaleViewport = true;
}
}
@ -452,6 +519,8 @@ export default class RFB extends EventTargetMixin {
sendKey(keysym, code, down) {
if (this._rfbConnectionState !== 'connected' || this._viewOnly) { return; }
this.sentEventsCounter+=1;
if (down === undefined) {
this.sendKey(keysym, code, true);
this.sendKey(keysym, code, false);
@ -486,7 +555,7 @@ export default class RFB extends EventTargetMixin {
clipboardPasteFrom(text) {
if (this._rfbConnectionState !== 'connected' || this._viewOnly) { return; }
this.sentEventsCounter+=1;
if (this._clipboardServerCapabilitiesFormats[extendedClipboardFormatText] &&
this._clipboardServerCapabilitiesActions[extendedClipboardActionNotify]) {
@ -503,6 +572,10 @@ export default class RFB extends EventTargetMixin {
}
}
requestBottleneckStats() {
RFB.messages.requestStats(this._sock);
}
// ===== PRIVATE METHODS =====
_connect() {
@ -512,6 +585,7 @@ export default class RFB extends EventTargetMixin {
try {
Log.Info(`connecting to ${this._url}`);
this._sock.open(this._url, this._wsProtocols);
this.sentEventsCounter+=1;
} catch (e) {
if (e.name === 'SyntaxError') {
this._fail("Invalid host or port (" + e + ")");
@ -682,14 +756,36 @@ export default class RFB extends EventTargetMixin {
Math.floor(size.w), Math.floor(size.h),
this._screenID, this._screenFlags);
this.sentEventsCounter+=1;
Log.Debug('Requested new desktop size: ' +
size.w + 'x' + size.h);
}
// Gets the the size of the available screen
_screenSize() {
let r = this._screen.getBoundingClientRect();
return { w: r.width, h: r.height };
_screenSize (limited) {
if (limited === undefined) {
limited = true;
}
var x = this._screen.offsetWidth;
var y = this._screen.offsetHeight;
try {
if (x > 1280 && limited && this.videoQuality == 1) {
var ratio = y / x;
console.log(ratio);
x = 1280;
y = x * ratio;
}
else if (limited && this.videoQuality == 0){
x = 1280;
y = 720;
}
} catch (err) {
console.log(err);
}
return { w: x,
h: y };
}
_fixScrollbars() {
@ -939,10 +1035,13 @@ export default class RFB extends EventTargetMixin {
// Otherwise we treat this as a mouse click event.
// Send the button down event here, as the button up
// event is sent at the end of this function.
this.sentEventsCounter+=1;
this._sendMouse(x, y, bmask);
}
}
this.sentEventsCounter+=1;
// Flush waiting move event first
if (this._mouseMoveTimer !== null) {
clearTimeout(this._mouseMoveTimer);
@ -1475,12 +1574,8 @@ export default class RFB extends EventTargetMixin {
_negotiateStdVNCAuth() {
if (this._sock.rQwait("auth challenge", 16)) { return false; }
if (this._rfbCredentials.password === undefined) {
this.dispatchEvent(new CustomEvent(
"credentialsrequired",
{ detail: { types: ["password"] } }));
return false;
}
// KasmVNC uses basic Auth, clear the VNC password, which is not used
this._rfbCredentials.password = "";
// TODO(directxman12): make genDES not require an Array
const challenge = Array.prototype.slice.call(this._sock.rQshiftBytes(16));
@ -1757,8 +1852,33 @@ export default class RFB extends EventTargetMixin {
return true;
}
_hasWebp() {
/*
return new Promise(res => {
const webP = new Image();
webP.src = 'data:image/webp;base64,UklGRjoAAABXRUJQVlA4IC4AAACyAgCdASoCAAIALmk0mk0iIiIiIgBoSygABc6WWgAA/veff/0PP8bA//LwYAAA';
webP.onload = webP.onerror = function () {
res(webP.height === 2);
};
})
*/
if (!this.enableWebP)
return false;
// It's not possible to check for webp synchronously, and hacking promises
// into everything would be too time-consuming. So test for FF and Chrome.
var uagent = navigator.userAgent.toLowerCase();
var match = uagent.match(/firefox\/([0-9]+)\./);
if (match && parseInt(match[1]) >= 65)
return true;
match = uagent.match(/chrome\/([0-9]+)\./);
if (match && parseInt(match[1]) >= 23)
return true;
return false;
}
_sendEncodings() {
const encs = [];
var hasWebp;
// In preference order
encs.push(encodings.encodingCopyRect);
@ -1772,6 +1892,19 @@ export default class RFB extends EventTargetMixin {
encs.push(encodings.encodingRaw);
// Psuedo-encoding settings
var quality = 6;
var compression = 2;
var screensize = this._screenSize(false);
if (this.videoQuality == 1) {
if (screensize.w > 1280) {
quality = 8; //higher quality needed because scaling enlarges artifacts
} else {
quality = 3; //twice the compression ratio as default, but not horrible quality
}
compression = 6;
} else if (this.videoQuality == 3) {
quality = 8
}
encs.push(encodings.pseudoEncodingQualityLevel0 + this._qualityLevel);
encs.push(encodings.pseudoEncodingCompressLevel0 + this._compressionLevel);
@ -1784,12 +1917,36 @@ export default class RFB extends EventTargetMixin {
encs.push(encodings.pseudoEncodingContinuousUpdates);
encs.push(encodings.pseudoEncodingDesktopName);
encs.push(encodings.pseudoEncodingExtendedClipboard);
if (this._hasWebp())
encs.push(encodings.pseudoEncodingWEBP);
// kasm settings; the server may be configured to ignore these
encs.push(encodings.pseudoEncodingJpegVideoQualityLevel0 + this.jpegVideoQuality);
encs.push(encodings.pseudoEncodingWebpVideoQualityLevel0 + this.webpVideoQuality);
encs.push(encodings.pseudoEncodingTreatLosslessLevel0 + this.treatLossless);
encs.push(encodings.pseudoEncodingDynamicQualityMinLevel0 + this.dynamicQualityMin);
encs.push(encodings.pseudoEncodingDynamicQualityMaxLevel0 + this.dynamicQualityMax);
encs.push(encodings.pseudoEncodingVideoAreaLevel1 + this.videoArea - 1);
encs.push(encodings.pseudoEncodingVideoTimeLevel0 + this.videoTime);
encs.push(encodings.pseudoEncodingVideoOutTimeLevel1 + this.videoOutTime - 1);
encs.push(encodings.pseudoEncodingVideoScalingLevel0 + this.videoScaling);
encs.push(encodings.pseudoEncodingFrameRateLevel10 + this.frameRate - 10);
encs.push(encodings.pseudoEncodingMaxVideoResolution);
if (this.preferBandwidth) // must be last - server processes in reverse order
encs.push(encodings.pseudoEncodingPreferBandwidth);
if (this._fbDepth == 24) {
encs.push(encodings.pseudoEncodingVMwareCursor);
encs.push(encodings.pseudoEncodingCursor);
}
//if (supportsCursorURIs() && this._fb_depth == 24){
// Allow the user to attempt using a local cursor even if they are using a touch device. KASM-395
if (this.preferLocalCursor || !isTouchDevice){
encs.push(encodings.pseudoEncodingCursor)
}
//}
RFB.messages.clientEncodings(this._sock, encs);
}
@ -2006,6 +2163,22 @@ export default class RFB extends EventTargetMixin {
return true;
}
_handle_server_stats_msg() {
this._sock.rQskipBytes(3); // Padding
const length = this._sock.rQshift32();
if (this._sock.rQwait("KASM bottleneck stats", length, 8)) { return false; }
const text = this._sock.rQshiftStr(length);
console.log("Received KASM bottleneck stats:");
console.log(text);
this.dispatchEvent(new CustomEvent(
"bottleneck_stats",
{ detail: { text: text } }));
return true;
}
_handleServerFenceMsg() {
if (this._sock.rQwait("ServerFence header", 8, 1)) { return false; }
this._sock.rQskipBytes(3); // Padding
@ -2116,6 +2289,9 @@ export default class RFB extends EventTargetMixin {
}
return true;
case 178: // KASM bottleneck stats
return this._handle_server_stats_msg();
case 248: // ServerFence
return this._handleServerFenceMsg();
@ -2174,7 +2350,9 @@ export default class RFB extends EventTargetMixin {
this._FBU.encoding = null;
}
if (document.visibilityState !== "hidden") {
this._display.flip();
}
return true; // We finished this FBU
}
@ -2403,6 +2581,11 @@ export default class RFB extends EventTargetMixin {
// resizing until we've gotten here.
if (firstUpdate) {
this._requestRemoteResize();
RFB.messages.setMaxVideoResolution(this._sock,
this._maxVideoResolutionX,
this._maxVideoResolutionY);
this.sentEventsCounter+=1;
}
this._sock.rQskipBytes(1); // number-of-screens
@ -2800,6 +2983,20 @@ RFB.messages = {
sock.flush();
},
setMaxVideoResolution(sock, width, height) {
const buff = sock._sQ;
const offset = sock._sQlen;
buff[offset] = 252; // msg-type
buff[offset + 1] = width >> 8; // width
buff[offset + 2] = width;
buff[offset + 3] = height >> 8; // height
buff[offset + 4] = height;
sock._sQlen += 5;
sock.flush();
},
clientFence(sock, flags, payload) {
const buff = sock._sQ;
const offset = sock._sQlen;
@ -2827,6 +3024,23 @@ RFB.messages = {
sock.flush();
},
requestStats(sock) {
const buff = sock._sQ;
const offset = sock._sQlen;
if (buff == null) { return; }
buff[offset] = 178; // msg-type
buff[offset + 1] = 0; // padding
buff[offset + 2] = 0; // padding
buff[offset + 3] = 0; // padding
sock._sQlen += 4;
sock.flush();
},
enableContinuousUpdates(sock, enable, x, y, width, height) {
const buff = sock._sQ;
const offset = sock._sQlen;

362
load.html Normal file
View File

@ -0,0 +1,362 @@
<!DOCTYPE html>
<html lang="en" class="noVNC_loading">
<head>
<!--
noVNC example: simple example using default UI
Copyright (C) 2019 The noVNC Authors
noVNC is licensed under the MPL 2.0 (see LICENSE.txt)
This file is licensed under the 2-Clause BSD license (see LICENSE.txt).
Connect parameters are provided in query string:
http://example.com/?host=HOST&port=PORT&encrypt=1
or the fragment:
http://example.com/#host=HOST&port=PORT&encrypt=1
-->
<title>KasmVNC</title>
<meta charset="utf-8">
<!-- Icons (see app/images/icons/Makefile for what the sizes are for) -->
<link rel="icon" sizes="16x16" type="image/png" href="app/images/icons/368_kasm_logo_only_16x16.png">
<link rel="icon" sizes="24x24" type="image/png" href="app/images/icons/368_kasm_logo_only_24x24.png">
<link rel="icon" sizes="32x32" type="image/png" href="app/images/icons/368_kasm_logo_only_32x32.png">
<link rel="icon" sizes="48x48" type="image/png" href="app/images/icons/368_kasm_logo_only_48x48.png">
<link rel="icon" sizes="60x60" type="image/png" href="app/images/icons/368_kasm_logo_only_60x60.png">
<link rel="icon" sizes="64x64" type="image/png" href="app/images/icons/368_kasm_logo_only_64x64.png">
<link rel="icon" sizes="72x72" type="image/png" href="app/images/icons/368_kasm_logo_only_72x72.png">
<link rel="icon" sizes="76x76" type="image/png" href="app/images/icons/368_kasm_logo_only_76x76.png">
<link rel="icon" sizes="96x96" type="image/png" href="app/images/icons/368_kasm_logo_only_96x96.png">
<link rel="icon" sizes="120x120" type="image/png" href="app/images/icons/368_kasm_logo_only_120x120.png">
<link rel="icon" sizes="144x144" type="image/png" href="app/images/icons/368_kasm_logo_only_144x144.png">
<link rel="icon" sizes="152x152" type="image/png" href="app/images/icons/368_kasm_logo_only_152x152.png">
<link rel="icon" sizes="192x192" type="image/png" href="app/images/icons/368_kasm_logo_only_192x192.png">
<!-- Firefox currently mishandles SVG, see #1419039
<link rel="icon" sizes="any" type="image/svg+xml" href="app/images/icons/kasm-icon.svg">
-->
<!-- Repeated last so that legacy handling will pick this -->
<link rel="icon" sizes="16x16" type="image/png" href="app/images/icons/368_kasm_logo_only_16x16.png">
<!-- Apple iOS Safari settings -->
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<!-- Home Screen Icons (favourites and bookmarks use the normal icons) -->
<link rel="apple-touch-icon" sizes="60x60" type="image/png" href="app/images/icons/368_kasm_logo_only_60x60.png">
<link rel="apple-touch-icon" sizes="76x76" type="image/png" href="app/images/icons/368_kasm_logo_only_76x76.png">
<link rel="apple-touch-icon" sizes="120x120" type="image/png" href="app/images/icons/368_kasm_logo_only_120x120.png">
<link rel="apple-touch-icon" sizes="152x152" type="image/png" href="app/images/icons/368_kasm_logo_only_152x152.png">
<!-- Stylesheets -->
<link rel="stylesheet" href="app/styles/base.css">
<script src="app/error-handler.js"></script>
<script>
let isInsideKasmVDI = false;
try {
isInsideKasmVDI = (window.self !== window.top);
} catch (e) {
isInsideKasmVDI = true;
}
if (!isInsideKasmVDI) {
window.addEventListener("load", function() {
document.getElementById("noVNC_connect_button").click();
});
}
</script>
<script type="module" crossorigin="use-credentials" src="app/ui.js"></script>
</head>
<body>
<div id="noVNC_fallback_error" class="noVNC_center">
<div>
<div>noVNC encountered an error:</div>
<br>
<div id="noVNC_fallback_errormsg"></div>
</div>
</div>
<div id="noVNC_connection_stats">
Loading statistics...
</div>
<!-- noVNC Control Bar -->
<div id="noVNC_control_bar_anchor" class="noVNC_vcenter">
<div id="noVNC_control_bar">
<div id="noVNC_control_bar_handle" title="Hide/Show the control bar"><div></div></div>
<div class="noVNC_scroll">
<h1 class="noVNC_logo"><img src="app/images/icons/368_kasm_logo_only_24x24.png" /></h1>
<!-- Drag/Pan the viewport -->
<input type="image" alt="Drag" src="app/images/drag.svg"
id="noVNC_view_drag_button" class="noVNC_button noVNC_hidden"
title="Move/Drag Viewport">
<!--noVNC Touch Device only buttons-->
<div id="noVNC_mobile_buttons">
<input type="image" alt="Keyboard" src="app/images/keyboard.svg"
id="noVNC_keyboard_button" class="noVNC_button" title="Show Keyboard">
</div>
<!-- Extra manual keys -->
<input type="image" alt="Extra keys" src="app/images/toggleextrakeys.svg"
id="noVNC_toggle_extra_keys_button" class="noVNC_button"
title="Show Extra Keys">
<div class="noVNC_vcenter">
<div id="noVNC_modifiers" class="noVNC_panel">
<input type="image" alt="Ctrl" src="app/images/ctrl.svg"
id="noVNC_toggle_ctrl_button" class="noVNC_button"
title="Toggle Ctrl">
<input type="image" alt="Alt" src="app/images/alt.svg"
id="noVNC_toggle_alt_button" class="noVNC_button"
title="Toggle Alt">
<input type="image" alt="Windows" src="app/images/windows.svg"
id="noVNC_toggle_windows_button" class="noVNC_button"
title="Toggle Windows">
<input type="image" alt="Tab" src="app/images/tab.svg"
id="noVNC_send_tab_button" class="noVNC_button"
title="Send Tab">
<input type="image" alt="Esc" src="app/images/esc.svg"
id="noVNC_send_esc_button" class="noVNC_button"
title="Send Escape">
<input type="image" alt="Ctrl+Alt+Del" src="app/images/ctrlaltdel.svg"
id="noVNC_send_ctrl_alt_del_button" class="noVNC_button"
title="Send Ctrl-Alt-Del">
</div>
</div>
<!-- Shutdown/Reboot -->
<input type="image" alt="Shutdown/Reboot" src="app/images/power.svg"
id="noVNC_power_button" class="noVNC_button"
title="Shutdown/Reboot...">
<div class="noVNC_vcenter">
<div id="noVNC_power" class="noVNC_panel">
<div class="noVNC_heading">
<img alt="" src="app/images/power.svg"> Power
</div>
<input type="button" id="noVNC_shutdown_button" value="Shutdown">
<input type="button" id="noVNC_reboot_button" value="Reboot">
<input type="button" id="noVNC_reset_button" value="Reset">
</div>
</div>
<!-- Clipboard -->
<input type="image" alt="Clipboard" src="app/images/clipboard.svg"
id="noVNC_clipboard_button" class="noVNC_button"
title="Clipboard">
<div class="noVNC_vcenter">
<div id="noVNC_clipboard" class="noVNC_panel">
<div class="noVNC_heading">
<img alt="" src="app/images/clipboard.svg"> Clipboard
</div>
<textarea id="noVNC_clipboard_text" rows=5></textarea>
<br>
<input id="noVNC_clipboard_clear_button" type="button"
value="Clear" class="noVNC_submit">
</div>
</div>
<!-- Toggle fullscreen -->
<input type="image" alt="Fullscreen" src="app/images/fullscreen.svg"
id="noVNC_fullscreen_button" class="noVNC_button noVNC_hidden"
title="Fullscreen">
<!-- Settings -->
<input type="image" alt="Settings" src="app/images/settings.svg"
id="noVNC_settings_button" class="noVNC_button"
title="Settings">
<div class="noVNC_vcenter">
<div id="noVNC_settings" class="noVNC_panel">
<ul>
<li class="noVNC_heading">
<img alt="" src="app/images/settings.svg"> Settings
</li>
<li>
<label><input id="noVNC_setting_shared" type="checkbox"> Shared Mode</label>
</li>
<li>
<label><input id="noVNC_setting_view_only" type="checkbox" /> View Only</label></li>
<li>
<label><input id="noVNC_setting_clipboard_up" type="checkbox" /> Clipboard Up</label></li>
<li>
<label><input id="noVNC_setting_clipboard_down" type="checkbox" /> Clipboard Down</label></li>
<li>
<label><input id="noVNC_setting_clipboard_seamless" type="checkbox" /> Clipboard Seamless</label></li>
<li>
<label><input id="noVNC_setting_prefer_local_cursor" type="checkbox" /> Prefer Local Cursor</label></li>
<li>
<label><input id="noVNC_setting_enable_webp" type="checkbox" /> Enable WebP Compression</label></li>
<li>
<label><input id="noVNC_setting_enable_perf_stats" type="checkbox" /> Enable Performance Stats</label></li>
<li>
<label><input id="noVNC_setting_toggle_control_panel" type="checkbox" /> Toggle Control Panel via Keystrokes</label></li>
<li>
<label for="noVNC_setting_video_quality">Video Quality:</label>
<select id="noVNC_setting_video_quality" name="vncVideoQuality">
<option value=0>Low</option>
<option value=1>Medium</option>
<option value=2>High</option>
</select>
</li>
<li>
<label for="noVNC_setting_idle_disconnect">Idle Timeout:</label>
<select id="noVNC_setting_idle_disconnect" name="vncIdleDisconnect">
<option value=20>20</option>
</select>
</li>
<li><hr></li>
<li>
<label><input id="noVNC_setting_view_clip" type="checkbox"> Clip to Window</label>
</li>
<li>
<label for="noVNC_setting_resize">Scaling Mode:</label>
<select id="noVNC_setting_resize" name="vncResize">
<option value="off">None</option>
<option value="scale">Local Scaling</option>
<option value="remote">Remote Resizing</option>
</select>
</li>
<li><hr></li>
<li>
<div class="noVNC_expander">Advanced</div>
<div><ul>
<li>
<label for="noVNC_setting_quality">Quality:</label>
<input id="noVNC_setting_quality" type="range" min="0" max="9" value="6">
</li>
<li>
<label for="noVNC_setting_compression">Compression level:</label>
<input id="noVNC_setting_compression" type="range" min="0" max="9" value="2">
</li>
<li><hr></li>
<li>
<label for="noVNC_setting_repeaterID">Repeater ID:</label>
<input id="noVNC_setting_repeaterID" type="text" value="">
</li>
<li>
<div class="noVNC_expander">WebSocket</div>
<div><ul>
<li>
<label><input id="noVNC_setting_encrypt" type="checkbox"> Encrypt</label>
</li>
<li>
<label for="noVNC_setting_host">Host:</label>
<input id="noVNC_setting_host">
</li>
<li>
<label for="noVNC_setting_port">Port:</label>
<input id="noVNC_setting_port" type="number">
</li>
<li>
<label for="noVNC_setting_path">Path:</label>
<input id="noVNC_setting_path" type="text" value="websockify">
</li>
</ul></div>
</li>
<li><hr></li>
<li>
<label><input id="noVNC_setting_reconnect" type="checkbox"> Automatic Reconnect</label>
</li>
<li>
<label for="noVNC_setting_reconnect_delay">Reconnect Delay (ms):</label>
<input id="noVNC_setting_reconnect_delay" type="number">
</li>
<li><hr></li>
<li>
<label><input id="noVNC_setting_show_dot" type="checkbox"> Show Dot when No Cursor</label>
</li>
<li><hr></li>
<!-- Logging selection dropdown -->
<li>
<label>Logging:
<select id="noVNC_setting_logging" name="vncLogging">
</select>
</label>
</li>
</ul></div>
</li>
<li class="noVNC_version_separator"><hr></li>
<li class="noVNC_version_wrapper">
<span>Version:</span>
<span class="noVNC_version"></span>
</li>
</ul>
</div>
</div>
<!-- Connection Controls -->
<input type="image" alt="Disconnect" src="app/images/disconnect.svg"
id="noVNC_disconnect_button" class="noVNC_button"
title="Disconnect">
</div>
</div>
<div id="noVNC_control_bar_hint"></div>
</div> <!-- End of noVNC_control_bar -->
<!-- Status Dialog -->
<div style="visibility: hidden" id="noVNC_status"></div>
<!-- Connect button -->
<div class="noVNC_center">
<div id="noVNC_connect_dlg">
<!--div class="noVNC_logo" translate="no"><span>no</span>VNC</div-->
<div id="noVNC_connect_button"><div>
<img alt="" src="app/images/connect.svg"> Connect
</div></div>
</div>
</div>
<!-- Password Dialog -->
<div class="noVNC_center noVNC_connect_layer">
<div id="noVNC_credentials_dlg" class="noVNC_panel"><form>
<ul>
<li id="noVNC_username_block">
<label>Username:</label>
<input id="noVNC_username_input">
</li>
<li id="noVNC_password_block">
<label>Password:</label>
<input id="noVNC_password_input" type="password">
</li>
<li>
<input id="noVNC_credentials_button" type="submit" value="Send Credentials" class="noVNC_submit">
</li>
</ul>
</form></div>
</div>
<!-- Transition Screens -->
<div id="noVNC_transition">
<div id="noVNC_transition_text"></div>
<div>
<input type="button" id="noVNC_cancel_reconnect_button" value="Cancel" class="noVNC_submit">
</div>
<div class="noVNC_spinner"></div>
</div>
<!-- This is where the RFB elements will attach -->
<div id="noVNC_container">
<!-- Note that Google Chrome on Android doesn't respect any of these,
html attributes which attempt to disable text suggestions on the
on-screen keyboard. Let's hope Chrome implements the ime-mode
style for example -->
<textarea id="noVNC_keyboardinput" autocapitalize="off"
autocomplete="off" spellcheck="false" tabindex="-1"></textarea>
</div>
<audio id="noVNC_bell">
<source src="app/sounds/bell.oga" type="audio/ogg">
<source src="app/sounds/bell.mp3" type="audio/mpeg">
</audio>
</body>
</html>

View File

@ -1,5 +1,5 @@
{
"name": "@novnc/novnc",
"name": "@kasmtech/novnc",
"version": "1.2.0",
"description": "An HTML5 VNC client",
"browser": "lib/rfb",
@ -21,26 +21,27 @@
"scripts": {
"lint": "eslint app core po/po2js po/xgettext-html tests utils",
"test": "karma start karma.conf.js",
"prepublish": "node ./utils/use_require.js --clean"
"prepublish": "node ./utils/use_require.js --as commonjs --clean",
"build": "webpack --config webpack.config.js",
"build-production": "cross-env NODE_ENV=production webpack --config webpack.config.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/novnc/noVNC.git"
"url": "git+https://github.com/kasmtech/noVNC.git"
},
"author": "Joel Martin <github@martintribe.org> (https://github.com/kanaka)",
"author": "Kasm Technologies (https://www.kasmweb.com)",
"contributors": [
"Solly Ross <sross@redhat.com> (https://github.com/directxman12)",
"Peter Åstrand <astrand@cendio.se> (https://github.com/astrand)",
"Samuel Mannehed <samuel@cendio.se> (https://github.com/samhed)",
"Pierre Ossman <ossman@cendio.se> (https://github.com/CendioOssman)"
],
"license": "MPL-2.0",
"bugs": {
"url": "https://github.com/novnc/noVNC/issues"
"url": "https://github.com/kasmtech/noVNC/issues"
},
"homepage": "https://github.com/novnc/noVNC",
"homepage": "https://github.com/kasmtech/noVNC",
"devDependencies": {
"@babel/core": "^7.12.10",
"@babel/preset-env": "^7.12.11",
"@babel/core": "*",
"babel-loader": "^8.2.2",
"@babel/plugin-syntax-dynamic-import": "*",
"@babel/plugin-transform-modules-commonjs": "*",
"@babel/preset-env": "*",
@ -50,10 +51,17 @@
"babelify": "*",
"core-js": "*",
"chai": "*",
"clean-webpack-plugin": "^3.0.0",
"commander": "*",
"css-loader": "^5.0.1",
"css-minimizer-webpack-plugin": "^1.1.5",
"es-module-loader": "*",
"eslint": "*",
"file-loader": "^6.2.0",
"fs-extra": "*",
"html-loader": "^1.3.2",
"html-webpack-inline-svg-plugin": "^2.3.0",
"html-webpack-plugin": "^4.5.0",
"jsdom": "*",
"karma": "*",
"karma-mocha": "*",
@ -65,14 +73,22 @@
"karma-safari-launcher": "*",
"karma-script-launcher": "*",
"karma-sinon-chai": "*",
"mini-css-extract-plugin": "^1.3.3",
"mocha": "*",
"node-getopt": "*",
"po2json": "*",
"postcss-loader": "^4.1.0",
"preload-webpack-plugin": "^3.0.0-beta.4",
"requirejs": "*",
"rollup": "*",
"rollup-plugin-node-resolve": "*",
"sass": "^1.30.0",
"sass-loader": "^10.1.0",
"sinon": "*",
"sinon-chai": "*"
"sinon-chai": "^2.8.0",
"svg-sprite-html-webpack": "^2.3.0",
"webpack": "^4.29.6",
"webpack-cli": "^3.2.3"
},
"dependencies": {},
"keywords": [

255
vendor/promise.js vendored Normal file
View File

@ -0,0 +1,255 @@
/* Copyright (c) 2014 Taylor Hakes
* Copyright (c) 2014 Forbes Lindesay
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
(function (root) {
// Store setTimeout reference so promise-polyfill will be unaffected by
// other code modifying setTimeout (like sinon.useFakeTimers())
var setTimeoutFunc = setTimeout;
function noop() {}
// Polyfill for Function.prototype.bind
function bind(fn, thisArg) {
return function () {
fn.apply(thisArg, arguments);
};
}
function Promise(fn) {
if (typeof this !== 'object') throw new TypeError('Promises must be constructed via new');
if (typeof fn !== 'function') throw new TypeError('not a function');
this._state = 0;
this._handled = false;
this._value = undefined;
this._deferreds = [];
doResolve(fn, this);
}
function handle(self, deferred) {
while (self._state === 3) {
self = self._value;
}
if (self._state === 0) {
self._deferreds.push(deferred);
return;
}
self._handled = true;
Promise._immediateFn(function () {
var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected;
if (cb === null) {
(self._state === 1 ? resolve : reject)(deferred.promise, self._value);
return;
}
var ret;
try {
ret = cb(self._value);
} catch (e) {
reject(deferred.promise, e);
return;
}
resolve(deferred.promise, ret);
});
}
function resolve(self, newValue) {
try {
// Promise Resolution Procedure: https://github.com/promises-aplus/promises-spec#the-promise-resolution-procedure
if (newValue === self) throw new TypeError('A promise cannot be resolved with itself.');
if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) {
var then = newValue.then;
if (newValue instanceof Promise) {
self._state = 3;
self._value = newValue;
finale(self);
return;
} else if (typeof then === 'function') {
doResolve(bind(then, newValue), self);
return;
}
}
self._state = 1;
self._value = newValue;
finale(self);
} catch (e) {
reject(self, e);
}
}
function reject(self, newValue) {
self._state = 2;
self._value = newValue;
finale(self);
}
function finale(self) {
if (self._state === 2 && self._deferreds.length === 0) {
Promise._immediateFn(function() {
if (!self._handled) {
Promise._unhandledRejectionFn(self._value);
}
});
}
for (var i = 0, len = self._deferreds.length; i < len; i++) {
handle(self, self._deferreds[i]);
}
self._deferreds = null;
}
function Handler(onFulfilled, onRejected, promise) {
this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null;
this.onRejected = typeof onRejected === 'function' ? onRejected : null;
this.promise = promise;
}
/**
* Take a potentially misbehaving resolver function and make sure
* onFulfilled and onRejected are only called once.
*
* Makes no guarantees about asynchrony.
*/
function doResolve(fn, self) {
var done = false;
try {
fn(function (value) {
if (done) return;
done = true;
resolve(self, value);
}, function (reason) {
if (done) return;
done = true;
reject(self, reason);
});
} catch (ex) {
if (done) return;
done = true;
reject(self, ex);
}
}
Promise.prototype['catch'] = function (onRejected) {
return this.then(null, onRejected);
};
Promise.prototype.then = function (onFulfilled, onRejected) {
var prom = new (this.constructor)(noop);
handle(this, new Handler(onFulfilled, onRejected, prom));
return prom;
};
Promise.all = function (arr) {
var args = Array.prototype.slice.call(arr);
return new Promise(function (resolve, reject) {
if (args.length === 0) return resolve([]);
var remaining = args.length;
function res(i, val) {
try {
if (val && (typeof val === 'object' || typeof val === 'function')) {
var then = val.then;
if (typeof then === 'function') {
then.call(val, function (val) {
res(i, val);
}, reject);
return;
}
}
args[i] = val;
if (--remaining === 0) {
resolve(args);
}
} catch (ex) {
reject(ex);
}
}
for (var i = 0; i < args.length; i++) {
res(i, args[i]);
}
});
};
Promise.resolve = function (value) {
if (value && typeof value === 'object' && value.constructor === Promise) {
return value;
}
return new Promise(function (resolve) {
resolve(value);
});
};
Promise.reject = function (value) {
return new Promise(function (resolve, reject) {
reject(value);
});
};
Promise.race = function (values) {
return new Promise(function (resolve, reject) {
for (var i = 0, len = values.length; i < len; i++) {
values[i].then(resolve, reject);
}
});
};
// Use polyfill for setImmediate for performance gains
Promise._immediateFn = (typeof setImmediate === 'function' && function (fn) { setImmediate(fn); }) ||
function (fn) {
setTimeoutFunc(fn, 0);
};
Promise._unhandledRejectionFn = function _unhandledRejectionFn(err) {
if (typeof console !== 'undefined' && console) {
console.warn('Possible Unhandled Promise Rejection:', err); // eslint-disable-line no-console
}
};
/**
* Set the immediate function to execute callbacks
* @param fn {function} Function to execute
* @deprecated
*/
Promise._setImmediateFn = function _setImmediateFn(fn) {
Promise._immediateFn = fn;
};
/**
* Change the function to execute on unhandled rejection
* @param {function} fn Function to execute on unhandled rejection
* @deprecated
*/
Promise._setUnhandledRejectionFn = function _setUnhandledRejectionFn(fn) {
Promise._unhandledRejectionFn = fn;
};
if (typeof module !== 'undefined' && module.exports) {
module.exports = Promise;
} else if (!root.Promise) {
root.Promise = Promise;
}
})(this);

View File

@ -13,45 +13,61 @@
or the fragment:
http://example.com/#host=HOST&port=PORT&encrypt=1
-->
<title>noVNC</title>
<title>KasmVNC</title>
<meta charset="utf-8">
<!-- Icons (see app/images/icons/Makefile for what the sizes are for) -->
<link rel="icon" sizes="16x16" type="image/png" href="app/images/icons/novnc-16x16.png">
<link rel="icon" sizes="24x24" type="image/png" href="app/images/icons/novnc-24x24.png">
<link rel="icon" sizes="32x32" type="image/png" href="app/images/icons/novnc-32x32.png">
<link rel="icon" sizes="48x48" type="image/png" href="app/images/icons/novnc-48x48.png">
<link rel="icon" sizes="60x60" type="image/png" href="app/images/icons/novnc-60x60.png">
<link rel="icon" sizes="64x64" type="image/png" href="app/images/icons/novnc-64x64.png">
<link rel="icon" sizes="72x72" type="image/png" href="app/images/icons/novnc-72x72.png">
<link rel="icon" sizes="76x76" type="image/png" href="app/images/icons/novnc-76x76.png">
<link rel="icon" sizes="96x96" type="image/png" href="app/images/icons/novnc-96x96.png">
<link rel="icon" sizes="120x120" type="image/png" href="app/images/icons/novnc-120x120.png">
<link rel="icon" sizes="144x144" type="image/png" href="app/images/icons/novnc-144x144.png">
<link rel="icon" sizes="152x152" type="image/png" href="app/images/icons/novnc-152x152.png">
<link rel="icon" sizes="192x192" type="image/png" href="app/images/icons/novnc-192x192.png">
<link rel="icon" sizes="16x16" type="image/png" href="app/images/icons/368_kasm_logo_only_16x16.png">
<link rel="icon" sizes="24x24" type="image/png" href="app/images/icons/368_kasm_logo_only_24x24.png">
<link rel="icon" sizes="32x32" type="image/png" href="app/images/icons/368_kasm_logo_only_32x32.png">
<link rel="icon" sizes="48x48" type="image/png" href="app/images/icons/368_kasm_logo_only_48x48.png">
<link rel="icon" sizes="60x60" type="image/png" href="app/images/icons/368_kasm_logo_only_60x60.png">
<link rel="icon" sizes="64x64" type="image/png" href="app/images/icons/368_kasm_logo_only_64x64.png">
<link rel="icon" sizes="72x72" type="image/png" href="app/images/icons/368_kasm_logo_only_72x72.png">
<link rel="icon" sizes="76x76" type="image/png" href="app/images/icons/368_kasm_logo_only_76x76.png">
<link rel="icon" sizes="96x96" type="image/png" href="app/images/icons/368_kasm_logo_only_96x96.png">
<link rel="icon" sizes="120x120" type="image/png" href="app/images/icons/368_kasm_logo_only_120x120.png">
<link rel="icon" sizes="144x144" type="image/png" href="app/images/icons/368_kasm_logo_only_144x144.png">
<link rel="icon" sizes="152x152" type="image/png" href="app/images/icons/368_kasm_logo_only_152x152.png">
<link rel="icon" sizes="192x192" type="image/png" href="app/images/icons/368_kasm_logo_only_192x192.png">
<!-- Firefox currently mishandles SVG, see #1419039
<link rel="icon" sizes="any" type="image/svg+xml" href="app/images/icons/novnc-icon.svg">
<link rel="icon" sizes="any" type="image/svg+xml" href="app/images/icons/kasm-icon.svg">
-->
<!-- Repeated last so that legacy handling will pick this -->
<link rel="icon" sizes="16x16" type="image/png" href="app/images/icons/novnc-16x16.png">
<link rel="icon" sizes="16x16" type="image/png" href="app/images/icons/368_kasm_logo_only_16x16.png">
<!-- Apple iOS Safari settings -->
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<!-- Home Screen Icons (favourites and bookmarks use the normal icons) -->
<link rel="apple-touch-icon" sizes="60x60" type="image/png" href="app/images/icons/novnc-60x60.png">
<link rel="apple-touch-icon" sizes="76x76" type="image/png" href="app/images/icons/novnc-76x76.png">
<link rel="apple-touch-icon" sizes="120x120" type="image/png" href="app/images/icons/novnc-120x120.png">
<link rel="apple-touch-icon" sizes="152x152" type="image/png" href="app/images/icons/novnc-152x152.png">
<link rel="apple-touch-icon" sizes="60x60" type="image/png" href="app/images/icons/368_kasm_logo_only_60x60.png">
<link rel="apple-touch-icon" sizes="76x76" type="image/png" href="app/images/icons/368_kasm_logo_only_76x76.png">
<link rel="apple-touch-icon" sizes="120x120" type="image/png" href="app/images/icons/368_kasm_logo_only_120x120.png">
<link rel="apple-touch-icon" sizes="152x152" type="image/png" href="app/images/icons/368_kasm_logo_only_152x152.png">
<!-- Stylesheets -->
<link rel="stylesheet" href="app/styles/base.css">
<script src="app/error-handler.js"></script>
<script type="module" crossorigin="anonymous" src="app/ui.js"></script>
<script>
let isInsideKasmVDI = false;
try {
isInsideKasmVDI = (window.self !== window.top);
} catch (e) {
isInsideKasmVDI = true;
}
if (!isInsideKasmVDI) {
window.addEventListener("load", function() {
document.getElementById("noVNC_connect_button").click();
});
}
</script>
<script type="module" crossorigin="use-credentials" src="app/ui.js"></script>
</head>
<body>
@ -64,6 +80,10 @@
</div>
</div>
<div id="noVNC_connection_stats">
Loading statistics...
</div>
<!-- noVNC Control Bar -->
<div id="noVNC_control_bar_anchor" class="noVNC_vcenter">
@ -72,7 +92,7 @@
<div class="noVNC_scroll">
<h1 class="noVNC_logo" translate="no"><span>no</span><br>VNC</h1>
<h1 class="noVNC_logo"><img src="app/images/icons/368_kasm_logo_only_24x24.png" /></h1>
<!-- Drag/Pan the viewport -->
<input type="image" alt="Drag" src="app/images/drag.svg"
@ -162,7 +182,34 @@
<label><input id="noVNC_setting_shared" type="checkbox"> Shared Mode</label>
</li>
<li>
<label><input id="noVNC_setting_view_only" type="checkbox"> View Only</label>
<label><input id="noVNC_setting_view_only" type="checkbox" /> View Only</label></li>
<li>
<label><input id="noVNC_setting_clipboard_up" type="checkbox" /> Clipboard Up</label></li>
<li>
<label><input id="noVNC_setting_clipboard_down" type="checkbox" /> Clipboard Down</label></li>
<li>
<label><input id="noVNC_setting_clipboard_seamless" type="checkbox" /> Clipboard Seamless</label></li>
<li>
<label><input id="noVNC_setting_prefer_local_cursor" type="checkbox" /> Prefer Local Cursor</label></li>
<li>
<label><input id="noVNC_setting_enable_webp" type="checkbox" /> Enable WebP Compression</label></li>
<li>
<label><input id="noVNC_setting_enable_perf_stats" type="checkbox" /> Enable Performance Stats</label></li>
<li>
<label><input id="noVNC_setting_toggle_control_panel" type="checkbox" /> Toggle Control Panel via Keystrokes</label></li>
<li>
<label for="noVNC_setting_video_quality">Video Quality:</label>
<select id="noVNC_setting_video_quality" name="vncVideoQuality">
<option value=0>Low</option>
<option value=1>Medium</option>
<option value=2>High</option>
</select>
</li>
<li>
<label for="noVNC_setting_idle_disconnect">Idle Timeout:</label>
<select id="noVNC_setting_idle_disconnect" name="vncIdleDisconnect">
<option value=20>20</option>
</select>
</li>
<li><hr></li>
<li>
@ -257,12 +304,12 @@
</div> <!-- End of noVNC_control_bar -->
<!-- Status Dialog -->
<div id="noVNC_status"></div>
<div style="visibility: hidden" id="noVNC_status"></div>
<!-- Connect button -->
<div class="noVNC_center">
<div id="noVNC_connect_dlg">
<div class="noVNC_logo" translate="no"><span>no</span>VNC</div>
<!--div class="noVNC_logo" translate="no"><span>no</span>VNC</div-->
<div id="noVNC_connect_button"><div>
<img alt="" src="app/images/connect.svg"> Connect
</div></div>

View File

@ -14,7 +14,7 @@
Connect parameters are provided in query string:
http://example.com/?host=HOST&port=PORT&scale=true
-->
<title>noVNC</title>
<title>KasmVNC</title>
<meta charset="utf-8">

138
webpack.config.js Normal file
View File

@ -0,0 +1,138 @@
const path = require('path');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const HtmlWebpackInlineSVGPlugin = require('html-webpack-inline-svg-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
// const SvgSpriteHtmlWebpackPlugin = require('svg-sprite-html-webpack');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
module.exports = {
mode: "production",
entry: {
main: './app/ui.js',
error_handler: './app/error-handler.js',
promise: './vendor/promise.js',
style: './app/styles/base.css'
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].bundle.js'
},
module: {
rules: [
{
test: /\.js$/,
exclude: /(node_modules)/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
},
{
test: /\.(sa|sc|c)ss$/,
use: [
{
loader: MiniCssExtractPlugin.loader
},
{
loader: "css-loader",
},
// {
// loader: "postcss-loader"
// },
{
loader: "sass-loader",
options: {
implementation: require("sass")
}
}
]
},
{
// Now we apply rule for images
test: /\.(png|jpe?g|gif|svg)$/,
use: [
{
// Using file-loader for these files
loader: "file-loader",
// In options we can set different things like format
// and directory to save
options: {
outputPath: 'images'
}
}
]
},
{
// Apply rule for fonts files
test: /\.(woff|woff2|ttf|otf|eot)$/,
use: [
{
// Using file-loader too
loader: "file-loader",
options: {
outputPath: 'fonts'
}
}
]
},
// {
// test: /\.svg$/,
// exclude: /node_modules/,
// use: SvgSpriteHtmlWebpackPlugin.getLoader(),
// }
]
},
optimization: {
minimize: true,
minimizer: [
new CssMinimizerPlugin(),
],
runtimeChunk: 'single',
splitChunks: {
chunks: 'all',
},
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
filename: '../index.html',
template: 'load.html',
minify: {
html5: true,
collapseWhitespace: true,
minifyCSS: true,
minifyJS: true,
minifyURLs: false,
removeAttributeQuotes: true,
removeComments: true, // false for Vue SSR to find app placeholder
removeEmptyAttributes: true,
removeOptionalTags: true,
removeRedundantAttributes: true,
removeScriptTypeAttributes: true,
removeStyleLinkTypeAttributese: true,
useShortDoctype: true
}
}),
// new SvgSpriteHtmlWebpackPlugin({
// append: true,
// includeFiles: [
// 'app/images/*.svg',
// ],
// generateSymbolId: function(svgFilePath, svgHash, svgContent) {
// return svgHash.toString();
// },
// }),
new HtmlWebpackInlineSVGPlugin({
inlineAll: true,
runPreEmit: true,
}),
new MiniCssExtractPlugin({
filename: "[name].bundle.css"
}),
],
};