1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2025-07-31 12:41:08 +00:00

noVNC Update 1.0.0 -> 1.1.0

This commit is contained in:
catborise 2019-06-24 14:26:25 +03:00
parent 61703b3faf
commit e601c23b4a
51 changed files with 18368 additions and 19828 deletions

View file

@ -1,7 +1,6 @@
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2012 Joel Martin
* Copyright (C) 2015 Samuel Mannehed for Cendio AB
* Copyright (C) 2018 The noVNC Authors
* Licensed under MPL 2.0 (see LICENSE.txt)
*
* See README.md for usage and integration instructions.
@ -9,111 +8,107 @@
import * as Log from './util/logging.js';
import Base64 from "./base64.js";
import { supportsImageMetadata } from './util/browser.js';
export default function Display(target) {
this._drawCtx = null;
this._c_forceCanvas = false;
export default class Display {
constructor(target) {
this._drawCtx = null;
this._c_forceCanvas = false;
this._renderQ = []; // queue drawing actions for in-oder rendering
this._flushing = false;
this._renderQ = []; // queue drawing actions for in-oder rendering
this._flushing = false;
// the full frame buffer (logical canvas) size
this._fb_width = 0;
this._fb_height = 0;
// the full frame buffer (logical canvas) size
this._fb_width = 0;
this._fb_height = 0;
this._prevDrawStyle = "";
this._tile = null;
this._tile16x16 = null;
this._tile_x = 0;
this._tile_y = 0;
this._prevDrawStyle = "";
this._tile = null;
this._tile16x16 = null;
this._tile_x = 0;
this._tile_y = 0;
Log.Debug(">> Display.constructor");
Log.Debug(">> Display.constructor");
// The visible canvas
this._target = target;
// The visible canvas
this._target = target;
if (!this._target) {
throw new Error("Target must be set");
if (!this._target) {
throw new Error("Target must be set");
}
if (typeof this._target === 'string') {
throw new Error('target must be a DOM element');
}
if (!this._target.getContext) {
throw new Error("no getContext method");
}
this._targetCtx = this._target.getContext('2d');
// the visible canvas viewport (i.e. what actually gets seen)
this._viewportLoc = { 'x': 0, 'y': 0, 'w': this._target.width, 'h': this._target.height };
// The hidden canvas, where we do the actual rendering
this._backbuffer = document.createElement('canvas');
this._drawCtx = this._backbuffer.getContext('2d');
this._damageBounds = { left: 0, top: 0,
right: this._backbuffer.width,
bottom: this._backbuffer.height };
Log.Debug("User Agent: " + navigator.userAgent);
this.clear();
// Check canvas features
if (!('createImageData' in this._drawCtx)) {
throw new Error("Canvas does not support createImageData");
}
this._tile16x16 = this._drawCtx.createImageData(16, 16);
Log.Debug("<< Display.constructor");
// ===== PROPERTIES =====
this._scale = 1.0;
this._clipViewport = false;
this.logo = null;
// ===== EVENT HANDLERS =====
this.onflush = () => {}; // A flush request has finished
}
if (typeof this._target === 'string') {
throw new Error('target must be a DOM element');
}
if (!this._target.getContext) {
throw new Error("no getContext method");
}
this._targetCtx = this._target.getContext('2d');
// the visible canvas viewport (i.e. what actually gets seen)
this._viewportLoc = { 'x': 0, 'y': 0, 'w': this._target.width, 'h': this._target.height };
// The hidden canvas, where we do the actual rendering
this._backbuffer = document.createElement('canvas');
this._drawCtx = this._backbuffer.getContext('2d');
this._damageBounds = { left:0, top:0,
right: this._backbuffer.width,
bottom: this._backbuffer.height };
Log.Debug("User Agent: " + navigator.userAgent);
this.clear();
// Check canvas features
if (!('createImageData' in this._drawCtx)) {
throw new Error("Canvas does not support createImageData");
}
this._tile16x16 = this._drawCtx.createImageData(16, 16);
Log.Debug("<< Display.constructor");
};
var SUPPORTS_IMAGEDATA_CONSTRUCTOR = false;
try {
new ImageData(new Uint8ClampedArray(4), 1, 1);
SUPPORTS_IMAGEDATA_CONSTRUCTOR = true;
} catch (ex) {
// ignore failure
}
Display.prototype = {
// ===== PROPERTIES =====
_scale: 1.0,
get scale() { return this._scale; },
get scale() { return this._scale; }
set scale(scale) {
this._rescale(scale);
},
}
_clipViewport: false,
get clipViewport() { return this._clipViewport; },
get clipViewport() { return this._clipViewport; }
set clipViewport(viewport) {
this._clipViewport = viewport;
// May need to readjust the viewport dimensions
var vp = this._viewportLoc;
const vp = this._viewportLoc;
this.viewportChangeSize(vp.w, vp.h);
this.viewportChangePos(0, 0);
},
}
get width() {
return this._fb_width;
},
}
get height() {
return this._fb_height;
},
logo: null,
// ===== EVENT HANDLERS =====
onflush: function () {}, // A flush request has finished
}
// ===== PUBLIC METHODS =====
viewportChangePos: function (deltaX, deltaY) {
var vp = this._viewportLoc;
viewportChangePos(deltaX, deltaY) {
const vp = this._viewportLoc;
deltaX = Math.floor(deltaX);
deltaY = Math.floor(deltaY);
@ -122,8 +117,8 @@ Display.prototype = {
deltaY = -vp.h;
}
var vx2 = vp.x + vp.w - 1;
var vy2 = vp.y + vp.h - 1;
const vx2 = vp.x + vp.w - 1;
const vy2 = vp.y + vp.h - 1;
// Position change
@ -152,9 +147,9 @@ Display.prototype = {
this._damage(vp.x, vp.y, vp.w, vp.h);
this.flip();
},
}
viewportChangeSize: function(width, height) {
viewportChangeSize(width, height) {
if (!this._clipViewport ||
typeof(width) === "undefined" ||
@ -165,6 +160,9 @@ Display.prototype = {
height = this._fb_height;
}
width = Math.floor(width);
height = Math.floor(height);
if (width > this._fb_width) {
width = this._fb_width;
}
@ -172,12 +170,12 @@ Display.prototype = {
height = this._fb_height;
}
var vp = this._viewportLoc;
const vp = this._viewportLoc;
if (vp.w !== width || vp.h !== height) {
vp.w = width;
vp.h = height;
var canvas = this._target;
const canvas = this._target;
canvas.width = width;
canvas.height = height;
@ -190,27 +188,33 @@ Display.prototype = {
// Update the visible size of the target canvas
this._rescale(this._scale);
}
},
}
absX: function (x) {
absX(x) {
if (this._scale === 0) {
return 0;
}
return x / this._scale + this._viewportLoc.x;
},
}
absY: function (y) {
absY(y) {
if (this._scale === 0) {
return 0;
}
return y / this._scale + this._viewportLoc.y;
},
}
resize: function (width, height) {
resize(width, height) {
this._prevDrawStyle = "";
this._fb_width = width;
this._fb_height = height;
var canvas = this._backbuffer;
const canvas = this._backbuffer;
if (canvas.width !== width || canvas.height !== height) {
// We have to save the canvas data since changing the size will clear it
var saveImg = null;
let saveImg = null;
if (canvas.width > 0 && canvas.height > 0) {
saveImg = this._drawCtx.getImageData(0, 0, canvas.width, canvas.height);
}
@ -229,13 +233,13 @@ Display.prototype = {
// Readjust the viewport as it may be incorrectly sized
// and positioned
var vp = this._viewportLoc;
const vp = this._viewportLoc;
this.viewportChangeSize(vp.w, vp.h);
this.viewportChangePos(0, 0);
},
}
// Track what parts of the visible canvas that need updating
_damage: function(x, y, w, h) {
_damage(x, y, w, h) {
if (x < this._damageBounds.left) {
this._damageBounds.left = x;
}
@ -248,25 +252,23 @@ Display.prototype = {
if ((y + h) > this._damageBounds.bottom) {
this._damageBounds.bottom = y + h;
}
},
}
// Update the visible canvas with the contents of the
// rendering canvas
flip: function(from_queue) {
flip(from_queue) {
if (this._renderQ.length !== 0 && !from_queue) {
this._renderQ_push({
'type': 'flip'
});
} else {
var x, y, vx, vy, w, h;
let x = this._damageBounds.left;
let y = this._damageBounds.top;
let w = this._damageBounds.right - x;
let h = this._damageBounds.bottom - y;
x = this._damageBounds.left;
y = this._damageBounds.top;
w = this._damageBounds.right - x;
h = this._damageBounds.bottom - y;
vx = x - this._viewportLoc.x;
vy = y - this._viewportLoc.y;
let vx = x - this._viewportLoc.x;
let vy = y - this._viewportLoc.y;
if (vx < 0) {
w += vx;
@ -298,9 +300,9 @@ Display.prototype = {
this._damageBounds.left = this._damageBounds.top = 65535;
this._damageBounds.right = this._damageBounds.bottom = 0;
}
},
}
clear: function () {
clear() {
if (this._logo) {
this.resize(this._logo.width, this._logo.height);
this.imageRect(0, 0, this._logo.type, this._logo.data);
@ -309,21 +311,21 @@ Display.prototype = {
this._drawCtx.clearRect(0, 0, this._fb_width, this._fb_height);
}
this.flip();
},
}
pending: function() {
pending() {
return this._renderQ.length > 0;
},
}
flush: function() {
flush() {
if (this._renderQ.length === 0) {
this.onflush();
} else {
this._flushing = true;
}
},
}
fillRect: function (x, y, width, height, color, from_queue) {
fillRect(x, y, width, height, color, from_queue) {
if (this._renderQ.length !== 0 && !from_queue) {
this._renderQ_push({
'type': 'fill',
@ -338,9 +340,9 @@ Display.prototype = {
this._drawCtx.fillRect(x, y, width, height);
this._damage(x, y, width, height);
}
},
}
copyImage: function (old_x, old_y, new_x, new_y, w, h, from_queue) {
copyImage(old_x, old_y, new_x, new_y, w, h, from_queue) {
if (this._renderQ.length !== 0 && !from_queue) {
this._renderQ_push({
'type': 'copy',
@ -369,10 +371,10 @@ Display.prototype = {
new_x, new_y, w, h);
this._damage(new_x, new_y, w, h);
}
},
}
imageRect: function(x, y, mime, arr) {
var img = new Image();
imageRect(x, y, mime, arr) {
const img = new Image();
img.src = "data: " + mime + ";base64," + Base64.encode(arr);
this._renderQ_push({
'type': 'img',
@ -380,10 +382,10 @@ Display.prototype = {
'x': x,
'y': y
});
},
}
// start updating a tile
startTile: function (x, y, width, height, color) {
startTile(x, y, width, height, color) {
this._tile_x = x;
this._tile_y = y;
if (width === 16 && height === 16) {
@ -392,53 +394,53 @@ Display.prototype = {
this._tile = this._drawCtx.createImageData(width, height);
}
var red = color[2];
var green = color[1];
var blue = color[0];
const red = color[2];
const green = color[1];
const blue = color[0];
var data = this._tile.data;
for (var i = 0; i < width * height * 4; i += 4) {
const data = this._tile.data;
for (let i = 0; i < width * height * 4; i += 4) {
data[i] = red;
data[i + 1] = green;
data[i + 2] = blue;
data[i + 3] = 255;
}
},
}
// update sub-rectangle of the current tile
subTile: function (x, y, w, h, color) {
var red = color[2];
var green = color[1];
var blue = color[0];
var xend = x + w;
var yend = y + h;
subTile(x, y, w, h, color) {
const red = color[2];
const green = color[1];
const blue = color[0];
const xend = x + w;
const yend = y + h;
var data = this._tile.data;
var width = this._tile.width;
for (var j = y; j < yend; j++) {
for (var i = x; i < xend; i++) {
var p = (i + (j * width)) * 4;
const data = this._tile.data;
const width = this._tile.width;
for (let j = y; j < yend; j++) {
for (let i = x; i < xend; i++) {
const p = (i + (j * width)) * 4;
data[p] = red;
data[p + 1] = green;
data[p + 2] = blue;
data[p + 3] = 255;
}
}
},
}
// draw the current tile to the screen
finishTile: function () {
finishTile() {
this._drawCtx.putImageData(this._tile, this._tile_x, this._tile_y);
this._damage(this._tile_x, this._tile_y,
this._tile.width, this._tile.height);
},
}
blitImage: function (x, y, width, height, arr, offset, from_queue) {
blitImage(x, y, width, height, arr, offset, from_queue) {
if (this._renderQ.length !== 0 && !from_queue) {
// NB(directxman12): it's technically more performant here to use preallocated arrays,
// but it's a lot of extra work for not a lot of payoff -- if we're using the render queue,
// this probably isn't getting called *nearly* as much
var new_arr = new Uint8Array(width * height * 4);
const new_arr = new Uint8Array(width * height * 4);
new_arr.set(new Uint8Array(arr.buffer, 0, new_arr.length));
this._renderQ_push({
'type': 'blit',
@ -451,14 +453,14 @@ Display.prototype = {
} else {
this._bgrxImageData(x, y, width, height, arr, offset);
}
},
}
blitRgbImage: function (x, y , width, height, arr, offset, from_queue) {
blitRgbImage(x, y, width, height, arr, offset, from_queue) {
if (this._renderQ.length !== 0 && !from_queue) {
// NB(directxman12): it's technically more performant here to use preallocated arrays,
// but it's a lot of extra work for not a lot of payoff -- if we're using the render queue,
// this probably isn't getting called *nearly* as much
var new_arr = new Uint8Array(width * height * 3);
const new_arr = new Uint8Array(width * height * 3);
new_arr.set(new Uint8Array(arr.buffer, 0, new_arr.length));
this._renderQ_push({
'type': 'blitRgb',
@ -471,14 +473,14 @@ Display.prototype = {
} else {
this._rgbImageData(x, y, width, height, arr, offset);
}
},
}
blitRgbxImage: function (x, y, width, height, arr, offset, from_queue) {
blitRgbxImage(x, y, width, height, arr, offset, from_queue) {
if (this._renderQ.length !== 0 && !from_queue) {
// NB(directxman12): it's technically more performant here to use preallocated arrays,
// but it's a lot of extra work for not a lot of payoff -- if we're using the render queue,
// this probably isn't getting called *nearly* as much
var new_arr = new Uint8Array(width * height * 4);
const new_arr = new Uint8Array(width * height * 4);
new_arr.set(new Uint8Array(arr.buffer, 0, new_arr.length));
this._renderQ_push({
'type': 'blitRgbx',
@ -491,72 +493,67 @@ Display.prototype = {
} else {
this._rgbxImageData(x, y, width, height, arr, offset);
}
},
}
drawImage: function (img, x, y) {
drawImage(img, x, y) {
this._drawCtx.drawImage(img, x, y);
this._damage(x, y, img.width, img.height);
},
}
changeCursor: function (pixels, mask, hotx, hoty, w, h) {
Display.changeCursor(this._target, pixels, mask, hotx, hoty, w, h);
},
autoscale(containerWidth, containerHeight) {
let scaleRatio;
defaultCursor: function () {
this._target.style.cursor = "default";
},
if (containerWidth === 0 || containerHeight === 0) {
scaleRatio = 0;
disableLocalCursor: function () {
this._target.style.cursor = "none";
},
autoscale: function (containerWidth, containerHeight) {
var vp = this._viewportLoc;
var targetAspectRatio = containerWidth / containerHeight;
var fbAspectRatio = vp.w / vp.h;
var scaleRatio;
if (fbAspectRatio >= targetAspectRatio) {
scaleRatio = containerWidth / vp.w;
} else {
scaleRatio = containerHeight / vp.h;
const vp = this._viewportLoc;
const targetAspectRatio = containerWidth / containerHeight;
const fbAspectRatio = vp.w / vp.h;
if (fbAspectRatio >= targetAspectRatio) {
scaleRatio = containerWidth / vp.w;
} else {
scaleRatio = containerHeight / vp.h;
}
}
this._rescale(scaleRatio);
},
}
// ===== PRIVATE METHODS =====
_rescale: function (factor) {
_rescale(factor) {
this._scale = factor;
var vp = this._viewportLoc;
const vp = this._viewportLoc;
// NB(directxman12): If you set the width directly, or set the
// style width to a number, the canvas is cleared.
// However, if you set the style width to a string
// ('NNNpx'), the canvas is scaled without clearing.
var width = Math.round(factor * vp.w) + 'px';
var height = Math.round(factor * vp.h) + 'px';
const width = factor * vp.w + 'px';
const height = factor * vp.h + 'px';
if ((this._target.style.width !== width) ||
(this._target.style.height !== height)) {
this._target.style.width = width;
this._target.style.height = height;
}
},
}
_setFillColor: function (color) {
var newStyle = 'rgb(' + color[2] + ',' + color[1] + ',' + color[0] + ')';
_setFillColor(color) {
const newStyle = 'rgb(' + color[2] + ',' + color[1] + ',' + color[0] + ')';
if (newStyle !== this._prevDrawStyle) {
this._drawCtx.fillStyle = newStyle;
this._prevDrawStyle = newStyle;
}
},
}
_rgbImageData: function (x, y, width, height, arr, offset) {
var img = this._drawCtx.createImageData(width, height);
var data = img.data;
for (var i = 0, j = offset; i < width * height * 4; i += 4, j += 3) {
_rgbImageData(x, y, width, height, arr, offset) {
const img = this._drawCtx.createImageData(width, height);
const data = img.data;
for (let i = 0, j = offset; i < width * height * 4; i += 4, j += 3) {
data[i] = arr[j];
data[i + 1] = arr[j + 1];
data[i + 2] = arr[j + 2];
@ -564,12 +561,12 @@ Display.prototype = {
}
this._drawCtx.putImageData(img, x, y);
this._damage(x, y, img.width, img.height);
},
}
_bgrxImageData: function (x, y, width, height, arr, offset) {
var img = this._drawCtx.createImageData(width, height);
var data = img.data;
for (var i = 0, j = offset; i < width * height * 4; i += 4, j += 4) {
_bgrxImageData(x, y, width, height, arr, offset) {
const img = this._drawCtx.createImageData(width, height);
const data = img.data;
for (let i = 0, j = offset; i < width * height * 4; i += 4, j += 4) {
data[i] = arr[j + 2];
data[i + 1] = arr[j + 1];
data[i + 2] = arr[j];
@ -577,12 +574,12 @@ Display.prototype = {
}
this._drawCtx.putImageData(img, x, y);
this._damage(x, y, img.width, img.height);
},
}
_rgbxImageData: function (x, y, width, height, arr, offset) {
_rgbxImageData(x, y, width, height, arr, offset) {
// NB(directxman12): arr must be an Type Array view
var img;
if (SUPPORTS_IMAGEDATA_CONSTRUCTOR) {
let img;
if (supportsImageMetadata) {
img = new ImageData(new Uint8ClampedArray(arr.buffer, arr.byteOffset, width * height * 4), width, height);
} else {
img = this._drawCtx.createImageData(width, height);
@ -590,28 +587,28 @@ Display.prototype = {
}
this._drawCtx.putImageData(img, x, y);
this._damage(x, y, img.width, img.height);
},
}
_renderQ_push: function (action) {
_renderQ_push(action) {
this._renderQ.push(action);
if (this._renderQ.length === 1) {
// If this can be rendered immediately it will be, otherwise
// the scanner will wait for the relevant event
this._scan_renderQ();
}
},
}
_resume_renderQ: function() {
_resume_renderQ() {
// "this" is the object that is ready, not the
// display object
this.removeEventListener('load', this._noVNC_display._resume_renderQ);
this._noVNC_display._scan_renderQ();
},
}
_scan_renderQ: function () {
var ready = true;
_scan_renderQ() {
let ready = true;
while (ready && this._renderQ.length > 0) {
var a = this._renderQ[0];
const a = this._renderQ[0];
switch (a.type) {
case 'flip':
this.flip(true);
@ -653,46 +650,5 @@ Display.prototype = {
this._flushing = false;
this.onflush();
}
},
};
// Class Methods
Display.changeCursor = function (target, pixels, mask, hotx, hoty, w, h) {
if ((w === 0) || (h === 0)) {
target.style.cursor = 'none';
return;
}
var cur = []
var y, x;
for (y = 0; y < h; y++) {
for (x = 0; x < w; x++) {
var idx = y * Math.ceil(w / 8) + Math.floor(x / 8);
var alpha = (mask[idx] << (x % 8)) & 0x80 ? 255 : 0;
idx = ((w * y) + x) * 4;
cur.push(pixels[idx + 2]); // red
cur.push(pixels[idx + 1]); // green
cur.push(pixels[idx]); // blue
cur.push(alpha); // alpha
}
}
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
canvas.width = w;
canvas.height = h;
var img;
if (SUPPORTS_IMAGEDATA_CONSTRUCTOR) {
img = new ImageData(new Uint8ClampedArray(cur), w, h);
} else {
img = ctx.createImageData(w, h);
img.data.set(new Uint8ClampedArray(cur));
}
ctx.clearRect(0, 0, w, h);
ctx.putImageData(img, 0, 0);
var url = canvas.toDataURL();
target.style.cursor = 'url(' + url + ')' + hotx + ' ' + hoty + ', default';
};
}