susi-qemu 0.0.3 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/bin/susi +9 -4
- data/lib/disk.rb +7 -5
- data/lib/novnc/core/base64.js +104 -0
- data/lib/novnc/core/crypto/aes.js +178 -0
- data/lib/novnc/core/crypto/bigint.js +34 -0
- data/lib/novnc/core/crypto/crypto.js +90 -0
- data/lib/novnc/core/crypto/des.js +330 -0
- data/lib/novnc/core/crypto/dh.js +55 -0
- data/lib/novnc/core/crypto/md5.js +82 -0
- data/lib/novnc/core/crypto/rsa.js +132 -0
- data/lib/novnc/core/decoders/copyrect.js +27 -0
- data/lib/novnc/core/decoders/h264.js +321 -0
- data/lib/novnc/core/decoders/hextile.js +181 -0
- data/lib/novnc/core/decoders/jpeg.js +146 -0
- data/lib/novnc/core/decoders/raw.js +59 -0
- data/lib/novnc/core/decoders/rre.js +44 -0
- data/lib/novnc/core/decoders/tight.js +393 -0
- data/lib/novnc/core/decoders/tightpng.js +27 -0
- data/lib/novnc/core/decoders/zlib.js +51 -0
- data/lib/novnc/core/decoders/zrle.js +185 -0
- data/lib/novnc/core/deflator.js +84 -0
- data/lib/novnc/core/display.js +575 -0
- data/lib/novnc/core/encodings.js +53 -0
- data/lib/novnc/core/inflator.js +65 -0
- data/lib/novnc/core/input/domkeytable.js +311 -0
- data/lib/novnc/core/input/fixedkeys.js +129 -0
- data/lib/novnc/core/input/gesturehandler.js +567 -0
- data/lib/novnc/core/input/keyboard.js +294 -0
- data/lib/novnc/core/input/keysym.js +616 -0
- data/lib/novnc/core/input/keysymdef.js +688 -0
- data/lib/novnc/core/input/util.js +191 -0
- data/lib/novnc/core/input/vkeys.js +116 -0
- data/lib/novnc/core/input/xtscancodes.js +173 -0
- data/lib/novnc/core/ra2.js +312 -0
- data/lib/novnc/core/rfb.js +3257 -0
- data/lib/novnc/core/util/browser.js +172 -0
- data/lib/novnc/core/util/cursor.js +249 -0
- data/lib/novnc/core/util/element.js +32 -0
- data/lib/novnc/core/util/events.js +138 -0
- data/lib/novnc/core/util/eventtarget.js +35 -0
- data/lib/novnc/core/util/int.js +15 -0
- data/lib/novnc/core/util/logging.js +56 -0
- data/lib/novnc/core/util/strings.js +28 -0
- data/lib/novnc/core/websock.js +365 -0
- data/lib/novnc/screen.html +21 -0
- data/lib/novnc/vendor/pako/lib/utils/common.js +45 -0
- data/lib/novnc/vendor/pako/lib/zlib/adler32.js +27 -0
- data/lib/novnc/vendor/pako/lib/zlib/constants.js +47 -0
- data/lib/novnc/vendor/pako/lib/zlib/crc32.js +36 -0
- data/lib/novnc/vendor/pako/lib/zlib/deflate.js +1846 -0
- data/lib/novnc/vendor/pako/lib/zlib/gzheader.js +35 -0
- data/lib/novnc/vendor/pako/lib/zlib/inffast.js +324 -0
- data/lib/novnc/vendor/pako/lib/zlib/inflate.js +1527 -0
- data/lib/novnc/vendor/pako/lib/zlib/inftrees.js +322 -0
- data/lib/novnc/vendor/pako/lib/zlib/messages.js +11 -0
- data/lib/novnc/vendor/pako/lib/zlib/trees.js +1195 -0
- data/lib/novnc/vendor/pako/lib/zlib/zstream.js +24 -0
- data/lib/output.rb +11 -0
- data/lib/qmp.rb +6 -0
- data/lib/ssh.rb +3 -1
- data/lib/susi.rb +7 -6
- data/lib/version.rb +1 -1
- data/lib/vm.rb +36 -13
- data/lib/vnc.rb +34 -30
- metadata +85 -1
@@ -0,0 +1,84 @@
|
|
1
|
+
/*
|
2
|
+
* noVNC: HTML5 VNC client
|
3
|
+
* Copyright (C) 2020 The noVNC Authors
|
4
|
+
* Licensed under MPL 2.0 (see LICENSE.txt)
|
5
|
+
*
|
6
|
+
* See README.md for usage and integration instructions.
|
7
|
+
*/
|
8
|
+
|
9
|
+
import { deflateInit, deflate } from "../vendor/pako/lib/zlib/deflate.js";
|
10
|
+
import { Z_FULL_FLUSH, Z_DEFAULT_COMPRESSION } from "../vendor/pako/lib/zlib/deflate.js";
|
11
|
+
import ZStream from "../vendor/pako/lib/zlib/zstream.js";
|
12
|
+
|
13
|
+
export default class Deflator {
|
14
|
+
constructor() {
|
15
|
+
this.strm = new ZStream();
|
16
|
+
this.chunkSize = 1024 * 10 * 10;
|
17
|
+
this.outputBuffer = new Uint8Array(this.chunkSize);
|
18
|
+
|
19
|
+
deflateInit(this.strm, Z_DEFAULT_COMPRESSION);
|
20
|
+
}
|
21
|
+
|
22
|
+
deflate(inData) {
|
23
|
+
/* eslint-disable camelcase */
|
24
|
+
this.strm.input = inData;
|
25
|
+
this.strm.avail_in = this.strm.input.length;
|
26
|
+
this.strm.next_in = 0;
|
27
|
+
this.strm.output = this.outputBuffer;
|
28
|
+
this.strm.avail_out = this.chunkSize;
|
29
|
+
this.strm.next_out = 0;
|
30
|
+
/* eslint-enable camelcase */
|
31
|
+
|
32
|
+
let lastRet = deflate(this.strm, Z_FULL_FLUSH);
|
33
|
+
let outData = new Uint8Array(this.strm.output.buffer, 0, this.strm.next_out);
|
34
|
+
|
35
|
+
if (lastRet < 0) {
|
36
|
+
throw new Error("zlib deflate failed");
|
37
|
+
}
|
38
|
+
|
39
|
+
if (this.strm.avail_in > 0) {
|
40
|
+
// Read chunks until done
|
41
|
+
|
42
|
+
let chunks = [outData];
|
43
|
+
let totalLen = outData.length;
|
44
|
+
do {
|
45
|
+
/* eslint-disable camelcase */
|
46
|
+
this.strm.output = new Uint8Array(this.chunkSize);
|
47
|
+
this.strm.next_out = 0;
|
48
|
+
this.strm.avail_out = this.chunkSize;
|
49
|
+
/* eslint-enable camelcase */
|
50
|
+
|
51
|
+
lastRet = deflate(this.strm, Z_FULL_FLUSH);
|
52
|
+
|
53
|
+
if (lastRet < 0) {
|
54
|
+
throw new Error("zlib deflate failed");
|
55
|
+
}
|
56
|
+
|
57
|
+
let chunk = new Uint8Array(this.strm.output.buffer, 0, this.strm.next_out);
|
58
|
+
totalLen += chunk.length;
|
59
|
+
chunks.push(chunk);
|
60
|
+
} while (this.strm.avail_in > 0);
|
61
|
+
|
62
|
+
// Combine chunks into a single data
|
63
|
+
|
64
|
+
let newData = new Uint8Array(totalLen);
|
65
|
+
let offset = 0;
|
66
|
+
|
67
|
+
for (let i = 0; i < chunks.length; i++) {
|
68
|
+
newData.set(chunks[i], offset);
|
69
|
+
offset += chunks[i].length;
|
70
|
+
}
|
71
|
+
|
72
|
+
outData = newData;
|
73
|
+
}
|
74
|
+
|
75
|
+
/* eslint-disable camelcase */
|
76
|
+
this.strm.input = null;
|
77
|
+
this.strm.avail_in = 0;
|
78
|
+
this.strm.next_in = 0;
|
79
|
+
/* eslint-enable camelcase */
|
80
|
+
|
81
|
+
return outData;
|
82
|
+
}
|
83
|
+
|
84
|
+
}
|
@@ -0,0 +1,575 @@
|
|
1
|
+
/*
|
2
|
+
* noVNC: HTML5 VNC client
|
3
|
+
* Copyright (C) 2019 The noVNC Authors
|
4
|
+
* Licensed under MPL 2.0 (see LICENSE.txt)
|
5
|
+
*
|
6
|
+
* See README.md for usage and integration instructions.
|
7
|
+
*/
|
8
|
+
|
9
|
+
import * as Log from './util/logging.js';
|
10
|
+
import Base64 from "./base64.js";
|
11
|
+
import { toSigned32bit } from './util/int.js';
|
12
|
+
|
13
|
+
export default class Display {
|
14
|
+
constructor(target) {
|
15
|
+
this._drawCtx = null;
|
16
|
+
|
17
|
+
this._renderQ = []; // queue drawing actions for in-oder rendering
|
18
|
+
this._flushPromise = null;
|
19
|
+
|
20
|
+
// the full frame buffer (logical canvas) size
|
21
|
+
this._fbWidth = 0;
|
22
|
+
this._fbHeight = 0;
|
23
|
+
|
24
|
+
this._prevDrawStyle = "";
|
25
|
+
|
26
|
+
Log.Debug(">> Display.constructor");
|
27
|
+
|
28
|
+
// The visible canvas
|
29
|
+
this._target = target;
|
30
|
+
|
31
|
+
if (!this._target) {
|
32
|
+
throw new Error("Target must be set");
|
33
|
+
}
|
34
|
+
|
35
|
+
if (typeof this._target === 'string') {
|
36
|
+
throw new Error('target must be a DOM element');
|
37
|
+
}
|
38
|
+
|
39
|
+
if (!this._target.getContext) {
|
40
|
+
throw new Error("no getContext method");
|
41
|
+
}
|
42
|
+
|
43
|
+
this._targetCtx = this._target.getContext('2d');
|
44
|
+
|
45
|
+
// the visible canvas viewport (i.e. what actually gets seen)
|
46
|
+
this._viewportLoc = { 'x': 0, 'y': 0, 'w': this._target.width, 'h': this._target.height };
|
47
|
+
|
48
|
+
// The hidden canvas, where we do the actual rendering
|
49
|
+
this._backbuffer = document.createElement('canvas');
|
50
|
+
this._drawCtx = this._backbuffer.getContext('2d');
|
51
|
+
|
52
|
+
this._damageBounds = { left: 0, top: 0,
|
53
|
+
right: this._backbuffer.width,
|
54
|
+
bottom: this._backbuffer.height };
|
55
|
+
|
56
|
+
Log.Debug("User Agent: " + navigator.userAgent);
|
57
|
+
|
58
|
+
Log.Debug("<< Display.constructor");
|
59
|
+
|
60
|
+
// ===== PROPERTIES =====
|
61
|
+
|
62
|
+
this._scale = 1.0;
|
63
|
+
this._clipViewport = false;
|
64
|
+
}
|
65
|
+
|
66
|
+
// ===== PROPERTIES =====
|
67
|
+
|
68
|
+
get scale() { return this._scale; }
|
69
|
+
set scale(scale) {
|
70
|
+
this._rescale(scale);
|
71
|
+
}
|
72
|
+
|
73
|
+
get clipViewport() { return this._clipViewport; }
|
74
|
+
set clipViewport(viewport) {
|
75
|
+
this._clipViewport = viewport;
|
76
|
+
// May need to readjust the viewport dimensions
|
77
|
+
const vp = this._viewportLoc;
|
78
|
+
this.viewportChangeSize(vp.w, vp.h);
|
79
|
+
this.viewportChangePos(0, 0);
|
80
|
+
}
|
81
|
+
|
82
|
+
get width() {
|
83
|
+
return this._fbWidth;
|
84
|
+
}
|
85
|
+
|
86
|
+
get height() {
|
87
|
+
return this._fbHeight;
|
88
|
+
}
|
89
|
+
|
90
|
+
// ===== PUBLIC METHODS =====
|
91
|
+
|
92
|
+
viewportChangePos(deltaX, deltaY) {
|
93
|
+
const vp = this._viewportLoc;
|
94
|
+
deltaX = Math.floor(deltaX);
|
95
|
+
deltaY = Math.floor(deltaY);
|
96
|
+
|
97
|
+
if (!this._clipViewport) {
|
98
|
+
deltaX = -vp.w; // clamped later of out of bounds
|
99
|
+
deltaY = -vp.h;
|
100
|
+
}
|
101
|
+
|
102
|
+
const vx2 = vp.x + vp.w - 1;
|
103
|
+
const vy2 = vp.y + vp.h - 1;
|
104
|
+
|
105
|
+
// Position change
|
106
|
+
|
107
|
+
if (deltaX < 0 && vp.x + deltaX < 0) {
|
108
|
+
deltaX = -vp.x;
|
109
|
+
}
|
110
|
+
if (vx2 + deltaX >= this._fbWidth) {
|
111
|
+
deltaX -= vx2 + deltaX - this._fbWidth + 1;
|
112
|
+
}
|
113
|
+
|
114
|
+
if (vp.y + deltaY < 0) {
|
115
|
+
deltaY = -vp.y;
|
116
|
+
}
|
117
|
+
if (vy2 + deltaY >= this._fbHeight) {
|
118
|
+
deltaY -= (vy2 + deltaY - this._fbHeight + 1);
|
119
|
+
}
|
120
|
+
|
121
|
+
if (deltaX === 0 && deltaY === 0) {
|
122
|
+
return;
|
123
|
+
}
|
124
|
+
Log.Debug("viewportChange deltaX: " + deltaX + ", deltaY: " + deltaY);
|
125
|
+
|
126
|
+
vp.x += deltaX;
|
127
|
+
vp.y += deltaY;
|
128
|
+
|
129
|
+
this._damage(vp.x, vp.y, vp.w, vp.h);
|
130
|
+
|
131
|
+
this.flip();
|
132
|
+
}
|
133
|
+
|
134
|
+
viewportChangeSize(width, height) {
|
135
|
+
|
136
|
+
if (!this._clipViewport ||
|
137
|
+
typeof(width) === "undefined" ||
|
138
|
+
typeof(height) === "undefined") {
|
139
|
+
|
140
|
+
Log.Debug("Setting viewport to full display region");
|
141
|
+
width = this._fbWidth;
|
142
|
+
height = this._fbHeight;
|
143
|
+
}
|
144
|
+
|
145
|
+
width = Math.floor(width);
|
146
|
+
height = Math.floor(height);
|
147
|
+
|
148
|
+
if (width > this._fbWidth) {
|
149
|
+
width = this._fbWidth;
|
150
|
+
}
|
151
|
+
if (height > this._fbHeight) {
|
152
|
+
height = this._fbHeight;
|
153
|
+
}
|
154
|
+
|
155
|
+
const vp = this._viewportLoc;
|
156
|
+
if (vp.w !== width || vp.h !== height) {
|
157
|
+
vp.w = width;
|
158
|
+
vp.h = height;
|
159
|
+
|
160
|
+
const canvas = this._target;
|
161
|
+
canvas.width = width;
|
162
|
+
canvas.height = height;
|
163
|
+
|
164
|
+
// The position might need to be updated if we've grown
|
165
|
+
this.viewportChangePos(0, 0);
|
166
|
+
|
167
|
+
this._damage(vp.x, vp.y, vp.w, vp.h);
|
168
|
+
this.flip();
|
169
|
+
|
170
|
+
// Update the visible size of the target canvas
|
171
|
+
this._rescale(this._scale);
|
172
|
+
}
|
173
|
+
}
|
174
|
+
|
175
|
+
absX(x) {
|
176
|
+
if (this._scale === 0) {
|
177
|
+
return 0;
|
178
|
+
}
|
179
|
+
return toSigned32bit(x / this._scale + this._viewportLoc.x);
|
180
|
+
}
|
181
|
+
|
182
|
+
absY(y) {
|
183
|
+
if (this._scale === 0) {
|
184
|
+
return 0;
|
185
|
+
}
|
186
|
+
return toSigned32bit(y / this._scale + this._viewportLoc.y);
|
187
|
+
}
|
188
|
+
|
189
|
+
resize(width, height) {
|
190
|
+
this._prevDrawStyle = "";
|
191
|
+
|
192
|
+
this._fbWidth = width;
|
193
|
+
this._fbHeight = height;
|
194
|
+
|
195
|
+
const canvas = this._backbuffer;
|
196
|
+
if (canvas.width !== width || canvas.height !== height) {
|
197
|
+
|
198
|
+
// We have to save the canvas data since changing the size will clear it
|
199
|
+
let saveImg = null;
|
200
|
+
if (canvas.width > 0 && canvas.height > 0) {
|
201
|
+
saveImg = this._drawCtx.getImageData(0, 0, canvas.width, canvas.height);
|
202
|
+
}
|
203
|
+
|
204
|
+
if (canvas.width !== width) {
|
205
|
+
canvas.width = width;
|
206
|
+
}
|
207
|
+
if (canvas.height !== height) {
|
208
|
+
canvas.height = height;
|
209
|
+
}
|
210
|
+
|
211
|
+
if (saveImg) {
|
212
|
+
this._drawCtx.putImageData(saveImg, 0, 0);
|
213
|
+
}
|
214
|
+
}
|
215
|
+
|
216
|
+
// Readjust the viewport as it may be incorrectly sized
|
217
|
+
// and positioned
|
218
|
+
const vp = this._viewportLoc;
|
219
|
+
this.viewportChangeSize(vp.w, vp.h);
|
220
|
+
this.viewportChangePos(0, 0);
|
221
|
+
}
|
222
|
+
|
223
|
+
getImageData() {
|
224
|
+
return this._drawCtx.getImageData(0, 0, this.width, this.height);
|
225
|
+
}
|
226
|
+
|
227
|
+
toDataURL(type, encoderOptions) {
|
228
|
+
return this._backbuffer.toDataURL(type, encoderOptions);
|
229
|
+
}
|
230
|
+
|
231
|
+
toBlob(callback, type, quality) {
|
232
|
+
return this._backbuffer.toBlob(callback, type, quality);
|
233
|
+
}
|
234
|
+
|
235
|
+
// Track what parts of the visible canvas that need updating
|
236
|
+
_damage(x, y, w, h) {
|
237
|
+
if (x < this._damageBounds.left) {
|
238
|
+
this._damageBounds.left = x;
|
239
|
+
}
|
240
|
+
if (y < this._damageBounds.top) {
|
241
|
+
this._damageBounds.top = y;
|
242
|
+
}
|
243
|
+
if ((x + w) > this._damageBounds.right) {
|
244
|
+
this._damageBounds.right = x + w;
|
245
|
+
}
|
246
|
+
if ((y + h) > this._damageBounds.bottom) {
|
247
|
+
this._damageBounds.bottom = y + h;
|
248
|
+
}
|
249
|
+
}
|
250
|
+
|
251
|
+
// Update the visible canvas with the contents of the
|
252
|
+
// rendering canvas
|
253
|
+
flip(fromQueue) {
|
254
|
+
if (this._renderQ.length !== 0 && !fromQueue) {
|
255
|
+
this._renderQPush({
|
256
|
+
'type': 'flip'
|
257
|
+
});
|
258
|
+
} else {
|
259
|
+
let x = this._damageBounds.left;
|
260
|
+
let y = this._damageBounds.top;
|
261
|
+
let w = this._damageBounds.right - x;
|
262
|
+
let h = this._damageBounds.bottom - y;
|
263
|
+
|
264
|
+
let vx = x - this._viewportLoc.x;
|
265
|
+
let vy = y - this._viewportLoc.y;
|
266
|
+
|
267
|
+
if (vx < 0) {
|
268
|
+
w += vx;
|
269
|
+
x -= vx;
|
270
|
+
vx = 0;
|
271
|
+
}
|
272
|
+
if (vy < 0) {
|
273
|
+
h += vy;
|
274
|
+
y -= vy;
|
275
|
+
vy = 0;
|
276
|
+
}
|
277
|
+
|
278
|
+
if ((vx + w) > this._viewportLoc.w) {
|
279
|
+
w = this._viewportLoc.w - vx;
|
280
|
+
}
|
281
|
+
if ((vy + h) > this._viewportLoc.h) {
|
282
|
+
h = this._viewportLoc.h - vy;
|
283
|
+
}
|
284
|
+
|
285
|
+
if ((w > 0) && (h > 0)) {
|
286
|
+
// FIXME: We may need to disable image smoothing here
|
287
|
+
// as well (see copyImage()), but we haven't
|
288
|
+
// noticed any problem yet.
|
289
|
+
this._targetCtx.drawImage(this._backbuffer,
|
290
|
+
x, y, w, h,
|
291
|
+
vx, vy, w, h);
|
292
|
+
}
|
293
|
+
|
294
|
+
this._damageBounds.left = this._damageBounds.top = 65535;
|
295
|
+
this._damageBounds.right = this._damageBounds.bottom = 0;
|
296
|
+
}
|
297
|
+
}
|
298
|
+
|
299
|
+
pending() {
|
300
|
+
return this._renderQ.length > 0;
|
301
|
+
}
|
302
|
+
|
303
|
+
flush() {
|
304
|
+
if (this._renderQ.length === 0) {
|
305
|
+
return Promise.resolve();
|
306
|
+
} else {
|
307
|
+
if (this._flushPromise === null) {
|
308
|
+
this._flushPromise = new Promise((resolve) => {
|
309
|
+
this._flushResolve = resolve;
|
310
|
+
});
|
311
|
+
}
|
312
|
+
return this._flushPromise;
|
313
|
+
}
|
314
|
+
}
|
315
|
+
|
316
|
+
fillRect(x, y, width, height, color, fromQueue) {
|
317
|
+
if (this._renderQ.length !== 0 && !fromQueue) {
|
318
|
+
this._renderQPush({
|
319
|
+
'type': 'fill',
|
320
|
+
'x': x,
|
321
|
+
'y': y,
|
322
|
+
'width': width,
|
323
|
+
'height': height,
|
324
|
+
'color': color
|
325
|
+
});
|
326
|
+
} else {
|
327
|
+
this._setFillColor(color);
|
328
|
+
this._drawCtx.fillRect(x, y, width, height);
|
329
|
+
this._damage(x, y, width, height);
|
330
|
+
}
|
331
|
+
}
|
332
|
+
|
333
|
+
copyImage(oldX, oldY, newX, newY, w, h, fromQueue) {
|
334
|
+
if (this._renderQ.length !== 0 && !fromQueue) {
|
335
|
+
this._renderQPush({
|
336
|
+
'type': 'copy',
|
337
|
+
'oldX': oldX,
|
338
|
+
'oldY': oldY,
|
339
|
+
'x': newX,
|
340
|
+
'y': newY,
|
341
|
+
'width': w,
|
342
|
+
'height': h,
|
343
|
+
});
|
344
|
+
} else {
|
345
|
+
// Due to this bug among others [1] we need to disable the image-smoothing to
|
346
|
+
// avoid getting a blur effect when copying data.
|
347
|
+
//
|
348
|
+
// 1. https://bugzilla.mozilla.org/show_bug.cgi?id=1194719
|
349
|
+
//
|
350
|
+
// We need to set these every time since all properties are reset
|
351
|
+
// when the the size is changed
|
352
|
+
this._drawCtx.mozImageSmoothingEnabled = false;
|
353
|
+
this._drawCtx.webkitImageSmoothingEnabled = false;
|
354
|
+
this._drawCtx.msImageSmoothingEnabled = false;
|
355
|
+
this._drawCtx.imageSmoothingEnabled = false;
|
356
|
+
|
357
|
+
this._drawCtx.drawImage(this._backbuffer,
|
358
|
+
oldX, oldY, w, h,
|
359
|
+
newX, newY, w, h);
|
360
|
+
this._damage(newX, newY, w, h);
|
361
|
+
}
|
362
|
+
}
|
363
|
+
|
364
|
+
imageRect(x, y, width, height, mime, arr) {
|
365
|
+
/* The internal logic cannot handle empty images, so bail early */
|
366
|
+
if ((width === 0) || (height === 0)) {
|
367
|
+
return;
|
368
|
+
}
|
369
|
+
|
370
|
+
const img = new Image();
|
371
|
+
img.src = "data: " + mime + ";base64," + Base64.encode(arr);
|
372
|
+
|
373
|
+
this._renderQPush({
|
374
|
+
'type': 'img',
|
375
|
+
'img': img,
|
376
|
+
'x': x,
|
377
|
+
'y': y,
|
378
|
+
'width': width,
|
379
|
+
'height': height
|
380
|
+
});
|
381
|
+
}
|
382
|
+
|
383
|
+
videoFrame(x, y, width, height, frame) {
|
384
|
+
this._renderQPush({
|
385
|
+
'type': 'frame',
|
386
|
+
'frame': frame,
|
387
|
+
'x': x,
|
388
|
+
'y': y,
|
389
|
+
'width': width,
|
390
|
+
'height': height
|
391
|
+
});
|
392
|
+
}
|
393
|
+
|
394
|
+
blitImage(x, y, width, height, arr, offset, fromQueue) {
|
395
|
+
if (this._renderQ.length !== 0 && !fromQueue) {
|
396
|
+
// NB(directxman12): it's technically more performant here to use preallocated arrays,
|
397
|
+
// but it's a lot of extra work for not a lot of payoff -- if we're using the render queue,
|
398
|
+
// this probably isn't getting called *nearly* as much
|
399
|
+
const newArr = new Uint8Array(width * height * 4);
|
400
|
+
newArr.set(new Uint8Array(arr.buffer, 0, newArr.length));
|
401
|
+
this._renderQPush({
|
402
|
+
'type': 'blit',
|
403
|
+
'data': newArr,
|
404
|
+
'x': x,
|
405
|
+
'y': y,
|
406
|
+
'width': width,
|
407
|
+
'height': height,
|
408
|
+
});
|
409
|
+
} else {
|
410
|
+
// NB(directxman12): arr must be an Type Array view
|
411
|
+
let data = new Uint8ClampedArray(arr.buffer,
|
412
|
+
arr.byteOffset + offset,
|
413
|
+
width * height * 4);
|
414
|
+
let img = new ImageData(data, width, height);
|
415
|
+
this._drawCtx.putImageData(img, x, y);
|
416
|
+
this._damage(x, y, width, height);
|
417
|
+
}
|
418
|
+
}
|
419
|
+
|
420
|
+
drawImage(img, ...args) {
|
421
|
+
this._drawCtx.drawImage(img, ...args);
|
422
|
+
|
423
|
+
if (args.length <= 4) {
|
424
|
+
const [x, y] = args;
|
425
|
+
this._damage(x, y, img.width, img.height);
|
426
|
+
} else {
|
427
|
+
const [,, sw, sh, dx, dy] = args;
|
428
|
+
this._damage(dx, dy, sw, sh);
|
429
|
+
}
|
430
|
+
}
|
431
|
+
|
432
|
+
autoscale(containerWidth, containerHeight) {
|
433
|
+
let scaleRatio;
|
434
|
+
|
435
|
+
if (containerWidth === 0 || containerHeight === 0) {
|
436
|
+
scaleRatio = 0;
|
437
|
+
|
438
|
+
} else {
|
439
|
+
|
440
|
+
const vp = this._viewportLoc;
|
441
|
+
const targetAspectRatio = containerWidth / containerHeight;
|
442
|
+
const fbAspectRatio = vp.w / vp.h;
|
443
|
+
|
444
|
+
if (fbAspectRatio >= targetAspectRatio) {
|
445
|
+
scaleRatio = containerWidth / vp.w;
|
446
|
+
} else {
|
447
|
+
scaleRatio = containerHeight / vp.h;
|
448
|
+
}
|
449
|
+
}
|
450
|
+
|
451
|
+
this._rescale(scaleRatio);
|
452
|
+
}
|
453
|
+
|
454
|
+
// ===== PRIVATE METHODS =====
|
455
|
+
|
456
|
+
_rescale(factor) {
|
457
|
+
this._scale = factor;
|
458
|
+
const vp = this._viewportLoc;
|
459
|
+
|
460
|
+
// NB(directxman12): If you set the width directly, or set the
|
461
|
+
// style width to a number, the canvas is cleared.
|
462
|
+
// However, if you set the style width to a string
|
463
|
+
// ('NNNpx'), the canvas is scaled without clearing.
|
464
|
+
const width = factor * vp.w + 'px';
|
465
|
+
const height = factor * vp.h + 'px';
|
466
|
+
|
467
|
+
if ((this._target.style.width !== width) ||
|
468
|
+
(this._target.style.height !== height)) {
|
469
|
+
this._target.style.width = width;
|
470
|
+
this._target.style.height = height;
|
471
|
+
}
|
472
|
+
}
|
473
|
+
|
474
|
+
_setFillColor(color) {
|
475
|
+
const newStyle = 'rgb(' + color[0] + ',' + color[1] + ',' + color[2] + ')';
|
476
|
+
if (newStyle !== this._prevDrawStyle) {
|
477
|
+
this._drawCtx.fillStyle = newStyle;
|
478
|
+
this._prevDrawStyle = newStyle;
|
479
|
+
}
|
480
|
+
}
|
481
|
+
|
482
|
+
_renderQPush(action) {
|
483
|
+
this._renderQ.push(action);
|
484
|
+
if (this._renderQ.length === 1) {
|
485
|
+
// If this can be rendered immediately it will be, otherwise
|
486
|
+
// the scanner will wait for the relevant event
|
487
|
+
this._scanRenderQ();
|
488
|
+
}
|
489
|
+
}
|
490
|
+
|
491
|
+
_resumeRenderQ() {
|
492
|
+
// "this" is the object that is ready, not the
|
493
|
+
// display object
|
494
|
+
this.removeEventListener('load', this._noVNCDisplay._resumeRenderQ);
|
495
|
+
this._noVNCDisplay._scanRenderQ();
|
496
|
+
}
|
497
|
+
|
498
|
+
_scanRenderQ() {
|
499
|
+
let ready = true;
|
500
|
+
while (ready && this._renderQ.length > 0) {
|
501
|
+
const a = this._renderQ[0];
|
502
|
+
switch (a.type) {
|
503
|
+
case 'flip':
|
504
|
+
this.flip(true);
|
505
|
+
break;
|
506
|
+
case 'copy':
|
507
|
+
this.copyImage(a.oldX, a.oldY, a.x, a.y, a.width, a.height, true);
|
508
|
+
break;
|
509
|
+
case 'fill':
|
510
|
+
this.fillRect(a.x, a.y, a.width, a.height, a.color, true);
|
511
|
+
break;
|
512
|
+
case 'blit':
|
513
|
+
this.blitImage(a.x, a.y, a.width, a.height, a.data, 0, true);
|
514
|
+
break;
|
515
|
+
case 'img':
|
516
|
+
if (a.img.complete) {
|
517
|
+
if (a.img.width !== a.width || a.img.height !== a.height) {
|
518
|
+
Log.Error("Decoded image has incorrect dimensions. Got " +
|
519
|
+
a.img.width + "x" + a.img.height + ". Expected " +
|
520
|
+
a.width + "x" + a.height + ".");
|
521
|
+
return;
|
522
|
+
}
|
523
|
+
this.drawImage(a.img, a.x, a.y);
|
524
|
+
} else {
|
525
|
+
a.img._noVNCDisplay = this;
|
526
|
+
a.img.addEventListener('load', this._resumeRenderQ);
|
527
|
+
// We need to wait for this image to 'load'
|
528
|
+
// to keep things in-order
|
529
|
+
ready = false;
|
530
|
+
}
|
531
|
+
break;
|
532
|
+
case 'frame':
|
533
|
+
if (a.frame.ready) {
|
534
|
+
// The encoded frame may be larger than the rect due to
|
535
|
+
// limitations of the encoder, so we need to crop the
|
536
|
+
// frame.
|
537
|
+
let frame = a.frame.frame;
|
538
|
+
if (frame.codedWidth < a.width || frame.codedHeight < a.height) {
|
539
|
+
Log.Warn("Decoded video frame does not cover its full rectangle area. Expecting at least " +
|
540
|
+
a.width + "x" + a.height + " but got " +
|
541
|
+
frame.codedWidth + "x" + frame.codedHeight);
|
542
|
+
}
|
543
|
+
const sx = 0;
|
544
|
+
const sy = 0;
|
545
|
+
const sw = a.width;
|
546
|
+
const sh = a.height;
|
547
|
+
const dx = a.x;
|
548
|
+
const dy = a.y;
|
549
|
+
const dw = sw;
|
550
|
+
const dh = sh;
|
551
|
+
this.drawImage(frame, sx, sy, sw, sh, dx, dy, dw, dh);
|
552
|
+
frame.close();
|
553
|
+
} else {
|
554
|
+
let display = this;
|
555
|
+
a.frame.promise.then(() => {
|
556
|
+
display._scanRenderQ();
|
557
|
+
});
|
558
|
+
ready = false;
|
559
|
+
}
|
560
|
+
break;
|
561
|
+
}
|
562
|
+
|
563
|
+
if (ready) {
|
564
|
+
this._renderQ.shift();
|
565
|
+
}
|
566
|
+
}
|
567
|
+
|
568
|
+
if (this._renderQ.length === 0 &&
|
569
|
+
this._flushPromise !== null) {
|
570
|
+
this._flushResolve();
|
571
|
+
this._flushPromise = null;
|
572
|
+
this._flushResolve = null;
|
573
|
+
}
|
574
|
+
}
|
575
|
+
}
|
@@ -0,0 +1,53 @@
|
|
1
|
+
/*
|
2
|
+
* noVNC: HTML5 VNC client
|
3
|
+
* Copyright (C) 2019 The noVNC Authors
|
4
|
+
* Licensed under MPL 2.0 (see LICENSE.txt)
|
5
|
+
*
|
6
|
+
* See README.md for usage and integration instructions.
|
7
|
+
*/
|
8
|
+
|
9
|
+
export const encodings = {
|
10
|
+
encodingRaw: 0,
|
11
|
+
encodingCopyRect: 1,
|
12
|
+
encodingRRE: 2,
|
13
|
+
encodingHextile: 5,
|
14
|
+
encodingZlib: 6,
|
15
|
+
encodingTight: 7,
|
16
|
+
encodingZRLE: 16,
|
17
|
+
encodingTightPNG: -260,
|
18
|
+
encodingJPEG: 21,
|
19
|
+
encodingH264: 50,
|
20
|
+
|
21
|
+
pseudoEncodingQualityLevel9: -23,
|
22
|
+
pseudoEncodingQualityLevel0: -32,
|
23
|
+
pseudoEncodingDesktopSize: -223,
|
24
|
+
pseudoEncodingLastRect: -224,
|
25
|
+
pseudoEncodingCursor: -239,
|
26
|
+
pseudoEncodingQEMUExtendedKeyEvent: -258,
|
27
|
+
pseudoEncodingQEMULedEvent: -261,
|
28
|
+
pseudoEncodingDesktopName: -307,
|
29
|
+
pseudoEncodingExtendedDesktopSize: -308,
|
30
|
+
pseudoEncodingXvp: -309,
|
31
|
+
pseudoEncodingFence: -312,
|
32
|
+
pseudoEncodingContinuousUpdates: -313,
|
33
|
+
pseudoEncodingCompressLevel9: -247,
|
34
|
+
pseudoEncodingCompressLevel0: -256,
|
35
|
+
pseudoEncodingVMwareCursor: 0x574d5664,
|
36
|
+
pseudoEncodingExtendedClipboard: 0xc0a1e5ce
|
37
|
+
};
|
38
|
+
|
39
|
+
export function encodingName(num) {
|
40
|
+
switch (num) {
|
41
|
+
case encodings.encodingRaw: return "Raw";
|
42
|
+
case encodings.encodingCopyRect: return "CopyRect";
|
43
|
+
case encodings.encodingRRE: return "RRE";
|
44
|
+
case encodings.encodingHextile: return "Hextile";
|
45
|
+
case encodings.encodingZlib: return "Zlib";
|
46
|
+
case encodings.encodingTight: return "Tight";
|
47
|
+
case encodings.encodingZRLE: return "ZRLE";
|
48
|
+
case encodings.encodingTightPNG: return "TightPNG";
|
49
|
+
case encodings.encodingJPEG: return "JPEG";
|
50
|
+
case encodings.encodingH264: return "H.264";
|
51
|
+
default: return "[unknown encoding " + num + "]";
|
52
|
+
}
|
53
|
+
}
|