--- /dev/null
- }
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2012 Joel Martin
+ * (c) 2012 Michael Tinglof, Joe Balaz, Les Piech (Mercuri.ca)
+ * Copyright (C) 2018 Samuel Mannehed for Cendio AB
+ * Copyright (C) 2018 Pierre Ossman for Cendio AB
+ * Licensed under MPL 2.0 (see LICENSE.txt)
+ *
+ * See README.md for usage and integration instructions.
+ *
+ */
+
+import * as Log from '../util/logging.js';
+import Inflator from "../inflator.js";
+
+export default class TightDecoder {
+ constructor() {
+ this._ctl = null;
+ this._filter = null;
+ this._numColors = 0;
+ this._palette = new Uint8Array(1024); // 256 * 4 (max palette size * max bytes-per-pixel)
+ this._len = 0;
+
+ this._zlibs = [];
+ for (let i = 0; i < 4; i++) {
+ this._zlibs[i] = new Inflator();
+ }
- display.fillRect(x, y, width, height,
++ }
+
+ decodeRect(x, y, width, height, sock, display, depth) {
+ if (this._ctl === null) {
+ if (sock.rQwait("TIGHT compression-control", 1)) {
+ return false;
+ }
+
+ this._ctl = sock.rQshift8();
+
+ // Reset streams if the server requests it
+ for (let i = 0; i < 4; i++) {
+ if ((this._ctl >> i) & 1) {
+ this._zlibs[i].reset();
+ Log.Info("Reset zlib stream " + i);
+ }
+ }
+
+ // Figure out filter
+ this._ctl = this._ctl >> 4;
+ }
+
+ let ret;
+
+ if (this._ctl === 0x08) {
+ ret = this._fillRect(x, y, width, height,
+ sock, display, depth);
+ } else if (this._ctl === 0x09) {
+ ret = this._jpegRect(x, y, width, height,
+ sock, display, depth);
+ } else if (this._ctl === 0x0A) {
+ ret = this._pngRect(x, y, width, height,
+ sock, display, depth);
+ } else if ((this._ctl & 0x80) == 0) {
+ ret = this._basicRect(this._ctl, x, y, width, height,
+ sock, display, depth);
+ } else {
+ throw Error("Illegal tight compression received (ctl: " +
+ this._ctl + ")");
+ }
+
+ if (ret) {
+ this._ctl = null;
+ }
+
+ return ret;
+ }
+
+ _fillRect(x, y, width, height, sock, display, depth) {
+ if (sock.rQwait("TIGHT", 3)) {
+ return false;
+ }
+
+ const rQi = sock.get_rQi();
+ const rQ = sock.rQwhole();
+
++ display.fillRect(x, y, width, height,
+ [rQ[rQi + 2], rQ[rQi + 1], rQ[rQi]], false);
+ sock.rQskipBytes(3);
+
+ return true;
+ }
+
+ _jpegRect(x, y, width, height, sock, display, depth) {
+ let data = this._readData(sock);
+ if (data === null) {
+ return false;
+ }
+
+ display.imageRect(x, y, "image/jpeg", data);
+
+ return true;
+ }
+
+ _pngRect(x, y, width, height, sock, display, depth) {
+ throw Error("PNG received in standard Tight rect");
+ }
+
+ _basicRect(ctl, x, y, width, height, sock, display, depth) {
+ if (this._filter === null) {
+ if (ctl & 0x4) {
+ if (sock.rQwait("TIGHT", 1)) {
+ return false;
+ }
+
+ this._filter = sock.rQshift8();
+ } else {
+ // Implicit CopyFilter
+ this._filter = 0;
+ }
+ }
+
+ let streamId = ctl & 0x3;
+
+ let ret;
+
+ switch (this._filter) {
+ case 0: // CopyFilter
+ ret = this._copyFilter(streamId, x, y, width, height,
+ sock, display, depth);
+ break;
+ case 1: // PaletteFilter
+ ret = this._paletteFilter(streamId, x, y, width, height,
+ sock, display, depth);
+ break;
+ case 2: // GradientFilter
+ ret = this._gradientFilter(streamId, x, y, width, height,
+ sock, display, depth);
+ break;
+ default:
+ throw Error("Illegal tight filter received (ctl: " +
+ this._filter + ")");
+ }
+
+ if (ret) {
+ this._filter = null;
+ }
+
+ return ret;
+ }
+
+ _copyFilter(streamId, x, y, width, height, sock, display, depth) {
+ const uncompressedSize = width * height * 3;
+ let data;
+
+ if (uncompressedSize < 12) {
+ if (sock.rQwait("TIGHT", uncompressedSize)) {
+ return false;
+ }
+
+ data = sock.rQshiftBytes(uncompressedSize);
+ } else {
+ data = this._readData(sock);
+ if (data === null) {
+ return false;
+ }
+
+ data = this._zlibs[streamId].inflate(data, true, uncompressedSize);
+ if (data.length != uncompressedSize) {
+ throw Error("Incomplete zlib block");
+ }
+ }
+
+ display.blitRgbImage(x, y, width, height, data, 0, false);
+
+ return true;
+ }
+
+ _paletteFilter(streamId, x, y, width, height, sock, display, depth) {
+ if (this._numColors === 0) {
+ if (sock.rQwait("TIGHT palette", 1)) {
+ return false;
+ }
+
+ const numColors = sock.rQpeek8() + 1;
+ const paletteSize = numColors * 3;
+
+ if (sock.rQwait("TIGHT palette", 1 + paletteSize)) {
+ return false;
+ }
+
+ this._numColors = numColors;
+ sock.rQskipBytes(1);
+
+ sock.rQshiftTo(this._palette, paletteSize);
+ }
+
+ const bpp = (this._numColors <= 2) ? 1 : 8;
+ const rowSize = Math.floor((width * bpp + 7) / 8);
+ const uncompressedSize = rowSize * height;
+
+ let data;
+
+ if (uncompressedSize < 12) {
+ if (sock.rQwait("TIGHT", uncompressedSize)) {
+ return false;
+ }
+
+ data = sock.rQshiftBytes(uncompressedSize);
+ } else {
+ data = this._readData(sock);
+ if (data === null) {
+ return false;
+ }
+
+ data = this._zlibs[streamId].inflate(data, true, uncompressedSize);
+ if (data.length != uncompressedSize) {
+ throw Error("Incomplete zlib block");
+ }
+ }
+
+ // Convert indexed (palette based) image data to RGB
+ if (this._numColors == 2) {
+ this._monoRect(x, y, width, height, data, this._palette, display);
+ } else {
+ this._paletteRect(x, y, width, height, data, this._palette, display);
+ }
+
+ this._numColors = 0;
+
+ return true;
+ }
+
+ _monoRect(x, y, width, height, data, palette, display) {
+ // Convert indexed (palette based) image data to RGB
+ // TODO: reduce number of calculations inside loop
+ const dest = this._getScratchBuffer(width * height * 4);
+ const w = Math.floor((width + 7) / 8);
+ const w1 = Math.floor(width / 8);
+
+ for (let y = 0; y < height; y++) {
+ let dp, sp, x;
+ for (x = 0; x < w1; x++) {
+ for (let b = 7; b >= 0; b--) {
+ dp = (y * width + x * 8 + 7 - b) * 4;
+ sp = (data[y * w + x] >> b & 1) * 3;
+ dest[dp] = palette[sp];
+ dest[dp + 1] = palette[sp + 1];
+ dest[dp + 2] = palette[sp + 2];
+ dest[dp + 3] = 255;
+ }
+ }
+
+ for (let b = 7; b >= 8 - width % 8; b--) {
+ dp = (y * width + x * 8 + 7 - b) * 4;
+ sp = (data[y * w + x] >> b & 1) * 3;
+ dest[dp] = palette[sp];
+ dest[dp + 1] = palette[sp + 1];
+ dest[dp + 2] = palette[sp + 2];
+ dest[dp + 3] = 255;
+ }
+ }
+
+ display.blitRgbxImage(x, y, width, height, dest, 0, false);
+ }
+
+ _paletteRect(x, y, width, height, data, palette, display) {
+ // Convert indexed (palette based) image data to RGB
+ const dest = this._getScratchBuffer(width * height * 4);
+ const total = width * height * 4;
+ for (let i = 0, j = 0; i < total; i += 4, j++) {
+ const sp = data[j] * 3;
+ dest[i] = palette[sp];
+ dest[i + 1] = palette[sp + 1];
+ dest[i + 2] = palette[sp + 2];
+ dest[i + 3] = 255;
+ }
+
+ display.blitRgbxImage(x, y, width, height, dest, 0, false);
+ }
+
+ _gradientFilter(streamId, x, y, width, height, sock, display, depth) {
+ throw Error("Gradient filter not implemented");
+ }
+
+ _readData(sock) {
+ if (this._len === 0) {
+ if (sock.rQwait("TIGHT", 3)) {
+ return null;
+ }
+
+ let byte;
+
+ byte = sock.rQshift8();
+ this._len = byte & 0x7f;
+ if (byte & 0x80) {
+ byte = sock.rQshift8();
+ this._len |= (byte & 0x7f) << 7;
+ if (byte & 0x80) {
+ byte = sock.rQshift8();
+ this._len |= byte << 14;
+ }
+ }
+ }
+
+ if (sock.rQwait("TIGHT", this._len)) {
+ return null;
+ }
+
+ let data = sock.rQshiftBytes(this._len);
+ this._len = 0;
+
+ return data;
+ }
+
+ _getScratchBuffer(size) {
+ if (!this._scratchBuffer || (this._scratchBuffer.length < size)) {
+ this._scratchBuffer = new Uint8Array(size);
+ }
+ return this._scratchBuffer;
+ }
+}
this._FBU.height = (hdr[6] << 8) + hdr[7];
this._FBU.encoding = parseInt((hdr[8] << 24) + (hdr[9] << 16) +
(hdr[10] << 8) + hdr[11], 10);
+ }
- if (!this._encHandlers[this._FBU.encoding]) {
- this._fail("Unsupported encoding (encoding: " +
- this._FBU.encoding + ")");
- return false;
- }
+ if (!this._handleRect()) {
+ return false;
}
- this._timing.last_fbu = (new Date()).getTime();
+ this._FBU.rects--;
+ this._FBU.encoding = null;
+ }
+
+ this._display.flip();
- const ret = this._encHandlers[this._FBU.encoding]();
+ return true; // We finished this FBU
+ }
- const now = (new Date()).getTime();
- this._timing.cur_fbu += (now - this._timing.last_fbu);
+ _handleRect() {
+ switch (this._FBU.encoding) {
+ case encodings.pseudoEncodingLastRect:
+ this._FBU.rects = 1; // Will be decreased when we return
+ return true;
- if (ret) {
- if (!(this._FBU.encoding in this._encStats)) {
- this._encStats[this._FBU.encoding] = [0, 0];
- }
- this._encStats[this._FBU.encoding][0]++;
- this._encStats[this._FBU.encoding][1]++;
- this._timing.pixels += this._FBU.width * this._FBU.height;
- }
+ case encodings.pseudoEncodingCursor:
+ return this._handleCursor();
- if (this._timing.pixels >= (this._fb_width * this._fb_height)) {
- if ((this._FBU.width === this._fb_width && this._FBU.height === this._fb_height) ||
- this._timing.fbu_rt_start > 0) {
- this._timing.full_fbu_total += this._timing.cur_fbu;
- this._timing.full_fbu_cnt++;
- Log.Info("Timing of full FBU, curr: " +
- this._timing.cur_fbu + ", total: " +
- this._timing.full_fbu_total + ", cnt: " +
- this._timing.full_fbu_cnt + ", avg: " +
- (this._timing.full_fbu_total / this._timing.full_fbu_cnt));
+ case encodings.pseudoEncodingQEMUExtendedKeyEvent:
+ // Old Safari doesn't support creating keyboard events
+ try {
+ const keyboardEvent = document.createEvent("keyboardEvent");
+ if (keyboardEvent.code !== undefined) {
+ this._qemuExtKeyEventSupported = true;
+ }
+ } catch (err) {
+ // Do nothing
}
+ return true;
- if (this._timing.fbu_rt_start > 0) {
- const fbu_rt_diff = now - this._timing.fbu_rt_start;
- this._timing.fbu_rt_total += fbu_rt_diff;
- this._timing.fbu_rt_cnt++;
- Log.Info("full FBU round-trip, cur: " +
- fbu_rt_diff + ", total: " +
- this._timing.fbu_rt_total + ", cnt: " +
- this._timing.fbu_rt_cnt + ", avg: " +
- (this._timing.fbu_rt_total / this._timing.fbu_rt_cnt));
- this._timing.fbu_rt_start = 0;
- }
+ case encodings.pseudoEncodingDesktopSize:
+ this._resize(this._FBU.width, this._FBU.height);
+ return true;
+
+ case encodings.pseudoEncodingExtendedDesktopSize:
+ return this._handleExtendedDesktopSize();
+
+ default:
+ return this._handleDataRect();
+ }
+ }
+
+ _handleCursor() {
+ const x = this._FBU.x; // hotspot-x
+ const y = this._FBU.y; // hotspot-y
+ const w = this._FBU.width;
+ const h = this._FBU.height;
+
+ const pixelslength = w * h * 4;
+ const masklength = Math.floor((w + 7) / 8) * h;
+
+ let bytes = pixelslength + masklength;
+ if (this._sock.rQwait("cursor encoding", bytes)) {
+ return false;
+ }
+
+ this._cursor.change(this._sock.rQshiftBytes(pixelslength),
+ this._sock.rQshiftBytes(masklength),
+ x, y, w, h);
+
+ return true;
+ }
+
+ _handleExtendedDesktopSize() {
+ if (this._sock.rQwait("ExtendedDesktopSize", 4)) {
+ return false;
+ }
+
+ const number_of_screens = this._sock.rQpeek8();
+
+ let bytes = 4 + (number_of_screens * 16);
+ if (this._sock.rQwait("ExtendedDesktopSize", bytes)) {
+ return false;
+ }
+
+ const firstUpdate = !this._supportsSetDesktopSize;
+ this._supportsSetDesktopSize = true;
+
+ // Normally we only apply the current resize mode after a
+ // window resize event. However there is no such trigger on the
+ // initial connect. And we don't know if the server supports
+ // resizing until we've gotten here.
+ if (firstUpdate) {
+ this._requestRemoteResize();
+ }
+
+ this._sock.rQskipBytes(1); // number-of-screens
+ this._sock.rQskipBytes(3); // padding
+
+ for (let i = 0; i < number_of_screens; i += 1) {
+ // Save the id and flags of the first screen
+ if (i === 0) {
+ this._screen_id = this._sock.rQshiftBytes(4); // id
+ this._sock.rQskipBytes(2); // x-position
+ this._sock.rQskipBytes(2); // y-position
+ this._sock.rQskipBytes(2); // width
+ this._sock.rQskipBytes(2); // height
+ this._screen_flags = this._sock.rQshiftBytes(4); // flags
+ } else {
+ this._sock.rQskipBytes(16);
}
+ }
- if (!ret) { return ret; } // need more data
+ /*
+ * The x-position indicates the reason for the change:
+ *
+ * 0 - server resized on its own
+ * 1 - this client requested the resize
+ * 2 - another client requested the resize
+ */
+
+ // We need to handle errors when we requested the resize.
+ if (this._FBU.x === 1 && this._FBU.y !== 0) {
+ let msg = "";
+ // The y-position indicates the status code from the server
+ switch (this._FBU.y) {
- case 1:
- msg = "Resize is administratively prohibited";
- break;
- case 2:
- msg = "Out of resources";
- break;
- case 3:
- msg = "Invalid screen layout";
- break;
- default:
- msg = "Unknown reason";
- break;
++ case 1:
++ msg = "Resize is administratively prohibited";
++ break;
++ case 2:
++ msg = "Out of resources";
++ break;
++ case 3:
++ msg = "Invalid screen layout";
++ break;
++ default:
++ msg = "Unknown reason";
++ break;
+ }
+ Log.Warn("Server did not accept the resize request: "
+ + msg);
+ } else {
+ this._resize(this._FBU.width, this._FBU.height);
}
- this._display.flip();
+ return true;
+ }
- return true; // We finished this FBU
+ _handleDataRect() {
+ let decoder = this._decoders[this._FBU.encoding];
+ if (!decoder) {
+ this._fail("Unsupported encoding (encoding: " +
+ this._FBU.encoding + ")");
+ return false;
+ }
+
+ try {
+ return decoder.decodeRect(this._FBU.x, this._FBU.y,
+ this._FBU.width, this._FBU.height,
+ this._sock, this._display,
+ this._fb_depth);
+ } catch (err) {
+ this._fail("Error decoding rect: " + err);
+ return false;
+ }
}
_updateContinuousUpdates() {
}
};
-
-RFB.encodingHandlers = {
- RAW() {
- if (this._FBU.lines === 0) {
- this._FBU.lines = this._FBU.height;
- }
-
- const pixelSize = this._fb_depth == 8 ? 1 : 4;
- this._FBU.bytes = this._FBU.width * pixelSize; // at least a line
- if (this._sock.rQwait("RAW", this._FBU.bytes)) { return false; }
- const cur_y = this._FBU.y + (this._FBU.height - this._FBU.lines);
- const curr_height = Math.min(this._FBU.lines,
- Math.floor(this._sock.rQlen() / (this._FBU.width * pixelSize)));
- let data = this._sock.get_rQ();
- let index = this._sock.get_rQi();
- if (this._fb_depth == 8) {
- const pixels = this._FBU.width * curr_height;
- const newdata = new Uint8Array(pixels * 4);
- for (let i = 0; i < pixels; i++) {
- newdata[i * 4 + 0] = ((data[index + i] >> 0) & 0x3) * 255 / 3;
- newdata[i * 4 + 1] = ((data[index + i] >> 2) & 0x3) * 255 / 3;
- newdata[i * 4 + 2] = ((data[index + i] >> 4) & 0x3) * 255 / 3;
- newdata[i * 4 + 4] = 0;
- }
- data = newdata;
- index = 0;
- }
- this._display.blitImage(this._FBU.x, cur_y, this._FBU.width,
- curr_height, data, index);
- this._sock.rQskipBytes(this._FBU.width * curr_height * pixelSize);
- this._FBU.lines -= curr_height;
-
- if (this._FBU.lines > 0) {
- this._FBU.bytes = this._FBU.width * pixelSize; // At least another line
- } else {
- this._FBU.rects--;
- this._FBU.bytes = 0;
- }
-
- return true;
- },
-
- COPYRECT() {
- this._FBU.bytes = 4;
- if (this._sock.rQwait("COPYRECT", 4)) { return false; }
- this._display.copyImage(this._sock.rQshift16(), this._sock.rQshift16(),
- this._FBU.x, this._FBU.y, this._FBU.width,
- this._FBU.height);
-
- this._FBU.rects--;
- this._FBU.bytes = 0;
- return true;
+RFB.cursors = {
+ none: {
+ rgbaPixels: new Uint8Array(),
+ w: 0, h: 0,
+ hotx: 0, hoty: 0,
},
- RRE() {
- let color;
- if (this._FBU.subrects === 0) {
- this._FBU.bytes = 4 + 4;
- if (this._sock.rQwait("RRE", 4 + 4)) { return false; }
- this._FBU.subrects = this._sock.rQshift32();
- color = this._sock.rQshiftBytes(4); // Background
- this._display.fillRect(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, color);
- }
-
- while (this._FBU.subrects > 0 && this._sock.rQlen() >= (4 + 8)) {
- color = this._sock.rQshiftBytes(4);
- const x = this._sock.rQshift16();
- const y = this._sock.rQshift16();
- const width = this._sock.rQshift16();
- const height = this._sock.rQshift16();
- this._display.fillRect(this._FBU.x + x, this._FBU.y + y, width, height, color);
- this._FBU.subrects--;
- }
-
- if (this._FBU.subrects > 0) {
- const chunk = Math.min(this._rre_chunk_sz, this._FBU.subrects);
- this._FBU.bytes = (4 + 8) * chunk;
- } else {
- this._FBU.rects--;
- this._FBU.bytes = 0;
- }
-
- return true;
- },
-
- HEXTILE() {
- const rQ = this._sock.get_rQ();
- let rQi = this._sock.get_rQi();
-
- if (this._FBU.tiles === 0) {
- this._FBU.tiles_x = Math.ceil(this._FBU.width / 16);
- this._FBU.tiles_y = Math.ceil(this._FBU.height / 16);
- this._FBU.total_tiles = this._FBU.tiles_x * this._FBU.tiles_y;
- this._FBU.tiles = this._FBU.total_tiles;
- }
-
- while (this._FBU.tiles > 0) {
- this._FBU.bytes = 1;
- if (this._sock.rQwait("HEXTILE subencoding", this._FBU.bytes)) { return false; }
- const subencoding = rQ[rQi]; // Peek
- if (subencoding > 30) { // Raw
- this._fail("Illegal hextile subencoding (subencoding: " +
- subencoding + ")");
- return false;
- }
-
- let subrects = 0;
- const curr_tile = this._FBU.total_tiles - this._FBU.tiles;
- const tile_x = curr_tile % this._FBU.tiles_x;
- const tile_y = Math.floor(curr_tile / this._FBU.tiles_x);
- const x = this._FBU.x + tile_x * 16;
- const y = this._FBU.y + tile_y * 16;
- const w = Math.min(16, (this._FBU.x + this._FBU.width) - x);
- const h = Math.min(16, (this._FBU.y + this._FBU.height) - y);
-
- // Figure out how much we are expecting
- if (subencoding & 0x01) { // Raw
- this._FBU.bytes += w * h * 4;
- } else {
- if (subencoding & 0x02) { // Background
- this._FBU.bytes += 4;
- }
- if (subencoding & 0x04) { // Foreground
- this._FBU.bytes += 4;
- }
- if (subencoding & 0x08) { // AnySubrects
- this._FBU.bytes++; // Since we aren't shifting it off
- if (this._sock.rQwait("hextile subrects header", this._FBU.bytes)) { return false; }
- subrects = rQ[rQi + this._FBU.bytes - 1]; // Peek
- if (subencoding & 0x10) { // SubrectsColoured
- this._FBU.bytes += subrects * (4 + 2);
- } else {
- this._FBU.bytes += subrects * 2;
- }
- }
- }
-
- if (this._sock.rQwait("hextile", this._FBU.bytes)) { return false; }
-
- // We know the encoding and have a whole tile
- this._FBU.subencoding = rQ[rQi];
- rQi++;
- if (this._FBU.subencoding === 0) {
- if (this._FBU.lastsubencoding & 0x01) {
- // Weird: ignore blanks are RAW
- Log.Debug(" Ignoring blank after RAW");
- } else {
- this._display.fillRect(x, y, w, h, this._FBU.background);
- }
- } else if (this._FBU.subencoding & 0x01) { // Raw
- this._display.blitImage(x, y, w, h, rQ, rQi);
- rQi += this._FBU.bytes - 1;
- } else {
- if (this._FBU.subencoding & 0x02) { // Background
- this._FBU.background = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]];
- rQi += 4;
- }
- if (this._FBU.subencoding & 0x04) { // Foreground
- this._FBU.foreground = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]];
- rQi += 4;
- }
-
- this._display.startTile(x, y, w, h, this._FBU.background);
- if (this._FBU.subencoding & 0x08) { // AnySubrects
- subrects = rQ[rQi];
- rQi++;
-
- for (let s = 0; s < subrects; s++) {
- let color;
- if (this._FBU.subencoding & 0x10) { // SubrectsColoured
- color = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]];
- rQi += 4;
- } else {
- color = this._FBU.foreground;
- }
- const xy = rQ[rQi];
- rQi++;
- const sx = (xy >> 4);
- const sy = (xy & 0x0f);
-
- const wh = rQ[rQi];
- rQi++;
- const sw = (wh >> 4) + 1;
- const sh = (wh & 0x0f) + 1;
-
- this._display.subTile(sx, sy, sw, sh, color);
- }
- }
- this._display.finishTile();
- }
- this._sock.set_rQi(rQi);
- this._FBU.lastsubencoding = this._FBU.subencoding;
- this._FBU.bytes = 0;
- this._FBU.tiles--;
- }
-
- if (this._FBU.tiles === 0) {
- this._FBU.rects--;
- }
-
- return true;
- },
-
- TIGHT(isTightPNG) {
- this._FBU.bytes = 1; // compression-control byte
- if (this._sock.rQwait("TIGHT compression-control", this._FBU.bytes)) { return false; }
-
- let resetStreams = 0;
- let streamId = -1;
- const decompress = (data, expected) => {
- for (let i = 0; i < 4; i++) {
- if ((resetStreams >> i) & 1) {
- this._FBU.zlibs[i].reset();
- Log.Info("Reset zlib stream " + i);
- }
- }
-
- //const uncompressed = this._FBU.zlibs[streamId].uncompress(data, 0);
- const uncompressed = this._FBU.zlibs[streamId].inflate(data, true, expected);
- /*if (uncompressed.status !== 0) {
- Log.Error("Invalid data in zlib stream");
- }*/
-
- //return uncompressed.data;
- return uncompressed;
- };
-
- const indexedToRGBX2Color = (data, palette, width, height) => {
- // Convert indexed (palette based) image data to RGB
- // TODO: reduce number of calculations inside loop
- const dest = this._destBuff;
- const w = Math.floor((width + 7) / 8);
- const w1 = Math.floor(width / 8);
-
- /*for (let y = 0; y < height; y++) {
- let b, x, dp, sp;
- const yoffset = y * width;
- const ybitoffset = y * w;
- let xoffset, targetbyte;
- for (x = 0; x < w1; x++) {
- xoffset = yoffset + x * 8;
- targetbyte = data[ybitoffset + x];
- for (b = 7; b >= 0; b--) {
- dp = (xoffset + 7 - b) * 3;
- sp = (targetbyte >> b & 1) * 3;
- dest[dp] = palette[sp];
- dest[dp + 1] = palette[sp + 1];
- dest[dp + 2] = palette[sp + 2];
- }
- }
-
- xoffset = yoffset + x * 8;
- targetbyte = data[ybitoffset + x];
- for (b = 7; b >= 8 - width % 8; b--) {
- dp = (xoffset + 7 - b) * 3;
- sp = (targetbyte >> b & 1) * 3;
- dest[dp] = palette[sp];
- dest[dp + 1] = palette[sp + 1];
- dest[dp + 2] = palette[sp + 2];
- }
- }*/
-
- for (let y = 0; y < height; y++) {
- let dp, sp, x;
- for (x = 0; x < w1; x++) {
- for (let b = 7; b >= 0; b--) {
- dp = (y * width + x * 8 + 7 - b) * 4;
- sp = (data[y * w + x] >> b & 1) * 3;
- dest[dp] = palette[sp];
- dest[dp + 1] = palette[sp + 1];
- dest[dp + 2] = palette[sp + 2];
- dest[dp + 3] = 255;
- }
- }
-
- for (let b = 7; b >= 8 - width % 8; b--) {
- dp = (y * width + x * 8 + 7 - b) * 4;
- sp = (data[y * w + x] >> b & 1) * 3;
- dest[dp] = palette[sp];
- dest[dp + 1] = palette[sp + 1];
- dest[dp + 2] = palette[sp + 2];
- dest[dp + 3] = 255;
- }
- }
-
- return dest;
- };
-
- const indexedToRGBX = (data, palette, width, height) => {
- // Convert indexed (palette based) image data to RGB
- const dest = this._destBuff;
- const total = width * height * 4;
- for (let i = 0, j = 0; i < total; i += 4, j++) {
- const sp = data[j] * 3;
- dest[i] = palette[sp];
- dest[i + 1] = palette[sp + 1];
- dest[i + 2] = palette[sp + 2];
- dest[i + 3] = 255;
- }
-
- return dest;
- };
-
- const rQi = this._sock.get_rQi();
- const rQ = this._sock.rQwhole();
- let cmode, data;
- let cl_header, cl_data;
-
- const handlePalette = () => {
- const numColors = rQ[rQi + 2] + 1;
- const paletteSize = numColors * 3;
- this._FBU.bytes += paletteSize;
- if (this._sock.rQwait("TIGHT palette " + cmode, this._FBU.bytes)) { return false; }
-
- const bpp = (numColors <= 2) ? 1 : 8;
- const rowSize = Math.floor((this._FBU.width * bpp + 7) / 8);
- let raw = false;
- if (rowSize * this._FBU.height < 12) {
- raw = true;
- cl_header = 0;
- cl_data = rowSize * this._FBU.height;
- //clength = [0, rowSize * this._FBU.height];
- } else {
- // begin inline getTightCLength (returning two-item arrays is bad for performance with GC)
- const cl_offset = rQi + 3 + paletteSize;
- cl_header = 1;
- cl_data = 0;
- cl_data += rQ[cl_offset] & 0x7f;
- if (rQ[cl_offset] & 0x80) {
- cl_header++;
- cl_data += (rQ[cl_offset + 1] & 0x7f) << 7;
- if (rQ[cl_offset + 1] & 0x80) {
- cl_header++;
- cl_data += rQ[cl_offset + 2] << 14;
- }
- }
- // end inline getTightCLength
- }
-
- this._FBU.bytes += cl_header + cl_data;
- if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; }
-
- // Shift ctl, filter id, num colors, palette entries, and clength off
- this._sock.rQskipBytes(3);
- //const palette = this._sock.rQshiftBytes(paletteSize);
- this._sock.rQshiftTo(this._paletteBuff, paletteSize);
- this._sock.rQskipBytes(cl_header);
-
- if (raw) {
- data = this._sock.rQshiftBytes(cl_data);
- } else {
- data = decompress(this._sock.rQshiftBytes(cl_data), rowSize * this._FBU.height);
- }
-
- // Convert indexed (palette based) image data to RGB
- let rgbx;
- if (numColors == 2) {
- rgbx = indexedToRGBX2Color(data, this._paletteBuff, this._FBU.width, this._FBU.height);
- } else {
- rgbx = indexedToRGBX(data, this._paletteBuff, this._FBU.width, this._FBU.height);
- }
-
- this._display.blitRgbxImage(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, rgbx, 0, false);
-
-
- return true;
- };
-
- const handleCopy = () => {
- let raw = false;
- const uncompressedSize = this._FBU.width * this._FBU.height * 3;
- if (uncompressedSize < 12) {
- raw = true;
- cl_header = 0;
- cl_data = uncompressedSize;
- } else {
- // begin inline getTightCLength (returning two-item arrays is for peformance with GC)
- const cl_offset = rQi + 1;
- cl_header = 1;
- cl_data = 0;
- cl_data += rQ[cl_offset] & 0x7f;
- if (rQ[cl_offset] & 0x80) {
- cl_header++;
- cl_data += (rQ[cl_offset + 1] & 0x7f) << 7;
- if (rQ[cl_offset + 1] & 0x80) {
- cl_header++;
- cl_data += rQ[cl_offset + 2] << 14;
- }
- }
- // end inline getTightCLength
- }
- this._FBU.bytes = 1 + cl_header + cl_data;
- if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; }
-
- // Shift ctl, clength off
- this._sock.rQshiftBytes(1 + cl_header);
-
- if (raw) {
- data = this._sock.rQshiftBytes(cl_data);
- } else {
- data = decompress(this._sock.rQshiftBytes(cl_data), uncompressedSize);
- }
-
- this._display.blitRgbImage(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, data, 0, false);
-
- return true;
- };
-
- let ctl = this._sock.rQpeek8();
-
- // Keep tight reset bits
- resetStreams = ctl & 0xF;
-
- // Figure out filter
- ctl = ctl >> 4;
- streamId = ctl & 0x3;
-
- if (ctl === 0x08) cmode = "fill";
- else if (ctl === 0x09) cmode = "jpeg";
- else if (ctl === 0x0A) cmode = "png";
- else if (ctl & 0x04) cmode = "filter";
- else if (ctl < 0x04) cmode = "copy";
- else {
- return this._fail("Illegal tight compression received (ctl: " +
- ctl + ")");
- }
-
- if (isTightPNG && (ctl < 0x08)) {
- return this._fail("BasicCompression received in TightPNG rect");
- }
- if (!isTightPNG && (ctl === 0x0A)) {
- return this._fail("PNG received in standard Tight rect");
- }
-
- switch (cmode) {
- // fill use depth because TPIXELs drop the padding byte
- case "fill": // TPIXEL
- this._FBU.bytes += 3;
- break;
- case "jpeg": // max clength
- this._FBU.bytes += 3;
- break;
- case "png": // max clength
- this._FBU.bytes += 3;
- break;
- case "filter": // filter id + num colors if palette
- this._FBU.bytes += 2;
- break;
- case "copy":
- break;
- }
-
- if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; }
-
- // Determine FBU.bytes
- let cl_offset, filterId;
- switch (cmode) {
- case "fill":
- // skip ctl byte
- this._display.fillRect(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, [rQ[rQi + 3], rQ[rQi + 2], rQ[rQi + 1]], false);
- this._sock.rQskipBytes(4);
- break;
- case "png":
- case "jpeg":
- // begin inline getTightCLength (returning two-item arrays is for peformance with GC)
- cl_offset = rQi + 1;
- cl_header = 1;
- cl_data = 0;
- cl_data += rQ[cl_offset] & 0x7f;
- if (rQ[cl_offset] & 0x80) {
- cl_header++;
- cl_data += (rQ[cl_offset + 1] & 0x7f) << 7;
- if (rQ[cl_offset + 1] & 0x80) {
- cl_header++;
- cl_data += rQ[cl_offset + 2] << 14;
- }
- }
- // end inline getTightCLength
- this._FBU.bytes = 1 + cl_header + cl_data; // ctl + clength size + jpeg-data
- if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; }
-
- // We have everything, render it
- this._sock.rQskipBytes(1 + cl_header); // shift off clt + compact length
- data = this._sock.rQshiftBytes(cl_data);
- this._display.imageRect(this._FBU.x, this._FBU.y, "image/" + cmode, data);
- break;
- case "filter":
- filterId = rQ[rQi + 1];
- if (filterId === 1) {
- if (!handlePalette()) { return false; }
- } else {
- // Filter 0, Copy could be valid here, but servers don't send it as an explicit filter
- // Filter 2, Gradient is valid but not use if jpeg is enabled
- this._fail("Unsupported tight subencoding received " +
- "(filter: " + filterId + ")");
- }
- break;
- case "copy":
- if (!handleCopy()) { return false; }
- break;
- }
-
-
- this._FBU.bytes = 0;
- this._FBU.rects--;
-
- return true;
- },
-
- last_rect() {
- this._FBU.rects = 0;
- return true;
- },
-
- ExtendedDesktopSize() {
- this._FBU.bytes = 1;
- if (this._sock.rQwait("ExtendedDesktopSize", this._FBU.bytes)) { return false; }
-
- const firstUpdate = !this._supportsSetDesktopSize;
- this._supportsSetDesktopSize = true;
-
- // Normally we only apply the current resize mode after a
- // window resize event. However there is no such trigger on the
- // initial connect. And we don't know if the server supports
- // resizing until we've gotten here.
- if (firstUpdate) {
- this._requestRemoteResize();
- }
-
- const number_of_screens = this._sock.rQpeek8();
-
- this._FBU.bytes = 4 + (number_of_screens * 16);
- if (this._sock.rQwait("ExtendedDesktopSize", this._FBU.bytes)) { return false; }
-
- this._sock.rQskipBytes(1); // number-of-screens
- this._sock.rQskipBytes(3); // padding
-
- for (let i = 0; i < number_of_screens; i += 1) {
- // Save the id and flags of the first screen
- if (i === 0) {
- this._screen_id = this._sock.rQshiftBytes(4); // id
- this._sock.rQskipBytes(2); // x-position
- this._sock.rQskipBytes(2); // y-position
- this._sock.rQskipBytes(2); // width
- this._sock.rQskipBytes(2); // height
- this._screen_flags = this._sock.rQshiftBytes(4); // flags
- } else {
- this._sock.rQskipBytes(16);
- }
- }
-
- /*
- * The x-position indicates the reason for the change:
- *
- * 0 - server resized on its own
- * 1 - this client requested the resize
- * 2 - another client requested the resize
- */
-
- // We need to handle errors when we requested the resize.
- if (this._FBU.x === 1 && this._FBU.y !== 0) {
- let msg = "";
- // The y-position indicates the status code from the server
- switch (this._FBU.y) {
- case 1:
- msg = "Resize is administratively prohibited";
- break;
- case 2:
- msg = "Out of resources";
- break;
- case 3:
- msg = "Invalid screen layout";
- break;
- default:
- msg = "Unknown reason";
- break;
- }
- Log.Warn("Server did not accept the resize request: "
- + msg);
- } else {
- this._resize(this._FBU.width, this._FBU.height);
- }
-
- this._FBU.bytes = 0;
- this._FBU.rects -= 1;
- return true;
- },
-
- DesktopSize() {
- this._resize(this._FBU.width, this._FBU.height);
- this._FBU.bytes = 0;
- this._FBU.rects -= 1;
- return true;
- },
-
- Cursor() {
- Log.Debug(">> set_cursor");
- const x = this._FBU.x; // hotspot-x
- const y = this._FBU.y; // hotspot-y
- const w = this._FBU.width;
- const h = this._FBU.height;
-
- const pixelslength = w * h * 4;
- const masklength = Math.floor((w + 7) / 8) * h;
-
- this._FBU.bytes = pixelslength + masklength;
- if (this._sock.rQwait("cursor encoding", this._FBU.bytes)) { return false; }
-
- this._cursor.change(this._sock.rQshiftBytes(pixelslength),
- this._sock.rQshiftBytes(masklength),
- x, y, w, h);
-
- this._FBU.bytes = 0;
- this._FBU.rects--;
-
- Log.Debug("<< set_cursor");
- return true;
- },
-
- QEMUExtendedKeyEvent() {
- this._FBU.rects--;
-
- // Old Safari doesn't support creating keyboard events
- try {
- const keyboardEvent = document.createEvent("keyboardEvent");
- if (keyboardEvent.code !== undefined) {
- this._qemuExtKeyEventSupported = true;
- }
- } catch (err) {
- // Do nothing
- }
+ dot: {
++ /* eslint-disable indent */
+ rgbaPixels: new Uint8Array([
+ 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 255,
+ 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 255,
+ 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 255,
+ ]),
++ /* eslint-enable indent */
+ w: 3, h: 3,
+ hotx: 1, hoty: 1,
}
};