diff options
author | Pierre Ossman <ossman@cendio.se> | 2018-07-20 16:05:16 +0200 |
---|---|---|
committer | Pierre Ossman <ossman@cendio.se> | 2018-08-22 15:12:34 +0200 |
commit | 923cd22083bf9c9cc5f460c863fedfbcce5c4907 (patch) | |
tree | 631943db45b90543ee47ef858c7568f65d2535ed /core | |
parent | 11309f32434bdd56dcea380ed7abd98abe9e9fae (diff) | |
download | novnc-923cd22083bf9c9cc5f460c863fedfbcce5c4907.tar.gz |
Move decoders to separate classes
Makes things a lot clearer by letting each encoding handle its own
details and state.
Diffstat (limited to 'core')
-rw-r--r-- | core/decoders/copyrect.js | 24 | ||||
-rw-r--r-- | core/decoders/hextile.js | 139 | ||||
-rw-r--r-- | core/decoders/raw.js | 58 | ||||
-rw-r--r-- | core/decoders/rre.js | 46 | ||||
-rw-r--r-- | core/decoders/tight.js | 319 | ||||
-rw-r--r-- | core/decoders/tightpng.js | 29 | ||||
-rw-r--r-- | core/rfb.js | 582 |
7 files changed, 649 insertions, 548 deletions
diff --git a/core/decoders/copyrect.js b/core/decoders/copyrect.js new file mode 100644 index 0000000..a78ded7 --- /dev/null +++ b/core/decoders/copyrect.js @@ -0,0 +1,24 @@ +/* + * noVNC: HTML5 VNC client + * Copyright (C) 2012 Joel Martin + * 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. + * + */ + +export default class CopyRectDecoder { + decodeRect(x, y, width, height, sock, display, depth) { + if (sock.rQwait("COPYRECT", 4)) { + return false; + } + + let deltaX = sock.rQshift16(); + let deltaY = sock.rQshift16(); + display.copyImage(deltaX, deltaY, x, y, width, height); + + return true; + } +} diff --git a/core/decoders/hextile.js b/core/decoders/hextile.js new file mode 100644 index 0000000..91141aa --- /dev/null +++ b/core/decoders/hextile.js @@ -0,0 +1,139 @@ +/* + * noVNC: HTML5 VNC client + * Copyright (C) 2012 Joel Martin + * 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'; + +export default class HextileDecoder { + constructor() { + this._tiles = 0; + this._lastsubencoding = 0; + } + + decodeRect(x, y, width, height, sock, display, depth) { + if (this._tiles === 0) { + this._tiles_x = Math.ceil(width / 16); + this._tiles_y = Math.ceil(height / 16); + this._total_tiles = this._tiles_x * this._tiles_y; + this._tiles = this._total_tiles; + } + + while (this._tiles > 0) { + let bytes = 1; + + if (sock.rQwait("HEXTILE", bytes)) { + return false; + } + + let rQ = sock.get_rQ(); + let rQi = sock.get_rQi(); + + let subencoding = rQ[rQi]; // Peek + if (subencoding > 30) { // Raw + throw Error("Illegal hextile subencoding (subencoding: " + + subencoding + ")"); + } + + const curr_tile = this._total_tiles - this._tiles; + const tile_x = curr_tile % this._tiles_x; + const tile_y = Math.floor(curr_tile / this._tiles_x); + const tx = x + tile_x * 16; + const ty = y + tile_y * 16; + const tw = Math.min(16, (x + width) - tx); + const th = Math.min(16, (y + height) - ty); + + // Figure out how much we are expecting + if (subencoding & 0x01) { // Raw + bytes += tw * th * 4; + } else { + if (subencoding & 0x02) { // Background + bytes += 4; + } + if (subencoding & 0x04) { // Foreground + bytes += 4; + } + if (subencoding & 0x08) { // AnySubrects + bytes++; // Since we aren't shifting it off + + if (sock.rQwait("HEXTILE", bytes)) { + return false; + } + + let subrects = rQ[rQi + bytes - 1]; // Peek + if (subencoding & 0x10) { // SubrectsColoured + bytes += subrects * (4 + 2); + } else { + bytes += subrects * 2; + } + } + } + + if (sock.rQwait("HEXTILE", bytes)) { + return false; + } + + // We know the encoding and have a whole tile + rQi++; + if (subencoding === 0) { + if (this._lastsubencoding & 0x01) { + // Weird: ignore blanks are RAW + Log.Debug(" Ignoring blank after RAW"); + } else { + display.fillRect(tx, ty, tw, th, this._background); + } + } else if (subencoding & 0x01) { // Raw + display.blitImage(tx, ty, tw, th, rQ, rQi); + rQi += bytes - 1; + } else { + if (subencoding & 0x02) { // Background + this._background = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]]; + rQi += 4; + } + if (subencoding & 0x04) { // Foreground + this._foreground = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]]; + rQi += 4; + } + + display.startTile(tx, ty, tw, th, this._background); + if (subencoding & 0x08) { // AnySubrects + let subrects = rQ[rQi]; + rQi++; + + for (let s = 0; s < subrects; s++) { + let color; + if (subencoding & 0x10) { // SubrectsColoured + color = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]]; + rQi += 4; + } else { + color = this._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; + + display.subTile(sx, sy, sw, sh, color); + } + } + display.finishTile(); + } + sock.set_rQi(rQi); + this._lastsubencoding = subencoding; + this._tiles--; + } + + return true; + } +} diff --git a/core/decoders/raw.js b/core/decoders/raw.js new file mode 100644 index 0000000..282ab45 --- /dev/null +++ b/core/decoders/raw.js @@ -0,0 +1,58 @@ +/* + * noVNC: HTML5 VNC client + * Copyright (C) 2012 Joel Martin + * 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. + * + */ + +export default class RawDecoder { + constructor() { + this._lines = 0; + } + + decodeRect(x, y, width, height, sock, display, depth) { + if (this._lines === 0) { + this._lines = height; + } + + const pixelSize = depth == 8 ? 1 : 4; + const bytesPerLine = width * pixelSize; + + if (sock.rQwait("RAW", bytesPerLine)) { + return false; + } + + const cur_y = y + (height - this._lines); + const curr_height = Math.min(this._lines, + Math.floor(sock.rQlen() / bytesPerLine)); + let data = sock.get_rQ(); + let index = sock.get_rQi(); + + // Convert data if needed + if (depth == 8) { + const pixels = 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; + } + + display.blitImage(x, cur_y, width, curr_height, data, index); + sock.rQskipBytes(curr_height * bytesPerLine); + this._lines -= curr_height; + if (this._lines > 0) { + return false; + } + + return true; + } +} diff --git a/core/decoders/rre.js b/core/decoders/rre.js new file mode 100644 index 0000000..57414a0 --- /dev/null +++ b/core/decoders/rre.js @@ -0,0 +1,46 @@ +/* + * noVNC: HTML5 VNC client + * Copyright (C) 2012 Joel Martin + * 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. + * + */ + +export default class RREDecoder { + constructor() { + this._subrects = 0; + } + + decodeRect(x, y, width, height, sock, display, depth) { + if (this._subrects === 0) { + if (sock.rQwait("RRE", 4 + 4)) { + return false; + } + + this._subrects = sock.rQshift32(); + + let color = sock.rQshiftBytes(4); // Background + display.fillRect(x, y, width, height, color); + } + + while (this._subrects > 0) { + if (sock.rQwait("RRE", 4 + 8)) { + return false; + } + + let color = sock.rQshiftBytes(4); + let sx = sock.rQshift16(); + let sy = sock.rQshift16(); + let swidth = sock.rQshift16(); + let sheight = sock.rQshift16(); + display.fillRect(x + sx, y + sy, swidth, sheight, color); + + this._subrects--; + } + + return true; + } +} diff --git a/core/decoders/tight.js b/core/decoders/tight.js new file mode 100644 index 0000000..fce341e --- /dev/null +++ b/core/decoders/tight.js @@ -0,0 +1,319 @@ +/* + * 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(); + } + } + + 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; + } +} diff --git a/core/decoders/tightpng.js b/core/decoders/tightpng.js new file mode 100644 index 0000000..2d91746 --- /dev/null +++ b/core/decoders/tightpng.js @@ -0,0 +1,29 @@ +/* + * noVNC: HTML5 VNC client + * Copyright (C) 2012 Joel Martin + * 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 TightDecoder from './tight.js'; + +export default class TightPNGDecoder extends TightDecoder { + _pngRect(x, y, width, height, sock, display, depth) { + let data = this._readData(sock); + if (data === null) { + return false; + } + + display.imageRect(x, y, "image/png", data); + + return true; + } + + _basicRect(ctl, x, y, width, height, sock, display, depth) { + throw Error("BasicCompression received in TightPNG rect"); + } +} diff --git a/core/rfb.js b/core/rfb.js index 1bfb282..42d682b 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -7,8 +7,6 @@ * * See README.md for usage and integration instructions. * - * TIGHT decoder portion: - * (c) 2012 Michael Tinglof, Joe Balaz, Les Piech (Mercuri.ca) */ import * as Log from './util/logging.js'; @@ -23,10 +21,16 @@ import Websock from "./websock.js"; import DES from "./des.js"; import KeyTable from "./input/keysym.js"; import XtScancode from "./input/xtscancodes.js"; -import Inflator from "./inflator.js"; import { encodings } from "./encodings.js"; import "./util/polyfill.js"; +import RawDecoder from "./decoders/raw.js"; +import CopyRectDecoder from "./decoders/copyrect.js"; +import RREDecoder from "./decoders/rre.js"; +import HextileDecoder from "./decoders/hextile.js"; +import TightDecoder from "./decoders/tight.js"; +import TightPNGDecoder from "./decoders/tightpng.js"; + // How many seconds to wait for a disconnect to finish const DISCONNECT_TIMEOUT = 3; @@ -92,33 +96,17 @@ export default class RFB extends EventTargetMixin { this._resizeTimeout = null; // resize rate limiting // Decoder states - this._encHandlers = {}; + this._decoders = {}; this._FBU = { rects: 0, - subrects: 0, // RRE and HEXTILE - lines: 0, // RAW - tiles: 0, // HEXTILE - bytes: 0, x: 0, y: 0, width: 0, height: 0, - encoding: 0, - subencoding: -1, - background: null, - zlibs: [] // TIGHT zlib streams + encoding: null, }; - for (let i = 0; i < 4; i++) { - this._FBU.zlibs[i] = new Inflator(); - } - - this._destBuff = null; - this._paletteBuff = new Uint8Array(1024); // 256 * 4 (max palette size * max bytes-per-pixel) - - this._rre_chunk_sz = 100; - // Mouse state this._mouse_buttonMask = 0; this._mouse_arr = []; @@ -155,13 +143,13 @@ export default class RFB extends EventTargetMixin { this._cursor = new Cursor(); - // populate encHandlers with bound versions - this._encHandlers[encodings.encodingRaw] = RFB.encodingHandlers.RAW.bind(this); - this._encHandlers[encodings.encodingCopyRect] = RFB.encodingHandlers.COPYRECT.bind(this); - this._encHandlers[encodings.encodingRRE] = RFB.encodingHandlers.RRE.bind(this); - this._encHandlers[encodings.encodingHextile] = RFB.encodingHandlers.HEXTILE.bind(this); - this._encHandlers[encodings.encodingTight] = RFB.encodingHandlers.TIGHT.bind(this, false); - this._encHandlers[encodings.encodingTightPNG] = RFB.encodingHandlers.TIGHT.bind(this, true); + // populate decoder array with objects + this._decoders[encodings.encodingRaw] = new RawDecoder(); + this._decoders[encodings.encodingCopyRect] = new CopyRectDecoder(); + this._decoders[encodings.encodingRRE] = new RREDecoder(); + this._decoders[encodings.encodingHextile] = new HextileDecoder(); + this._decoders[encodings.encodingTight] = new TightDecoder(); + this._decoders[encodings.encodingTightPNG] = new TightPNGDecoder(); // NB: nothing that needs explicit teardown should be done // before this point, since this can throw an exception @@ -1446,7 +1434,6 @@ export default class RFB extends EventTargetMixin { if (this._sock.rQwait("FBU header", 3, 1)) { return false; } this._sock.rQskip8(); // Padding this._FBU.rects = this._sock.rQshift16(); - this._FBU.bytes = 0; // Make sure the previous frame is fully rendered first // to avoid building up an excessive queue @@ -1458,10 +1445,7 @@ export default class RFB extends EventTargetMixin { } while (this._FBU.rects > 0) { - if (this._rfb_connection_state !== 'connected') { return false; } - - if (this._sock.rQwait("FBU", this._FBU.bytes)) { return false; } - if (this._FBU.bytes === 0) { + if (this._FBU.encoding === null) { if (this._sock.rQwait("rect header", 12)) { return false; } /* New FramebufferUpdate */ @@ -1479,6 +1463,7 @@ export default class RFB extends EventTargetMixin { } this._FBU.rects--; + this._FBU.encoding = null; } this._display.flip(); @@ -1528,8 +1513,8 @@ export default class RFB extends EventTargetMixin { 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)) { + let bytes = pixelslength + masklength; + if (this._sock.rQwait("cursor encoding", bytes)) { return false; } @@ -1537,21 +1522,18 @@ export default class RFB extends EventTargetMixin { this._sock.rQshiftBytes(masklength), x, y, w, h); - this._FBU.bytes = 0; - return true; } _handleExtendedDesktopSize() { - this._FBU.bytes = 4; - if (this._sock.rQwait("ExtendedDesktopSize", this._FBU.bytes)) { + if (this._sock.rQwait("ExtendedDesktopSize", 4)) { return false; } const number_of_screens = this._sock.rQpeek8(); - this._FBU.bytes += number_of_screens * 16; - if (this._sock.rQwait("ExtendedDesktopSize", this._FBU.bytes)) { + let bytes = 4 + (number_of_screens * 16); + if (this._sock.rQwait("ExtendedDesktopSize", bytes)) { return false; } @@ -1615,20 +1597,26 @@ export default class RFB extends EventTargetMixin { this._resize(this._FBU.width, this._FBU.height); } - this._FBU.bytes = 0; - return true; } _handleDataRect() { - let handler = this._encHandlers[this._FBU.encoding]; - if (!handler) { + let decoder = this._decoders[this._FBU.encoding]; + if (!decoder) { this._fail("Unsupported encoding (encoding: " + this._FBU.encoding + ")"); return false; } - return handler(); + 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() { @@ -1642,8 +1630,6 @@ export default class RFB extends EventTargetMixin { this._fb_width = width; this._fb_height = height; - this._destBuff = new Uint8Array(this._fb_width * this._fb_height * 4); - this._display.resize(this._fb_width, this._fb_height); // Adjust the visible viewport based on the new dimensions @@ -1983,503 +1969,3 @@ RFB.messages = { sock.flush(); } }; - - -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.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.bytes = 0; - return true; - }, - - 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.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--; - } - - 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; - - return true; - }, -} |