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,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
|
+
}
|