Add client-side pixel format conversion support

I (@kelleyk) have updated the original patch to be compatible with
the double-buffering changes made upstream, repaired some test cases,
and cleaned up the patch a little bit.

The author of the original patch is @jimdigriz (Alexander Clouter).
This commit is contained in:
Kevin Kelley 2017-01-09 19:09:02 -08:00
parent 41f476a863
commit 7541906688
8 changed files with 362 additions and 187 deletions

View File

@ -140,6 +140,7 @@ WebSockets to TCP socket proxy. There is a python proxy included
* UI and Icons : Pierre Ossman, Chris Gordon * UI and Icons : Pierre Ossman, Chris Gordon
* Original Logo : Michael Sersen * Original Logo : Michael Sersen
* tight encoding : Michael Tinglof (Mercuri.ca) * tight encoding : Michael Tinglof (Mercuri.ca)
* pixel format conversion : [Alexander Clouter](http://www.digriz.org.uk/)
* Included libraries: * Included libraries:
* as3crypto : Henri Torgemane (code.google.com/p/as3crypto) * as3crypto : Henri Torgemane (code.google.com/p/as3crypto)

View File

@ -197,7 +197,6 @@ var UI;
UI.initSetting('host', window.location.hostname); UI.initSetting('host', window.location.hostname);
UI.initSetting('port', port); UI.initSetting('port', port);
UI.initSetting('encrypt', (window.location.protocol === "https:")); UI.initSetting('encrypt', (window.location.protocol === "https:"));
UI.initSetting('true_color', true);
UI.initSetting('cursor', !Util.isTouchDevice); UI.initSetting('cursor', !Util.isTouchDevice);
UI.initSetting('resize', 'off'); UI.initSetting('resize', 'off');
UI.initSetting('shared', true); UI.initSetting('shared', true);
@ -451,7 +450,6 @@ var UI;
updateVisualState: function() { updateVisualState: function() {
//Util.Debug(">> updateVisualState"); //Util.Debug(">> updateVisualState");
document.getElementById('noVNC_setting_encrypt').disabled = UI.connected; document.getElementById('noVNC_setting_encrypt').disabled = UI.connected;
document.getElementById('noVNC_setting_true_color').disabled = UI.connected;
if (Util.browserSupportsCursorURIs()) { if (Util.browserSupportsCursorURIs()) {
document.getElementById('noVNC_setting_cursor').disabled = UI.connected; document.getElementById('noVNC_setting_cursor').disabled = UI.connected;
} else { } else {
@ -825,7 +823,6 @@ var UI;
settingsApply: function() { settingsApply: function() {
//Util.Debug(">> settingsApply"); //Util.Debug(">> settingsApply");
UI.saveSetting('encrypt'); UI.saveSetting('encrypt');
UI.saveSetting('true_color');
if (Util.browserSupportsCursorURIs()) { if (Util.browserSupportsCursorURIs()) {
UI.saveSetting('cursor'); UI.saveSetting('cursor');
} }
@ -876,7 +873,6 @@ var UI;
UI.openControlbar(); UI.openControlbar();
UI.updateSetting('encrypt'); UI.updateSetting('encrypt');
UI.updateSetting('true_color');
if (Util.browserSupportsCursorURIs()) { if (Util.browserSupportsCursorURIs()) {
UI.updateSetting('cursor'); UI.updateSetting('cursor');
} else { } else {
@ -1060,7 +1056,6 @@ var UI;
UI.closeConnectPanel(); UI.closeConnectPanel();
UI.rfb.set_encrypt(UI.getSetting('encrypt')); UI.rfb.set_encrypt(UI.getSetting('encrypt'));
UI.rfb.set_true_color(UI.getSetting('true_color'));
UI.rfb.set_local_cursor(UI.getSetting('cursor')); UI.rfb.set_local_cursor(UI.getSetting('cursor'));
UI.rfb.set_shared(UI.getSetting('shared')); UI.rfb.set_shared(UI.getSetting('shared'));
UI.rfb.set_view_only(UI.getSetting('view_only')); UI.rfb.set_view_only(UI.getSetting('view_only'));

View File

@ -456,7 +456,12 @@
// else: No-op -- already done by setSubTile // else: No-op -- already done by setSubTile
}, },
blitImage: function (x, y, width, height, arr, offset, from_queue) { // N.B.: You *cannot* call this during a test in RGBX-order true-color
// mode, or PhantomJS dies with a Uint8ClampeddArray error.
// N.B.: If you know that your data will always be RGB (that is, isRgb==true
// and this._true_color==true), then you should be calling blitRgbxImage()
// instead of blitImage() to avoid the extra branches.
blitImage: function (x, y, width, height, arr, offset, isRgb, from_queue) {
if (this._renderQ.length !== 0 && !from_queue) { if (this._renderQ.length !== 0 && !from_queue) {
// NB(directxman12): it's technically more performant here to use preallocated arrays, // NB(directxman12): it's technically more performant here to use preallocated arrays,
// but it's a lot of extra work for not a lot of payoff -- if we're using the render queue, // but it's a lot of extra work for not a lot of payoff -- if we're using the render queue,
@ -470,9 +475,14 @@
'y': y, 'y': y,
'width': width, 'width': width,
'height': height, 'height': height,
'isRgb': isRgb,
}); });
} else if (this._true_color) { } else if (this._true_color) {
if (isRgb) {
this._rgbxImageData(x, y, width, height, arr, offset);
} else {
this._bgrxImageData(x, y, width, height, arr, offset); this._bgrxImageData(x, y, width, height, arr, offset);
}
} else { } else {
this._cmapImageData(x, y, width, height, arr, offset); this._cmapImageData(x, y, width, height, arr, offset);
} }
@ -501,6 +511,8 @@
} }
}, },
// this is different from the above in that it assumes the data is always rgbx format, instead
// of dealing with the possibility of cmap data
blitRgbxImage: function (x, y, width, height, arr, offset, from_queue) { blitRgbxImage: function (x, y, width, height, arr, offset, from_queue) {
if (this._renderQ.length !== 0 && !from_queue) { if (this._renderQ.length !== 0 && !from_queue) {
// NB(directxman12): it's technically more performant here to use preallocated arrays, // NB(directxman12): it's technically more performant here to use preallocated arrays,
@ -716,10 +728,7 @@
this.fillRect(a.x, a.y, a.width, a.height, a.color, true); this.fillRect(a.x, a.y, a.width, a.height, a.color, true);
break; break;
case 'blit': case 'blit':
this.blitImage(a.x, a.y, a.width, a.height, a.data, 0, true); this.blitImage(a.x, a.y, a.width, a.height, a.data, 0, a.rgb, true);
break;
case 'blitRgb':
this.blitRgbImage(a.x, a.y, a.width, a.height, a.data, 0, true);
break; break;
case 'blitRgbx': case 'blitRgbx':
this.blitRgbxImage(a.x, a.y, a.width, a.height, a.data, 0, true); this.blitRgbxImage(a.x, a.y, a.width, a.height, a.data, 0, true);

View File

@ -88,6 +88,8 @@
this._supportsContinuousUpdates = false; this._supportsContinuousUpdates = false;
this._enabledContinuousUpdates = false; this._enabledContinuousUpdates = false;
this._convert_color = false;
// Frame buffer update state // Frame buffer update state
this._FBU = { this._FBU = {
rects: 0, rects: 0,
@ -105,14 +107,14 @@
zlib: [] // TIGHT zlib streams zlib: [] // TIGHT zlib streams
}; };
this._fb_Bpp = 4; this._pixelFormat = {};
this._fb_depth = 3;
this._fb_width = 0; this._fb_width = 0;
this._fb_height = 0; this._fb_height = 0;
this._fb_name = ""; this._fb_name = "";
this._destBuff = null; this._destBuff = null;
this._paletteBuff = new Uint8Array(1024); // 256 * 4 (max palette size * max bytes-per-pixel) this._paletteRawBuff = new Uint8Array(1024); // 256 * 4 (max palette size * max bytes-per-pixel)
this._paletteConvertedBuff = new Uint8Array(1024); // 256 * 4 (max palette size * rgbx bytes-per-pixel)
this._rre_chunk_sz = 100; this._rre_chunk_sz = 100;
@ -148,7 +150,6 @@
'target': 'null', // VNC display rendering Canvas object 'target': 'null', // VNC display rendering Canvas object
'focusContainer': document, // DOM element that captures keyboard input 'focusContainer': document, // DOM element that captures keyboard input
'encrypt': false, // Use TLS/SSL/wss encryption 'encrypt': false, // Use TLS/SSL/wss encryption
'true_color': true, // Request true color pixel data
'local_cursor': false, // Request locally rendered cursor 'local_cursor': false, // Request locally rendered cursor
'shared': true, // Request shared mode 'shared': true, // Request shared mode
'view_only': false, // Disable client mouse/keyboard 'view_only': false, // Disable client mouse/keyboard
@ -414,6 +415,7 @@
this._mouse_buttonMask = 0; this._mouse_buttonMask = 0;
this._mouse_arr = []; this._mouse_arr = [];
this._rfb_tightvnc = false; this._rfb_tightvnc = false;
this._convert_color = false;
// Clear the per connection encoding stats // Clear the per connection encoding stats
var i; var i;
@ -1021,17 +1023,17 @@
this._destBuff = new Uint8Array(this._fb_width * this._fb_height * 4); this._destBuff = new Uint8Array(this._fb_width * this._fb_height * 4);
/* PIXEL_FORMAT */ /* PIXEL_FORMAT */
var bpp = this._sock.rQshift8(); this._pixelFormat.bpp = this._sock.rQshift8();
var depth = this._sock.rQshift8(); this._pixelFormat.depth = this._sock.rQshift8();
var big_endian = this._sock.rQshift8(); this._pixelFormat.big_endian = (this._sock.rQshift8() !== 0) ? true : false;
var true_color = this._sock.rQshift8(); this._pixelFormat.true_color = (this._sock.rQshift8() !== 0) ? true : false;
var red_max = this._sock.rQshift16(); this._pixelFormat.red_max = this._sock.rQshift16();
var green_max = this._sock.rQshift16(); this._pixelFormat.green_max = this._sock.rQshift16();
var blue_max = this._sock.rQshift16(); this._pixelFormat.blue_max = this._sock.rQshift16();
var red_shift = this._sock.rQshift8(); this._pixelFormat.red_shift = this._sock.rQshift8();
var green_shift = this._sock.rQshift8(); this._pixelFormat.green_shift = this._sock.rQshift8();
var blue_shift = this._sock.rQshift8(); this._pixelFormat.blue_shift = this._sock.rQshift8();
this._sock.rQskipBytes(3); // padding this._sock.rQskipBytes(3); // padding
// NB(directxman12): we don't want to call any callbacks or print messages until // NB(directxman12): we don't want to call any callbacks or print messages until
@ -1069,53 +1071,84 @@
// NB(directxman12): these are down here so that we don't run them multiple times // NB(directxman12): these are down here so that we don't run them multiple times
// if we backtrack // if we backtrack
Util.Info("Screen: " + this._fb_width + "x" + this._fb_height + Util.Info("Screen: " + this._fb_width + "x" + this._fb_height +
", bpp: " + bpp + ", depth: " + depth + ", bpp: " + this._pixelFormat.bpp + ", depth: " + this._pixelFormat.depth +
", big_endian: " + big_endian + ", big_endian: " + this._pixelFormat.big_endian +
", true_color: " + true_color + ", true_color: " + this._pixelFormat.true_color +
", red_max: " + red_max + ", red_max: " + this._pixelFormat.red_max +
", green_max: " + green_max + ", green_max: " + this._pixelFormat.green_max +
", blue_max: " + blue_max + ", blue_max: " + this._pixelFormat.blue_max +
", red_shift: " + red_shift + ", red_shift: " + this._pixelFormat.red_shift +
", green_shift: " + green_shift + ", green_shift: " + this._pixelFormat.green_shift +
", blue_shift: " + blue_shift); ", blue_shift: " + this._pixelFormat.blue_shift);
if (big_endian !== 0) {
Util.Warn("Server native endian is not little endian");
}
if (red_shift !== 16) {
Util.Warn("Server native red-shift is not 16");
}
if (blue_shift !== 0) {
Util.Warn("Server native blue-shift is not 0");
}
// we're past the point where we could backtrack, so it's safe to call this // we're past the point where we could backtrack, so it's safe to call this
this._onDesktopName(this, this._fb_name); this._onDesktopName(this, this._fb_name);
if (this._true_color && this._fb_name === "Intel(r) AMT KVM") { if (this._fb_name === "Intel(r) AMT KVM") {
Util.Warn("Intel AMT KVM only supports 8/16 bit depths. Disabling true color"); Util.Warn("Intel AMT KVM only supports 8/16 bit depths, using server pixel format");
this._true_color = false; this._convert_color = true;
} }
this._display.set_true_color(this._true_color); if (this._convert_color)
this._display.set_true_color(this._pixelFormat.true_color);
this._display.resize(this._fb_width, this._fb_height); this._display.resize(this._fb_width, this._fb_height);
this._onFBResize(this, this._fb_width, this._fb_height); this._onFBResize(this, this._fb_width, this._fb_height);
if (!this._view_only) { this._keyboard.grab(); } if (!this._view_only) { this._keyboard.grab(); }
if (!this._view_only) { this._mouse.grab(); } if (!this._view_only) { this._mouse.grab(); }
if (this._true_color) { // only send if not native, and we think the server will honor the conversion
this._fb_Bpp = 4; if (!this._convert_color) {
this._fb_depth = 3; if (this._pixelFormat.big_endian !== false ||
this._pixelFormat.red_max !== 255 ||
this._pixelFormat.green_max !== 255 ||
this._pixelFormat.blue_max !== 255 ||
this._pixelFormat.red_shift !== 16 ||
this._pixelFormat.green_shift !== 8 ||
this._pixelFormat.blue_shift !== 0 ||
!(this._pixelFormat.bpp === 32 &&
this._pixelFormat.depth === 24 &&
this._pixelFormat.true_color === true) ||
!(this._pixelFormat.bpp === 8 &&
this._pixelFormat.depth === 8 &&
this._pixelFormat.true_color === false)) {
this._pixelFormat.big_endian = false;
this._pixelFormat.red_max = 255;
this._pixelFormat.green_max = 255;
this._pixelFormat.blue_max = 255;
this._pixelFormat.red_shift = 16;
this._pixelFormat.green_shift = 8;
this._pixelFormat.blue_shift = 0;
if (this._pixelFormat.true_color) {
this._pixelFormat.bpp = 32;
this._pixelFormat.depth = 24;
} else { } else {
this._fb_Bpp = 1; this._pixelFormat.bpp = 8;
this._fb_depth = 1; this._pixelFormat.depth = 8;
}
RFB.messages.pixelFormat(this._sock, this._pixelFormat);
} else {
Util.Warn("Server pixel format matches our preferred native, disabling color conversion");
this._convert_color = false;
}
} }
RFB.messages.pixelFormat(this._sock, this._fb_Bpp, this._fb_depth, this._true_color); this._pixelFormat.Bpp = this._pixelFormat.bpp / 8;
RFB.messages.clientEncodings(this._sock, this._encodings, this._local_cursor, this._true_color); this._pixelFormat.Bdepth = Math.ceil(this._pixelFormat.depth / 8);
if (this._pixelFormat.bpp < this._pixelFormat.depth) {
return this._fail('server claims greater depth than bpp');
}
var max_depth = Math.ceil(Math.log(this._pixelFormat.red_max)/Math.LN2) +
Math.ceil(Math.log(this._pixelFormat.green_max)/Math.LN2) +
Math.ceil(Math.log(this._pixelFormat.blue_max)/Math.LN2);
if (this._pixelFormat.true_color && this._pixelFormat.depth > max_depth) {
return this._fail('server claims greater depth than sum of RGB maximums');
}
RFB.messages.clientEncodings(this._sock, this._encodings, this._local_cursor, this._pixelFormat.true_color);
RFB.messages.fbUpdateRequest(this._sock, false, 0, 0, this._fb_width, this._fb_height); RFB.messages.fbUpdateRequest(this._sock, false, 0, 0, this._fb_width, this._fb_height);
this._timing.fbu_rt_start = (new Date()).getTime(); this._timing.fbu_rt_start = (new Date()).getTime();
@ -1437,14 +1470,97 @@
RFB.messages.enableContinuousUpdates(this._sock, true, 0, 0, RFB.messages.enableContinuousUpdates(this._sock, true, 0, 0,
this._fb_width, this._fb_height); this._fb_width, this._fb_height);
},
// like _convert_color, but always outputs bgr, and for only one pixel
_convert_one_color: function (arr, offset, Bpp) {
if (Bpp === undefined) {
Bpp = this._pixelFormat.Bpp;
} }
if (offset === undefined) {
offset = 0;
}
if (!this._convert_color ||
// HACK? Xtightvnc needs this and I have no idea why
(this._FBU.encoding === 0x07 && this._pixelFormat.depth === 24)) {
if (Bpp === 4) {
return [arr[offset + 0], arr[offset + 1], arr[offset + 2], arr[offset + 3]];
} else if (Bpp === 3) {
return [arr[offset + 2], arr[offset + 1], arr[offset + 0]];
} else {
Util.Error('convert color disabled, but Bpp is not 3 or 4!');
}
}
var bgr = new Array(3);
var redMult = 256/(this._pixelFormat.red_max + 1);
var greenMult = 256/(this._pixelFormat.red_max + 1);
var blueMult = 256/(this._pixelFormat.blue_max + 1);
var pix = 0;
for (var k = 0; k < Bpp; k++) {
if (this._pixelFormat.big_endian) {
pix = (pix << 8) | arr[k + offset];
} else {
pix = (arr[k + offset] << (k*8)) | pix;
}
}
bgr[2] = ((pix >>> this._pixelFormat.red_shift) & this._pixelFormat.red_max) * redMult;
bgr[1] = ((pix >>> this._pixelFormat.green_shift) & this._pixelFormat.green_max) * greenMult;
bgr[0] = ((pix >>> this._pixelFormat.blue_shift) & this._pixelFormat.blue_max) * blueMult;
return bgr;
},
// takes a byte stream in the pixel format, and outputs rgbx into the output buffer
_convert_color_and_copy: function (out_arr, in_arr, Bpp) {
if (Bpp === undefined) {
Bpp = this._pixelFormat.Bpp;
}
if (!this._convert_color ||
// HACK? Xtightvnc needs this and I have no idea why
(this._FBU.encoding === 0x07 && this._pixelFormat.depth === 24)) {
if (Bpp !== 4 && Bpp !== 3) {
Util.Error('convert color disabled, but Bpp is not 3 or 4!');
} else {
out_arr.set(in_arr);
return;
}
}
var redMult = 256/(this._pixelFormat.red_max + 1);
var greenMult = 256/(this._pixelFormat.red_max + 1);
var blueMult = 256/(this._pixelFormat.blue_max + 1);
for (var i = 0, j = 0; i < in_arr.length; i += Bpp, j += 4) {
var pix = 0;
for (var k = 0; k < Bpp; k++) {
if (this._pixelFormat.big_endian) {
pix = (pix << 8) | in_arr[i + k];
} else {
pix = (in_arr[i + k] << (k*8)) | pix;
}
}
out_arr[j] = ((pix >>> this._pixelFormat.red_shift) & this._pixelFormat.red_max) * redMult;
out_arr[j + 1] = ((pix >>> this._pixelFormat.green_shift) & this._pixelFormat.green_max) * greenMult;
out_arr[j + 2] = ((pix >>> this._pixelFormat.blue_shift) & this._pixelFormat.blue_max) * blueMult;
out_arr[j + 3] = 255;
}
},
}; };
Util.make_properties(RFB, [ Util.make_properties(RFB, [
['target', 'wo', 'dom'], // VNC display rendering Canvas object ['target', 'wo', 'dom'], // VNC display rendering Canvas object
['focusContainer', 'wo', 'dom'], // DOM element that captures keyboard input ['focusContainer', 'wo', 'dom'], // DOM element that captures keyboard input
['encrypt', 'rw', 'bool'], // Use TLS/SSL/wss encryption ['encrypt', 'rw', 'bool'], // Use TLS/SSL/wss encryption
['true_color', 'rw', 'bool'], // Request true color pixel data ['convert_color', 'rw', 'bool'], // Client will not honor request for native color
['local_cursor', 'rw', 'bool'], // Request locally rendered cursor ['local_cursor', 'rw', 'bool'], // Request locally rendered cursor
['shared', 'rw', 'bool'], // Request shared mode ['shared', 'rw', 'bool'], // Request shared mode
['view_only', 'rw', 'bool'], // Disable client mouse/keyboard ['view_only', 'rw', 'bool'], // Disable client mouse/keyboard
@ -1670,7 +1786,7 @@
sock.flush(); sock.flush();
}, },
pixelFormat: function (sock, bpp, depth, true_color) { pixelFormat: function (sock, pf) {
var buff = sock._sQ; var buff = sock._sQ;
var offset = sock._sQlen; var offset = sock._sQlen;
@ -1680,23 +1796,23 @@
buff[offset + 2] = 0; // padding buff[offset + 2] = 0; // padding
buff[offset + 3] = 0; // padding buff[offset + 3] = 0; // padding
buff[offset + 4] = bpp * 8; // bits-per-pixel buff[offset + 4] = pf.bpp; // bits-per-pixel
buff[offset + 5] = depth * 8; // depth buff[offset + 5] = pf.depth; // depth
buff[offset + 6] = 0; // little-endian buff[offset + 6] = pf.big_endian ? 1 : 0; // big-endian
buff[offset + 7] = true_color ? 1 : 0; // true-color buff[offset + 7] = pf.true_color ? 1 : 0; // true-color
buff[offset + 8] = 0; // red-max buff[offset + 8] = (pf.red_max >> 8) & 0xFF; // red-max
buff[offset + 9] = 255; // red-max buff[offset + 9] = pf.red_max & 0xFF; // red-max
buff[offset + 10] = 0; // green-max buff[offset + 10] = (pf.green_max >> 8) & 0xFF; // green-max
buff[offset + 11] = 255; // green-max buff[offset + 11] = pf.green_max & 0xFF; // green-max
buff[offset + 12] = 0; // blue-max buff[offset + 12] = (pf.blue_max >> 8) & 0xFF; // blue-max
buff[offset + 13] = 255; // blue-max buff[offset + 13] = (pf.blue_max) & 0xFF; // blue-max
buff[offset + 14] = 16; // red-shift buff[offset + 14] = pf.red_shift; // red-shift
buff[offset + 15] = 8; // green-shift buff[offset + 15] = pf.green_shift; // green-shift
buff[offset + 16] = 0; // blue-shift buff[offset + 16] = pf.blue_shift; // blue-shift
buff[offset + 17] = 0; // padding buff[offset + 17] = 0; // padding
buff[offset + 18] = 0; // padding buff[offset + 18] = 0; // padding
@ -1782,19 +1898,21 @@
this._FBU.lines = this._FBU.height; this._FBU.lines = this._FBU.height;
} }
this._FBU.bytes = this._FBU.width * this._fb_Bpp; // at least a line this._FBU.bytes = this._FBU.width * this._pixelFormat.Bpp; // at least a line
if (this._sock.rQwait("RAW", this._FBU.bytes)) { return false; } if (this._sock.rQwait("RAW", this._FBU.bytes)) { return false; }
var cur_y = this._FBU.y + (this._FBU.height - this._FBU.lines); var cur_y = this._FBU.y + (this._FBU.height - this._FBU.lines);
var curr_height = Math.min(this._FBU.lines, var curr_height = Math.min(this._FBU.lines,
Math.floor(this._sock.rQlen() / (this._FBU.width * this._fb_Bpp))); Math.floor(this._sock.rQlen() / (this._FBU.width * this._pixelFormat.Bpp)));
this._display.blitImage(this._FBU.x, cur_y, this._FBU.width,
curr_height, this._sock.get_rQ(), // NB(directxman12): renderQ_push automatically clones the data is we have to push
this._sock.get_rQi()); // to the render queue
this._sock.rQskipBytes(this._FBU.width * curr_height * this._fb_Bpp); this._convert_color_and_copy(this._destBuff, this._sock.rQshiftBytes(curr_height * this._FBU.width * this._pixelFormat.Bpp));
this._display.blitImage(this._FBU.x, cur_y, this._FBU.width, curr_height, this._destBuff, 0, this._convert_color || this._pixelFormat.Bpp === 3, false);
this._FBU.lines -= curr_height; this._FBU.lines -= curr_height;
if (this._FBU.lines > 0) { if (this._FBU.lines > 0) {
this._FBU.bytes = this._FBU.width * this._fb_Bpp; // At least another line this._FBU.bytes = this._FBU.width * this._pixelFormat.Bpp; // At least another line
} else { } else {
this._FBU.rects--; this._FBU.rects--;
this._FBU.bytes = 0; this._FBU.bytes = 0;
@ -1818,15 +1936,15 @@
RRE: function () { RRE: function () {
var color; var color;
if (this._FBU.subrects === 0) { if (this._FBU.subrects === 0) {
this._FBU.bytes = 4 + this._fb_Bpp; this._FBU.bytes = 4 + this._pixelFormat.Bpp;
if (this._sock.rQwait("RRE", 4 + this._fb_Bpp)) { return false; } if (this._sock.rQwait("RRE", 4 + this._pixelFormat.Bpp)) { return false; }
this._FBU.subrects = this._sock.rQshift32(); this._FBU.subrects = this._sock.rQshift32();
color = this._sock.rQshiftBytes(this._fb_Bpp); // Background color = this._convert_one_color(this._sock.rQshiftBytes(this._pixelFormat.Bpp)); // Background
this._display.fillRect(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, color); this._display.fillRect(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, color);
} }
while (this._FBU.subrects > 0 && this._sock.rQlen() >= (this._fb_Bpp + 8)) { while (this._FBU.subrects > 0 && this._sock.rQlen() >= (this._pixelFormat.Bpp + 8)) {
color = this._sock.rQshiftBytes(this._fb_Bpp); color = this._convert_one_color(this._sock.rQshiftBytes(this._pixelFormat.Bpp));
var x = this._sock.rQshift16(); var x = this._sock.rQshift16();
var y = this._sock.rQshift16(); var y = this._sock.rQshift16();
var width = this._sock.rQshift16(); var width = this._sock.rQshift16();
@ -1837,7 +1955,7 @@
if (this._FBU.subrects > 0) { if (this._FBU.subrects > 0) {
var chunk = Math.min(this._rre_chunk_sz, this._FBU.subrects); var chunk = Math.min(this._rre_chunk_sz, this._FBU.subrects);
this._FBU.bytes = (this._fb_Bpp + 8) * chunk; this._FBU.bytes = (this._pixelFormat.Bpp + 8) * chunk;
} else { } else {
this._FBU.rects--; this._FBU.rects--;
this._FBU.bytes = 0; this._FBU.bytes = 0;
@ -1878,20 +1996,20 @@
// Figure out how much we are expecting // Figure out how much we are expecting
if (subencoding & 0x01) { // Raw if (subencoding & 0x01) { // Raw
this._FBU.bytes += w * h * this._fb_Bpp; this._FBU.bytes += w * h * this._pixelFormat.Bpp;
} else { } else {
if (subencoding & 0x02) { // Background if (subencoding & 0x02) { // Background
this._FBU.bytes += this._fb_Bpp; this._FBU.bytes += this._pixelFormat.Bpp;
} }
if (subencoding & 0x04) { // Foreground if (subencoding & 0x04) { // Foreground
this._FBU.bytes += this._fb_Bpp; this._FBU.bytes += this._pixelFormat.Bpp;
} }
if (subencoding & 0x08) { // AnySubrects if (subencoding & 0x08) { // AnySubrects
this._FBU.bytes++; // Since we aren't shifting it off this._FBU.bytes++; // Since we aren't shifting it off
if (this._sock.rQwait("hextile subrects header", this._FBU.bytes)) { return false; } if (this._sock.rQwait("hextile subrects header", this._FBU.bytes)) { return false; }
subrects = rQ[rQi + this._FBU.bytes - 1]; // Peek subrects = rQ[rQi + this._FBU.bytes - 1]; // Peek
if (subencoding & 0x10) { // SubrectsColoured if (subencoding & 0x10) { // SubrectsColoured
this._FBU.bytes += subrects * (this._fb_Bpp + 2); this._FBU.bytes += subrects * (this._pixelFormat.Bpp + 2);
} else { } else {
this._FBU.bytes += subrects * 2; this._FBU.bytes += subrects * 2;
} }
@ -1911,26 +2029,19 @@
this._display.fillRect(x, y, w, h, this._FBU.background); this._display.fillRect(x, y, w, h, this._FBU.background);
} }
} else if (this._FBU.subencoding & 0x01) { // Raw } else if (this._FBU.subencoding & 0x01) { // Raw
this._display.blitImage(x, y, w, h, rQ, rQi); // NB(directxman12): renderQ_push automatically clones the data is we have to push
// to the render queue
this._convert_color_and_copy(this._destBuff, new Uint8Array(rQ.buffer, rQi, this._FBU.bytes - 1));
this._display.blitImage(x, y, w, h, this._destBuff, 0, this._convert_color || this._pixelFormat.Bpp === 3, false);
rQi += this._FBU.bytes - 1; rQi += this._FBU.bytes - 1;
} else { } else {
if (this._FBU.subencoding & 0x02) { // Background if (this._FBU.subencoding & 0x02) { // Background
if (this._fb_Bpp == 1) { this._FBU.background = this._convert_one_color(rQ, rQi);
this._FBU.background = rQ[rQi]; rQi += this._pixelFormat.Bpp;
} else {
// fb_Bpp is 4
this._FBU.background = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]];
}
rQi += this._fb_Bpp;
} }
if (this._FBU.subencoding & 0x04) { // Foreground if (this._FBU.subencoding & 0x04) { // Foreground
if (this._fb_Bpp == 1) { this._FBU.foreground = this._convert_one_color(rQ, rQi);
this._FBU.foreground = rQ[rQi]; rQi += this._pixelFormat.Bpp;
} else {
// this._fb_Bpp is 4
this._FBU.foreground = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]];
}
rQi += this._fb_Bpp;
} }
this._display.startTile(x, y, w, h, this._FBU.background); this._display.startTile(x, y, w, h, this._FBU.background);
@ -1941,13 +2052,8 @@
for (var s = 0; s < subrects; s++) { for (var s = 0; s < subrects; s++) {
var color; var color;
if (this._FBU.subencoding & 0x10) { // SubrectsColoured if (this._FBU.subencoding & 0x10) { // SubrectsColoured
if (this._fb_Bpp === 1) { color = this._convert_one_color(rQ, rQi);
color = rQ[rQi]; rQi += this._pixelFormat.Bpp;
} else {
// _fb_Bpp is 4
color = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]];
}
rQi += this._fb_Bpp;
} else { } else {
color = this._FBU.foreground; color = this._FBU.foreground;
} }
@ -1994,10 +2100,8 @@
}, },
display_tight: function (isTightPNG) { display_tight: function (isTightPNG) {
if (this._fb_depth === 1) { if (this._pixelFormat.Bdepth === 1) {
this._fail("Internal error", this._fail("Internal error", "Tight protocol handler only implements true color mode");
"Tight protocol handler only implements " +
"true color mode");
} }
this._FBU.bytes = 1; // compression-control byte this._FBU.bytes = 1; // compression-control byte
@ -2115,7 +2219,7 @@
var handlePalette = function () { var handlePalette = function () {
var numColors = rQ[rQi + 2] + 1; var numColors = rQ[rQi + 2] + 1;
var paletteSize = numColors * this._fb_depth; var paletteSize = numColors * this._pixelFormat.Bdepth;
this._FBU.bytes += paletteSize; this._FBU.bytes += paletteSize;
if (this._sock.rQwait("TIGHT palette " + cmode, this._FBU.bytes)) { return false; } if (this._sock.rQwait("TIGHT palette " + cmode, this._FBU.bytes)) { return false; }
@ -2149,8 +2253,8 @@
// Shift ctl, filter id, num colors, palette entries, and clength off // Shift ctl, filter id, num colors, palette entries, and clength off
this._sock.rQskipBytes(3); this._sock.rQskipBytes(3);
//var palette = this._sock.rQshiftBytes(paletteSize); this._sock.rQshiftTo(this._paletteRawBuff, paletteSize);
this._sock.rQshiftTo(this._paletteBuff, paletteSize); this._convert_color_and_copy(this._paletteConvertedBuff, this._paletteRawBuff, this._pixelFormat.Bdepth);
this._sock.rQskipBytes(cl_header); this._sock.rQskipBytes(cl_header);
if (raw) { if (raw) {
@ -2162,10 +2266,10 @@
// Convert indexed (palette based) image data to RGB // Convert indexed (palette based) image data to RGB
var rgbx; var rgbx;
if (numColors == 2) { if (numColors == 2) {
rgbx = indexedToRGBX2Color(data, this._paletteBuff, this._FBU.width, this._FBU.height); rgbx = indexedToRGBX2Color(data, this._paletteConvertedBuff, this._FBU.width, this._FBU.height);
this._display.blitRgbxImage(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, rgbx, 0, false); this._display.blitRgbxImage(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, rgbx, 0, false);
} else { } else {
rgbx = indexedToRGBX(data, this._paletteBuff, this._FBU.width, this._FBU.height); rgbx = indexedToRGBX(data, this._paletteConvertedBuff, this._FBU.width, this._FBU.height);
this._display.blitRgbxImage(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, rgbx, 0, false); this._display.blitRgbxImage(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, rgbx, 0, false);
} }
@ -2175,7 +2279,7 @@
var handleCopy = function () { var handleCopy = function () {
var raw = false; var raw = false;
var uncompressedSize = this._FBU.width * this._FBU.height * this._fb_depth; var uncompressedSize = this._FBU.width * this._FBU.height * this._pixelFormat.Bdepth;
if (uncompressedSize < 12) { if (uncompressedSize < 12) {
raw = true; raw = true;
cl_header = 0; cl_header = 0;
@ -2208,7 +2312,8 @@
data = decompress(this._sock.rQshiftBytes(cl_data), uncompressedSize); 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); this._convert_color_and_copy(this._destBuff, data, this._pixelFormat.Bdepth);
this._display.blitImage(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, this._destBuff, 0, this._convert_color || this._pixelFormat.Bpp === 3, false);
return true; return true;
}.bind(this); }.bind(this);
@ -2239,7 +2344,7 @@
switch (cmode) { switch (cmode) {
// fill use fb_depth because TPIXELs drop the padding byte // fill use fb_depth because TPIXELs drop the padding byte
case "fill": // TPIXEL case "fill": // TPIXEL
this._FBU.bytes += this._fb_depth; this._FBU.bytes += this._pixelFormat.Bdepth;
break; break;
case "jpeg": // max clength case "jpeg": // max clength
this._FBU.bytes += 3; this._FBU.bytes += 3;
@ -2260,8 +2365,9 @@
switch (cmode) { switch (cmode) {
case "fill": case "fill":
// skip ctl byte // 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); var color = this._convert_one_color(rQ, rQi + 1, this._pixelFormat.Bdepth);
this._sock.rQskipBytes(4); this._display.fillRect(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, color, false);
this._sock.rQskipBytes(this._pixelFormat.Bdepth + 1);
break; break;
case "png": case "png":
case "jpeg": case "jpeg":
@ -2407,7 +2513,7 @@
var w = this._FBU.width; var w = this._FBU.width;
var h = this._FBU.height; var h = this._FBU.height;
var pixelslength = w * h * this._fb_Bpp; var pixelslength = w * h * this._pixelFormat.Bpp;
var masklength = Math.floor((w + 7) / 8) * h; var masklength = Math.floor((w + 7) / 8) * h;
this._FBU.bytes = pixelslength + masklength; this._FBU.bytes = pixelslength + masklength;

View File

@ -392,19 +392,14 @@ describe('Display/Canvas Helper', function () {
data[i * 4 + 2] = checked_data[i * 4]; data[i * 4 + 2] = checked_data[i * 4];
data[i * 4 + 3] = checked_data[i * 4 + 3]; data[i * 4 + 3] = checked_data[i * 4 + 3];
} }
display.blitImage(0, 0, 4, 4, data, 0); display.blitImage(0, 0, 4, 4, data, 0);
display.flip(); display.flip();
expect(display).to.have.displayed(checked_data); expect(display).to.have.displayed(checked_data);
}); });
it('should support drawing RGB blit images with true color via #blitRgbImage', function () { it('should support drawing RGBX blit images with true color via #blitImage', function () {
var data = []; display.blitImage(0, 0, 4, 4, checked_data, 0, true);
for (var i = 0; i < 16; i++) {
data[i * 3] = checked_data[i * 4];
data[i * 3 + 1] = checked_data[i * 4 + 1];
data[i * 3 + 2] = checked_data[i * 4 + 2];
}
display.blitRgbImage(0, 0, 4, 4, data, 0);
display.flip(); display.flip();
expect(display).to.have.displayed(checked_data); expect(display).to.have.displayed(checked_data);
}); });
@ -490,18 +485,19 @@ describe('Display/Canvas Helper', function () {
expect(display.get_onFlush()).to.have.been.calledOnce; expect(display.get_onFlush()).to.have.been.calledOnce;
}); });
it('should draw a blit image on type "blit"', function () { it('should draw a blit image on type "blit" with "rgb" set to false', function () {
display.blitImage = sinon.spy(); display.blitImage = sinon.spy();
display._renderQ_push({ type: 'blit', x: 3, y: 4, width: 5, height: 6, data: [7, 8, 9] }); display._renderQ_push({ type: 'blit', x: 3, y: 4, width: 5, height: 6, data: [7, 8, 9], rgb: false });
expect(display.blitImage).to.have.been.calledOnce; expect(display.blitImage).to.have.been.calledOnce;
expect(display.blitImage).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9], 0); expect(display.blitImage).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9], 0, false);
}); });
it('should draw a blit RGB image on type "blitRgb"', function () {
display.blitRgbImage = sinon.spy(); it('should draw a blit RGBX image on type "blit" with "rgb" set to true', function () {
display._renderQ_push({ type: 'blitRgb', x: 3, y: 4, width: 5, height: 6, data: [7, 8, 9] }); display.blitImage = sinon.spy();
expect(display.blitRgbImage).to.have.been.calledOnce; display._renderQ_push({ type: 'blit', x: 3, y: 4, width: 5, height: 6, data: [7, 8, 9, 10], rgb: true });
expect(display.blitRgbImage).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9], 0); expect(display.blitImage).to.have.been.calledOnce;
expect(display.blitImage).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9, 10], 0, true);
}); });
it('should copy a region on type "copy"', function () { it('should copy a region on type "copy"', function () {

View File

@ -1103,8 +1103,6 @@ describe('Remote Frame Buffer Protocol Client', function() {
expect(client._fb_height).to.equal(84); expect(client._fb_height).to.equal(84);
}); });
// NB(sross): we just warn, not fail, for endian-ness and shifts, so we don't test them
it('should set the framebuffer name and call the callback', function () { it('should set the framebuffer name and call the callback', function () {
client.set_onDesktopName(sinon.spy()); client.set_onDesktopName(sinon.spy());
send_server_init({ name: 'some name' }, client); send_server_init({ name: 'some name' }, client);
@ -1134,14 +1132,6 @@ describe('Remote Frame Buffer Protocol Client', function() {
expect(client._rfb_connection_state).to.equal('connected'); expect(client._rfb_connection_state).to.equal('connected');
}); });
it('should set the true color mode on the display to the configuration variable', function () {
client.set_true_color(false);
sinon.spy(client._display, 'set_true_color');
send_server_init({ true_color: 1 }, client);
expect(client._display.set_true_color).to.have.been.calledOnce;
expect(client._display.set_true_color).to.have.been.calledWith(false);
});
it('should call the resize callback and resize the display', function () { it('should call the resize callback and resize the display', function () {
client.set_onFBResize(sinon.spy()); client.set_onFBResize(sinon.spy());
sinon.spy(client._display, 'resize'); sinon.spy(client._display, 'resize');
@ -1163,29 +1153,35 @@ describe('Remote Frame Buffer Protocol Client', function() {
expect(client._mouse.grab).to.have.been.calledOnce; expect(client._mouse.grab).to.have.been.calledOnce;
}); });
it('should set the BPP and depth to 4 and 3 respectively if in true color mode', function () { it('should set the BPP and depth to 4 and 3 respectively if server can send native (true color)', function () {
client.set_true_color(true); send_server_init({ true_color: 1, bpp: 8, depth: 8 }, client);
send_server_init({}, client); expect(client._pixelFormat.Bpp).to.equal(4);
expect(client._fb_Bpp).to.equal(4); expect(client._pixelFormat.Bdepth).to.equal(3);
expect(client._fb_depth).to.equal(3);
}); });
it('should set the BPP and depth to 1 and 1 respectively if not in true color mode', function () { it('should set the BPP and depth to 2 and 2 respectively if server cannot send native (true color)', function () {
client.set_true_color(false); client.set_convert_color(true);
send_server_init({}, client); send_server_init({ true_color: 1, bpp: 16, depth: 15 }, client);
expect(client._fb_Bpp).to.equal(1); expect(client._pixelFormat.Bpp).to.equal(2);
expect(client._fb_depth).to.equal(1); expect(client._pixelFormat.Bdepth).to.equal(2);
});
it('should set the BPP and depth to 1 and 1 respectively if server cannot send native (not true color)', function () {
client.set_convert_color(true);
send_server_init({ true_color: 0, bpp: 8, depth: 8 }, client);
expect(client._pixelFormat.Bpp).to.equal(1);
expect(client._pixelFormat.Bdepth).to.equal(1);
}); });
// TODO(directxman12): test the various options in this configuration matrix // TODO(directxman12): test the various options in this configuration matrix
it('should reply with the pixel format, client encodings, and initial update request', function () { it('should reply with the pixel format, client encodings, and initial update request', function () {
client.set_true_color(true);
client.set_local_cursor(false); client.set_local_cursor(false);
// we skip the cursor encoding // we skip the cursor encoding
var expected = {_sQ: new Uint8Array(34 + 4 * (client._encodings.length - 1)), var expected = {_sQ: new Uint8Array(34 + 4 * (client._encodings.length - 1)),
_sQlen: 0, _sQlen: 0,
flush: function () {}}; flush: function () {}};
RFB.messages.pixelFormat(expected, 4, 3, true); var pf = { bpp: 32, depth: 24, big_endian: false, true_color: true, red_max: 255, green_max: 255, blue_max: 255, red_shift: 16, green_shift: 8, blue_shift: 0 };
RFB.messages.pixelFormat(expected, pf);
RFB.messages.clientEncodings(expected, client._encodings, false, true); RFB.messages.clientEncodings(expected, client._encodings, false, true);
RFB.messages.fbUpdateRequest(expected, false, 0, 0, 27, 32); RFB.messages.fbUpdateRequest(expected, false, 0, 0, 27, 32);
@ -1197,6 +1193,7 @@ describe('Remote Frame Buffer Protocol Client', function() {
send_server_init({}, client); send_server_init({}, client);
expect(client._rfb_connection_state).to.equal('connected'); expect(client._rfb_connection_state).to.equal('connected');
}); });
}); });
}); });
@ -1224,13 +1221,14 @@ describe('Remote Frame Buffer Protocol Client', function() {
client._fb_name = 'some device'; client._fb_name = 'some device';
client._fb_width = 640; client._fb_width = 640;
client._fb_height = 20; client._fb_height = 20;
client._pixelFormat.Bpp = 4;
}); });
var target_data_arr = [ var target_data_arr = [
0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0xf8, 0x00, 0x00, 255, 0x00, 0xf8, 0x00, 255, 0x00, 0x00, 0xf8, 255, 0x00, 0x00, 0xf8, 255,
0x00, 0xff, 0x00, 255, 0xff, 0x00, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xf8, 0x00, 255, 0xf8, 0x00, 0x00, 255, 0x00, 0x00, 0xf8, 255, 0x00, 0x00, 0xf8, 255,
0xee, 0x00, 0xff, 255, 0x00, 0xee, 0xff, 255, 0xaa, 0xee, 0xff, 255, 0xab, 0xee, 0xff, 255, 0xe8, 0x00, 0xf8, 255, 0x00, 0xe8, 0xf8, 255, 0xa8, 0xe8, 0xf8, 255, 0xa8, 0xe8, 0xf8, 255,
0xee, 0x00, 0xff, 255, 0x00, 0xee, 0xff, 255, 0xaa, 0xee, 0xff, 255, 0xab, 0xee, 0xff, 255 0xe8, 0x00, 0xf8, 255, 0x00, 0xe8, 0xf8, 255, 0xa8, 0xe8, 0xf8, 255, 0xa8, 0xe8, 0xf8, 255
]; ];
var target_data; var target_data;
@ -1379,24 +1377,98 @@ describe('Remote Frame Buffer Protocol Client', function() {
client._fb_width = 4; client._fb_width = 4;
client._fb_height = 4; client._fb_height = 4;
client._display.resize(4, 4); client._display.resize(4, 4);
client._fb_Bpp = 4; client._pixelFormat.Bpp = 4;
client._destBuff = new Uint8Array(client._fb_width * client._fb_height * 4);
}); });
it('should handle the RAW encoding', function () { // warning: the fbupdates *overlap* so you have to send all rects for the numbers
// to even make sense; this means (ironically) no iterative building of your tests
describe('should handle the RAW encoding', function () {
it('should handle 24bit depth (RGBX888) @ 32bpp [native]', function () {
client._convert_color = true;
client._pixelFormat.big_endian = false;
client._pixelFormat.red_shift = 0;
client._pixelFormat.red_max = 255;
client._pixelFormat.green_shift = 8;
client._pixelFormat.green_max = 255;
client._pixelFormat.blue_shift = 16;
client._pixelFormat.blue_max = 255;
var info = [{ x: 0, y: 0, width: 2, height: 2, encoding: 0x00 }, var info = [{ x: 0, y: 0, width: 2, height: 2, encoding: 0x00 },
{ x: 2, y: 0, width: 2, height: 2, encoding: 0x00 }, { x: 2, y: 0, width: 2, height: 2, encoding: 0x00 },
{ x: 0, y: 2, width: 4, height: 1, encoding: 0x00 }, { x: 0, y: 2, width: 4, height: 1, encoding: 0x00 },
{ x: 0, y: 3, width: 4, height: 1, encoding: 0x00 }]; { x: 0, y: 3, width: 4, height: 1, encoding: 0x00 }];
// data is in bgrx
var rects = [ var rects = [
[0x00, 0x00, 0xff, 0, 0x00, 0xff, 0x00, 0, 0x00, 0xff, 0x00, 0, 0x00, 0x00, 0xff, 0], [0xf8, 0x00, 0x00, 0, 0x00, 0xf8, 0x00, 0, 0x00, 0xf8, 0x00, 0, 0xf8, 0x00, 0x00, 0],
[0xff, 0x00, 0x00, 0, 0xff, 0x00, 0x00, 0, 0xff, 0x00, 0x00, 0, 0xff, 0x00, 0x00, 0], [0x00, 0x00, 0xf8, 0, 0x00, 0x00, 0xf8, 0, 0x00, 0x00, 0xf8, 0, 0x00, 0x00, 0xf8, 0],
[0xff, 0x00, 0xee, 0, 0xff, 0xee, 0x00, 0, 0xff, 0xee, 0xaa, 0, 0xff, 0xee, 0xab, 0], [0xe8, 0x00, 0xf8, 0, 0x00, 0xe8, 0xf8, 0, 0xa8, 0xe8, 0xf8, 0, 0xa8, 0xe8, 0xf8, 0],
[0xff, 0x00, 0xee, 0, 0xff, 0xee, 0x00, 0, 0xff, 0xee, 0xaa, 0, 0xff, 0xee, 0xab, 0]]; [0xe8, 0x00, 0xf8, 0, 0x00, 0xe8, 0xf8, 0, 0xa8, 0xe8, 0xf8, 0, 0xa8, 0xe8, 0xf8, 0]];
send_fbu_msg(info, rects, client); send_fbu_msg(info, rects, client);
expect(client._display).to.have.displayed(target_data); expect(client._display).to.have.displayed(target_data);
}); });
it('should handle 24bit depth (BGRX888) @ 32bpp', function () {
var info = [{ x: 0, y: 0, width: 2, height: 2, encoding: 0x00 },
{ x: 2, y: 0, width: 2, height: 2, encoding: 0x00 },
{ x: 0, y: 2, width: 4, height: 1, encoding: 0x00 },
{ x: 0, y: 3, width: 4, height: 1, encoding: 0x00 }];
var rects = [
[0x00, 0x00, 0xf8, 0, 0x00, 0xf8, 0x00, 0, 0x00, 0xf8, 0x00, 0, 0x00, 0x00, 0xf8, 0],
[0xf8, 0x00, 0x00, 0, 0xf8, 0x00, 0x00, 0, 0xf8, 0x00, 0x00, 0, 0xf8, 0x00, 0x00, 0],
[0xf8, 0x00, 0xe8, 0, 0xf8, 0xe8, 0x00, 0, 0xf8, 0xe8, 0xa8, 0, 0xf8, 0xe8, 0xa8, 0],
[0xf8, 0x00, 0xe8, 0, 0xf8, 0xe8, 0x00, 0, 0xf8, 0xe8, 0xa8, 0, 0xf8, 0xe8, 0xa8, 0]];
send_fbu_msg(info, rects, client);
expect(client._display).to.have.displayed(target_data);
});
// for wisdom: perl -e '($w, $r, $g, $b) = @ARGV; $W=2**$w; $nb = $b*($W/256); $ng = $g*($W/256); $nr = $r*($W/256); printf "%f:%f:%f %04x\n", $nr, $ng, $nb, unpack("S", pack("n", ($nr << (2*$w)) | ($ng << (1*$w)) | ($nb << (0*$w))))' 5 0 248 0
it('should handle 15bit depth (BGR555) @ 16bpp', function () {
client._convert_color = true;
client._pixelFormat.big_endian = false;
client._pixelFormat.Bpp = 2;
client._pixelFormat.red_shift = 10;
client._pixelFormat.red_max = 31;
client._pixelFormat.green_shift = 5;
client._pixelFormat.green_max = 31;
client._pixelFormat.blue_shift = 0;
client._pixelFormat.blue_max = 31;
var info = [{ x: 0, y: 0, width: 2, height: 2, encoding: 0x00 },
{ x: 2, y: 0, width: 2, height: 2, encoding: 0x00 },
{ x: 0, y: 2, width: 4, height: 1, encoding: 0x00 },
{ x: 0, y: 3, width: 4, height: 1, encoding: 0x00 }];
var rects = [
[0x00, 0x7c, 0xe0, 0x03, 0xe0, 0x03, 0x00, 0x7c],
[0x1f, 0x00, 0x1f, 0x00, 0x1f, 0x00, 0x1f, 0x00],
[0x1f, 0x74, 0xbf, 0x03, 0xbf, 0x57, 0xbf, 0x57],
[0x1f, 0x74, 0xbf, 0x03, 0xbf, 0x57, 0xbf, 0x57]];
send_fbu_msg(info, rects, client);
expect(client._display).to.have.displayed(target_data);
});
it('should handle 15bit depth (BGR555) @ 16bpp big-endian', function () {
client._convert_color = true;
client._pixelFormat.big_endian = false;
client._pixelFormat.Bpp = 2;
client._pixelFormat.big_endian = true;
client._pixelFormat.red_shift = 10;
client._pixelFormat.red_max = 31;
client._pixelFormat.green_shift = 5;
client._pixelFormat.green_max = 31;
client._pixelFormat.blue_shift = 0;
client._pixelFormat.blue_max = 31;
var info = [{ x: 0, y: 0, width: 2, height: 2, encoding: 0x00 },
{ x: 2, y: 0, width: 2, height: 2, encoding: 0x00 },
{ x: 0, y: 2, width: 4, height: 1, encoding: 0x00 },
{ x: 0, y: 3, width: 4, height: 1, encoding: 0x00 }];
var rects = [
[0x7c, 0x00, 0x03, 0xe0, 0x03, 0xe0, 0x7c, 0x00],
[0x00, 0x1f, 0x00, 0x1f, 0x00, 0x1f, 0x00, 0x1f],
[0x74, 0x1f, 0x03, 0xbf, 0x57, 0xbf, 0x57, 0xbf],
[0x74, 0x1f, 0x03, 0xbf, 0x57, 0xbf, 0x57, 0xbf]];
send_fbu_msg(info, rects, client);
expect(client._display).to.have.displayed(target_data);
});
});
it('should handle the COPYRECT encoding', function () { it('should handle the COPYRECT encoding', function () {
// seed some initial data to copy // seed some initial data to copy
client._display.blitRgbxImage(0, 0, 4, 2, new Uint8Array(target_data_check_arr.slice(0, 32)), 0); client._display.blitRgbxImage(0, 0, 4, 2, new Uint8Array(target_data_check_arr.slice(0, 32)), 0);
@ -1426,7 +1498,7 @@ describe('Remote Frame Buffer Protocol Client', function() {
push16(rect, 2); // width: 2 push16(rect, 2); // width: 2
push16(rect, 2); // height: 2 push16(rect, 2); // height: 2
rect.push(0xff); // becomes ff0000ff --> #0000FF color rect.push(0xff); // becomes ff0000ff --> #0000FF color
rect.push(0x00); rect.push(0x00); // becomes 0000ffff --> #0000FF color
rect.push(0x00); rect.push(0x00);
rect.push(0xff); rect.push(0xff);
push16(rect, 2); // x: 2 push16(rect, 2); // x: 2
@ -1450,7 +1522,8 @@ describe('Remote Frame Buffer Protocol Client', function() {
client._fb_width = 4; client._fb_width = 4;
client._fb_height = 4; client._fb_height = 4;
client._display.resize(4, 4); client._display.resize(4, 4);
client._fb_Bpp = 4; client._pixelFormat.Bpp = 4;
client._destBuff = new Uint8Array(client._fb_width * client._fb_height * 4);
}); });
it('should handle a tile with fg, bg specified, normal subrects', function () { it('should handle a tile with fg, bg specified, normal subrects', function () {

View File

@ -11,9 +11,9 @@
This file is licensed under the 2-Clause BSD license (see LICENSE.txt). This file is licensed under the 2-Clause BSD license (see LICENSE.txt).
Connect parameters are provided in query string: Connect parameters are provided in query string:
http://example.com/?host=HOST&port=PORT&encrypt=1&true_color=1 http://example.com/?host=HOST&port=PORT&encrypt=1
or the fragment: or the fragment:
http://example.com/#host=HOST&port=PORT&encrypt=1&true_color=1 http://example.com/#host=HOST&port=PORT&encrypt=1
--> -->
<title>noVNC</title> <title>noVNC</title>
@ -199,9 +199,6 @@
<li> <li>
<div class="noVNC_expander">Advanced</div> <div class="noVNC_expander">Advanced</div>
<div><ul> <div><ul>
<li>
<label><input id="noVNC_setting_true_color" type="checkbox" checked /> True Color</label>
</li>
<li> <li>
<label><input id="noVNC_setting_cursor" type="checkbox" /> Local Cursor</label> <label><input id="noVNC_setting_cursor" type="checkbox" /> Local Cursor</label>
</li> </li>

View File

@ -10,9 +10,9 @@
This file is licensed under the 2-Clause BSD license (see LICENSE.txt). This file is licensed under the 2-Clause BSD license (see LICENSE.txt).
Connect parameters are provided in query string: Connect parameters are provided in query string:
http://example.com/?host=HOST&port=PORT&encrypt=1&true_color=1 http://example.com/?host=HOST&port=PORT&encrypt=1
or the fragment: or the fragment:
http://example.com/#host=HOST&port=PORT&encrypt=1&true_color=1 http://example.com/#host=HOST&port=PORT&encrypt=1
--> -->
<title>noVNC</title> <title>noVNC</title>
@ -261,7 +261,6 @@
'encrypt': WebUtil.getConfigVar('encrypt', 'encrypt': WebUtil.getConfigVar('encrypt',
(window.location.protocol === "https:")), (window.location.protocol === "https:")),
'repeaterID': WebUtil.getConfigVar('repeaterID', ''), 'repeaterID': WebUtil.getConfigVar('repeaterID', ''),
'true_color': WebUtil.getConfigVar('true_color', true),
'local_cursor': WebUtil.getConfigVar('cursor', true), 'local_cursor': WebUtil.getConfigVar('cursor', true),
'shared': WebUtil.getConfigVar('shared', true), 'shared': WebUtil.getConfigVar('shared', true),
'view_only': WebUtil.getConfigVar('view_only', false), 'view_only': WebUtil.getConfigVar('view_only', false),
@ -276,7 +275,6 @@
status('Unable to create RFB client -- ' + exc, 'error'); status('Unable to create RFB client -- ' + exc, 'error');
return; // don't continue trying to connect return; // don't continue trying to connect
} }
rfb.connect(host, port, password, path); rfb.connect(host, port, password, path);
}; };
</script> </script>