|
@ -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.
|
||||
|
|
After Width: | Height: | Size: 123 B |
After Width: | Height: | Size: 9.1 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 4.2 KiB |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 4.9 KiB |
After Width: | Height: | Size: 5.1 KiB |
After Width: | Height: | Size: 6.2 KiB |
|
@ -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 "$<" "$@"
|
||||
|
||||
|
|
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 137 B |
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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": "σύρσιμο θεατού πεδίου",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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": "ビューポートを移動/ドラッグ",
|
||||
|
|
|
@ -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": "뷰포트 드래그",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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": "Переместить окно",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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": "显示范围拖放",
|
||||
|
|
|
@ -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": "顯示範圍拖放",
|
||||
|
|
|
@ -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;*/
|
||||
}
|
||||
|
|
303
app/ui.js
|
@ -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) {
|
||||
Log.Debug(">> UI.clipboardReceive: " + e.detail.text.substr(0, 40) + "...");
|
||||
document.getElementById('noVNC_clipboard_text').value = e.detail.text;
|
||||
Log.Debug("<< UI.clipboardReceive");
|
||||
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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
|
238
core/rfb.js
|
@ -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;
|
||||
}
|
||||
|
||||
this._display.flip();
|
||||
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;
|
||||
|
|
|
@ -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>
|
38
package.json
|
@ -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": [
|
||||
|
|
|
@ -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);
|
97
vnc.html
|
@ -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>
|
||||
|
|
|
@ -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">
|
||||
|
||||
|
|
|
@ -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"
|
||||
}),
|
||||
],
|
||||
};
|