Merge remote-tracking branch 'origin/master' into feature/support-existing-rtcdatachannel-or-websocket
* origin/master: Fix focus problem after closing the toolbar Set a default value for the quality input Add UI for quality setting adds qualityLevel property to RFB class for updating JPEG quality level encoding on the fly Hide clipboard side bar button when view only mode Fix crash with too large clipboard data Style all input types for consistent UI Add extended clipboard Pseudo-Encoding Export constants in inflate.js for easier usage Fix bug where inflate would read too much data Split api of inflate Handle errors from zlib/pako Move error handling to Inflate class Add missing copyright header for Inflator.js Remove unused inflate argument Add util for unsigned and signed int. conversion Add deflator helper class for deflating data Make clipBoardPasteFrom() test more specific Correcting path to package.json for running at a path other than root.
This commit is contained in:
commit
c5ca2be33e
|
|
@ -83,8 +83,20 @@ html {
|
|||
* ----------------------------------------
|
||||
*/
|
||||
|
||||
input[type=input], input[type=password], input[type=number],
|
||||
input:not([type]), textarea {
|
||||
input:not([type]),
|
||||
input[type=date],
|
||||
input[type=datetime-local],
|
||||
input[type=email],
|
||||
input[type=month],
|
||||
input[type=number],
|
||||
input[type=password],
|
||||
input[type=search],
|
||||
input[type=tel],
|
||||
input[type=text],
|
||||
input[type=time],
|
||||
input[type=url],
|
||||
input[type=week],
|
||||
textarea {
|
||||
/* Disable default rendering */
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
|
|
@ -98,7 +110,11 @@ input:not([type]), textarea {
|
|||
background: linear-gradient(to top, rgb(255, 255, 255) 80%, rgb(240, 240, 240));
|
||||
}
|
||||
|
||||
input[type=button], input[type=submit], select {
|
||||
input[type=button],
|
||||
input[type=color],
|
||||
input[type=reset],
|
||||
input[type=submit],
|
||||
select {
|
||||
/* Disable default rendering */
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
|
|
@ -116,7 +132,10 @@ input[type=button], input[type=submit], select {
|
|||
vertical-align: middle;
|
||||
}
|
||||
|
||||
input[type=button], input[type=submit] {
|
||||
input[type=button],
|
||||
input[type=color],
|
||||
input[type=reset],
|
||||
input[type=submit] {
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
|
@ -126,35 +145,72 @@ option {
|
|||
background: white;
|
||||
}
|
||||
|
||||
input[type=input]:focus, input[type=password]:focus,
|
||||
input:not([type]):focus, input[type=button]:focus,
|
||||
input:not([type]):focus,
|
||||
input[type=button]:focus,
|
||||
input[type=color]:focus,
|
||||
input[type=date]:focus,
|
||||
input[type=datetime-local]:focus,
|
||||
input[type=email]:focus,
|
||||
input[type=month]:focus,
|
||||
input[type=number]:focus,
|
||||
input[type=password]:focus,
|
||||
input[type=reset]:focus,
|
||||
input[type=search]:focus,
|
||||
input[type=submit]:focus,
|
||||
textarea:focus, select:focus {
|
||||
input[type=tel]:focus,
|
||||
input[type=text]:focus,
|
||||
input[type=time]:focus,
|
||||
input[type=url]:focus,
|
||||
input[type=week]:focus,
|
||||
select:focus,
|
||||
textarea:focus {
|
||||
box-shadow: 0px 0px 3px rgba(74, 144, 217, 0.5);
|
||||
border-color: rgb(74, 144, 217);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
input[type=button]::-moz-focus-inner,
|
||||
input[type=color]::-moz-focus-inner,
|
||||
input[type=reset]::-moz-focus-inner,
|
||||
input[type=submit]::-moz-focus-inner {
|
||||
border: none;
|
||||
}
|
||||
|
||||
input[type=input]:disabled, input[type=password]:disabled,
|
||||
input:not([type]):disabled, input[type=button]:disabled,
|
||||
input[type=submit]:disabled, input[type=number]:disabled,
|
||||
textarea:disabled, select:disabled {
|
||||
input:not([type]):disabled,
|
||||
input[type=button]:disabled,
|
||||
input[type=color]:disabled,
|
||||
input[type=date]:disabled,
|
||||
input[type=datetime-local]:disabled,
|
||||
input[type=email]:disabled,
|
||||
input[type=month]:disabled,
|
||||
input[type=number]:disabled,
|
||||
input[type=password]:disabled,
|
||||
input[type=reset]:disabled,
|
||||
input[type=search]:disabled,
|
||||
input[type=submit]:disabled,
|
||||
input[type=tel]:disabled,
|
||||
input[type=text]:disabled,
|
||||
input[type=time]:disabled,
|
||||
input[type=url]:disabled,
|
||||
input[type=week]:disabled,
|
||||
select:disabled,
|
||||
textarea:disabled {
|
||||
color: rgb(128, 128, 128);
|
||||
background: rgb(240, 240, 240);
|
||||
}
|
||||
|
||||
input[type=button]:active, input[type=submit]:active,
|
||||
input[type=button]:active,
|
||||
input[type=color]:active,
|
||||
input[type=reset]:active,
|
||||
input[type=submit]:active,
|
||||
select:active {
|
||||
border-bottom-width: 1px;
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
:root:not(.noVNC_touch) input[type=button]:hover:not(:disabled),
|
||||
:root:not(.noVNC_touch) input[type=color]:hover:not(:disabled),
|
||||
:root:not(.noVNC_touch) input[type=reset]:hover:not(:disabled),
|
||||
:root:not(.noVNC_touch) input[type=submit]:hover:not(:disabled),
|
||||
:root:not(.noVNC_touch) select:hover:not(:disabled) {
|
||||
background: linear-gradient(to top, rgb(255, 255, 255), rgb(250, 250, 250));
|
||||
|
|
|
|||
24
app/ui.js
24
app/ui.js
|
|
@ -61,7 +61,7 @@ const UI = {
|
|||
// Translate the DOM
|
||||
l10n.translateDOM();
|
||||
|
||||
WebUtil.fetchJSON('../package.json')
|
||||
WebUtil.fetchJSON('./package.json')
|
||||
.then((packageInfo) => {
|
||||
Array.from(document.getElementsByClassName('noVNC_version')).forEach(el => el.innerText = packageInfo.version);
|
||||
})
|
||||
|
|
@ -161,6 +161,7 @@ const UI = {
|
|||
UI.initSetting('encrypt', (window.location.protocol === "https:"));
|
||||
UI.initSetting('view_clip', false);
|
||||
UI.initSetting('resize', 'off');
|
||||
UI.initSetting('quality', 6);
|
||||
UI.initSetting('shared', true);
|
||||
UI.initSetting('view_only', false);
|
||||
UI.initSetting('show_dot', false);
|
||||
|
|
@ -347,6 +348,8 @@ const UI = {
|
|||
UI.addSettingChangeHandler('resize');
|
||||
UI.addSettingChangeHandler('resize', UI.applyResizeMode);
|
||||
UI.addSettingChangeHandler('resize', UI.updateViewClip);
|
||||
UI.addSettingChangeHandler('quality');
|
||||
UI.addSettingChangeHandler('quality', UI.updateQuality);
|
||||
UI.addSettingChangeHandler('view_clip');
|
||||
UI.addSettingChangeHandler('view_clip', UI.updateViewClip);
|
||||
UI.addSettingChangeHandler('shared');
|
||||
|
|
@ -532,6 +535,7 @@ const UI = {
|
|||
UI.closeAllPanels();
|
||||
document.getElementById('noVNC_control_bar')
|
||||
.classList.remove("noVNC_open");
|
||||
UI.rfb.focus();
|
||||
},
|
||||
|
||||
toggleControlbar() {
|
||||
|
|
@ -829,6 +833,7 @@ const UI = {
|
|||
UI.updateSetting('encrypt');
|
||||
UI.updateSetting('view_clip');
|
||||
UI.updateSetting('resize');
|
||||
UI.updateSetting('quality');
|
||||
UI.updateSetting('shared');
|
||||
UI.updateSetting('view_only');
|
||||
UI.updateSetting('path');
|
||||
|
|
@ -1030,6 +1035,7 @@ const UI = {
|
|||
UI.rfb.clipViewport = UI.getSetting('view_clip');
|
||||
UI.rfb.scaleViewport = UI.getSetting('resize') === 'scale';
|
||||
UI.rfb.resizeSession = UI.getSetting('resize') === 'remote';
|
||||
UI.rfb.qualityLevel = parseInt(UI.getSetting('quality'));
|
||||
UI.rfb.showDotCursor = UI.getSetting('show_dot');
|
||||
|
||||
UI.updateViewOnly(); // requires UI.rfb
|
||||
|
|
@ -1324,6 +1330,18 @@ const UI = {
|
|||
/* ------^-------
|
||||
* /VIEWDRAG
|
||||
* ==============
|
||||
* QUALITY
|
||||
* ------v------*/
|
||||
|
||||
updateQuality() {
|
||||
if (!UI.rfb) return;
|
||||
|
||||
UI.rfb.qualityLevel = parseInt(UI.getSetting('quality'));
|
||||
},
|
||||
|
||||
/* ------^-------
|
||||
* /QUALITY
|
||||
* ==============
|
||||
* KEYBOARD
|
||||
* ------v------*/
|
||||
|
||||
|
|
@ -1621,6 +1639,8 @@ const UI = {
|
|||
.classList.add('noVNC_hidden');
|
||||
document.getElementById('noVNC_mouse_button' + UI.rfb.touchButton)
|
||||
.classList.add('noVNC_hidden');
|
||||
document.getElementById('noVNC_clipboard_button')
|
||||
.classList.add('noVNC_hidden');
|
||||
} else {
|
||||
document.getElementById('noVNC_keyboard_button')
|
||||
.classList.remove('noVNC_hidden');
|
||||
|
|
@ -1628,6 +1648,8 @@ const UI = {
|
|||
.classList.remove('noVNC_hidden');
|
||||
document.getElementById('noVNC_mouse_button' + UI.rfb.touchButton)
|
||||
.classList.remove('noVNC_hidden');
|
||||
document.getElementById('noVNC_clipboard_button')
|
||||
.classList.remove('noVNC_hidden');
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -160,10 +160,9 @@ export default class TightDecoder {
|
|||
return false;
|
||||
}
|
||||
|
||||
data = this._zlibs[streamId].inflate(data, true, uncompressedSize);
|
||||
if (data.length != uncompressedSize) {
|
||||
throw new Error("Incomplete zlib block");
|
||||
}
|
||||
this._zlibs[streamId].setInput(data);
|
||||
data = this._zlibs[streamId].inflate(uncompressedSize);
|
||||
this._zlibs[streamId].setInput(null);
|
||||
}
|
||||
|
||||
display.blitRgbImage(x, y, width, height, data, 0, false);
|
||||
|
|
@ -208,10 +207,9 @@ export default class TightDecoder {
|
|||
return false;
|
||||
}
|
||||
|
||||
data = this._zlibs[streamId].inflate(data, true, uncompressedSize);
|
||||
if (data.length != uncompressedSize) {
|
||||
throw new Error("Incomplete zlib block");
|
||||
}
|
||||
this._zlibs[streamId].setInput(data);
|
||||
data = this._zlibs[streamId].inflate(uncompressedSize);
|
||||
this._zlibs[streamId].setInput(null);
|
||||
}
|
||||
|
||||
// Convert indexed (palette based) image data to RGB
|
||||
|
|
|
|||
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* noVNC: HTML5 VNC client
|
||||
* Copyright (C) 2020 The noVNC Authors
|
||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||
*
|
||||
* See README.md for usage and integration instructions.
|
||||
*/
|
||||
|
||||
import { deflateInit, deflate } from "../vendor/pako/lib/zlib/deflate.js";
|
||||
import { Z_FULL_FLUSH } from "../vendor/pako/lib/zlib/deflate.js";
|
||||
import ZStream from "../vendor/pako/lib/zlib/zstream.js";
|
||||
|
||||
export default class Deflator {
|
||||
constructor() {
|
||||
this.strm = new ZStream();
|
||||
this.chunkSize = 1024 * 10 * 10;
|
||||
this.outputBuffer = new Uint8Array(this.chunkSize);
|
||||
this.windowBits = 5;
|
||||
|
||||
deflateInit(this.strm, this.windowBits);
|
||||
}
|
||||
|
||||
deflate(inData) {
|
||||
this.strm.input = inData;
|
||||
this.strm.avail_in = this.strm.input.length;
|
||||
this.strm.next_in = 0;
|
||||
this.strm.output = this.outputBuffer;
|
||||
this.strm.avail_out = this.chunkSize;
|
||||
this.strm.next_out = 0;
|
||||
|
||||
let lastRet = deflate(this.strm, Z_FULL_FLUSH);
|
||||
let outData = new Uint8Array(this.strm.output.buffer, 0, this.strm.next_out);
|
||||
|
||||
if (lastRet < 0) {
|
||||
throw new Error("zlib deflate failed");
|
||||
}
|
||||
|
||||
if (this.strm.avail_in > 0) {
|
||||
// Read chunks until done
|
||||
|
||||
let chunks = [outData];
|
||||
let totalLen = outData.length;
|
||||
do {
|
||||
this.strm.output = new Uint8Array(this.chunkSize);
|
||||
this.strm.next_out = 0;
|
||||
this.strm.avail_out = this.chunkSize;
|
||||
|
||||
lastRet = deflate(this.strm, Z_FULL_FLUSH);
|
||||
|
||||
if (lastRet < 0) {
|
||||
throw new Error("zlib deflate failed");
|
||||
}
|
||||
|
||||
let chunk = new Uint8Array(this.strm.output.buffer, 0, this.strm.next_out);
|
||||
totalLen += chunk.length;
|
||||
chunks.push(chunk);
|
||||
} while (this.strm.avail_in > 0);
|
||||
|
||||
// Combine chunks into a single data
|
||||
|
||||
let newData = new Uint8Array(totalLen);
|
||||
let offset = 0;
|
||||
|
||||
for (let i = 0; i < chunks.length; i++) {
|
||||
newData.set(chunks[i], offset);
|
||||
offset += chunks[i].length;
|
||||
}
|
||||
|
||||
outData = newData;
|
||||
}
|
||||
|
||||
this.strm.input = null;
|
||||
this.strm.avail_in = 0;
|
||||
this.strm.next_in = 0;
|
||||
|
||||
return outData;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -27,7 +27,8 @@ export const encodings = {
|
|||
pseudoEncodingContinuousUpdates: -313,
|
||||
pseudoEncodingCompressLevel9: -247,
|
||||
pseudoEncodingCompressLevel0: -256,
|
||||
pseudoEncodingVMwareCursor: 0x574d5664
|
||||
pseudoEncodingVMwareCursor: 0x574d5664,
|
||||
pseudoEncodingExtendedClipboard: 0xc0a1e5ce
|
||||
};
|
||||
|
||||
export function encodingName(num) {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,11 @@
|
|||
/*
|
||||
* noVNC: HTML5 VNC client
|
||||
* Copyright (C) 2020 The noVNC Authors
|
||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||
*
|
||||
* See README.md for usage and integration instructions.
|
||||
*/
|
||||
|
||||
import { inflateInit, inflate, inflateReset } from "../vendor/pako/lib/zlib/inflate.js";
|
||||
import ZStream from "../vendor/pako/lib/zlib/zstream.js";
|
||||
|
||||
|
|
@ -11,12 +19,20 @@ export default class Inflate {
|
|||
inflateInit(this.strm, this.windowBits);
|
||||
}
|
||||
|
||||
inflate(data, flush, expected) {
|
||||
this.strm.input = data;
|
||||
this.strm.avail_in = this.strm.input.length;
|
||||
this.strm.next_in = 0;
|
||||
this.strm.next_out = 0;
|
||||
setInput(data) {
|
||||
if (!data) {
|
||||
//FIXME: flush remaining data.
|
||||
this.strm.input = null;
|
||||
this.strm.avail_in = 0;
|
||||
this.strm.next_in = 0;
|
||||
} else {
|
||||
this.strm.input = data;
|
||||
this.strm.avail_in = this.strm.input.length;
|
||||
this.strm.next_in = 0;
|
||||
}
|
||||
}
|
||||
|
||||
inflate(expected) {
|
||||
// resize our output buffer if it's too small
|
||||
// (we could just use multiple chunks, but that would cause an extra
|
||||
// allocation each time to flatten the chunks)
|
||||
|
|
@ -25,9 +41,17 @@ export default class Inflate {
|
|||
this.strm.output = new Uint8Array(this.chunkSize);
|
||||
}
|
||||
|
||||
this.strm.avail_out = this.chunkSize;
|
||||
this.strm.next_out = 0;
|
||||
this.strm.avail_out = expected;
|
||||
|
||||
inflate(this.strm, flush);
|
||||
let ret = inflate(this.strm, 0); // Flush argument not used.
|
||||
if (ret < 0) {
|
||||
throw new Error("zlib inflate failed");
|
||||
}
|
||||
|
||||
if (this.strm.next_out != expected) {
|
||||
throw new Error("Incomplete zlib block");
|
||||
}
|
||||
|
||||
return new Uint8Array(this.strm.output.buffer, 0, this.strm.next_out);
|
||||
}
|
||||
|
|
|
|||
348
core/rfb.js
348
core/rfb.js
|
|
@ -1,17 +1,20 @@
|
|||
/*
|
||||
* noVNC: HTML5 VNC client
|
||||
* Copyright (C) 2019 The noVNC Authors
|
||||
* Copyright (C) 2020 The noVNC Authors
|
||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||
*
|
||||
* See README.md for usage and integration instructions.
|
||||
*
|
||||
*/
|
||||
|
||||
import { toUnsigned32bit, toSigned32bit } from './util/int.js';
|
||||
import * as Log from './util/logging.js';
|
||||
import { decodeUTF8 } from './util/strings.js';
|
||||
import { encodeUTF8, decodeUTF8 } from './util/strings.js';
|
||||
import { dragThreshold } from './util/browser.js';
|
||||
import EventTargetMixin from './util/eventtarget.js';
|
||||
import Display from "./display.js";
|
||||
import Inflator from "./inflator.js";
|
||||
import Deflator from "./deflator.js";
|
||||
import Keyboard from "./input/keyboard.js";
|
||||
import Mouse from "./input/mouse.js";
|
||||
import Cursor from "./util/cursor.js";
|
||||
|
|
@ -33,6 +36,23 @@ import TightPNGDecoder from "./decoders/tightpng.js";
|
|||
const DISCONNECT_TIMEOUT = 3;
|
||||
const DEFAULT_BACKGROUND = 'rgb(40, 40, 40)';
|
||||
|
||||
// Extended clipboard pseudo-encoding formats
|
||||
const extendedClipboardFormatText = 1;
|
||||
/*eslint-disable no-unused-vars */
|
||||
const extendedClipboardFormatRtf = 1 << 1;
|
||||
const extendedClipboardFormatHtml = 1 << 2;
|
||||
const extendedClipboardFormatDib = 1 << 3;
|
||||
const extendedClipboardFormatFiles = 1 << 4;
|
||||
/*eslint-enable */
|
||||
|
||||
// Extended clipboard pseudo-encoding actions
|
||||
const extendedClipboardActionCaps = 1 << 24;
|
||||
const extendedClipboardActionRequest = 1 << 25;
|
||||
const extendedClipboardActionPeek = 1 << 26;
|
||||
const extendedClipboardActionNotify = 1 << 27;
|
||||
const extendedClipboardActionProvide = 1 << 28;
|
||||
|
||||
|
||||
export default class RFB extends EventTargetMixin {
|
||||
constructor(target, urlOrChannel, options) {
|
||||
if (!target) {
|
||||
|
|
@ -84,6 +104,10 @@ export default class RFB extends EventTargetMixin {
|
|||
|
||||
this._qemuExtKeyEventSupported = false;
|
||||
|
||||
this._clipboardText = null;
|
||||
this._clipboardServerCapabilitiesActions = {};
|
||||
this._clipboardServerCapabilitiesFormats = {};
|
||||
|
||||
// Internal objects
|
||||
this._sock = null; // WebSocket or RTCDataChannel object
|
||||
this._display = null; // Display object
|
||||
|
|
@ -251,6 +275,8 @@ export default class RFB extends EventTargetMixin {
|
|||
Log.Warn("Specifying showDotCursor as a RFB constructor argument is deprecated");
|
||||
this._showDotCursor = options.showDotCursor;
|
||||
}
|
||||
|
||||
this._qualityLevel = 6;
|
||||
}
|
||||
|
||||
// ===== PROPERTIES =====
|
||||
|
|
@ -313,6 +339,26 @@ export default class RFB extends EventTargetMixin {
|
|||
get background() { return this._screen.style.background; }
|
||||
set background(cssValue) { this._screen.style.background = cssValue; }
|
||||
|
||||
get qualityLevel() {
|
||||
return this._qualityLevel;
|
||||
}
|
||||
set qualityLevel(qualityLevel) {
|
||||
if (!Number.isInteger(qualityLevel) || qualityLevel < 0 || qualityLevel > 9) {
|
||||
Log.Error("qualityLevel must be an integer between 0 and 9");
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._qualityLevel === qualityLevel) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._qualityLevel = qualityLevel;
|
||||
|
||||
if (this._rfb_connection_state === 'connected') {
|
||||
this._sendEncodings();
|
||||
}
|
||||
}
|
||||
|
||||
// ===== PUBLIC METHODS =====
|
||||
|
||||
disconnect() {
|
||||
|
|
@ -390,7 +436,21 @@ export default class RFB extends EventTargetMixin {
|
|||
|
||||
clipboardPasteFrom(text) {
|
||||
if (this._rfb_connection_state !== 'connected' || this._viewOnly) { return; }
|
||||
RFB.messages.clientCutText(this._sock, text);
|
||||
|
||||
if (this._clipboardServerCapabilitiesFormats[extendedClipboardFormatText] &&
|
||||
this._clipboardServerCapabilitiesActions[extendedClipboardActionNotify]) {
|
||||
|
||||
this._clipboardText = text;
|
||||
RFB.messages.extendedClipboardNotify(this._sock, [extendedClipboardFormatText]);
|
||||
} else {
|
||||
let data = new Uint8Array(text.length);
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
// FIXME: text can have values outside of Latin1/Uint8
|
||||
data[i] = text.charCodeAt(i);
|
||||
}
|
||||
|
||||
RFB.messages.clientCutText(this._sock, data);
|
||||
}
|
||||
}
|
||||
|
||||
// ===== PRIVATE METHODS =====
|
||||
|
|
@ -1267,7 +1327,7 @@ export default class RFB extends EventTargetMixin {
|
|||
encs.push(encodings.encodingRaw);
|
||||
|
||||
// Psuedo-encoding settings
|
||||
encs.push(encodings.pseudoEncodingQualityLevel0 + 6);
|
||||
encs.push(encodings.pseudoEncodingQualityLevel0 + this._qualityLevel);
|
||||
encs.push(encodings.pseudoEncodingCompressLevel0 + 2);
|
||||
|
||||
encs.push(encodings.pseudoEncodingDesktopSize);
|
||||
|
|
@ -1278,6 +1338,7 @@ export default class RFB extends EventTargetMixin {
|
|||
encs.push(encodings.pseudoEncodingFence);
|
||||
encs.push(encodings.pseudoEncodingContinuousUpdates);
|
||||
encs.push(encodings.pseudoEncodingDesktopName);
|
||||
encs.push(encodings.pseudoEncodingExtendedClipboard);
|
||||
|
||||
if (this._fb_depth == 24) {
|
||||
encs.push(encodings.pseudoEncodingVMwareCursor);
|
||||
|
|
@ -1336,18 +1397,167 @@ export default class RFB extends EventTargetMixin {
|
|||
Log.Debug("ServerCutText");
|
||||
|
||||
if (this._sock.rQwait("ServerCutText header", 7, 1)) { return false; }
|
||||
|
||||
this._sock.rQskipBytes(3); // Padding
|
||||
const length = this._sock.rQshift32();
|
||||
if (this._sock.rQwait("ServerCutText", length, 8)) { return false; }
|
||||
|
||||
const text = this._sock.rQshiftStr(length);
|
||||
let length = this._sock.rQshift32();
|
||||
length = toSigned32bit(length);
|
||||
|
||||
if (this._viewOnly) { return true; }
|
||||
if (this._sock.rQwait("ServerCutText content", Math.abs(length), 8)) { return false; }
|
||||
|
||||
this.dispatchEvent(new CustomEvent(
|
||||
"clipboard",
|
||||
{ detail: { text: text } }));
|
||||
if (length >= 0) {
|
||||
//Standard msg
|
||||
const text = this._sock.rQshiftStr(length);
|
||||
if (this._viewOnly) {
|
||||
return true;
|
||||
}
|
||||
|
||||
this.dispatchEvent(new CustomEvent(
|
||||
"clipboard",
|
||||
{ detail: { text: text } }));
|
||||
|
||||
} else {
|
||||
//Extended msg.
|
||||
length = Math.abs(length);
|
||||
const flags = this._sock.rQshift32();
|
||||
let formats = flags & 0x0000FFFF;
|
||||
let actions = flags & 0xFF000000;
|
||||
|
||||
let isCaps = (!!(actions & extendedClipboardActionCaps));
|
||||
if (isCaps) {
|
||||
this._clipboardServerCapabilitiesFormats = {};
|
||||
this._clipboardServerCapabilitiesActions = {};
|
||||
|
||||
// Update our server capabilities for Formats
|
||||
for (let i = 0; i <= 15; i++) {
|
||||
let index = 1 << i;
|
||||
|
||||
// Check if format flag is set.
|
||||
if ((formats & index)) {
|
||||
this._clipboardServerCapabilitiesFormats[index] = true;
|
||||
// We don't send unsolicited clipboard, so we
|
||||
// ignore the size
|
||||
this._sock.rQshift32();
|
||||
}
|
||||
}
|
||||
|
||||
// Update our server capabilities for Actions
|
||||
for (let i = 24; i <= 31; i++) {
|
||||
let index = 1 << i;
|
||||
this._clipboardServerCapabilitiesActions[index] = !!(actions & index);
|
||||
}
|
||||
|
||||
/* Caps handling done, send caps with the clients
|
||||
capabilities set as a response */
|
||||
let clientActions = [
|
||||
extendedClipboardActionCaps,
|
||||
extendedClipboardActionRequest,
|
||||
extendedClipboardActionPeek,
|
||||
extendedClipboardActionNotify,
|
||||
extendedClipboardActionProvide
|
||||
];
|
||||
RFB.messages.extendedClipboardCaps(this._sock, clientActions, {extendedClipboardFormatText: 0});
|
||||
|
||||
} else if (actions === extendedClipboardActionRequest) {
|
||||
if (this._viewOnly) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if server has told us it can handle Provide and there is clipboard data to send.
|
||||
if (this._clipboardText != null &&
|
||||
this._clipboardServerCapabilitiesActions[extendedClipboardActionProvide]) {
|
||||
|
||||
if (formats & extendedClipboardFormatText) {
|
||||
RFB.messages.extendedClipboardProvide(this._sock, [extendedClipboardFormatText], [this._clipboardText]);
|
||||
}
|
||||
}
|
||||
|
||||
} else if (actions === extendedClipboardActionPeek) {
|
||||
if (this._viewOnly) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this._clipboardServerCapabilitiesActions[extendedClipboardActionNotify]) {
|
||||
|
||||
if (this._clipboardText != null) {
|
||||
RFB.messages.extendedClipboardNotify(this._sock, [extendedClipboardFormatText]);
|
||||
} else {
|
||||
RFB.messages.extendedClipboardNotify(this._sock, []);
|
||||
}
|
||||
}
|
||||
|
||||
} else if (actions === extendedClipboardActionNotify) {
|
||||
if (this._viewOnly) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this._clipboardServerCapabilitiesActions[extendedClipboardActionRequest]) {
|
||||
|
||||
if (formats & extendedClipboardFormatText) {
|
||||
RFB.messages.extendedClipboardRequest(this._sock, [extendedClipboardFormatText]);
|
||||
}
|
||||
}
|
||||
|
||||
} else if (actions === extendedClipboardActionProvide) {
|
||||
if (this._viewOnly) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!(formats & extendedClipboardFormatText)) {
|
||||
return true;
|
||||
}
|
||||
// Ignore what we had in our clipboard client side.
|
||||
this._clipboardText = null;
|
||||
|
||||
// FIXME: Should probably verify that this data was actually requested
|
||||
let zlibStream = this._sock.rQshiftBytes(length - 4);
|
||||
let streamInflator = new Inflator();
|
||||
let textData = null;
|
||||
|
||||
streamInflator.setInput(zlibStream);
|
||||
for (let i = 0; i <= 15; i++) {
|
||||
let format = 1 << i;
|
||||
|
||||
if (formats & format) {
|
||||
|
||||
let size = 0x00;
|
||||
let sizeArray = streamInflator.inflate(4);
|
||||
|
||||
size |= (sizeArray[0] << 24);
|
||||
size |= (sizeArray[1] << 16);
|
||||
size |= (sizeArray[2] << 8);
|
||||
size |= (sizeArray[3]);
|
||||
let chunk = streamInflator.inflate(size);
|
||||
|
||||
if (format === extendedClipboardFormatText) {
|
||||
textData = chunk;
|
||||
}
|
||||
}
|
||||
}
|
||||
streamInflator.setInput(null);
|
||||
|
||||
if (textData !== null) {
|
||||
let tmpText = "";
|
||||
for (let i = 0; i < textData.length; i++) {
|
||||
tmpText += String.fromCharCode(textData[i]);
|
||||
}
|
||||
textData = tmpText;
|
||||
|
||||
textData = decodeUTF8(textData);
|
||||
if ((textData.length > 0) && "\0" === textData.charAt(textData.length - 1)) {
|
||||
textData = textData.slice(0, -1);
|
||||
}
|
||||
|
||||
textData = textData.replace("\r\n", "\n");
|
||||
|
||||
this.dispatchEvent(new CustomEvent(
|
||||
"clipboard",
|
||||
{ detail: { text: textData } }));
|
||||
}
|
||||
} else {
|
||||
return this._fail("Unexpected action in extended clipboard message: " + actions);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -1977,8 +2187,102 @@ RFB.messages = {
|
|||
sock.flush();
|
||||
},
|
||||
|
||||
// TODO(directxman12): make this unicode compatible?
|
||||
clientCutText(sock, text) {
|
||||
// Used to build Notify and Request data.
|
||||
_buildExtendedClipboardFlags(actions, formats) {
|
||||
let data = new Uint8Array(4);
|
||||
let formatFlag = 0x00000000;
|
||||
let actionFlag = 0x00000000;
|
||||
|
||||
for (let i = 0; i < actions.length; i++) {
|
||||
actionFlag |= actions[i];
|
||||
}
|
||||
|
||||
for (let i = 0; i < formats.length; i++) {
|
||||
formatFlag |= formats[i];
|
||||
}
|
||||
|
||||
data[0] = actionFlag >> 24; // Actions
|
||||
data[1] = 0x00; // Reserved
|
||||
data[2] = 0x00; // Reserved
|
||||
data[3] = formatFlag; // Formats
|
||||
|
||||
return data;
|
||||
},
|
||||
|
||||
extendedClipboardProvide(sock, formats, inData) {
|
||||
// Deflate incomming data and their sizes
|
||||
let deflator = new Deflator();
|
||||
let dataToDeflate = [];
|
||||
|
||||
for (let i = 0; i < formats.length; i++) {
|
||||
// We only support the format Text at this time
|
||||
if (formats[i] != extendedClipboardFormatText) {
|
||||
throw new Error("Unsupported extended clipboard format for Provide message.");
|
||||
}
|
||||
|
||||
// Change lone \r or \n into \r\n as defined in rfbproto
|
||||
inData[i] = inData[i].replace(/\r\n|\r|\n/gm, "\r\n");
|
||||
|
||||
// Check if it already has \0
|
||||
let text = encodeUTF8(inData[i] + "\0");
|
||||
|
||||
dataToDeflate.push( (text.length >> 24) & 0xFF,
|
||||
(text.length >> 16) & 0xFF,
|
||||
(text.length >> 8) & 0xFF,
|
||||
(text.length & 0xFF));
|
||||
|
||||
for (let j = 0; j < text.length; j++) {
|
||||
dataToDeflate.push(text.charCodeAt(j));
|
||||
}
|
||||
}
|
||||
|
||||
let deflatedData = deflator.deflate(new Uint8Array(dataToDeflate));
|
||||
|
||||
// Build data to send
|
||||
let data = new Uint8Array(4 + deflatedData.length);
|
||||
data.set(RFB.messages._buildExtendedClipboardFlags([extendedClipboardActionProvide],
|
||||
formats));
|
||||
data.set(deflatedData, 4);
|
||||
|
||||
RFB.messages.clientCutText(sock, data, true);
|
||||
},
|
||||
|
||||
extendedClipboardNotify(sock, formats) {
|
||||
let flags = RFB.messages._buildExtendedClipboardFlags([extendedClipboardActionNotify],
|
||||
formats);
|
||||
RFB.messages.clientCutText(sock, flags, true);
|
||||
},
|
||||
|
||||
extendedClipboardRequest(sock, formats) {
|
||||
let flags = RFB.messages._buildExtendedClipboardFlags([extendedClipboardActionRequest],
|
||||
formats);
|
||||
RFB.messages.clientCutText(sock, flags, true);
|
||||
},
|
||||
|
||||
extendedClipboardCaps(sock, actions, formats) {
|
||||
let formatKeys = Object.keys(formats);
|
||||
let data = new Uint8Array(4 + (4 * formatKeys.length));
|
||||
|
||||
formatKeys.map(x => parseInt(x));
|
||||
formatKeys.sort((a, b) => a - b);
|
||||
|
||||
data.set(RFB.messages._buildExtendedClipboardFlags(actions, []));
|
||||
|
||||
let loopOffset = 4;
|
||||
for (let i = 0; i < formatKeys.length; i++) {
|
||||
data[loopOffset] = formats[formatKeys[i]] >> 24;
|
||||
data[loopOffset + 1] = formats[formatKeys[i]] >> 16;
|
||||
data[loopOffset + 2] = formats[formatKeys[i]] >> 8;
|
||||
data[loopOffset + 3] = formats[formatKeys[i]] >> 0;
|
||||
|
||||
loopOffset += 4;
|
||||
data[3] |= (1 << formatKeys[i]); // Update our format flags
|
||||
}
|
||||
|
||||
RFB.messages.clientCutText(sock, data, true);
|
||||
},
|
||||
|
||||
clientCutText(sock, data, extended = false) {
|
||||
const buff = sock._sQ;
|
||||
const offset = sock._sQlen;
|
||||
|
||||
|
|
@ -1988,7 +2292,12 @@ RFB.messages = {
|
|||
buff[offset + 2] = 0; // padding
|
||||
buff[offset + 3] = 0; // padding
|
||||
|
||||
let length = text.length;
|
||||
let length;
|
||||
if (extended) {
|
||||
length = toUnsigned32bit(-data.length);
|
||||
} else {
|
||||
length = data.length;
|
||||
}
|
||||
|
||||
buff[offset + 4] = length >> 24;
|
||||
buff[offset + 5] = length >> 16;
|
||||
|
|
@ -1997,24 +2306,25 @@ RFB.messages = {
|
|||
|
||||
sock._sQlen += 8;
|
||||
|
||||
// We have to keep track of from where in the text we begin creating the
|
||||
// We have to keep track of from where in the data we begin creating the
|
||||
// buffer for the flush in the next iteration.
|
||||
let textOffset = 0;
|
||||
let dataOffset = 0;
|
||||
|
||||
let remaining = length;
|
||||
let remaining = data.length;
|
||||
while (remaining > 0) {
|
||||
|
||||
let flushSize = Math.min(remaining, (sock._sQbufferSize - sock._sQlen));
|
||||
for (let i = 0; i < flushSize; i++) {
|
||||
buff[sock._sQlen + i] = text.charCodeAt(textOffset + i);
|
||||
buff[sock._sQlen + i] = data[dataOffset + i];
|
||||
}
|
||||
|
||||
sock._sQlen += flushSize;
|
||||
sock.flush();
|
||||
|
||||
remaining -= flushSize;
|
||||
textOffset += flushSize;
|
||||
dataOffset += flushSize;
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
setDesktopSize(sock, width, height, id, flags) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* noVNC: HTML5 VNC client
|
||||
* Copyright (C) 2020 The noVNC Authors
|
||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||
*
|
||||
* See README.md for usage and integration instructions.
|
||||
*/
|
||||
|
||||
export function toUnsigned32bit(toConvert) {
|
||||
return toConvert >>> 0;
|
||||
}
|
||||
|
||||
export function toSigned32bit(toConvert) {
|
||||
return toConvert | 0;
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* noVNC: HTML5 VNC client
|
||||
* Copyright (C) 2018 The noVNC Authors
|
||||
* Copyright (C) 2020 The noVNC Authors
|
||||
* Licensed under MPL 2.0 or any later version (see LICENSE.txt)
|
||||
*/
|
||||
|
||||
|
|
@ -52,3 +52,10 @@ if (typeof Object.assign != 'function') {
|
|||
window.CustomEvent = CustomEvent;
|
||||
}
|
||||
})();
|
||||
|
||||
/* Number.isInteger() (taken from MDN) */
|
||||
Number.isInteger = Number.isInteger || function isInteger(value) {
|
||||
return typeof value === 'number' &&
|
||||
isFinite(value) &&
|
||||
Math.floor(value) === value;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -64,6 +64,11 @@ protocol stream.
|
|||
to the element containing the remote session screen. The default value is `rgb(40, 40, 40)`
|
||||
(solid gray color).
|
||||
|
||||
`qualityLevel`
|
||||
- Is an `int` in range `[0-9]` controlling the desired JPEG quality.
|
||||
Value `0` implies low quality and `9` implies high quality.
|
||||
Default value is `6`.
|
||||
|
||||
`capabilities` *Read only*
|
||||
- Is an `Object` indicating which optional extensions are available
|
||||
on the server. Some methods may only be called if the corresponding
|
||||
|
|
|
|||
|
|
@ -61,6 +61,8 @@ query string. Currently the following options are available:
|
|||
* `resize` - How to resize the remote session if it is not the same size as
|
||||
the browser window. Can be one of `off`, `scale` and `remote`.
|
||||
|
||||
* `quality` - The session JPEG quality level. Can be `0` to `9`.
|
||||
|
||||
* `show_dot` - If a dot cursor should be shown when the remote server provides
|
||||
no local cursor, or provides a fully-transparent (invisible) cursor.
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,80 @@
|
|||
/* eslint-disable no-console */
|
||||
const expect = chai.expect;
|
||||
|
||||
import { inflateInit, inflate } from "../vendor/pako/lib/zlib/inflate.js";
|
||||
import ZStream from "../vendor/pako/lib/zlib/zstream.js";
|
||||
import Deflator from "../core/deflator.js";
|
||||
|
||||
function _inflator(compText, expected) {
|
||||
let strm = new ZStream();
|
||||
let chunkSize = 1024 * 10 * 10;
|
||||
strm.output = new Uint8Array(chunkSize);
|
||||
|
||||
inflateInit(strm, 5);
|
||||
|
||||
if (expected > chunkSize) {
|
||||
chunkSize = expected;
|
||||
strm.output = new Uint8Array(chunkSize);
|
||||
}
|
||||
|
||||
strm.input = compText;
|
||||
strm.avail_in = strm.input.length;
|
||||
strm.next_in = 0;
|
||||
|
||||
strm.next_out = 0;
|
||||
strm.avail_out = expected.length;
|
||||
|
||||
let ret = inflate(strm, 0);
|
||||
|
||||
// Check that return code is not an error
|
||||
expect(ret).to.be.greaterThan(-1);
|
||||
|
||||
return new Uint8Array(strm.output.buffer, 0, strm.next_out);
|
||||
}
|
||||
|
||||
describe('Deflate data', function () {
|
||||
|
||||
it('should be able to deflate messages', function () {
|
||||
let deflator = new Deflator();
|
||||
|
||||
let text = "123asdf";
|
||||
let preText = new Uint8Array(text.length);
|
||||
for (let i = 0; i < preText.length; i++) {
|
||||
preText[i] = text.charCodeAt(i);
|
||||
}
|
||||
|
||||
let compText = deflator.deflate(preText);
|
||||
|
||||
let inflatedText = _inflator(compText, text.length);
|
||||
expect(inflatedText).to.array.equal(preText);
|
||||
|
||||
});
|
||||
|
||||
it('should be able to deflate large messages', function () {
|
||||
let deflator = new Deflator();
|
||||
|
||||
/* Generate a big string with random characters. Used because
|
||||
repetition of letters might be deflated more effectively than
|
||||
random ones. */
|
||||
let text = "";
|
||||
let characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
for (let i = 0; i < 300000; i++) {
|
||||
text += characters.charAt(Math.floor(Math.random() * characters.length));
|
||||
}
|
||||
|
||||
let preText = new Uint8Array(text.length);
|
||||
for (let i = 0; i < preText.length; i++) {
|
||||
preText[i] = text.charCodeAt(i);
|
||||
}
|
||||
|
||||
let compText = deflator.deflate(preText);
|
||||
|
||||
//Check that the compressed size is expected size
|
||||
expect(compText.length).to.be.greaterThan((1024 * 10 * 10) * 2);
|
||||
|
||||
let inflatedText = _inflator(compText, text.length);
|
||||
|
||||
expect(inflatedText).to.array.equal(preText);
|
||||
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
/* eslint-disable no-console */
|
||||
const expect = chai.expect;
|
||||
|
||||
import { toUnsigned32bit, toSigned32bit } from '../core/util/int.js';
|
||||
|
||||
describe('Integer casting', function () {
|
||||
it('should cast unsigned to signed', function () {
|
||||
let expected = 4294967286;
|
||||
expect(toUnsigned32bit(-10)).to.equal(expected);
|
||||
});
|
||||
|
||||
it('should cast signed to unsigned', function () {
|
||||
let expected = -10;
|
||||
expect(toSigned32bit(4294967286)).to.equal(expected);
|
||||
});
|
||||
});
|
||||
|
|
@ -2,7 +2,11 @@ const expect = chai.expect;
|
|||
|
||||
import RFB from '../core/rfb.js';
|
||||
import WebChannel from '../core/websock.js';
|
||||
import ZStream from "../vendor/pako/lib/zlib/zstream.js";
|
||||
import { deflateInit, deflate } from "../vendor/pako/lib/zlib/deflate.js";
|
||||
import { encodings } from '../core/encodings.js';
|
||||
import { toUnsigned32bit } from '../core/util/int.js';
|
||||
import { encodeUTF8 } from '../core/util/strings.js';
|
||||
|
||||
import FakeWebSocket from './fake.websocket.js';
|
||||
|
||||
|
|
@ -48,6 +52,35 @@ function pushString(arr, string) {
|
|||
}
|
||||
}
|
||||
|
||||
function deflateWithSize(data) {
|
||||
// Adds the size of the string in front before deflating
|
||||
|
||||
let unCompData = [];
|
||||
unCompData.push((data.length >> 24) & 0xFF,
|
||||
(data.length >> 16) & 0xFF,
|
||||
(data.length >> 8) & 0xFF,
|
||||
(data.length & 0xFF));
|
||||
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
unCompData.push(data.charCodeAt(i));
|
||||
}
|
||||
|
||||
let strm = new ZStream();
|
||||
let chunkSize = 1024 * 10 * 10;
|
||||
strm.output = new Uint8Array(chunkSize);
|
||||
deflateInit(strm, 5);
|
||||
|
||||
strm.input = unCompData;
|
||||
strm.avail_in = strm.input.length;
|
||||
strm.next_in = 0;
|
||||
strm.next_out = 0;
|
||||
strm.avail_out = chunkSize;
|
||||
|
||||
deflate(strm, 3);
|
||||
|
||||
return new Uint8Array(strm.output.buffer, 0, strm.next_out);
|
||||
}
|
||||
|
||||
describe('Remote Frame Buffer Protocol Client', function () {
|
||||
let clock;
|
||||
let raf;
|
||||
|
|
@ -291,12 +324,39 @@ describe('Remote Frame Buffer Protocol Client', function () {
|
|||
});
|
||||
|
||||
describe('#clipboardPasteFrom', function () {
|
||||
it('should send the given text in a paste event', function () {
|
||||
const expected = {_sQ: new Uint8Array(11), _sQlen: 0,
|
||||
_sQbufferSize: 11, flush: () => {}};
|
||||
RFB.messages.clientCutText(expected, 'abc');
|
||||
client.clipboardPasteFrom('abc');
|
||||
expect(client._sock).to.have.sent(expected._sQ);
|
||||
describe('Clipboard update handling', function () {
|
||||
beforeEach(function () {
|
||||
sinon.spy(RFB.messages, 'clientCutText');
|
||||
sinon.spy(RFB.messages, 'extendedClipboardNotify');
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
RFB.messages.clientCutText.restore();
|
||||
RFB.messages.extendedClipboardNotify.restore();
|
||||
});
|
||||
|
||||
it('should send the given text in an clipboard update', function () {
|
||||
client.clipboardPasteFrom('abc');
|
||||
|
||||
expect(RFB.messages.clientCutText).to.have.been.calledOnce;
|
||||
expect(RFB.messages.clientCutText).to.have.been.calledWith(client._sock,
|
||||
new Uint8Array([97, 98, 99]));
|
||||
});
|
||||
|
||||
it('should send an notify if extended clipboard is supported by server', function () {
|
||||
// Send our capabilities
|
||||
let data = [3, 0, 0, 0];
|
||||
const flags = [0x1F, 0x00, 0x00, 0x01];
|
||||
let fileSizes = [0x00, 0x00, 0x00, 0x1E];
|
||||
|
||||
push32(data, toUnsigned32bit(-8));
|
||||
data = data.concat(flags);
|
||||
data = data.concat(fileSizes);
|
||||
client._sock._websocket._receive_data(new Uint8Array(data));
|
||||
|
||||
client.clipboardPasteFrom('extended test');
|
||||
expect(RFB.messages.extendedClipboardNotify).to.have.been.calledOnce;
|
||||
});
|
||||
});
|
||||
|
||||
it('should flush multiple times for large clipboards', function () {
|
||||
|
|
@ -2336,17 +2396,249 @@ describe('Remote Frame Buffer Protocol Client', function () {
|
|||
});
|
||||
});
|
||||
|
||||
it('should fire the clipboard callback with the retrieved text on ServerCutText', function () {
|
||||
const expected_str = 'cheese!';
|
||||
const data = [3, 0, 0, 0];
|
||||
push32(data, expected_str.length);
|
||||
for (let i = 0; i < expected_str.length; i++) { data.push(expected_str.charCodeAt(i)); }
|
||||
const spy = sinon.spy();
|
||||
client.addEventListener("clipboard", spy);
|
||||
describe('Normal Clipboard Handling Receive', function () {
|
||||
it('should fire the clipboard callback with the retrieved text on ServerCutText', function () {
|
||||
const expected_str = 'cheese!';
|
||||
const data = [3, 0, 0, 0];
|
||||
push32(data, expected_str.length);
|
||||
for (let i = 0; i < expected_str.length; i++) { data.push(expected_str.charCodeAt(i)); }
|
||||
const spy = sinon.spy();
|
||||
client.addEventListener("clipboard", spy);
|
||||
|
||||
client._sock._websocket._receive_data(new Uint8Array(data));
|
||||
expect(spy).to.have.been.calledOnce;
|
||||
expect(spy.args[0][0].detail.text).to.equal(expected_str);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Extended clipboard Handling', function () {
|
||||
|
||||
describe('Extended clipboard initialization', function () {
|
||||
beforeEach(function () {
|
||||
sinon.spy(RFB.messages, 'extendedClipboardCaps');
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
RFB.messages.extendedClipboardCaps.restore();
|
||||
});
|
||||
|
||||
it('should update capabilities when receiving a Caps message', function () {
|
||||
let data = [3, 0, 0, 0];
|
||||
const flags = [0x1F, 0x00, 0x00, 0x03];
|
||||
let fileSizes = [0x00, 0x00, 0x00, 0x1E,
|
||||
0x00, 0x00, 0x00, 0x3C];
|
||||
|
||||
push32(data, toUnsigned32bit(-12));
|
||||
data = data.concat(flags);
|
||||
data = data.concat(fileSizes);
|
||||
client._sock._websocket._receive_data(new Uint8Array(data));
|
||||
|
||||
// Check that we give an response caps when we receive one
|
||||
expect(RFB.messages.extendedClipboardCaps).to.have.been.calledOnce;
|
||||
|
||||
// FIXME: Can we avoid checking internal variables?
|
||||
expect(client._clipboardServerCapabilitiesFormats[0]).to.not.equal(true);
|
||||
expect(client._clipboardServerCapabilitiesFormats[1]).to.equal(true);
|
||||
expect(client._clipboardServerCapabilitiesFormats[2]).to.equal(true);
|
||||
expect(client._clipboardServerCapabilitiesActions[(1 << 24)]).to.equal(true);
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
describe('Extended Clipboard Handling Receive', function () {
|
||||
|
||||
beforeEach(function () {
|
||||
// Send our capabilities
|
||||
let data = [3, 0, 0, 0];
|
||||
const flags = [0x1F, 0x00, 0x00, 0x01];
|
||||
let fileSizes = [0x00, 0x00, 0x00, 0x1E];
|
||||
|
||||
push32(data, toUnsigned32bit(-8));
|
||||
data = data.concat(flags);
|
||||
data = data.concat(fileSizes);
|
||||
client._sock._websocket._receive_data(new Uint8Array(data));
|
||||
});
|
||||
|
||||
describe('Handle Provide', function () {
|
||||
it('should update clipboard with correct Unicode data from a Provide message', function () {
|
||||
let expectedData = "Aå漢字!";
|
||||
let data = [3, 0, 0, 0];
|
||||
const flags = [0x10, 0x00, 0x00, 0x01];
|
||||
|
||||
/* The size 10 (utf8 encoded string size) and the
|
||||
string "Aå漢字!" utf8 encoded and deflated. */
|
||||
let deflatedData = [120, 94, 99, 96, 96, 224, 114, 60,
|
||||
188, 244, 217, 158, 69, 79, 215,
|
||||
78, 87, 4, 0, 35, 207, 6, 66];
|
||||
|
||||
// How much data we are sending.
|
||||
push32(data, toUnsigned32bit(-(4 + deflatedData.length)));
|
||||
|
||||
data = data.concat(flags);
|
||||
data = data.concat(deflatedData);
|
||||
|
||||
const spy = sinon.spy();
|
||||
client.addEventListener("clipboard", spy);
|
||||
|
||||
client._sock._websocket._receive_data(new Uint8Array(data));
|
||||
expect(spy).to.have.been.calledOnce;
|
||||
expect(spy.args[0][0].detail.text).to.equal(expectedData);
|
||||
client.removeEventListener("clipboard", spy);
|
||||
});
|
||||
|
||||
it('should update clipboard with correct escape characters from a Provide message ', function () {
|
||||
let expectedData = "Oh\nmy!";
|
||||
let data = [3, 0, 0, 0];
|
||||
const flags = [0x10, 0x00, 0x00, 0x01];
|
||||
|
||||
let text = encodeUTF8("Oh\r\nmy!\0");
|
||||
|
||||
let deflatedText = deflateWithSize(text);
|
||||
|
||||
// How much data we are sending.
|
||||
push32(data, toUnsigned32bit(-(4 + deflatedText.length)));
|
||||
|
||||
data = data.concat(flags);
|
||||
|
||||
let sendData = new Uint8Array(data.length + deflatedText.length);
|
||||
sendData.set(data);
|
||||
sendData.set(deflatedText, data.length);
|
||||
|
||||
const spy = sinon.spy();
|
||||
client.addEventListener("clipboard", spy);
|
||||
|
||||
client._sock._websocket._receive_data(sendData);
|
||||
expect(spy).to.have.been.calledOnce;
|
||||
expect(spy.args[0][0].detail.text).to.equal(expectedData);
|
||||
client.removeEventListener("clipboard", spy);
|
||||
});
|
||||
|
||||
it('should be able to handle large Provide messages', function () {
|
||||
// repeat() is not supported in IE so a loop is needed instead
|
||||
let expectedData = "hello";
|
||||
for (let i = 1; i <= 100000; i++) {
|
||||
expectedData += "hello";
|
||||
}
|
||||
|
||||
let data = [3, 0, 0, 0];
|
||||
const flags = [0x10, 0x00, 0x00, 0x01];
|
||||
|
||||
let text = encodeUTF8(expectedData + "\0");
|
||||
|
||||
let deflatedText = deflateWithSize(text);
|
||||
|
||||
// How much data we are sending.
|
||||
push32(data, toUnsigned32bit(-(4 + deflatedText.length)));
|
||||
|
||||
data = data.concat(flags);
|
||||
|
||||
let sendData = new Uint8Array(data.length + deflatedText.length);
|
||||
sendData.set(data);
|
||||
sendData.set(deflatedText, data.length);
|
||||
|
||||
const spy = sinon.spy();
|
||||
client.addEventListener("clipboard", spy);
|
||||
|
||||
client._sock._websocket._receive_data(sendData);
|
||||
expect(spy).to.have.been.calledOnce;
|
||||
expect(spy.args[0][0].detail.text).to.equal(expectedData);
|
||||
client.removeEventListener("clipboard", spy);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('Handle Notify', function () {
|
||||
beforeEach(function () {
|
||||
sinon.spy(RFB.messages, 'extendedClipboardRequest');
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
RFB.messages.extendedClipboardRequest.restore();
|
||||
});
|
||||
|
||||
it('should make a request with supported formats when receiving a notify message', function () {
|
||||
let data = [3, 0, 0, 0];
|
||||
const flags = [0x08, 0x00, 0x00, 0x07];
|
||||
push32(data, toUnsigned32bit(-4));
|
||||
data = data.concat(flags);
|
||||
let expectedData = [0x01];
|
||||
|
||||
client._sock._websocket._receive_data(new Uint8Array(data));
|
||||
|
||||
expect(RFB.messages.extendedClipboardRequest).to.have.been.calledOnce;
|
||||
expect(RFB.messages.extendedClipboardRequest).to.have.been.calledWith(client._sock, expectedData);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Handle Peek', function () {
|
||||
beforeEach(function () {
|
||||
sinon.spy(RFB.messages, 'extendedClipboardNotify');
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
RFB.messages.extendedClipboardNotify.restore();
|
||||
});
|
||||
|
||||
it('should send an empty Notify when receiving a Peek and no excisting clipboard data', function () {
|
||||
let data = [3, 0, 0, 0];
|
||||
const flags = [0x04, 0x00, 0x00, 0x00];
|
||||
push32(data, toUnsigned32bit(-4));
|
||||
data = data.concat(flags);
|
||||
let expectedData = [];
|
||||
|
||||
client._sock._websocket._receive_data(new Uint8Array(data));
|
||||
|
||||
expect(RFB.messages.extendedClipboardNotify).to.have.been.calledOnce;
|
||||
expect(RFB.messages.extendedClipboardNotify).to.have.been.calledWith(client._sock, expectedData);
|
||||
});
|
||||
|
||||
it('should send a Notify message with supported formats when receiving a Peek', function () {
|
||||
let data = [3, 0, 0, 0];
|
||||
const flags = [0x04, 0x00, 0x00, 0x00];
|
||||
push32(data, toUnsigned32bit(-4));
|
||||
data = data.concat(flags);
|
||||
let expectedData = [0x01];
|
||||
|
||||
// Needed to have clipboard data to read.
|
||||
// This will trigger a call to Notify, reset history
|
||||
client.clipboardPasteFrom("HejHej");
|
||||
RFB.messages.extendedClipboardNotify.resetHistory();
|
||||
|
||||
client._sock._websocket._receive_data(new Uint8Array(data));
|
||||
|
||||
expect(RFB.messages.extendedClipboardNotify).to.have.been.calledOnce;
|
||||
expect(RFB.messages.extendedClipboardNotify).to.have.been.calledWith(client._sock, expectedData);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Handle Request', function () {
|
||||
beforeEach(function () {
|
||||
sinon.spy(RFB.messages, 'extendedClipboardProvide');
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
RFB.messages.extendedClipboardProvide.restore();
|
||||
});
|
||||
|
||||
it('should send a Provide message with supported formats when receiving a Request', function () {
|
||||
let data = [3, 0, 0, 0];
|
||||
const flags = [0x02, 0x00, 0x00, 0x01];
|
||||
push32(data, toUnsigned32bit(-4));
|
||||
data = data.concat(flags);
|
||||
let expectedData = [0x01];
|
||||
|
||||
client.clipboardPasteFrom("HejHej");
|
||||
expect(RFB.messages.extendedClipboardProvide).to.not.have.been.called;
|
||||
|
||||
client._sock._websocket._receive_data(new Uint8Array(data));
|
||||
|
||||
expect(RFB.messages.extendedClipboardProvide).to.have.been.calledOnce;
|
||||
expect(RFB.messages.extendedClipboardProvide).to.have.been.calledWith(client._sock, expectedData, ["HejHej"]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
client._sock._webChannel._receive_data(new Uint8Array(data));
|
||||
expect(spy).to.have.been.calledOnce;
|
||||
expect(spy.args[0][0].detail.text).to.equal(expected_str);
|
||||
});
|
||||
|
||||
it('should fire the bell callback on Bell', function () {
|
||||
|
|
@ -2573,4 +2865,269 @@ describe('Remote Frame Buffer Protocol Client', function () {
|
|||
// error events do nothing
|
||||
});
|
||||
});
|
||||
|
||||
describe('Quality level setting', function () {
|
||||
const defaultQuality = 6;
|
||||
|
||||
let client;
|
||||
|
||||
beforeEach(function () {
|
||||
client = make_rfb();
|
||||
sinon.spy(RFB.messages, "clientEncodings");
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
RFB.messages.clientEncodings.restore();
|
||||
});
|
||||
|
||||
it(`should equal ${defaultQuality} by default`, function () {
|
||||
expect(client._qualityLevel).to.equal(defaultQuality);
|
||||
});
|
||||
|
||||
it('should ignore non-integers when set', function () {
|
||||
client.qualityLevel = '1';
|
||||
expect(RFB.messages.clientEncodings).to.not.have.been.called;
|
||||
|
||||
RFB.messages.clientEncodings.resetHistory();
|
||||
|
||||
client.qualityLevel = 1.5;
|
||||
expect(RFB.messages.clientEncodings).to.not.have.been.called;
|
||||
|
||||
RFB.messages.clientEncodings.resetHistory();
|
||||
|
||||
client.qualityLevel = null;
|
||||
expect(RFB.messages.clientEncodings).to.not.have.been.called;
|
||||
|
||||
RFB.messages.clientEncodings.resetHistory();
|
||||
|
||||
client.qualityLevel = undefined;
|
||||
expect(RFB.messages.clientEncodings).to.not.have.been.called;
|
||||
|
||||
RFB.messages.clientEncodings.resetHistory();
|
||||
|
||||
client.qualityLevel = {};
|
||||
expect(RFB.messages.clientEncodings).to.not.have.been.called;
|
||||
});
|
||||
|
||||
it('should ignore integers out of range [0, 9]', function () {
|
||||
client.qualityLevel = -1;
|
||||
expect(RFB.messages.clientEncodings).to.not.have.been.called;
|
||||
|
||||
RFB.messages.clientEncodings.resetHistory();
|
||||
|
||||
client.qualityLevel = 10;
|
||||
expect(RFB.messages.clientEncodings).to.not.have.been.called;
|
||||
});
|
||||
|
||||
it('should send clientEncodings with new quality value', function () {
|
||||
let newQuality;
|
||||
|
||||
newQuality = 8;
|
||||
client.qualityLevel = newQuality;
|
||||
expect(client.qualityLevel).to.equal(newQuality);
|
||||
expect(RFB.messages.clientEncodings).to.have.been.calledOnce;
|
||||
expect(RFB.messages.clientEncodings.getCall(0).args[1]).to.include(encodings.pseudoEncodingQualityLevel0 + newQuality);
|
||||
});
|
||||
|
||||
it('should not send clientEncodings if quality is the same', function () {
|
||||
let newQuality;
|
||||
|
||||
newQuality = 2;
|
||||
client.qualityLevel = newQuality;
|
||||
expect(RFB.messages.clientEncodings).to.have.been.calledOnce;
|
||||
expect(RFB.messages.clientEncodings.getCall(0).args[1]).to.include(encodings.pseudoEncodingQualityLevel0 + newQuality);
|
||||
|
||||
RFB.messages.clientEncodings.resetHistory();
|
||||
|
||||
client.qualityLevel = newQuality;
|
||||
expect(RFB.messages.clientEncodings).to.not.have.been.called;
|
||||
});
|
||||
|
||||
it('should not send clientEncodings if not in connected state', function () {
|
||||
let newQuality;
|
||||
|
||||
client._rfb_connection_state = '';
|
||||
newQuality = 2;
|
||||
client.qualityLevel = newQuality;
|
||||
expect(RFB.messages.clientEncodings).to.not.have.been.called;
|
||||
|
||||
RFB.messages.clientEncodings.resetHistory();
|
||||
|
||||
client._rfb_connection_state = 'connnecting';
|
||||
newQuality = 6;
|
||||
client.qualityLevel = newQuality;
|
||||
expect(RFB.messages.clientEncodings).to.not.have.been.called;
|
||||
|
||||
RFB.messages.clientEncodings.resetHistory();
|
||||
|
||||
client._rfb_connection_state = 'connected';
|
||||
newQuality = 5;
|
||||
client.qualityLevel = newQuality;
|
||||
expect(RFB.messages.clientEncodings).to.have.been.calledOnce;
|
||||
expect(RFB.messages.clientEncodings.getCall(0).args[1]).to.include(encodings.pseudoEncodingQualityLevel0 + newQuality);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('RFB messages', function () {
|
||||
let sock;
|
||||
|
||||
before(function () {
|
||||
FakeWebSocket.replace();
|
||||
sock = new WebChannel();
|
||||
sock.open();
|
||||
});
|
||||
|
||||
after(function () {
|
||||
FakeWebSocket.restore();
|
||||
});
|
||||
|
||||
describe('Extended Clipboard Handling Send', function () {
|
||||
beforeEach(function () {
|
||||
sinon.spy(RFB.messages, 'clientCutText');
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
RFB.messages.clientCutText.restore();
|
||||
});
|
||||
|
||||
it('should call clientCutText with correct Caps data', function () {
|
||||
let formats = {
|
||||
0: 2,
|
||||
2: 4121
|
||||
};
|
||||
let expectedData = new Uint8Array([0x1F, 0x00, 0x00, 0x05,
|
||||
0x00, 0x00, 0x00, 0x02,
|
||||
0x00, 0x00, 0x10, 0x19]);
|
||||
let actions = [
|
||||
1 << 24, // Caps
|
||||
1 << 25, // Request
|
||||
1 << 26, // Peek
|
||||
1 << 27, // Notify
|
||||
1 << 28 // Provide
|
||||
];
|
||||
|
||||
RFB.messages.extendedClipboardCaps(sock, actions, formats);
|
||||
expect(RFB.messages.clientCutText).to.have.been.calledOnce;
|
||||
expect(RFB.messages.clientCutText).to.have.been.calledWith(sock, expectedData);
|
||||
});
|
||||
|
||||
it('should call clientCutText with correct Request data', function () {
|
||||
let formats = new Uint8Array([0x01]);
|
||||
let expectedData = new Uint8Array([0x02, 0x00, 0x00, 0x01]);
|
||||
|
||||
RFB.messages.extendedClipboardRequest(sock, formats);
|
||||
expect(RFB.messages.clientCutText).to.have.been.calledOnce;
|
||||
expect(RFB.messages.clientCutText).to.have.been.calledWith(sock, expectedData);
|
||||
});
|
||||
|
||||
it('should call clientCutText with correct Notify data', function () {
|
||||
let formats = new Uint8Array([0x01]);
|
||||
let expectedData = new Uint8Array([0x08, 0x00, 0x00, 0x01]);
|
||||
|
||||
RFB.messages.extendedClipboardNotify(sock, formats);
|
||||
expect(RFB.messages.clientCutText).to.have.been.calledOnce;
|
||||
expect(RFB.messages.clientCutText).to.have.been.calledWith(sock, expectedData);
|
||||
});
|
||||
|
||||
it('should call clientCutText with correct Provide data', function () {
|
||||
let testText = "Test string";
|
||||
let expectedText = encodeUTF8(testText + "\0");
|
||||
|
||||
let deflatedData = deflateWithSize(expectedText);
|
||||
|
||||
// Build Expected with flags and deflated data
|
||||
let expectedData = new Uint8Array(4 + deflatedData.length);
|
||||
expectedData[0] = 0x10; // The client capabilities
|
||||
expectedData[1] = 0x00; // Reserved flags
|
||||
expectedData[2] = 0x00; // Reserved flags
|
||||
expectedData[3] = 0x01; // The formats client supports
|
||||
expectedData.set(deflatedData, 4);
|
||||
|
||||
RFB.messages.extendedClipboardProvide(sock, [0x01], [testText]);
|
||||
expect(RFB.messages.clientCutText).to.have.been.calledOnce;
|
||||
expect(RFB.messages.clientCutText).to.have.been.calledWith(sock, expectedData, true);
|
||||
|
||||
});
|
||||
|
||||
describe('End of line characters', function () {
|
||||
it('Carriage return', function () {
|
||||
|
||||
let testText = "Hello\rworld\r\r!";
|
||||
let expectedText = encodeUTF8("Hello\r\nworld\r\n\r\n!\0");
|
||||
|
||||
let deflatedData = deflateWithSize(expectedText);
|
||||
|
||||
// Build Expected with flags and deflated data
|
||||
let expectedData = new Uint8Array(4 + deflatedData.length);
|
||||
expectedData[0] = 0x10; // The client capabilities
|
||||
expectedData[1] = 0x00; // Reserved flags
|
||||
expectedData[2] = 0x00; // Reserved flags
|
||||
expectedData[3] = 0x01; // The formats client supports
|
||||
expectedData.set(deflatedData, 4);
|
||||
|
||||
RFB.messages.extendedClipboardProvide(sock, [0x01], [testText]);
|
||||
expect(RFB.messages.clientCutText).to.have.been.calledOnce;
|
||||
expect(RFB.messages.clientCutText).to.have.been.calledWith(sock, expectedData, true);
|
||||
});
|
||||
|
||||
it('Carriage return Line feed', function () {
|
||||
|
||||
let testText = "Hello\r\n\r\nworld\r\n!";
|
||||
let expectedText = encodeUTF8(testText + "\0");
|
||||
|
||||
let deflatedData = deflateWithSize(expectedText);
|
||||
|
||||
// Build Expected with flags and deflated data
|
||||
let expectedData = new Uint8Array(4 + deflatedData.length);
|
||||
expectedData[0] = 0x10; // The client capabilities
|
||||
expectedData[1] = 0x00; // Reserved flags
|
||||
expectedData[2] = 0x00; // Reserved flags
|
||||
expectedData[3] = 0x01; // The formats client supports
|
||||
expectedData.set(deflatedData, 4);
|
||||
|
||||
RFB.messages.extendedClipboardProvide(sock, [0x01], [testText]);
|
||||
expect(RFB.messages.clientCutText).to.have.been.calledOnce;
|
||||
expect(RFB.messages.clientCutText).to.have.been.calledWith(sock, expectedData, true);
|
||||
});
|
||||
|
||||
it('Line feed', function () {
|
||||
let testText = "Hello\n\n\nworld\n!";
|
||||
let expectedText = encodeUTF8("Hello\r\n\r\n\r\nworld\r\n!\0");
|
||||
|
||||
let deflatedData = deflateWithSize(expectedText);
|
||||
|
||||
// Build Expected with flags and deflated data
|
||||
let expectedData = new Uint8Array(4 + deflatedData.length);
|
||||
expectedData[0] = 0x10; // The client capabilities
|
||||
expectedData[1] = 0x00; // Reserved flags
|
||||
expectedData[2] = 0x00; // Reserved flags
|
||||
expectedData[3] = 0x01; // The formats client supports
|
||||
expectedData.set(deflatedData, 4);
|
||||
|
||||
RFB.messages.extendedClipboardProvide(sock, [0x01], [testText]);
|
||||
expect(RFB.messages.clientCutText).to.have.been.calledOnce;
|
||||
expect(RFB.messages.clientCutText).to.have.been.calledWith(sock, expectedData, true);
|
||||
});
|
||||
|
||||
it('Carriage return and Line feed mixed', function () {
|
||||
let testText = "\rHello\r\n\rworld\n\n!";
|
||||
let expectedText = encodeUTF8("\r\nHello\r\n\r\nworld\r\n\r\n!\0");
|
||||
|
||||
let deflatedData = deflateWithSize(expectedText);
|
||||
|
||||
// Build Expected with flags and deflated data
|
||||
let expectedData = new Uint8Array(4 + deflatedData.length);
|
||||
expectedData[0] = 0x10; // The client capabilities
|
||||
expectedData[1] = 0x00; // Reserved flags
|
||||
expectedData[2] = 0x00; // Reserved flags
|
||||
expectedData[3] = 0x01; // The formats client supports
|
||||
expectedData.set(deflatedData, 4);
|
||||
|
||||
RFB.messages.extendedClipboardProvide(sock, [0x01], [testText]);
|
||||
expect(RFB.messages.clientCutText).to.have.been.calledOnce;
|
||||
expect(RFB.messages.clientCutText).to.have.been.calledWith(sock, expectedData, true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -9,51 +9,51 @@ import msg from "./messages.js";
|
|||
|
||||
|
||||
/* Allowed flush values; see deflate() and inflate() below for details */
|
||||
var Z_NO_FLUSH = 0;
|
||||
var Z_PARTIAL_FLUSH = 1;
|
||||
//var Z_SYNC_FLUSH = 2;
|
||||
var Z_FULL_FLUSH = 3;
|
||||
var Z_FINISH = 4;
|
||||
var Z_BLOCK = 5;
|
||||
//var Z_TREES = 6;
|
||||
export const Z_NO_FLUSH = 0;
|
||||
export const Z_PARTIAL_FLUSH = 1;
|
||||
//export const Z_SYNC_FLUSH = 2;
|
||||
export const Z_FULL_FLUSH = 3;
|
||||
export const Z_FINISH = 4;
|
||||
export const Z_BLOCK = 5;
|
||||
//export const Z_TREES = 6;
|
||||
|
||||
|
||||
/* Return codes for the compression/decompression functions. Negative values
|
||||
* are errors, positive values are used for special but normal events.
|
||||
*/
|
||||
var Z_OK = 0;
|
||||
var Z_STREAM_END = 1;
|
||||
//var Z_NEED_DICT = 2;
|
||||
//var Z_ERRNO = -1;
|
||||
var Z_STREAM_ERROR = -2;
|
||||
var Z_DATA_ERROR = -3;
|
||||
//var Z_MEM_ERROR = -4;
|
||||
var Z_BUF_ERROR = -5;
|
||||
//var Z_VERSION_ERROR = -6;
|
||||
export const Z_OK = 0;
|
||||
export const Z_STREAM_END = 1;
|
||||
//export const Z_NEED_DICT = 2;
|
||||
//export const Z_ERRNO = -1;
|
||||
export const Z_STREAM_ERROR = -2;
|
||||
export const Z_DATA_ERROR = -3;
|
||||
//export const Z_MEM_ERROR = -4;
|
||||
export const Z_BUF_ERROR = -5;
|
||||
//export const Z_VERSION_ERROR = -6;
|
||||
|
||||
|
||||
/* compression levels */
|
||||
//var Z_NO_COMPRESSION = 0;
|
||||
//var Z_BEST_SPEED = 1;
|
||||
//var Z_BEST_COMPRESSION = 9;
|
||||
var Z_DEFAULT_COMPRESSION = -1;
|
||||
//export const Z_NO_COMPRESSION = 0;
|
||||
//export const Z_BEST_SPEED = 1;
|
||||
//export const Z_BEST_COMPRESSION = 9;
|
||||
export const Z_DEFAULT_COMPRESSION = -1;
|
||||
|
||||
|
||||
var Z_FILTERED = 1;
|
||||
var Z_HUFFMAN_ONLY = 2;
|
||||
var Z_RLE = 3;
|
||||
var Z_FIXED = 4;
|
||||
var Z_DEFAULT_STRATEGY = 0;
|
||||
export const Z_FILTERED = 1;
|
||||
export const Z_HUFFMAN_ONLY = 2;
|
||||
export const Z_RLE = 3;
|
||||
export const Z_FIXED = 4;
|
||||
export const Z_DEFAULT_STRATEGY = 0;
|
||||
|
||||
/* Possible values of the data_type field (though see inflate()) */
|
||||
//var Z_BINARY = 0;
|
||||
//var Z_TEXT = 1;
|
||||
//var Z_ASCII = 1; // = Z_TEXT
|
||||
var Z_UNKNOWN = 2;
|
||||
//export const Z_BINARY = 0;
|
||||
//export const Z_TEXT = 1;
|
||||
//export const Z_ASCII = 1; // = Z_TEXT
|
||||
export const Z_UNKNOWN = 2;
|
||||
|
||||
|
||||
/* The deflate compression method */
|
||||
var Z_DEFLATED = 8;
|
||||
export const Z_DEFLATED = 8;
|
||||
|
||||
/*============================================================================*/
|
||||
|
||||
|
|
|
|||
|
|
@ -13,30 +13,30 @@ var DISTS = 2;
|
|||
|
||||
|
||||
/* Allowed flush values; see deflate() and inflate() below for details */
|
||||
//var Z_NO_FLUSH = 0;
|
||||
//var Z_PARTIAL_FLUSH = 1;
|
||||
//var Z_SYNC_FLUSH = 2;
|
||||
//var Z_FULL_FLUSH = 3;
|
||||
var Z_FINISH = 4;
|
||||
var Z_BLOCK = 5;
|
||||
var Z_TREES = 6;
|
||||
//export const Z_NO_FLUSH = 0;
|
||||
//export const Z_PARTIAL_FLUSH = 1;
|
||||
//export const Z_SYNC_FLUSH = 2;
|
||||
//export const Z_FULL_FLUSH = 3;
|
||||
export const Z_FINISH = 4;
|
||||
export const Z_BLOCK = 5;
|
||||
export const Z_TREES = 6;
|
||||
|
||||
|
||||
/* Return codes for the compression/decompression functions. Negative values
|
||||
* are errors, positive values are used for special but normal events.
|
||||
*/
|
||||
var Z_OK = 0;
|
||||
var Z_STREAM_END = 1;
|
||||
var Z_NEED_DICT = 2;
|
||||
//var Z_ERRNO = -1;
|
||||
var Z_STREAM_ERROR = -2;
|
||||
var Z_DATA_ERROR = -3;
|
||||
var Z_MEM_ERROR = -4;
|
||||
var Z_BUF_ERROR = -5;
|
||||
//var Z_VERSION_ERROR = -6;
|
||||
export const Z_OK = 0;
|
||||
export const Z_STREAM_END = 1;
|
||||
export const Z_NEED_DICT = 2;
|
||||
//export const Z_ERRNO = -1;
|
||||
export const Z_STREAM_ERROR = -2;
|
||||
export const Z_DATA_ERROR = -3;
|
||||
export const Z_MEM_ERROR = -4;
|
||||
export const Z_BUF_ERROR = -5;
|
||||
//export const Z_VERSION_ERROR = -6;
|
||||
|
||||
/* The deflate compression method */
|
||||
var Z_DEFLATED = 8;
|
||||
export const Z_DEFLATED = 8;
|
||||
|
||||
|
||||
/* STATES ====================================================================*/
|
||||
|
|
|
|||
5
vnc.html
5
vnc.html
|
|
@ -207,6 +207,11 @@
|
|||
<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><hr></li>
|
||||
<li>
|
||||
<label for="noVNC_setting_repeaterID">Repeater ID:</label>
|
||||
<input id="noVNC_setting_repeaterID" type="text" value="">
|
||||
|
|
|
|||
Loading…
Reference in New Issue