susi-qemu 0.0.2 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- 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 +44 -26
- data/lib/vnc.rb +34 -31
- metadata +57 -1
@@ -0,0 +1,321 @@
|
|
1
|
+
/*
|
2
|
+
* noVNC: HTML5 VNC client
|
3
|
+
* Copyright (C) 2024 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
|
+
|
10
|
+
import * as Log from '../util/logging.js';
|
11
|
+
|
12
|
+
export class H264Parser {
|
13
|
+
constructor(data) {
|
14
|
+
this._data = data;
|
15
|
+
this._index = 0;
|
16
|
+
this.profileIdc = null;
|
17
|
+
this.constraintSet = null;
|
18
|
+
this.levelIdc = null;
|
19
|
+
}
|
20
|
+
|
21
|
+
_getStartSequenceLen(index) {
|
22
|
+
let data = this._data;
|
23
|
+
if (data[index + 0] == 0 && data[index + 1] == 0 && data[index + 2] == 0 && data[index + 3] == 1) {
|
24
|
+
return 4;
|
25
|
+
}
|
26
|
+
if (data[index + 0] == 0 && data[index + 1] == 0 && data[index + 2] == 1) {
|
27
|
+
return 3;
|
28
|
+
}
|
29
|
+
return 0;
|
30
|
+
}
|
31
|
+
|
32
|
+
_indexOfNextNalUnit(index) {
|
33
|
+
let data = this._data;
|
34
|
+
for (let i = index; i < data.length; ++i) {
|
35
|
+
if (this._getStartSequenceLen(i) != 0) {
|
36
|
+
return i;
|
37
|
+
}
|
38
|
+
}
|
39
|
+
return -1;
|
40
|
+
}
|
41
|
+
|
42
|
+
_parseSps(index) {
|
43
|
+
this.profileIdc = this._data[index];
|
44
|
+
this.constraintSet = this._data[index + 1];
|
45
|
+
this.levelIdc = this._data[index + 2];
|
46
|
+
}
|
47
|
+
|
48
|
+
_parseNalUnit(index) {
|
49
|
+
const firstByte = this._data[index];
|
50
|
+
if (firstByte & 0x80) {
|
51
|
+
throw new Error('H264 parsing sanity check failed, forbidden zero bit is set');
|
52
|
+
}
|
53
|
+
const unitType = firstByte & 0x1f;
|
54
|
+
|
55
|
+
switch (unitType) {
|
56
|
+
case 1: // coded slice, non-idr
|
57
|
+
return { slice: true };
|
58
|
+
case 5: // coded slice, idr
|
59
|
+
return { slice: true, key: true };
|
60
|
+
case 6: // sei
|
61
|
+
return {};
|
62
|
+
case 7: // sps
|
63
|
+
this._parseSps(index + 1);
|
64
|
+
return {};
|
65
|
+
case 8: // pps
|
66
|
+
return {};
|
67
|
+
default:
|
68
|
+
Log.Warn("Unhandled unit type: ", unitType);
|
69
|
+
break;
|
70
|
+
}
|
71
|
+
return {};
|
72
|
+
}
|
73
|
+
|
74
|
+
parse() {
|
75
|
+
const startIndex = this._index;
|
76
|
+
let isKey = false;
|
77
|
+
|
78
|
+
while (this._index < this._data.length) {
|
79
|
+
const startSequenceLen = this._getStartSequenceLen(this._index);
|
80
|
+
if (startSequenceLen == 0) {
|
81
|
+
throw new Error('Invalid start sequence in bit stream');
|
82
|
+
}
|
83
|
+
|
84
|
+
const { slice, key } = this._parseNalUnit(this._index + startSequenceLen);
|
85
|
+
|
86
|
+
let nextIndex = this._indexOfNextNalUnit(this._index + startSequenceLen);
|
87
|
+
if (nextIndex == -1) {
|
88
|
+
this._index = this._data.length;
|
89
|
+
} else {
|
90
|
+
this._index = nextIndex;
|
91
|
+
}
|
92
|
+
|
93
|
+
if (key) {
|
94
|
+
isKey = true;
|
95
|
+
}
|
96
|
+
if (slice) {
|
97
|
+
break;
|
98
|
+
}
|
99
|
+
}
|
100
|
+
|
101
|
+
if (startIndex === this._index) {
|
102
|
+
return null;
|
103
|
+
}
|
104
|
+
|
105
|
+
return {
|
106
|
+
frame: this._data.subarray(startIndex, this._index),
|
107
|
+
key: isKey,
|
108
|
+
};
|
109
|
+
}
|
110
|
+
}
|
111
|
+
|
112
|
+
export class H264Context {
|
113
|
+
constructor(width, height) {
|
114
|
+
this.lastUsed = 0;
|
115
|
+
this._width = width;
|
116
|
+
this._height = height;
|
117
|
+
this._profileIdc = null;
|
118
|
+
this._constraintSet = null;
|
119
|
+
this._levelIdc = null;
|
120
|
+
this._decoder = null;
|
121
|
+
this._pendingFrames = [];
|
122
|
+
}
|
123
|
+
|
124
|
+
_handleFrame(frame) {
|
125
|
+
let pending = this._pendingFrames.shift();
|
126
|
+
if (pending === undefined) {
|
127
|
+
throw new Error("Pending frame queue empty when receiving frame from decoder");
|
128
|
+
}
|
129
|
+
|
130
|
+
if (pending.timestamp != frame.timestamp) {
|
131
|
+
throw new Error("Video frame timestamp mismatch. Expected " +
|
132
|
+
frame.timestamp + " but but got " + pending.timestamp);
|
133
|
+
}
|
134
|
+
|
135
|
+
pending.frame = frame;
|
136
|
+
pending.ready = true;
|
137
|
+
pending.resolve();
|
138
|
+
|
139
|
+
if (!pending.keep) {
|
140
|
+
frame.close();
|
141
|
+
}
|
142
|
+
}
|
143
|
+
|
144
|
+
_handleError(e) {
|
145
|
+
throw new Error("Failed to decode frame: " + e.message);
|
146
|
+
}
|
147
|
+
|
148
|
+
_configureDecoder(profileIdc, constraintSet, levelIdc) {
|
149
|
+
if (this._decoder === null || this._decoder.state === 'closed') {
|
150
|
+
this._decoder = new VideoDecoder({
|
151
|
+
output: frame => this._handleFrame(frame),
|
152
|
+
error: e => this._handleError(e),
|
153
|
+
});
|
154
|
+
}
|
155
|
+
const codec = 'avc1.' +
|
156
|
+
profileIdc.toString(16).padStart(2, '0') +
|
157
|
+
constraintSet.toString(16).padStart(2, '0') +
|
158
|
+
levelIdc.toString(16).padStart(2, '0');
|
159
|
+
this._decoder.configure({
|
160
|
+
codec: codec,
|
161
|
+
codedWidth: this._width,
|
162
|
+
codedHeight: this._height,
|
163
|
+
optimizeForLatency: true,
|
164
|
+
});
|
165
|
+
}
|
166
|
+
|
167
|
+
_preparePendingFrame(timestamp) {
|
168
|
+
let pending = {
|
169
|
+
timestamp: timestamp,
|
170
|
+
promise: null,
|
171
|
+
resolve: null,
|
172
|
+
frame: null,
|
173
|
+
ready: false,
|
174
|
+
keep: false,
|
175
|
+
};
|
176
|
+
pending.promise = new Promise((resolve) => {
|
177
|
+
pending.resolve = resolve;
|
178
|
+
});
|
179
|
+
this._pendingFrames.push(pending);
|
180
|
+
|
181
|
+
return pending;
|
182
|
+
}
|
183
|
+
|
184
|
+
decode(payload) {
|
185
|
+
let parser = new H264Parser(payload);
|
186
|
+
let result = null;
|
187
|
+
|
188
|
+
// Ideally, this timestamp should come from the server, but we'll just
|
189
|
+
// approximate it instead.
|
190
|
+
let timestamp = Math.round(window.performance.now() * 1e3);
|
191
|
+
|
192
|
+
while (true) {
|
193
|
+
let encodedFrame = parser.parse();
|
194
|
+
if (encodedFrame === null) {
|
195
|
+
break;
|
196
|
+
}
|
197
|
+
|
198
|
+
if (parser.profileIdc !== null) {
|
199
|
+
self._profileIdc = parser.profileIdc;
|
200
|
+
self._constraintSet = parser.constraintSet;
|
201
|
+
self._levelIdc = parser.levelIdc;
|
202
|
+
}
|
203
|
+
|
204
|
+
if (this._decoder === null || this._decoder.state !== 'configured') {
|
205
|
+
if (!encodedFrame.key) {
|
206
|
+
Log.Warn("Missing key frame. Can't decode until one arrives");
|
207
|
+
continue;
|
208
|
+
}
|
209
|
+
if (self._profileIdc === null) {
|
210
|
+
Log.Warn('Cannot config decoder. Have not received SPS and PPS yet.');
|
211
|
+
continue;
|
212
|
+
}
|
213
|
+
this._configureDecoder(self._profileIdc, self._constraintSet,
|
214
|
+
self._levelIdc);
|
215
|
+
}
|
216
|
+
|
217
|
+
result = this._preparePendingFrame(timestamp);
|
218
|
+
|
219
|
+
const chunk = new EncodedVideoChunk({
|
220
|
+
timestamp: timestamp,
|
221
|
+
type: encodedFrame.key ? 'key' : 'delta',
|
222
|
+
data: encodedFrame.frame,
|
223
|
+
});
|
224
|
+
|
225
|
+
try {
|
226
|
+
this._decoder.decode(chunk);
|
227
|
+
} catch (e) {
|
228
|
+
Log.Warn("Failed to decode:", e);
|
229
|
+
}
|
230
|
+
}
|
231
|
+
|
232
|
+
// We only keep last frame of each payload
|
233
|
+
if (result !== null) {
|
234
|
+
result.keep = true;
|
235
|
+
}
|
236
|
+
|
237
|
+
return result;
|
238
|
+
}
|
239
|
+
}
|
240
|
+
|
241
|
+
export default class H264Decoder {
|
242
|
+
constructor() {
|
243
|
+
this._tick = 0;
|
244
|
+
this._contexts = {};
|
245
|
+
}
|
246
|
+
|
247
|
+
_contextId(x, y, width, height) {
|
248
|
+
return [x, y, width, height].join(',');
|
249
|
+
}
|
250
|
+
|
251
|
+
_findOldestContextId() {
|
252
|
+
let oldestTick = Number.MAX_VALUE;
|
253
|
+
let oldestKey = undefined;
|
254
|
+
for (const [key, value] of Object.entries(this._contexts)) {
|
255
|
+
if (value.lastUsed < oldestTick) {
|
256
|
+
oldestTick = value.lastUsed;
|
257
|
+
oldestKey = key;
|
258
|
+
}
|
259
|
+
}
|
260
|
+
return oldestKey;
|
261
|
+
}
|
262
|
+
|
263
|
+
_createContext(x, y, width, height) {
|
264
|
+
const maxContexts = 64;
|
265
|
+
if (Object.keys(this._contexts).length >= maxContexts) {
|
266
|
+
let oldestContextId = this._findOldestContextId();
|
267
|
+
delete this._contexts[oldestContextId];
|
268
|
+
}
|
269
|
+
let context = new H264Context(width, height);
|
270
|
+
this._contexts[this._contextId(x, y, width, height)] = context;
|
271
|
+
return context;
|
272
|
+
}
|
273
|
+
|
274
|
+
_getContext(x, y, width, height) {
|
275
|
+
let context = this._contexts[this._contextId(x, y, width, height)];
|
276
|
+
return context !== undefined ? context : this._createContext(x, y, width, height);
|
277
|
+
}
|
278
|
+
|
279
|
+
_resetContext(x, y, width, height) {
|
280
|
+
delete this._contexts[this._contextId(x, y, width, height)];
|
281
|
+
}
|
282
|
+
|
283
|
+
_resetAllContexts() {
|
284
|
+
this._contexts = {};
|
285
|
+
}
|
286
|
+
|
287
|
+
decodeRect(x, y, width, height, sock, display, depth) {
|
288
|
+
const resetContextFlag = 1;
|
289
|
+
const resetAllContextsFlag = 2;
|
290
|
+
|
291
|
+
if (sock.rQwait("h264 header", 8)) {
|
292
|
+
return false;
|
293
|
+
}
|
294
|
+
|
295
|
+
const length = sock.rQshift32();
|
296
|
+
const flags = sock.rQshift32();
|
297
|
+
|
298
|
+
if (sock.rQwait("h264 payload", length, 8)) {
|
299
|
+
return false;
|
300
|
+
}
|
301
|
+
|
302
|
+
if (flags & resetAllContextsFlag) {
|
303
|
+
this._resetAllContexts();
|
304
|
+
} else if (flags & resetContextFlag) {
|
305
|
+
this._resetContext(x, y, width, height);
|
306
|
+
}
|
307
|
+
|
308
|
+
let context = this._getContext(x, y, width, height);
|
309
|
+
context.lastUsed = this._tick++;
|
310
|
+
|
311
|
+
if (length !== 0) {
|
312
|
+
let payload = sock.rQshiftBytes(length, false);
|
313
|
+
let frame = context.decode(payload);
|
314
|
+
if (frame !== null) {
|
315
|
+
display.videoFrame(x, y, width, height, frame);
|
316
|
+
}
|
317
|
+
}
|
318
|
+
|
319
|
+
return true;
|
320
|
+
}
|
321
|
+
}
|
@@ -0,0 +1,181 @@
|
|
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
|
+
|
10
|
+
import * as Log from '../util/logging.js';
|
11
|
+
|
12
|
+
export default class HextileDecoder {
|
13
|
+
constructor() {
|
14
|
+
this._tiles = 0;
|
15
|
+
this._lastsubencoding = 0;
|
16
|
+
this._tileBuffer = new Uint8Array(16 * 16 * 4);
|
17
|
+
}
|
18
|
+
|
19
|
+
decodeRect(x, y, width, height, sock, display, depth) {
|
20
|
+
if (this._tiles === 0) {
|
21
|
+
this._tilesX = Math.ceil(width / 16);
|
22
|
+
this._tilesY = Math.ceil(height / 16);
|
23
|
+
this._totalTiles = this._tilesX * this._tilesY;
|
24
|
+
this._tiles = this._totalTiles;
|
25
|
+
}
|
26
|
+
|
27
|
+
while (this._tiles > 0) {
|
28
|
+
let bytes = 1;
|
29
|
+
|
30
|
+
if (sock.rQwait("HEXTILE", bytes)) {
|
31
|
+
return false;
|
32
|
+
}
|
33
|
+
|
34
|
+
let subencoding = sock.rQpeek8();
|
35
|
+
if (subencoding > 30) { // Raw
|
36
|
+
throw new Error("Illegal hextile subencoding (subencoding: " +
|
37
|
+
subencoding + ")");
|
38
|
+
}
|
39
|
+
|
40
|
+
const currTile = this._totalTiles - this._tiles;
|
41
|
+
const tileX = currTile % this._tilesX;
|
42
|
+
const tileY = Math.floor(currTile / this._tilesX);
|
43
|
+
const tx = x + tileX * 16;
|
44
|
+
const ty = y + tileY * 16;
|
45
|
+
const tw = Math.min(16, (x + width) - tx);
|
46
|
+
const th = Math.min(16, (y + height) - ty);
|
47
|
+
|
48
|
+
// Figure out how much we are expecting
|
49
|
+
if (subencoding & 0x01) { // Raw
|
50
|
+
bytes += tw * th * 4;
|
51
|
+
} else {
|
52
|
+
if (subencoding & 0x02) { // Background
|
53
|
+
bytes += 4;
|
54
|
+
}
|
55
|
+
if (subencoding & 0x04) { // Foreground
|
56
|
+
bytes += 4;
|
57
|
+
}
|
58
|
+
if (subencoding & 0x08) { // AnySubrects
|
59
|
+
bytes++; // Since we aren't shifting it off
|
60
|
+
|
61
|
+
if (sock.rQwait("HEXTILE", bytes)) {
|
62
|
+
return false;
|
63
|
+
}
|
64
|
+
|
65
|
+
let subrects = sock.rQpeekBytes(bytes).at(-1);
|
66
|
+
if (subencoding & 0x10) { // SubrectsColoured
|
67
|
+
bytes += subrects * (4 + 2);
|
68
|
+
} else {
|
69
|
+
bytes += subrects * 2;
|
70
|
+
}
|
71
|
+
}
|
72
|
+
}
|
73
|
+
|
74
|
+
if (sock.rQwait("HEXTILE", bytes)) {
|
75
|
+
return false;
|
76
|
+
}
|
77
|
+
|
78
|
+
// We know the encoding and have a whole tile
|
79
|
+
sock.rQshift8();
|
80
|
+
if (subencoding === 0) {
|
81
|
+
if (this._lastsubencoding & 0x01) {
|
82
|
+
// Weird: ignore blanks are RAW
|
83
|
+
Log.Debug(" Ignoring blank after RAW");
|
84
|
+
} else {
|
85
|
+
display.fillRect(tx, ty, tw, th, this._background);
|
86
|
+
}
|
87
|
+
} else if (subencoding & 0x01) { // Raw
|
88
|
+
let pixels = tw * th;
|
89
|
+
let data = sock.rQshiftBytes(pixels * 4, false);
|
90
|
+
// Max sure the image is fully opaque
|
91
|
+
for (let i = 0;i < pixels;i++) {
|
92
|
+
data[i * 4 + 3] = 255;
|
93
|
+
}
|
94
|
+
display.blitImage(tx, ty, tw, th, data, 0);
|
95
|
+
} else {
|
96
|
+
if (subencoding & 0x02) { // Background
|
97
|
+
this._background = new Uint8Array(sock.rQshiftBytes(4));
|
98
|
+
}
|
99
|
+
if (subencoding & 0x04) { // Foreground
|
100
|
+
this._foreground = new Uint8Array(sock.rQshiftBytes(4));
|
101
|
+
}
|
102
|
+
|
103
|
+
this._startTile(tx, ty, tw, th, this._background);
|
104
|
+
if (subencoding & 0x08) { // AnySubrects
|
105
|
+
let subrects = sock.rQshift8();
|
106
|
+
|
107
|
+
for (let s = 0; s < subrects; s++) {
|
108
|
+
let color;
|
109
|
+
if (subencoding & 0x10) { // SubrectsColoured
|
110
|
+
color = sock.rQshiftBytes(4);
|
111
|
+
} else {
|
112
|
+
color = this._foreground;
|
113
|
+
}
|
114
|
+
const xy = sock.rQshift8();
|
115
|
+
const sx = (xy >> 4);
|
116
|
+
const sy = (xy & 0x0f);
|
117
|
+
|
118
|
+
const wh = sock.rQshift8();
|
119
|
+
const sw = (wh >> 4) + 1;
|
120
|
+
const sh = (wh & 0x0f) + 1;
|
121
|
+
|
122
|
+
this._subTile(sx, sy, sw, sh, color);
|
123
|
+
}
|
124
|
+
}
|
125
|
+
this._finishTile(display);
|
126
|
+
}
|
127
|
+
this._lastsubencoding = subencoding;
|
128
|
+
this._tiles--;
|
129
|
+
}
|
130
|
+
|
131
|
+
return true;
|
132
|
+
}
|
133
|
+
|
134
|
+
// start updating a tile
|
135
|
+
_startTile(x, y, width, height, color) {
|
136
|
+
this._tileX = x;
|
137
|
+
this._tileY = y;
|
138
|
+
this._tileW = width;
|
139
|
+
this._tileH = height;
|
140
|
+
|
141
|
+
const red = color[0];
|
142
|
+
const green = color[1];
|
143
|
+
const blue = color[2];
|
144
|
+
|
145
|
+
const data = this._tileBuffer;
|
146
|
+
for (let i = 0; i < width * height * 4; i += 4) {
|
147
|
+
data[i] = red;
|
148
|
+
data[i + 1] = green;
|
149
|
+
data[i + 2] = blue;
|
150
|
+
data[i + 3] = 255;
|
151
|
+
}
|
152
|
+
}
|
153
|
+
|
154
|
+
// update sub-rectangle of the current tile
|
155
|
+
_subTile(x, y, w, h, color) {
|
156
|
+
const red = color[0];
|
157
|
+
const green = color[1];
|
158
|
+
const blue = color[2];
|
159
|
+
const xend = x + w;
|
160
|
+
const yend = y + h;
|
161
|
+
|
162
|
+
const data = this._tileBuffer;
|
163
|
+
const width = this._tileW;
|
164
|
+
for (let j = y; j < yend; j++) {
|
165
|
+
for (let i = x; i < xend; i++) {
|
166
|
+
const p = (i + (j * width)) * 4;
|
167
|
+
data[p] = red;
|
168
|
+
data[p + 1] = green;
|
169
|
+
data[p + 2] = blue;
|
170
|
+
data[p + 3] = 255;
|
171
|
+
}
|
172
|
+
}
|
173
|
+
}
|
174
|
+
|
175
|
+
// draw the current tile to the screen
|
176
|
+
_finishTile(display) {
|
177
|
+
display.blitImage(this._tileX, this._tileY,
|
178
|
+
this._tileW, this._tileH,
|
179
|
+
this._tileBuffer, 0);
|
180
|
+
}
|
181
|
+
}
|
@@ -0,0 +1,146 @@
|
|
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
|
+
|
10
|
+
export default class JPEGDecoder {
|
11
|
+
constructor() {
|
12
|
+
// RealVNC will reuse the quantization tables
|
13
|
+
// and Huffman tables, so we need to cache them.
|
14
|
+
this._cachedQuantTables = [];
|
15
|
+
this._cachedHuffmanTables = [];
|
16
|
+
|
17
|
+
this._segments = [];
|
18
|
+
}
|
19
|
+
|
20
|
+
decodeRect(x, y, width, height, sock, display, depth) {
|
21
|
+
// A rect of JPEG encodings is simply a JPEG file
|
22
|
+
while (true) {
|
23
|
+
let segment = this._readSegment(sock);
|
24
|
+
if (segment === null) {
|
25
|
+
return false;
|
26
|
+
}
|
27
|
+
this._segments.push(segment);
|
28
|
+
// End of image?
|
29
|
+
if (segment[1] === 0xD9) {
|
30
|
+
break;
|
31
|
+
}
|
32
|
+
}
|
33
|
+
|
34
|
+
let huffmanTables = [];
|
35
|
+
let quantTables = [];
|
36
|
+
for (let segment of this._segments) {
|
37
|
+
let type = segment[1];
|
38
|
+
if (type === 0xC4) {
|
39
|
+
// Huffman tables
|
40
|
+
huffmanTables.push(segment);
|
41
|
+
} else if (type === 0xDB) {
|
42
|
+
// Quantization tables
|
43
|
+
quantTables.push(segment);
|
44
|
+
}
|
45
|
+
}
|
46
|
+
|
47
|
+
const sofIndex = this._segments.findIndex(
|
48
|
+
x => x[1] == 0xC0 || x[1] == 0xC2
|
49
|
+
);
|
50
|
+
if (sofIndex == -1) {
|
51
|
+
throw new Error("Illegal JPEG image without SOF");
|
52
|
+
}
|
53
|
+
|
54
|
+
if (quantTables.length === 0) {
|
55
|
+
this._segments.splice(sofIndex+1, 0,
|
56
|
+
...this._cachedQuantTables);
|
57
|
+
}
|
58
|
+
if (huffmanTables.length === 0) {
|
59
|
+
this._segments.splice(sofIndex+1, 0,
|
60
|
+
...this._cachedHuffmanTables);
|
61
|
+
}
|
62
|
+
|
63
|
+
let length = 0;
|
64
|
+
for (let segment of this._segments) {
|
65
|
+
length += segment.length;
|
66
|
+
}
|
67
|
+
|
68
|
+
let data = new Uint8Array(length);
|
69
|
+
length = 0;
|
70
|
+
for (let segment of this._segments) {
|
71
|
+
data.set(segment, length);
|
72
|
+
length += segment.length;
|
73
|
+
}
|
74
|
+
|
75
|
+
display.imageRect(x, y, width, height, "image/jpeg", data);
|
76
|
+
|
77
|
+
if (huffmanTables.length !== 0) {
|
78
|
+
this._cachedHuffmanTables = huffmanTables;
|
79
|
+
}
|
80
|
+
if (quantTables.length !== 0) {
|
81
|
+
this._cachedQuantTables = quantTables;
|
82
|
+
}
|
83
|
+
|
84
|
+
this._segments = [];
|
85
|
+
|
86
|
+
return true;
|
87
|
+
}
|
88
|
+
|
89
|
+
_readSegment(sock) {
|
90
|
+
if (sock.rQwait("JPEG", 2)) {
|
91
|
+
return null;
|
92
|
+
}
|
93
|
+
|
94
|
+
let marker = sock.rQshift8();
|
95
|
+
if (marker != 0xFF) {
|
96
|
+
throw new Error("Illegal JPEG marker received (byte: " +
|
97
|
+
marker + ")");
|
98
|
+
}
|
99
|
+
let type = sock.rQshift8();
|
100
|
+
if (type >= 0xD0 && type <= 0xD9 || type == 0x01) {
|
101
|
+
// No length after marker
|
102
|
+
return new Uint8Array([marker, type]);
|
103
|
+
}
|
104
|
+
|
105
|
+
if (sock.rQwait("JPEG", 2, 2)) {
|
106
|
+
return null;
|
107
|
+
}
|
108
|
+
|
109
|
+
let length = sock.rQshift16();
|
110
|
+
if (length < 2) {
|
111
|
+
throw new Error("Illegal JPEG length received (length: " +
|
112
|
+
length + ")");
|
113
|
+
}
|
114
|
+
|
115
|
+
if (sock.rQwait("JPEG", length-2, 4)) {
|
116
|
+
return null;
|
117
|
+
}
|
118
|
+
|
119
|
+
let extra = 0;
|
120
|
+
if (type === 0xDA) {
|
121
|
+
// start of scan
|
122
|
+
extra += 2;
|
123
|
+
while (true) {
|
124
|
+
if (sock.rQwait("JPEG", length-2+extra, 4)) {
|
125
|
+
return null;
|
126
|
+
}
|
127
|
+
let data = sock.rQpeekBytes(length-2+extra, false);
|
128
|
+
if (data.at(-2) === 0xFF && data.at(-1) !== 0x00 &&
|
129
|
+
!(data.at(-1) >= 0xD0 && data.at(-1) <= 0xD7)) {
|
130
|
+
extra -= 2;
|
131
|
+
break;
|
132
|
+
}
|
133
|
+
extra++;
|
134
|
+
}
|
135
|
+
}
|
136
|
+
|
137
|
+
let segment = new Uint8Array(2 + length + extra);
|
138
|
+
segment[0] = marker;
|
139
|
+
segment[1] = type;
|
140
|
+
segment[2] = length >> 8;
|
141
|
+
segment[3] = length;
|
142
|
+
segment.set(sock.rQshiftBytes(length-2+extra, false), 4);
|
143
|
+
|
144
|
+
return segment;
|
145
|
+
}
|
146
|
+
}
|
@@ -0,0 +1,59 @@
|
|
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
|
+
|
10
|
+
export default class RawDecoder {
|
11
|
+
constructor() {
|
12
|
+
this._lines = 0;
|
13
|
+
}
|
14
|
+
|
15
|
+
decodeRect(x, y, width, height, sock, display, depth) {
|
16
|
+
if ((width === 0) || (height === 0)) {
|
17
|
+
return true;
|
18
|
+
}
|
19
|
+
|
20
|
+
if (this._lines === 0) {
|
21
|
+
this._lines = height;
|
22
|
+
}
|
23
|
+
|
24
|
+
const pixelSize = depth == 8 ? 1 : 4;
|
25
|
+
const bytesPerLine = width * pixelSize;
|
26
|
+
|
27
|
+
while (this._lines > 0) {
|
28
|
+
if (sock.rQwait("RAW", bytesPerLine)) {
|
29
|
+
return false;
|
30
|
+
}
|
31
|
+
|
32
|
+
const curY = y + (height - this._lines);
|
33
|
+
|
34
|
+
let data = sock.rQshiftBytes(bytesPerLine, false);
|
35
|
+
|
36
|
+
// Convert data if needed
|
37
|
+
if (depth == 8) {
|
38
|
+
const newdata = new Uint8Array(width * 4);
|
39
|
+
for (let i = 0; i < width; i++) {
|
40
|
+
newdata[i * 4 + 0] = ((data[i] >> 0) & 0x3) * 255 / 3;
|
41
|
+
newdata[i * 4 + 1] = ((data[i] >> 2) & 0x3) * 255 / 3;
|
42
|
+
newdata[i * 4 + 2] = ((data[i] >> 4) & 0x3) * 255 / 3;
|
43
|
+
newdata[i * 4 + 3] = 255;
|
44
|
+
}
|
45
|
+
data = newdata;
|
46
|
+
}
|
47
|
+
|
48
|
+
// Max sure the image is fully opaque
|
49
|
+
for (let i = 0; i < width; i++) {
|
50
|
+
data[i * 4 + 3] = 255;
|
51
|
+
}
|
52
|
+
|
53
|
+
display.blitImage(x, curY, width, 1, data, 0);
|
54
|
+
this._lines--;
|
55
|
+
}
|
56
|
+
|
57
|
+
return true;
|
58
|
+
}
|
59
|
+
}
|