Commit 729508a2 authored by Alexander Clouter's avatar Alexander Clouter Committed by Solly Ross

added client side pixel format conversion support

parent 24ed0b45
......@@ -128,6 +128,7 @@ use a WebSockets to TCP socket proxy. There is a python proxy included
* UI and Icons : Chris Gordon
* Original Logo : Michael Sersen
* tight encoding : Michael Tinglof (Mercuri.ca)
* pixel format conversion : [Alexander Clouter](http://www.digriz.org.uk/)
* Included libraries:
* web-socket-js : Hiroshi Ichikawa (github.com/gimite/web-socket-js)
......
......@@ -412,19 +412,14 @@ var Display;
// else: No-op -- already done by setSubTile
},
blitImage: function (x, y, width, height, arr, offset) {
blitImage: function (x, y, width, height, arr, offset, isRgb) {
if (this._true_color) {
this._bgrxImageData(x, y, this._viewportLoc.x, this._viewportLoc.y, width, height, arr, offset);
} else {
this._cmapImageData(x, y, this._viewportLoc.x, this._viewportLoc.y, width, height, arr, offset);
}
},
blitRgbImage: function (x, y , width, height, arr, offset) {
if (this._true_color) {
this._rgbImageData(x, y, this._viewportLoc.x, this._viewportLoc.y, width, height, arr, offset);
if (isRgb) {
this._rgbImageData(x, y, this._viewportLoc.x, this._viewportLoc.y, width, height, arr, offset);
} else {
this._bgrxImageData(x, y, this._viewportLoc.x, this._viewportLoc.y, width, height, arr, offset);
}
} else {
// probably wrong?
this._cmapImageData(x, y, this._viewportLoc.x, this._viewportLoc.y, width, height, arr, offset);
}
},
......@@ -592,10 +587,7 @@ var Display;
this.fillRect(a.x, a.y, a.width, a.height, a.color);
break;
case 'blit':
this.blitImage(a.x, a.y, a.width, a.height, a.data, 0);
break;
case 'blitRgb':
this.blitRgbImage(a.x, a.y, a.width, a.height, a.data, 0);
this.blitImage(a.x, a.y, a.width, a.height, a.data, 0, a.rgb);
break;
case 'img':
if (a.img.complete) {
......
......@@ -85,8 +85,7 @@ var RFB;
zlib: [] // TIGHT zlib streams
};
this._fb_Bpp = 4;
this._fb_depth = 3;
this._pixelFormat = {};
this._fb_width = 0;
this._fb_height = 0;
this._fb_name = "";
......@@ -117,7 +116,7 @@ var RFB;
'target': 'null', // VNC display rendering Canvas object
'focusContainer': document, // DOM element that captures keyboard input
'encrypt': false, // Use TLS/SSL/wss encryption
'true_color': true, // Request true color pixel data
'convertColor': false, // Client will not honor request for native color
'local_cursor': false, // Request locally rendered cursor
'shared': true, // Request shared mode
'view_only': false, // Disable client mouse/keyboard
......@@ -854,21 +853,21 @@ var RFB;
if (this._sock.rQwait("server initialization", 24)) { return false; }
/* Screen size */
this._fb_width = this._sock.rQshift16();
this._fb_height = this._sock.rQshift16();
this._fb_width = this._sock.rQshift16();
this._fb_height = this._sock.rQshift16();
/* PIXEL_FORMAT */
var bpp = this._sock.rQshift8();
var depth = this._sock.rQshift8();
var big_endian = this._sock.rQshift8();
var true_color = this._sock.rQshift8();
var red_max = this._sock.rQshift16();
var green_max = this._sock.rQshift16();
var blue_max = this._sock.rQshift16();
var red_shift = this._sock.rQshift8();
var green_shift = this._sock.rQshift8();
var blue_shift = this._sock.rQshift8();
this._pixelFormat.bpp = this._sock.rQshift8();
this._pixelFormat.depth = this._sock.rQshift8();
this._pixelFormat.big_endian = (this._sock.rQshift8() === 1) ? true : false;
this._pixelFormat.true_color = (this._sock.rQshift8() === 1) ? true : false;
this._pixelFormat.red_max = this._sock.rQshift16();
this._pixelFormat.green_max = this._sock.rQshift16();
this._pixelFormat.blue_max = this._sock.rQshift16();
this._pixelFormat.red_shift = this._sock.rQshift8();
this._pixelFormat.green_shift = this._sock.rQshift8();
this._pixelFormat.blue_shift = this._sock.rQshift8();
this._sock.rQskipBytes(3); // padding
// NB(directxman12): we don't want to call any callbacks or print messages until
......@@ -907,56 +906,90 @@ var RFB;
// NB(directxman12): these are down here so that we don't run them multiple times
// if we backtrack
Util.Info("Screen: " + this._fb_width + "x" + this._fb_height +
", bpp: " + bpp + ", depth: " + depth +
", big_endian: " + big_endian +
", true_color: " + true_color +
", red_max: " + red_max +
", green_max: " + green_max +
", blue_max: " + blue_max +
", red_shift: " + red_shift +
", green_shift: " + green_shift +
", blue_shift: " + 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");
}
", bpp: " + this._pixelFormat.bpp + ", depth: " + this._pixelFormat.depth +
", big_endian: " + this._pixelFormat.big_endian +
", true_color: " + this._pixelFormat.true_color +
", red_max: " + this._pixelFormat.red_max +
", green_max: " + this._pixelFormat.green_max +
", blue_max: " + this._pixelFormat.blue_max +
", red_shift: " + this._pixelFormat.red_shift +
", green_shift: " + this._pixelFormat.green_shift +
", blue_shift: " + this._pixelFormat.blue_shift);
// we're past the point where we could backtrack, so it's safe to call this
this._onDesktopName(this, this._fb_name);
if (this._true_color && this._fb_name === "Intel(r) AMT KVM") {
Util.Warn("Intel AMT KVM only supports 8/16 bit depths. Disabling true color");
this._true_color = false;
if (this._fb_name === "Intel(r) AMT KVM") {
Util.Warn("Intel AMT KVM only supports 8/16 bit depths, using server pixel format");
this._convertColor = true;
}
this._display.set_true_color(this._true_color);
if (this._convertColor)
this._display.set_true_color(this._pixelFormat.true_color);
this._onFBResize(this, this._fb_width, this._fb_height);
this._display.resize(this._fb_width, this._fb_height);
this._keyboard.grab();
this._mouse.grab();
if (this._true_color) {
this._fb_Bpp = 4;
this._fb_depth = 3;
} else {
this._fb_Bpp = 1;
this._fb_depth = 1;
var response = [];
// only send if not native, and we think the server will honor the conversion
if (!this._convertColor) {
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 {
this._pixelFormat.bpp = 8;
this._pixelFormat.depth = 8;
}
response = response.concat(RFB.messages.pixelFormat(this._pixelFormat));
} else {
Util.Warn("Server pixel format matches our preferred native, disabling color conversion");
this._convertColor = false;
}
}
var response = RFB.messages.pixelFormat(this._fb_Bpp, this._fb_depth, this._true_color);
response = response.concat(
RFB.messages.clientEncodings(this._encodings, this._local_cursor, this._true_color));
response = response.concat(
RFB.messages.fbUpdateRequests(this._display.getCleanDirtyReset(),
this._fb_width, this._fb_height));
this._pixelFormat.Bpp = this._pixelFormat.bpp / 8;
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');
}
response = response.concat(RFB.messages.clientEncodings(
this._encodings, this._local_cursor,
this._pixelFormat.true_color));
response = response.concat(RFB.messages.fbUpdateRequests(
this._display.getCleanDirtyReset(),
this._fb_width, this._fb_height));
this._timing.fbu_rt_start = (new Date()).getTime();
this._timing.pixels = 0;
......@@ -1184,13 +1217,95 @@ var RFB;
return true; // We finished this FBU
},
// like _convert_color, but always outputs bgr, and for only one pixel
_convert_one_color: function (arr, Bpp) {
if (Bpp === undefined) {
Bpp = this._pixelFormat.Bpp;
}
if (!this._convertColor ||
// HACK? Xtightvnc needs this and I have no idea why
(this._FBU.encoding === 0x07 && this._pixelFormat.depth === 24)) {
if (Bpp === 4) {
return arr;
} else if (Bpp === 3) {
return arr.reverse();
} 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];
} else {
pix = (arr[k] << (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;
},
// input: byte stream in pixel format
// output: RGB
_convert_color: function (arr, Bpp) {
if (Bpp === undefined) {
Bpp = this._pixelFormat.Bpp;
}
if (!this._convertColor ||
// 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 {
return arr;
}
}
var data = new Array(arr.length / Bpp * 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);
for (var i = 0, j = 0; i < arr.length; i += Bpp, j += 3) {
var pix = 0;
for (var k = 0; k < Bpp; k++) {
if (this._pixelFormat.big_endian) {
pix = (pix << 8) | arr[i + k];
} else {
pix = (arr[i + k] << (k*8)) | pix;
}
}
data[j] = ((pix >>> this._pixelFormat.red_shift) & this._pixelFormat.red_max) * redMult;
data[j + 1] = ((pix >>> this._pixelFormat.green_shift) & this._pixelFormat.green_max) * greenMult;
data[j + 2] = ((pix >>> this._pixelFormat.blue_shift) & this._pixelFormat.blue_max) * blueMult;
}
return data;
},
};
Util.make_properties(RFB, [
['target', 'wo', 'dom'], // VNC display rendering Canvas object
['focusContainer', 'wo', 'dom'], // DOM element that captures keyboard input
['encrypt', 'rw', 'bool'], // Use TLS/SSL/wss encryption
['true_color', 'rw', 'bool'], // Request true color pixel data
['convertColor', 'rw', 'bool'], // Client will not honor request for native color
['local_cursor', 'rw', 'bool'], // Request locally rendered cursor
['shared', 'rw', 'bool'], // Request shared mode
['view_only', 'rw', 'bool'], // Disable client mouse/keyboard
......@@ -1261,23 +1376,23 @@ var RFB;
return arr;
},
pixelFormat: function (bpp, depth, true_color) {
pixelFormat: function (pf) {
var arr = [0]; // msg-type
arr.push8(0); // padding
arr.push8(0); // padding
arr.push8(0); // padding
arr.push8(bpp * 8); // bits-per-pixel
arr.push8(depth * 8); // depth
arr.push8(0); // little-endian
arr.push8(true_color ? 1 : 0); // true-color
arr.push8(pf.bpp);
arr.push8(pf.depth);
arr.push8(pf.big_endian ? 1 : 0);
arr.push8(pf.true_color ? 1 : 0);
arr.push16(255); // red-max
arr.push16(255); // green-max
arr.push16(255); // blue-max
arr.push8(16); // red-shift
arr.push8(8); // green-shift
arr.push8(0); // blue-shift
arr.push16(pf.red_max);
arr.push16(pf.green_max);
arr.push16(pf.blue_max);
arr.push8(pf.red_shift);
arr.push8(pf.green_shift);
arr.push8(pf.blue_shift);
arr.push8(0); // padding
arr.push8(0); // padding
......@@ -1366,19 +1481,26 @@ var RFB;
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; }
var cur_y = this._FBU.y + (this._FBU.height - this._FBU.lines);
var curr_height = Math.min(this._FBU.lines,
Math.floor(this._sock.rQlen() / (this._FBU.width * this._fb_Bpp)));
this._display.blitImage(this._FBU.x, cur_y, this._FBU.width,
curr_height, this._sock.get_rQ(),
this._sock.get_rQi());
this._sock.rQskipBytes(this._FBU.width * curr_height * this._fb_Bpp);
Math.floor(this._sock.rQlen() / (this._FBU.width * this._pixelFormat.Bpp)));
this._display.renderQ_push({
'type': 'blit',
'data': this._convert_color(this._sock.rQshiftBytes(curr_height * this._FBU.width * this._pixelFormat.Bpp)),
'rgb': this._convertColor || this._pixelFormat.Bpp === 3,
'x': this._FBU.x,
'y': cur_y,
'width': this._FBU.width,
'height': curr_height
});
this._FBU.lines -= curr_height;
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 {
this._FBU.rects--;
this._FBU.bytes = 0;
......@@ -1407,15 +1529,15 @@ var RFB;
RRE: function () {
var color;
if (this._FBU.subrects === 0) {
this._FBU.bytes = 4 + this._fb_Bpp;
if (this._sock.rQwait("RRE", 4 + this._fb_Bpp)) { return false; }
this._FBU.bytes = 4 + this._pixelFormat.Bpp;
if (this._sock.rQwait("RRE", 4 + this._pixelFormat.Bpp)) { return false; }
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);
}
while (this._FBU.subrects > 0 && this._sock.rQlen() >= (this._fb_Bpp + 8)) {
color = this._sock.rQshiftBytes(this._fb_Bpp);
while (this._FBU.subrects > 0 && this._sock.rQlen() >= (this._pixelFormat.Bpp + 8)) {
color = this._convert_one_color(this._sock.rQshiftBytes(this._pixelFormat.Bpp));
var x = this._sock.rQshift16();
var y = this._sock.rQshift16();
var width = this._sock.rQshift16();
......@@ -1426,7 +1548,7 @@ var RFB;
if (this._FBU.subrects > 0) {
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 {
this._FBU.rects--;
this._FBU.bytes = 0;
......@@ -1466,20 +1588,20 @@ var RFB;
// Figure out how much we are expecting
if (subencoding & 0x01) { // Raw
this._FBU.bytes += w * h * this._fb_Bpp;
this._FBU.bytes += w * h * this._pixelFormat.Bpp;
} else {
if (subencoding & 0x02) { // Background
this._FBU.bytes += this._fb_Bpp;
this._FBU.bytes += this._pixelFormat.Bpp;
}
if (subencoding & 0x04) { // Foreground
this._FBU.bytes += this._fb_Bpp;
this._FBU.bytes += this._pixelFormat.Bpp;
}
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 * (this._fb_Bpp + 2);
this._FBU.bytes += subrects * (this._pixelFormat.Bpp + 2);
} else {
this._FBU.bytes += subrects * 2;
}
......@@ -1499,16 +1621,24 @@ var RFB;
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);
this._display.renderQ_push({
'type': 'blit',
'rgb': this._convertColor || this._pixelFormat.Bpp === 3,
'data': this._convert_color(rQ.slice(rQi, rQi + this._FBU.bytes - 1)),
'x': x,
'y': y,
'width': w,
'height': h
});
rQi += this._FBU.bytes - 1;
} else {
if (this._FBU.subencoding & 0x02) { // Background
this._FBU.background = rQ.slice(rQi, rQi + this._fb_Bpp);
rQi += this._fb_Bpp;
this._FBU.background = this._convert_one_color(rQ.slice(rQi, rQi + this._pixelFormat.Bpp));
rQi += this._pixelFormat.Bpp;
}
if (this._FBU.subencoding & 0x04) { // Foreground
this._FBU.foreground = rQ.slice(rQi, rQi + this._fb_Bpp);
rQi += this._fb_Bpp;
this._FBU.foreground = this._convert_one_color(rQ.slice(rQi, rQi + this._pixelFormat.Bpp));
rQi += this._pixelFormat.Bpp;
}
this._display.startTile(x, y, w, h, this._FBU.background);
......@@ -1519,8 +1649,8 @@ var RFB;
for (var s = 0; s < subrects; s++) {
var color;
if (this._FBU.subencoding & 0x10) { // SubrectsColoured
color = rQ.slice(rQi, rQi + this._fb_Bpp);
rQi += this._fb_Bpp;
color = this._convert_one_color(rQ.slice(rQi, rQi + this._pixelFormat.Bpp));
rQi += this._pixelFormat.Bpp;
} else {
color = this._FBU.foreground;
}
......@@ -1567,7 +1697,7 @@ var RFB;
},
display_tight: function (isTightPNG) {
if (this._fb_depth === 1) {
if (this._pixelFormat.Bdepth === 1) {
this._fail("Tight protocol handler only implements true color mode");
}
......@@ -1651,7 +1781,7 @@ var RFB;
var handlePalette = function () {
var numColors = rQ[rQi + 2] + 1;
var paletteSize = numColors * this._fb_depth;
var paletteSize = numColors * this._pixelFormat.Bdepth;
this._FBU.bytes += paletteSize;
if (this._sock.rQwait("TIGHT palette " + cmode, this._FBU.bytes)) { return false; }
......@@ -1671,7 +1801,7 @@ var RFB;
// Shift ctl, filter id, num colors, palette entries, and clength off
this._sock.rQskipBytes(3);
var palette = this._sock.rQshiftBytes(paletteSize);
var palette = this._convert_color(this._sock.rQshiftBytes(paletteSize), this._pixelFormat.Bdepth);
this._sock.rQskipBytes(clength[0]);
if (raw) {
......@@ -1684,7 +1814,8 @@ var RFB;
var rgb = indexedToRGB(data, numColors, palette, this._FBU.width, this._FBU.height);
this._display.renderQ_push({
'type': 'blitRgb',
'type': 'blit',
'rgb': true,
'data': rgb,
'x': this._FBU.x,
'y': this._FBU.y,
......@@ -1697,7 +1828,7 @@ var RFB;
var handleCopy = function () {
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) {
raw = true;
clength = [0, uncompressedSize];
......@@ -1717,8 +1848,9 @@ var RFB;
}
this._display.renderQ_push({
'type': 'blitRgb',
'data': data,
'type': 'blit',
'data': this._convert_color(data, this._pixelFormat.Bdepth),
'rgb': this._convertColor || this._pixelFormat.Bpp === 3,
'x': this._FBU.x,
'y': this._FBU.y,
'width': this._FBU.width,
......@@ -1751,7 +1883,7 @@ var RFB;
switch (cmode) {
// fill use fb_depth because TPIXELs drop the padding byte
case "fill": // TPIXEL
this._FBU.bytes += this._fb_depth;
this._FBU.bytes += this._pixelFormat.Bdepth;
break;
case "jpeg": // max clength
this._FBU.bytes += 3;
......@@ -1772,14 +1904,13 @@ var RFB;
switch (cmode) {
case "fill":
this._sock.rQskip8(); // shift off ctl
var color = this._sock.rQshiftBytes(this._fb_depth);
this._display.renderQ_push({
'type': 'fill',
'x': this._FBU.x,
'y': this._FBU.y,
'width': this._FBU.width,
'height': this._FBU.height,
'color': [color[2], color[1], color[0]]
'color': this._convert_one_color(this._sock.rQshiftBytes(this._pixelFormat.Bdepth), this._pixelFormat.Bdepth)
});
break;
case "png":
......@@ -1854,7 +1985,7 @@ var RFB;
var w = this._FBU.width;
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;
this._FBU.bytes = pixelslength + masklength;
......
......@@ -88,7 +88,7 @@ var UI;
UI.initSetting('port', port);
UI.initSetting('password', '');
UI.initSetting('encrypt', (window.location.protocol === "https:"));
UI.initSetting('true_color', true);
UI.initSetting('convertColor', false);
UI.initSetting('cursor', !UI.isTouchDevice);
UI.initSetting('shared', true);
UI.initSetting('view_only', false);
......@@ -418,7 +418,7 @@ var UI;
UI.closeSettingsMenu();
} else {
UI.updateSetting('encrypt');
UI.updateSetting('true_color');
UI.updateSetting('convertColor');
if (UI.rfb.get_display().get_cursor_uri()) {
UI.updateSetting('cursor');
} else {
......@@ -473,7 +473,7 @@ var UI;
settingsApply: function() {
//Util.Debug(">> settingsApply");
UI.saveSetting('encrypt');
UI.saveSetting('true_color');
UI.saveSetting('convertColor');
if (UI.rfb.get_display().get_cursor_uri()) {
UI.saveSetting('cursor');
}
......@@ -586,7 +586,7 @@ var UI;
//Util.Debug(">> updateVisualState");
$D('noVNC_encrypt').disabled = connected;
$D('noVNC_true_color').disabled = connected;
$D('noVNC_convertColor').disabled = connected;
if (UI.rfb && UI.rfb.get_display() &&
UI.rfb.get_display().get_cursor_uri()) {
$D('noVNC_cursor').disabled = connected;
......@@ -673,7 +673,7 @@ var UI;
}
UI.rfb.set_encrypt(UI.getSetting('encrypt'));
UI.rfb.set_true_color(UI.getSetting('true_color'));
UI.rfb.set_convertColor(UI.getSetting('convertColor'));
UI.rfb.set_local_cursor(UI.getSetting('cursor'));
UI.rfb.set_shared(UI.getSetting('shared'));
UI.rfb.set_view_only(UI.getSetting('view_only'));
......
......@@ -216,18 +216,18 @@ describe('Display/Canvas Helper', function () {
data[i * 4 + 2] = checked_data[i * 4];
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, false);
expect(display).to.have.displayed(checked_data);
});
it('should support drawing RGB blit images with true color via #blitRgbImage', function () {
it('should support drawing RGB blit images with true color via #blitImage', function () {
var data = [];
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.blitImage(0, 0, 4, 4, data, 0, true);
expect(display).to.have.displayed(checked_data);
});
......@@ -315,18 +315,18 @@ describe('Display/Canvas Helper', function () {
expect(display.fillRect).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.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.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();
display.renderQ_push({ type: 'blitRgb', x: 3, y: 4, width: 5, height: 6, data: [7, 8, 9] });
expect(display.blitRgbImage).to.have.been.calledOnce;
expect(display.blitRgbImage).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9], 0);
it('should draw a blit RGB image on type "blit" with "rgb" set to true', function () {
display.blitImage = sinon.spy();
display.renderQ_push({ type: 'blit', x: 3, y: 4, width: 5, height: 6, data: [7, 8, 9], rgb: true });
expect(display.blitImage).to.have.been.calledOnce;
expect(display.blitImage).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9], 0, true);
});
it('should copy a region on type "copy"', function () {
......
......@@ -907,8 +907,6 @@ describe('Remote Frame Buffer Protocol Client', function() {
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 () {
client.set_onDesktopName(sinon.spy());
send_server_init({ name: 'some name' }, client);
......@@ -938,14 +936,6 @@ describe('Remote Frame Buffer Protocol Client', function() {
expect(client._rfb_state).to.equal('normal');
});
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 () {
client.set_onFBResize(sinon.spy());
sinon.spy(client._display, 'resize');
......@@ -967,25 +957,30 @@ describe('Remote Frame Buffer Protocol Client', function() {
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 () {
client.set_true_color(true);
send_server_init({}, client);
expect(client._fb_Bpp).to.equal(4);
expect(client._fb_depth).to.equal(3);
it('should set the BPP and depth to 4 and 3 respectively if server can send native (true color)', function () {
send_server_init({ true_color: 1, bpp: 8, depth: 8 }, client);
expect(client._pixelFormat.Bpp).to.equal(4);
expect(client._pixelFormat.Bdepth).to.equal(3);
});
it('should set the BPP and depth to 1 and 1 respectively if not in true color mode', function () {
client.set_true_color(false);
send_server_init({}, client);
expect(client._fb_Bpp).to.equal(1);
expect(client._fb_depth).to.equal(1);
it('should set the BPP and depth to 2 and 2 respectively if server cannot send native (true color)', function () {
client.set_convertColor(true);
send_server_init({ true_color: 1, bpp: 16, depth: 15 }, client);
expect(client._pixelFormat.Bpp).to.equal(2);
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_convertColor(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
it('should reply with the pixel format, client encodings, and initial update request', function () {
client.set_true_color(true);
client.set_local_cursor(false);
var expected = RFB.messages.pixelFormat(4, 3, true);
var expected = RFB.messages.pixelFormat({ 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 });
expected = expected.concat(RFB.messages.clientEncodings(client._encodings, false, true));
var expected_cdr = { cleanBox: { x: 0, y: 0, w: 0, h: 0 },
dirtyBoxes: [ { x: 0, y: 0, w: 27, h: 32 } ] };
......@@ -1036,10 +1031,10 @@ describe('Remote Frame Buffer Protocol Client', function() {
});
var target_data_arr = [
0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0x00, 0xff, 0x00, 255, 0xff, 0x00, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0xee, 0x00, 0xff, 255, 0x00, 0xee, 0xff, 255, 0xaa, 0xee, 0xff, 255, 0xab, 0xee, 0xff, 255,
0xee, 0x00, 0xff, 255, 0x00, 0xee, 0xff, 255, 0xaa, 0xee, 0xff, 255, 0xab, 0xee, 0xff, 255
0xf8, 0x00, 0x00, 255, 0x00, 0xf8, 0x00, 255, 0x00, 0x00, 0xf8, 255, 0x00, 0x00, 0xf8, 255,
0x00, 0xf8, 0x00, 255, 0xf8, 0x00, 0x00, 255, 0x00, 0x00, 0xf8, 255, 0x00, 0x00, 0xf8, 255,
0xe8, 0x00, 0xf8, 255, 0x00, 0xe8, 0xf8, 255, 0xa8, 0xe8, 0xf8, 255, 0xa8, 0xe8, 0xf8, 255,
0xe8, 0x00, 0xf8, 255, 0x00, 0xe8, 0xf8, 255, 0xa8, 0xe8, 0xf8, 255, 0xa8, 0xe8, 0xf8, 255
];
var target_data;
......@@ -1188,22 +1183,95 @@ describe('Remote Frame Buffer Protocol Client', function() {
client._display._fb_height = 4;
client._display._viewportLoc.w = 4;
client._display._viewportLoc.h = 4;
client._fb_Bpp = 4;
client._pixelFormat.Bpp = 4;
});
it('should handle the RAW encoding', 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 }];
// data is in bgrx
var rects = [
[0x00, 0x00, 0xff, 0, 0x00, 0xff, 0x00, 0, 0x00, 0xff, 0x00, 0, 0x00, 0x00, 0xff, 0],
[0xff, 0x00, 0x00, 0, 0xff, 0x00, 0x00, 0, 0xff, 0x00, 0x00, 0, 0xff, 0x00, 0x00, 0],
[0xff, 0x00, 0xee, 0, 0xff, 0xee, 0x00, 0, 0xff, 0xee, 0xaa, 0, 0xff, 0xee, 0xab, 0],
[0xff, 0x00, 0xee, 0, 0xff, 0xee, 0x00, 0, 0xff, 0xee, 0xaa, 0, 0xff, 0xee, 0xab, 0]];
send_fbu_msg(info, rects, client);
expect(client._display).to.have.displayed(target_data);
// 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._convertColor = 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 },
{ 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 = [
[0xf8, 0x00, 0x00, 0, 0x00, 0xf8, 0x00, 0, 0x00, 0xf8, 0x00, 0, 0xf8, 0x00, 0x00, 0],
[0x00, 0x00, 0xf8, 0, 0x00, 0x00, 0xf8, 0, 0x00, 0x00, 0xf8, 0, 0x00, 0x00, 0xf8, 0],
[0xe8, 0x00, 0xf8, 0, 0x00, 0xe8, 0xf8, 0, 0xa8, 0xe8, 0xf8, 0, 0xa8, 0xe8, 0xf8, 0],
[0xe8, 0x00, 0xf8, 0, 0x00, 0xe8, 0xf8, 0, 0xa8, 0xe8, 0xf8, 0, 0xa8, 0xe8, 0xf8, 0]];
send_fbu_msg(info, rects, client);
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._convertColor = 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._convertColor = 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 () {
......@@ -1238,7 +1306,7 @@ describe('Remote Frame Buffer Protocol Client', function() {
rect.push16(2); // width: 2
rect.push16(2); // height: 2
rect.push(0xff); // becomes ff0000ff --> #0000FF color
rect.push(0x00);
rect.push(0x00); // becomes 0000ffff --> #0000FF color
rect.push(0x00);
rect.push(0xff);
rect.push16(2); // x: 2
......@@ -1265,7 +1333,7 @@ describe('Remote Frame Buffer Protocol Client', function() {
client._display._fb_height = 4;
client._display._viewportLoc.w = 4;
client._display._viewportLoc.h = 4;
client._fb_Bpp = 4;
client._pixelFormat.Bpp = 4;
});
it('should handle a tile with fg, bg specified, normal subrects', function () {
......
......@@ -10,7 +10,7 @@
This file is licensed under the 2-Clause BSD license (see LICENSE.txt).
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&convertColor=1
-->
<title>noVNC</title>
......@@ -154,7 +154,7 @@
<span id="noVNC_settings_menu">
<ul>
<li><input id="noVNC_encrypt" type="checkbox"> Encrypt</li>
<li><input id="noVNC_true_color" type="checkbox" checked> True Color</li>
<li><input id="noVNC_convertColor" type="checkbox" checked> Convert Color</li>
<li><input id="noVNC_cursor" type="checkbox"> Local Cursor</li>
<li><input id="noVNC_clip" type="checkbox"> Clip to Window</li>
<li><input id="noVNC_shared" type="checkbox"> Shared Mode</li>
......
......@@ -10,7 +10,7 @@
This file is licensed under the 2-Clause BSD license (see LICENSE.txt).
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&convertColor=1
-->
<title>noVNC</title>
......@@ -194,7 +194,7 @@
'encrypt': WebUtil.getQueryVar('encrypt',
(window.location.protocol === "https:")),
'repeaterID': WebUtil.getQueryVar('repeaterID', ''),
'true_color': WebUtil.getQueryVar('true_color', true),
'convertColor': WebUtil.getQueryVar('convertColor', true),
'local_cursor': WebUtil.getQueryVar('cursor', true),
'shared': WebUtil.getQueryVar('shared', true),
'view_only': WebUtil.getQueryVar('view_only', false),
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment