Move decoders to separate classes
Makes things a lot clearer by letting each encoding handle its own details and state.
This commit is contained in:
parent
11309f3243
commit
923cd22083
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
582
core/rfb.js
582
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;
|
||||
},
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue