This commit is contained in:
Alexander Clouter 2015-02-09 15:28:46 +00:00
commit 8d1109a540
10 changed files with 813 additions and 209 deletions

View File

@ -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 and ATEN iKVM support : [Alexander Clouter](http://www.digriz.org.uk/)
* Included libraries:
* web-socket-js : Hiroshi Ichikawa (github.com/gimite/web-socket-js)

View File

@ -415,21 +415,12 @@ var Display;
blitImage: function (x, y, width, height, arr, offset) {
if (this._true_color) {
this._bgrxImageData(x, y, this._viewportLoc.x, this._viewportLoc.y, width, height, arr, offset);
this._imageData(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);
} else {
// probably wrong?
this._cmapImageData(x, y, this._viewportLoc.x, this._viewportLoc.y, width, height, arr, offset);
}
},
blitStringImage: function (str, x, y) {
var img = new Image();
img.onload = function () {
@ -547,7 +538,7 @@ var Display;
}
},
_rgbImageData: function (x, y, vx, vy, width, height, arr, offset) {
_imageData: function (x, y, vx, vy, width, height, arr, offset) {
var img = this._drawCtx.createImageData(width, height);
var data = img.data;
for (var i = 0, j = offset; i < width * height * 4; i += 4, j += 3) {
@ -559,18 +550,6 @@ var Display;
this._drawCtx.putImageData(img, x - vx, y - vy);
},
_bgrxImageData: function (x, y, vx, vy, width, height, arr, offset) {
var img = this._drawCtx.createImageData(width, height);
var data = img.data;
for (var i = 0, j = offset; i < width * height * 4; i += 4, j += 4) {
data[i] = arr[j + 2];
data[i + 1] = arr[j + 1];
data[i + 2] = arr[j];
data[i + 3] = 255; // Alpha
}
this._drawCtx.putImageData(img, x - vx, y - vy);
},
_cmapImageData: function (x, y, vx, vy, width, height, arr, offset) {
var img = this._drawCtx.createImageData(width, height);
var data = img.data;
@ -599,9 +578,6 @@ var Display;
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);
break;
case 'img':
if (a.img.complete) {
this.drawImage(a.img, a.x, a.y);

View File

@ -376,3 +376,80 @@ XK_udiaeresis = 0x00fc, /* U+00FC LATIN SMALL LETTER U WITH DIA
XK_yacute = 0x00fd, /* U+00FD LATIN SMALL LETTER Y WITH ACUTE */
XK_thorn = 0x00fe, /* U+00FE LATIN SMALL LETTER THORN */
XK_ydiaeresis = 0x00ff; /* U+00FF LATIN SMALL LETTER Y WITH DIAERESIS */
var XK2HID = {};
// F{1..12}
for (var i = XK_F1; i <= XK_F12; i++) {
XK2HID[i] = 0x3a + (i - XK_F1);
}
// A-Za-z
for (var i = XK_A; i <= XK_Z; i++) {
XK2HID[i] = 0x04 + (i - XK_A);
XK2HID[i + (XK_a - XK_A)] = 0x04 + (i - XK_A);
}
// 1-9
for (var i = XK_1; i <= XK_9; i++) {
XK2HID[i] = 0x1e + (i - XK_1);
}
XK2HID[XK_0] = 0x27;
XK2HID[XK_Return] = 0x28;
XK2HID[XK_Escape] = 0x29;
XK2HID[XK_BackSpace] = 0x2a;
XK2HID[XK_Tab] = 0x2b;
XK2HID[XK_space] = 0x2c;
XK2HID[XK_minus] = 0x2d;
XK2HID[XK_equal] = 0x2e;
XK2HID[XK_bracketleft] = 0x2f;
XK2HID[XK_bracketright] = 0x30;
XK2HID[XK_backslash] = 0x31;
XK2HID[XK_semicolon] = 0x33;
XK2HID[XK_apostrophe] = 0x34;
XK2HID[XK_grave] = 0x35;
XK2HID[XK_comma] = 0x36;
XK2HID[XK_period] = 0x37;
XK2HID[XK_slash] = 0x38;
XK2HID[XK_Print] = 0x46;
XK2HID[XK_Scroll_Lock] = 0x47;
XK2HID[XK_Pause] = 0x48;
XK2HID[XK_Insert] = 0x49;
XK2HID[XK_Home] = 0x4a;
XK2HID[XK_Page_Up] = 0x4b;
XK2HID[XK_Delete] = 0x4c;
XK2HID[XK_End] = 0x4d;
XK2HID[XK_Page_Down] = 0x4e;
XK2HID[XK_Right] = 0x4f;
XK2HID[XK_Left] = 0x50;
XK2HID[XK_Down] = 0x51;
XK2HID[XK_Up] = 0x52;
XK2HID[XK_Control_L] = 0xe0;
XK2HID[XK_Control_R] = XK2HID[XK_Control_L];
XK2HID[XK_Shift_L] = 0xe1;
XK2HID[XK_Shift_R] = XK2HID[XK_Shift_L];
XK2HID[XK_Alt_L] = 0xe2;
XK2HID[XK_Alt_R] = XK2HID[XK_Alt_L];
XK2HID[XK_Super_L] = 0xe3;
XK2HID[XK_Super_R] = XK2HID[XK_Super_L];
XK2HID[XK_Caps_Lock] = 0x39;
// locale hardcoded hack
XK2HID[XK_less] = XK2HID[XK_comma];
XK2HID[XK_greater] = XK2HID[XK_period];
XK2HID[XK_exclam] = XK2HID[XK_1];
XK2HID[XK_at] = XK2HID[XK_2];
XK2HID[XK_numbersign] = XK2HID[XK_3];
XK2HID[XK_dollar] = XK2HID[XK_4];
XK2HID[XK_percent] = XK2HID[XK_5];
XK2HID[XK_asciicircum] = XK2HID[XK_6];
XK2HID[XK_ampersand] = XK2HID[XK_7];
XK2HID[XK_asterisk] = XK2HID[XK_8];
XK2HID[XK_parenleft] = XK2HID[XK_9];
XK2HID[XK_parenright] = XK2HID[XK_0];
XK2HID[XK_underscore] = XK2HID[XK_minus];
XK2HID[XK_bar] = XK2HID[XK_backslash];
XK2HID[XK_quotedbl] = XK2HID[XK_apostrophe];
XK2HID[XK_asciitilde] = XK2HID[XK_grave];

View File

@ -33,6 +33,7 @@ var RFB;
this._rfb_auth_scheme = '';
this._rfb_tightvnc = false;
this._rfb_atenikvm = false;
this._rfb_xvp_ver = 0;
// In preference order
@ -43,6 +44,7 @@ var RFB;
['HEXTILE', 0x05 ],
['RRE', 0x02 ],
['RAW', 0x00 ],
['ATEN', 0x59 ],
['DesktopSize', -223 ],
['Cursor', -239 ],
@ -74,6 +76,8 @@ var RFB;
subrects: 0, // RRE
lines: 0, // RAW
tiles: 0, // HEXTILE
aten_len: -1, // ATEN
aten_type: -1, // ATEN
bytes: 0,
x: 0,
y: 0,
@ -85,8 +89,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,10 +120,11 @@ 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
'aten_password_sep': ':', // Separator for ATEN iKVM password fields
'xvp_password_sep': '@', // Separator for XVP password fields
'disconnectTimeout': 3, // Time (s) to wait for disconnection
'wsProtocols': ['binary', 'base64'], // Protocols to use in the WebSocket connection
@ -334,9 +338,12 @@ var RFB;
this._FBU.lines = 0; // RAW
this._FBU.tiles = 0; // HEXTILE
this._FBU.zlibs = []; // TIGHT zlib encoders
this._FBU.aten_len = -1 // ATEN
this._FBU.aten_type = -1; // ATEN
this._mouse_buttonMask = 0;
this._mouse_arr = [];
this._rfb_tightvnc = false;
this._rfb_atenikvm = false;
// Clear the per connection encoding stats
var i;
@ -690,6 +697,36 @@ var RFB;
},
// authentication
_negotiate_aten_auth: function () {
var aten_sep = this._aten_password_sep;
var aten_auth = this._rfb_password.split(aten_sep);
if (aten_auth.length < 2) {
this._updateState('password', 'ATEN iKVM credentials required (user' + aten_sep +
'password) -- got only ' + this._rfb_password);
this._onPasswordRequired(this);
return false;
}
this._rfb_atenikvm = true;
if (this._rfb_tightvnc) {
this._rfb_tightvnc = false;
} else {
this._sock.rQskipBytes(4);
}
this._sock.rQskipBytes(16);
var username = aten_auth[0];
username += Array(24-username.length+1).join("\x00");
var password = aten_auth.slice(1).join(aten_sep);
password += Array(24-password.length+1).join("\x00");
this._sock.send_string(username+password);
this._updateState("SecurityResult");
return true;
},
_negotiate_xvp_auth: function () {
var xvp_sep = this._xvp_password_sep;
var xvp_auth = this._rfb_password.split(xvp_sep);
@ -757,6 +794,7 @@ var RFB;
if (!this._rfb_tightvnc) { // first pass, do the tunnel negotiation
if (this._sock.rQwait("num tunnels", 4)) { return false; }
var numTunnels = this._sock.rQshift32();
if (this._rfb_version === 3.8 && (numTunnels & 0xffff0ff0) >>> 0 === 0xaff90fb0) { return this._negotiate_aten_auth(); }
if (numTunnels > 0 && this._sock.rQwait("tunnel capabilities", 16 * numTunnels, 4)) { return false; }
this._rfb_tightvnc = true;
@ -772,6 +810,12 @@ var RFB;
var subAuthCount = this._sock.rQshift32();
if (this._sock.rQwait("sub auth capabilities", 16 * subAuthCount, 4)) { return false; }
// Newer X10 Supermicro motherboards get here
if (this._rfb_version === 3.8 && numTunnels === 0 && subAuthCount === 0) {
Util.Warn("Newer ATEN iKVM detected, you may get an 'unsupported encoding 87'");
return this._negotiate_aten_auth();
}
var clientSupportedTypes = {
'STDVNOAUTH__': 1,
'STDVVNCAUTH_': 2
@ -858,23 +902,24 @@ var RFB;
_negotiate_server_init: function () {
if (this._sock.rQwait("server initialization", 24)) { return false; }
if (this._rfb_atenikvm && this._sock.rQwait("ATEN server initialization", 36)) { 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();
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;
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.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
@ -885,6 +930,14 @@ var RFB;
if (this._sock.rQwait('server init name', name_length, 24)) { return false; }
this._fb_name = Util.decodeUTF8(this._sock.rQshiftStr(name_length));
if (this._rfb_atenikvm) {
this._sock.rQskipBytes(8); // unknown
this._sock.rQskip8(); // IKVMVideoEnable
this._sock.rQskip8(); // IKVMKMEnable
this._sock.rQskip8(); // IKVMKickEnable
this._sock.rQskip8(); // VUSBEnable
}
if (this._rfb_tightvnc) {
if (this._sock.rQwait('TightVNC extended server init header', 8, 24 + name_length)) { return false; }
// In TightVNC mode, ServerInit message is extended
@ -913,56 +966,167 @@ 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);
// ATEN 'wisdom' from chicken-aten-ikvm:lens/lens.rb
// tested against the following Supermicro motherboards
// (use 'dmidecode -s baseboard-product-name' for model):
// - X7SPA-F
// - X8DTL
// - X8SIE-F
// - X9SCL/X9SCM
// - X9SCM-F
// - X9DRD-iF
// - X9SRE/X9SRE-3F/X9SRi/X9SRi-3F
// - X9DRL-3F/X9DRL-6F
// - X10SLD
//
// Not supported (uses encoding 87):
// - X10SL7-F
// - X10SLD-F
// - X10SLM-F
// - X10SLE
//
// Simply does not work:
// Hermon (WPMC450) [hangs at login]:
// - X7SB3-F
// - X8DTU-F
// - X8STi-3F
// Peppercon (Raritan/Kira100) [connection refused]:
// - X7SBi
//
// Thanks to Brian Rak and Erik Smit for testing
if (this._rfb_atenikvm) {
// we do not know the resolution till the first fbupdate so go large
// although, not necessary, saves a pointless full screen refresh
this._fb_width = 10000;
this._fb_height = 10000;
// lies about what it supports
Util.Warn("ATEN iKVM lies and only does 15 bit depth with RGB555");
this._convertColor = true;
this._pixelFormat.bpp = 16;
this._pixelFormat.depth = 15;
this._pixelFormat.red_max = (1 << 5) - 1;
this._pixelFormat.green_max = (1 << 5) - 1;
this._pixelFormat.blue_max = (1 << 5) - 1;
this._pixelFormat.red_shift = 10;
this._pixelFormat.green_shift = 5;
this._pixelFormat.blue_shift = 0;
// changes to protocol format
RFB.messages.keyEvent = function (keysym, down) {
if (XK2HID[keysym] === undefined) {
Util.Warn("XK2HID["+keysym+"] returns undefined");
return;
}
var arr = [4];
arr.push8(0);
arr.push8(down);
arr.push16(0);
arr.push32(XK2HID[keysym]);
arr.push32(0);
arr.push32(0);
arr.push8(0);
return arr;
};
RFB.messages.pointerEvent = function (x, y, mask) {
var arr = [5];
arr.push8(0);
arr.push8(mask);
arr.push16(x);
arr.push16(y);
arr.push32(0);
arr.push32(0);
arr.push16(0);
arr.push8(0);
return arr;
};
}
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');
}
if (this._pixelFormat.true_color
&& this._pixelFormat.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)) {
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;
@ -1066,6 +1230,42 @@ var RFB;
msg_type = this._sock.rQshift8();
}
if (this._rfb_atenikvm) {
switch (msg_type) {
case 4: // Front Ground Event
Util.Debug("ATEN iKVM Front Ground Event");
this._sock.rQskipBytes(20);
return true;
case 22: // Keep Alive Event
Util.Debug("ATEN iKVM Keep Alive Event");
this._sock.rQskipBytes(1);
return true;
case 51: // Video Get Info
Util.Debug("ATEN iKVM Video Get Info");
this._sock.rQskipBytes(4);
return true;
case 55: // Mouse Get Info
Util.Debug("ATEN iKVM Mouse Get Info");
this._sock.rQskipBytes(2);
return true;
case 57: // Session Message
Util.Debug("ATEN iKVM Session Message");
this._sock.rQskipBytes(4); // u32
this._sock.rQskipBytes(4); // u32
this._sock.rQskipBytes(256);
return true;
case 60: // Get Viewer Lang
Util.Debug("ATEN iKVM Get Viewer Lang");
this._sock.rQskipBytes(8);
return true;
}
}
switch (msg_type) {
case 0: // FramebufferUpdate
var ret = this._framebufferUpdate();
@ -1139,6 +1339,11 @@ var RFB;
this._FBU.encoding);
return false;
}
// ATEN uses 0x00 even when it is meant to be 0x59
if (this._rfb_atenikvm && this._FBU.encoding === 0x00) {
this._FBU.encoding = 0x59;
}
}
this._timing.last_fbu = (new Date()).getTime();
@ -1190,16 +1395,62 @@ var RFB;
return true; // We finished this FBU
},
// 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)) {
switch (Bpp) {
case 4:
for (var i = arr.length; i > 0; i -= 4) {
arr.splice(i - 1, 1); // convert from RGBX to RGB
}
case 3:
return arr;
default:
Util.Error('convert color disabled, but Bpp is not 3 or 4!');
}
}
var data = [];
for (var i = 0; i < arr.length; i += Bpp) {
var pix = 0;
for (var j = 0; j < Bpp; j++) {
if (this._pixelFormat.big_endian) {
pix = (pix << 8) | arr[i + j];
} else {
pix = (arr[i + j] << (j*8)) | pix;
}
}
data.push(((pix >>> this._pixelFormat.red_shift) & this._pixelFormat.red_max)
* (256/(this._pixelFormat.red_max + 1)));
data.push(((pix >>> this._pixelFormat.green_shift) & this._pixelFormat.green_max)
* (256/(this._pixelFormat.green_max + 1)));
data.push(((pix >>> this._pixelFormat.blue_shift) & this._pixelFormat.blue_max)
* (256/(this._pixelFormat.blue_max + 1)));
}
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
['aten_password_sep', 'rw', 'str'], // Separator for ATEN iKVM password fields
['xvp_password_sep', 'rw', 'str'], // Separator for XVP password fields
['disconnectTimeout', 'rw', 'int'], // Time (s) to wait for disconnection
['wsProtocols', 'rw', 'arr'], // Protocols to use in the WebSocket connection
@ -1268,23 +1519,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
@ -1373,19 +1624,25 @@ 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)),
'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;
@ -1414,15 +1671,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_color(this._sock.rQshiftBytes(this._pixelFormat.Bpp)).reverse(); // 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_color(this._sock.rQshiftBytes(this._pixelFormat.Bpp)).reverse();
var x = this._sock.rQshift16();
var y = this._sock.rQshift16();
var width = this._sock.rQshift16();
@ -1433,7 +1690,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;
@ -1473,20 +1730,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;
}
@ -1506,16 +1763,23 @@ 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',
'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_color(rQ.slice(rQi, rQi + this._pixelFormat.Bpp)).reverse();
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_color(rQ.slice(rQi, rQi + this._pixelFormat.Bpp)).reverse();
rQi += this._pixelFormat.Bpp;
}
this._display.startTile(x, y, w, h, this._FBU.background);
@ -1526,8 +1790,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_color(rQ.slice(rQi, rQi + this._pixelFormat.Bpp)).reverse();
rQi += this._pixelFormat.Bpp;
} else {
color = this._FBU.foreground;
}
@ -1574,7 +1838,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");
}
@ -1658,7 +1922,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; }
@ -1678,7 +1942,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) {
@ -1691,7 +1955,7 @@ var RFB;
var rgb = indexedToRGB(data, numColors, palette, this._FBU.width, this._FBU.height);
this._display.renderQ_push({
'type': 'blitRgb',
'type': 'blit',
'data': rgb,
'x': this._FBU.x,
'y': this._FBU.y,
@ -1704,7 +1968,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];
@ -1724,8 +1988,8 @@ var RFB;
}
this._display.renderQ_push({
'type': 'blitRgb',
'data': data,
'type': 'blit',
'data': this._convert_color(data, this._pixelFormat.Bdepth),
'x': this._FBU.x,
'y': this._FBU.y,
'width': this._FBU.width,
@ -1758,7 +2022,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;
@ -1779,14 +2043,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_color(this._sock.rQshiftBytes(this._pixelFormat.Bdepth), this._pixelFormat.Bdepth).reverse(),
});
break;
case "png":
@ -1861,7 +2124,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;
@ -1884,6 +2147,92 @@ var RFB;
compress_lo: function () {
Util.Error("Server sent compress level pseudo-encoding");
},
ATEN: function () {
if (this._FBU.aten_len === - 1) {
this._FBU.bytes = 8;
if (this._sock.rQwait("ATEN", this._FBU.bytes)) { return false; }
this._FBU.bytes = 0;
this._sock.rQskipBytes(4);
this._FBU.aten_len = this._sock.rQshift32();
if (this._FBU.width === 64896 && this._FBU.height === 65056) {
Util.Info("ATEN iKVM screen is probably off");
if (this._FBU.aten_len !== 10) {
Util.Debug(">> ATEN iKVM screen off (aten_len="+this._FBU.aten_len+")");
this._fail('expected aten_len to be 10 when screen is off');
}
this._FBU.aten_len -= 10;
return true;
}
if (this._fb_width !== this._FBU.width && this._fb_height !== this._FBU.height) {
Util.Debug(">> ATEN resize desktop");
this._fb_width = this._FBU.width;
this._fb_height = this._FBU.height;
this._onFBResize(this, this._fb_width, this._fb_height);
this._display.resize(this._fb_width, this._fb_height);
Util.Debug("<< ATEN resize desktop");
}
}
if (this._FBU.aten_type === -1) {
this._FBU.bytes = 10;
if (this._sock.rQwait("ATEN", this._FBU.bytes)) { return false; }
this._FBU.bytes = 0;
this._FBU.aten_type = this._sock.rQshift8();
this._sock.rQskip8();
this._sock.rQshift32(); // number of subrects
if (this._FBU.aten_len !== this._sock.rQshift32()) {
return this._fail('ATEN RAW len mis-match');
}
this._FBU.aten_len -= 10;
}
while (this._FBU.aten_len > 0) {
switch (this._FBU.aten_type) {
case 0: // Subrects
this._FBU.bytes = 6 + (16 * 16 * this._pixelFormat.Bpp); // at least a subrect
if (this._sock.rQwait("ATEN", this._FBU.bytes)) { return false; }
var a = this._sock.rQshift16();
var b = this._sock.rQshift16();
var y = this._sock.rQshift8();
var x = this._sock.rQshift8();
this._display.renderQ_push({
'type': 'blit',
'data': this._convert_color(this._sock.rQshiftBytes(this._FBU.bytes - 6)),
'x': x * 16,
'y': y * 16,
'width': 16,
'height': 16
});
this._FBU.aten_len -= this._FBU.bytes;
this._FBU.bytes = 0;
break;
case 1: // RAW
var olines = (this._FBU.lines === 0) ? this._FBU.height : this._FBU.lines;
this._encHandlers.RAW();
this._FBU.aten_len -= (olines - this._FBU.lines) * this._FBU.width * this._pixelFormat.Bpp;
if (this._FBU.bytes > 0) return false;
break;
default:
return this._fail('unknown ATEN type: '+this._FBU.aten_type);
}
}
if (this._FBU.aten_len < 0) {
this._fail('aten_len dropped below zero');
}
if (this._FBU.aten_type === 0) {
this._FBU.rects--;
}
this._FBU.aten_len = -1;
this._FBU.aten_type = -1;
return true;
}
};
})();

View File

@ -87,7 +87,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);
@ -419,7 +419,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 {
@ -474,7 +474,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');
}
@ -587,7 +587,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;
@ -674,7 +674,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'));

View File

@ -8,7 +8,7 @@
"test": "tests"
},
"scripts": {
"test": "karma start karma.conf.js"
"test": "PATH=$PATH:node_modules/karma/bin karma start karma.conf.js"
},
"repository": {
"type": "git",

View File

@ -208,26 +208,14 @@ describe('Display/Canvas Helper', function () {
expect(display).to.have.displayed(checked_data);
});
it('should support drawing BGRX blit images with true color via #blitImage', function () {
var data = [];
for (var i = 0; i < 16; i++) {
data[i * 4] = checked_data[i * 4 + 2];
data[i * 4 + 1] = checked_data[i * 4 + 1];
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);
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);
expect(display).to.have.displayed(checked_data);
});
@ -322,13 +310,6 @@ describe('Display/Canvas Helper', function () {
expect(display.blitImage).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9], 0);
});
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 copy a region on type "copy"', function () {
display.copyImage = sinon.spy();
display.renderQ_push({ type: 'copy', x: 3, y: 4, width: 5, height: 6, old_x: 7, old_y: 8 });

View File

@ -807,6 +807,51 @@ describe('Remote Frame Buffer Protocol Client', function() {
expect(client._rfb_state).to.equal('failed');
});
});
describe('ATEN iKVM Authentication Handler', function () {
var client;
beforeEach(function () {
client = make_rfb();
client.connect('host', 8675);
client._sock._websocket._open();
client._rfb_state = 'Security';
client._rfb_version = 3.8;
send_security(16, client);
client._sock._websocket._get_sent_data(); // skip the security reply
client._rfb_password = 'test1:test2';
});
var auth = [
116, 101, 115, 116, 49, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
116, 101, 115, 116, 50, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0];
it('via old style method', function () {
client._sock._websocket._receive_data(new Uint8Array([
0xaf, 0xf9, 0x0f, 0xb0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0]));
expect(client._rfb_tightvnc).to.be.false;
expect(client._rfb_atenikvm).to.be.true;
expect(client._sock).to.have.sent(auth);
});
it('via new style method', function () {
client._sock._websocket._receive_data(new Uint8Array([
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0]));
expect(client._rfb_tightvnc).to.be.false;
expect(client._rfb_atenikvm).to.be.true;
expect(client._sock).to.have.sent(auth);
});
});
});
describe('SecurityResult', function () {
@ -925,8 +970,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);
@ -956,12 +999,21 @@ 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 handle an ATEN iKVM server initialization', function () {
client._rfb_atenikvm = true;
send_server_init({ true_color: 1, bpp: 32 }, client);
client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]));
expect(client._pixelFormat.bpp).to.equal(16);
expect(client._pixelFormat.depth).to.equal(15);
expect(client._pixelFormat.red_max).to.equal(31);
expect(client._pixelFormat.green_max).to.equal(31);
expect(client._pixelFormat.blue_max).to.equal(31);
expect(client._pixelFormat.red_shift).to.equal(10);
expect(client._pixelFormat.green_shift).to.equal(5);
expect(client._pixelFormat.blue_shift).to.equal(0);
expect(client._pixelFormat.Bpp).to.equal(2);
expect(client._pixelFormat.Bdepth).to.equal(2);
expect(client._rfb_state).to.equal('normal');
});
it('should call the resize callback and resize the display', function () {
@ -985,25 +1037,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 } ] };
@ -1054,10 +1111,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;
@ -1206,22 +1263,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('24bit depth (RGBX888) @ 32bpp [native]', 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 = [
[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('24bit depth (BGRX888) @ 32bpp', function () {
client._convertColor = true;
client._pixelFormat.big_endian = false;
client._pixelFormat.red_shift = 16;
client._pixelFormat.red_max = 255;
client._pixelFormat.green_shift = 8;
client._pixelFormat.green_max = 255;
client._pixelFormat.blue_shift = 0;
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 = [
[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('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('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 () {
@ -1247,18 +1377,18 @@ describe('Remote Frame Buffer Protocol Client', function() {
var rect = [];
rect.push32(2); // 2 subrects
rect.push32(0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
rect.push(0xff); // becomes ff0000ff --> #0000FF color
rect.push(0x00);
rect.push(0x00); // becomes 0000ffff --> #0000FF color
rect.push(0x00);
rect.push(0xff);
rect.push(0xff);
rect.push16(0); // x: 0
rect.push16(0); // y: 0
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.push(0xff);
rect.push16(2); // x: 2
rect.push16(2); // y: 2
rect.push16(2); // width: 2
@ -1283,7 +1413,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 () {
@ -1291,10 +1421,10 @@ describe('Remote Frame Buffer Protocol Client', function() {
var rect = [];
rect.push(0x02 | 0x04 | 0x08); // bg spec, fg spec, anysubrects
rect.push32(0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
rect.push(0xff); // becomes ff0000ff --> #0000FF fg color
rect.push(0x00);
rect.push(0x00); // becomes 0000ffff --> #0000FF fg color
rect.push(0x00);
rect.push(0xff);
rect.push(0xff);
rect.push(2); // 2 subrects
rect.push(0); // x: 0, y: 0
rect.push(1 | (1 << 4)); // width: 2, height: 2
@ -1309,9 +1439,9 @@ describe('Remote Frame Buffer Protocol Client', function() {
var rect = [];
rect.push(0x01); // raw
for (var i = 0; i < target_data.length; i += 4) {
rect.push(target_data[i + 2]);
rect.push(target_data[i + 1]);
rect.push(target_data[i]);
rect.push(target_data[i + 1]);
rect.push(target_data[i + 2]);
rect.push(target_data[i + 3]);
}
send_fbu_msg(info, [rect], client);
@ -1362,16 +1492,16 @@ describe('Remote Frame Buffer Protocol Client', function() {
rect.push(0x02 | 0x08 | 0x10); // bg spec, anysubrects, colouredsubrects
rect.push32(0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
rect.push(2); // 2 subrects
rect.push(0xff); // becomes ff0000ff --> #0000FF fg color
rect.push(0x00);
rect.push(0x00); // becomes 0000ffff --> #0000FF fg color
rect.push(0x00);
rect.push(0xff);
rect.push(0xff);
rect.push(0); // x: 0, y: 0
rect.push(1 | (1 << 4)); // width: 2, height: 2
rect.push(0xff); // becomes ff0000ff --> #0000FF fg color
rect.push(0x00);
rect.push(0x00); // becomes 0000ffff --> #0000FF fg color
rect.push(0x00);
rect.push(0xff);
rect.push(0xff);
rect.push(2 | (2 << 4)); // x: 2, y: 2
rect.push(1 | (1 << 4)); // width: 2, height: 2
send_fbu_msg(info, [rect], client);
@ -1387,10 +1517,10 @@ describe('Remote Frame Buffer Protocol Client', function() {
var rect = [];
rect.push(0x02 | 0x04 | 0x08); // bg spec, fg spec, anysubrects
rect.push32(0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
rect.push(0xff); // becomes ff0000ff --> #0000FF fg color
rect.push(0x00);
rect.push(0x00); // becomes 0000ffff --> #0000FF fg color
rect.push(0x00);
rect.push(0xff);
rect.push(0xff);
rect.push(8); // 8 subrects
var i;
for (i = 0; i < 4; i++) {
@ -1427,6 +1557,96 @@ describe('Remote Frame Buffer Protocol Client', function() {
// TODO(directxman12): test this
});
describe('the ATEN encoding handler', function () {
var client;
beforeEach(function () {
client = make_rfb();
client.connect('host', 8675);
client._sock._websocket._open();
client._rfb_state = 'normal';
client._fb_name = 'some device';
client._rfb_atenikvm = true;
// start large, then go small
client._fb_width = 10000;
client._fb_height = 10000;
client._display._fb_width = 10000;
client._display._fb_height = 10000;
client._display._viewportLoc.w = 10000;
client._display._viewportLoc.h = 10000;
client._convertColor = true;
client._pixelFormat.Bpp = 2;
client._pixelFormat.big_endian = false;
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 aten_target_data_arr = [0xa8, 0xe8, 0xf8, 0xff];
var aten_target_data;
before(function () {
for (var i = 0; i < 10; i++) {
aten_target_data_arr = aten_target_data_arr.concat(aten_target_data_arr);
}
aten_target_data = new Uint8Array(aten_target_data_arr);
});
it('should handle subtype subrect encoding', function () {
var info = [{ x: 0, y: 0, width: 32, height: 32, encoding: 0x59 }];
var rect = [];
rect.push32(0); // padding
rect.push32(2082); // 10 + 32x32x2Bpp + 6*(num of subrects)
rect.push8(0); // type
rect.push8(0); // padding
rect.push32(4); // num of subrects (32/16)*(32/16)
rect.push32(2082); // length (again)
for (var y = 0; y < 2; y++) {
for (var x = 0; x < 2; x++) {
rect.push16(0); // a
rect.push16(0); // b
rect.push8(y);
rect.push8(x);
for (var i = 0; i < 16*16; i++) {
rect.push16(0xbf57);
}
}
}
send_fbu_msg(info, [rect], client);
expect(client._display).to.have.displayed(aten_target_data);
});
it('should handle subtype RAW encoding', function () {
// do not use encoding=0x59 here, as rfb.js should override it
var info = [{ x: 0, y: 0, width: 32, height: 32, encoding: 0x00 }];
var rect = [];
rect.push32(0); // padding
rect.push32(2058); // 10 + 32x32x2Bpp
rect.push8(1); // type
rect.push8(0); // padding
rect.push32(0); // padding
rect.push32(2058); // length (again)
for (var i = 0; i < 32*32; i++) {
rect.push16(0xbf57);
}
send_fbu_msg(info, [rect], client);
expect(client._display).to.have.displayed(aten_target_data);
});
});
it('should handle the DesktopSize pseduo-encoding', function () {
client.set_onFBResize(sinon.spy());
sinon.spy(client._display, 'resize');

View File

@ -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>

View File

@ -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),