Merge pull request #1299 from CendioNiko/vmwarecursor
Add support for VMware cursor encoding
This commit is contained in:
commit
3055307d3d
|
@ -27,6 +27,7 @@ export const encodings = {
|
||||||
pseudoEncodingContinuousUpdates: -313,
|
pseudoEncodingContinuousUpdates: -313,
|
||||||
pseudoEncodingCompressLevel9: -247,
|
pseudoEncodingCompressLevel9: -247,
|
||||||
pseudoEncodingCompressLevel0: -256,
|
pseudoEncodingCompressLevel0: -256,
|
||||||
|
pseudoEncodingVMwareCursor: 0x574d5664
|
||||||
};
|
};
|
||||||
|
|
||||||
export function encodingName(num) {
|
export function encodingName(num) {
|
||||||
|
|
120
core/rfb.js
120
core/rfb.js
|
@ -1244,6 +1244,7 @@ export default class RFB extends EventTargetMixin {
|
||||||
encs.push(encodings.pseudoEncodingDesktopName);
|
encs.push(encodings.pseudoEncodingDesktopName);
|
||||||
|
|
||||||
if (this._fb_depth == 24) {
|
if (this._fb_depth == 24) {
|
||||||
|
encs.push(encodings.pseudoEncodingVMwareCursor);
|
||||||
encs.push(encodings.pseudoEncodingCursor);
|
encs.push(encodings.pseudoEncodingCursor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1493,6 +1494,9 @@ export default class RFB extends EventTargetMixin {
|
||||||
this._FBU.rects = 1; // Will be decreased when we return
|
this._FBU.rects = 1; // Will be decreased when we return
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
case encodings.pseudoEncodingVMwareCursor:
|
||||||
|
return this._handleVMwareCursor();
|
||||||
|
|
||||||
case encodings.pseudoEncodingCursor:
|
case encodings.pseudoEncodingCursor:
|
||||||
return this._handleCursor();
|
return this._handleCursor();
|
||||||
|
|
||||||
|
@ -1523,6 +1527,122 @@ export default class RFB extends EventTargetMixin {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_handleVMwareCursor() {
|
||||||
|
const hotx = this._FBU.x; // hotspot-x
|
||||||
|
const hoty = this._FBU.y; // hotspot-y
|
||||||
|
const w = this._FBU.width;
|
||||||
|
const h = this._FBU.height;
|
||||||
|
if (this._sock.rQwait("VMware cursor encoding", 1)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cursor_type = this._sock.rQshift8();
|
||||||
|
|
||||||
|
this._sock.rQshift8(); //Padding
|
||||||
|
|
||||||
|
let rgba;
|
||||||
|
const bytesPerPixel = 4;
|
||||||
|
|
||||||
|
//Classic cursor
|
||||||
|
if (cursor_type == 0) {
|
||||||
|
//Used to filter away unimportant bits.
|
||||||
|
//OR is used for correct conversion in js.
|
||||||
|
const PIXEL_MASK = 0xffffff00 | 0;
|
||||||
|
rgba = new Array(w * h * bytesPerPixel);
|
||||||
|
|
||||||
|
if (this._sock.rQwait("VMware cursor classic encoding",
|
||||||
|
(w * h * bytesPerPixel) * 2, 2)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let and_mask = new Array(w * h);
|
||||||
|
for (let pixel = 0; pixel < (w * h); pixel++) {
|
||||||
|
and_mask[pixel] = this._sock.rQshift32();
|
||||||
|
}
|
||||||
|
|
||||||
|
let xor_mask = new Array(w * h);
|
||||||
|
for (let pixel = 0; pixel < (w * h); pixel++) {
|
||||||
|
xor_mask[pixel] = this._sock.rQshift32();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let pixel = 0; pixel < (w * h); pixel++) {
|
||||||
|
if (and_mask[pixel] == 0) {
|
||||||
|
//Fully opaque pixel
|
||||||
|
let bgr = xor_mask[pixel];
|
||||||
|
let r = bgr >> 8 & 0xff;
|
||||||
|
let g = bgr >> 16 & 0xff;
|
||||||
|
let b = bgr >> 24 & 0xff;
|
||||||
|
|
||||||
|
rgba[(pixel * bytesPerPixel) ] = r; //r
|
||||||
|
rgba[(pixel * bytesPerPixel) + 1 ] = g; //g
|
||||||
|
rgba[(pixel * bytesPerPixel) + 2 ] = b; //b
|
||||||
|
rgba[(pixel * bytesPerPixel) + 3 ] = 0xff; //a
|
||||||
|
|
||||||
|
} else if ((and_mask[pixel] & PIXEL_MASK) ==
|
||||||
|
PIXEL_MASK) {
|
||||||
|
//Only screen value matters, no mouse colouring
|
||||||
|
if (xor_mask[pixel] == 0) {
|
||||||
|
//Transparent pixel
|
||||||
|
rgba[(pixel * bytesPerPixel) ] = 0x00;
|
||||||
|
rgba[(pixel * bytesPerPixel) + 1 ] = 0x00;
|
||||||
|
rgba[(pixel * bytesPerPixel) + 2 ] = 0x00;
|
||||||
|
rgba[(pixel * bytesPerPixel) + 3 ] = 0x00;
|
||||||
|
|
||||||
|
} else if ((xor_mask[pixel] & PIXEL_MASK) ==
|
||||||
|
PIXEL_MASK) {
|
||||||
|
//Inverted pixel, not supported in browsers.
|
||||||
|
//Fully opaque instead.
|
||||||
|
rgba[(pixel * bytesPerPixel) ] = 0x00;
|
||||||
|
rgba[(pixel * bytesPerPixel) + 1 ] = 0x00;
|
||||||
|
rgba[(pixel * bytesPerPixel) + 2 ] = 0x00;
|
||||||
|
rgba[(pixel * bytesPerPixel) + 3 ] = 0xff;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
//Unhandled xor_mask
|
||||||
|
rgba[(pixel * bytesPerPixel) ] = 0x00;
|
||||||
|
rgba[(pixel * bytesPerPixel) + 1 ] = 0x00;
|
||||||
|
rgba[(pixel * bytesPerPixel) + 2 ] = 0x00;
|
||||||
|
rgba[(pixel * bytesPerPixel) + 3 ] = 0xff;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
//Unhandled and_mask
|
||||||
|
rgba[(pixel * bytesPerPixel) ] = 0x00;
|
||||||
|
rgba[(pixel * bytesPerPixel) + 1 ] = 0x00;
|
||||||
|
rgba[(pixel * bytesPerPixel) + 2 ] = 0x00;
|
||||||
|
rgba[(pixel * bytesPerPixel) + 3 ] = 0xff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Alpha cursor.
|
||||||
|
} else if (cursor_type == 1) {
|
||||||
|
if (this._sock.rQwait("VMware cursor alpha encoding",
|
||||||
|
(w * h * 4), 2)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
rgba = new Array(w * h * bytesPerPixel);
|
||||||
|
|
||||||
|
for (let pixel = 0; pixel < (w * h); pixel++) {
|
||||||
|
let data = this._sock.rQshift32();
|
||||||
|
|
||||||
|
rgba[(pixel * 4) ] = data >> 8 & 0xff; //r
|
||||||
|
rgba[(pixel * 4) + 1 ] = data >> 16 & 0xff; //g
|
||||||
|
rgba[(pixel * 4) + 2 ] = data >> 24 & 0xff; //b
|
||||||
|
rgba[(pixel * 4) + 3 ] = data & 0xff; //a
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
Log.Warn("The given cursor type is not supported: "
|
||||||
|
+ cursor_type + " given.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._updateCursor(rgba, hotx, hoty, w, h);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
_handleCursor() {
|
_handleCursor() {
|
||||||
const hotx = this._FBU.x; // hotspot-x
|
const hotx = this._FBU.x; // hotspot-x
|
||||||
const hoty = this._FBU.y; // hotspot-y
|
const hoty = this._FBU.y; // hotspot-y
|
||||||
|
|
|
@ -2132,6 +2132,170 @@ describe('Remote Frame Buffer Protocol Client', function () {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('the VMware Cursor pseudo-encoding handler', function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
sinon.spy(client._cursor, 'change');
|
||||||
|
});
|
||||||
|
afterEach(function () {
|
||||||
|
client._cursor.change.resetHistory();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle the VMware cursor pseudo-encoding', function () {
|
||||||
|
let data = [0x00, 0x00, 0xff, 0,
|
||||||
|
0x00, 0xff, 0x00, 0,
|
||||||
|
0x00, 0xff, 0x00, 0,
|
||||||
|
0x00, 0x00, 0xff, 0];
|
||||||
|
let rect = [];
|
||||||
|
push8(rect, 0);
|
||||||
|
push8(rect, 0);
|
||||||
|
|
||||||
|
//AND-mask
|
||||||
|
for (let i = 0; i < data.length; i++) {
|
||||||
|
push8(rect, data[i]);
|
||||||
|
}
|
||||||
|
//XOR-mask
|
||||||
|
for (let i = 0; i < data.length; i++) {
|
||||||
|
push8(rect, data[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
send_fbu_msg([{ x: 0, y: 0, width: 2, height: 2,
|
||||||
|
encoding: 0x574d5664}],
|
||||||
|
[rect], client);
|
||||||
|
expect(client._FBU.rects).to.equal(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle insufficient cursor pixel data', function () {
|
||||||
|
|
||||||
|
// Specified 14x23 pixels for the cursor,
|
||||||
|
// but only send 2x2 pixels worth of data
|
||||||
|
let w = 14;
|
||||||
|
let h = 23;
|
||||||
|
let data = [0x00, 0x00, 0xff, 0,
|
||||||
|
0x00, 0xff, 0x00, 0];
|
||||||
|
let rect = [];
|
||||||
|
|
||||||
|
push8(rect, 0);
|
||||||
|
push8(rect, 0);
|
||||||
|
|
||||||
|
//AND-mask
|
||||||
|
for (let i = 0; i < data.length; i++) {
|
||||||
|
push8(rect, data[i]);
|
||||||
|
}
|
||||||
|
//XOR-mask
|
||||||
|
for (let i = 0; i < data.length; i++) {
|
||||||
|
push8(rect, data[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
send_fbu_msg([{ x: 0, y: 0, width: w, height: h,
|
||||||
|
encoding: 0x574d5664}],
|
||||||
|
[rect], client);
|
||||||
|
|
||||||
|
// expect one FBU to remain unhandled
|
||||||
|
expect(client._FBU.rects).to.equal(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update the cursor when type is classic', function () {
|
||||||
|
let and_mask =
|
||||||
|
[0xff, 0xff, 0xff, 0xff, //Transparent
|
||||||
|
0xff, 0xff, 0xff, 0xff, //Transparent
|
||||||
|
0x00, 0x00, 0x00, 0x00, //Opaque
|
||||||
|
0xff, 0xff, 0xff, 0xff]; //Inverted
|
||||||
|
|
||||||
|
let xor_mask =
|
||||||
|
[0x00, 0x00, 0x00, 0x00, //Transparent
|
||||||
|
0x00, 0x00, 0x00, 0x00, //Transparent
|
||||||
|
0x11, 0x22, 0x33, 0x44, //Opaque
|
||||||
|
0xff, 0xff, 0xff, 0x44]; //Inverted
|
||||||
|
|
||||||
|
let rect = [];
|
||||||
|
push8(rect, 0); //cursor_type
|
||||||
|
push8(rect, 0); //padding
|
||||||
|
let hotx = 0;
|
||||||
|
let hoty = 0;
|
||||||
|
let w = 2;
|
||||||
|
let h = 2;
|
||||||
|
|
||||||
|
//AND-mask
|
||||||
|
for (let i = 0; i < and_mask.length; i++) {
|
||||||
|
push8(rect, and_mask[i]);
|
||||||
|
}
|
||||||
|
//XOR-mask
|
||||||
|
for (let i = 0; i < xor_mask.length; i++) {
|
||||||
|
push8(rect, xor_mask[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let expected_rgba = [0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x33, 0x22, 0x11, 0xff,
|
||||||
|
0x00, 0x00, 0x00, 0xff];
|
||||||
|
|
||||||
|
send_fbu_msg([{ x: hotx, y: hoty,
|
||||||
|
width: w, height: h,
|
||||||
|
encoding: 0x574d5664}],
|
||||||
|
[rect], client);
|
||||||
|
|
||||||
|
expect(client._cursor.change)
|
||||||
|
.to.have.been.calledOnce;
|
||||||
|
expect(client._cursor.change)
|
||||||
|
.to.have.been.calledWith(expected_rgba,
|
||||||
|
hotx, hoty,
|
||||||
|
w, h);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update the cursor when type is alpha', function () {
|
||||||
|
let data = [0xee, 0x55, 0xff, 0x00, // bgra
|
||||||
|
0x00, 0xff, 0x00, 0xff,
|
||||||
|
0x00, 0xff, 0x00, 0x22,
|
||||||
|
0x00, 0xff, 0x00, 0x22,
|
||||||
|
0x00, 0xff, 0x00, 0x22,
|
||||||
|
0x00, 0x00, 0xff, 0xee];
|
||||||
|
let rect = [];
|
||||||
|
push8(rect, 1); //cursor_type
|
||||||
|
push8(rect, 0); //padding
|
||||||
|
let hotx = 0;
|
||||||
|
let hoty = 0;
|
||||||
|
let w = 3;
|
||||||
|
let h = 2;
|
||||||
|
|
||||||
|
for (let i = 0; i < data.length; i++) {
|
||||||
|
push8(rect, data[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let expected_rgba = [0xff, 0x55, 0xee, 0x00,
|
||||||
|
0x00, 0xff, 0x00, 0xff,
|
||||||
|
0x00, 0xff, 0x00, 0x22,
|
||||||
|
0x00, 0xff, 0x00, 0x22,
|
||||||
|
0x00, 0xff, 0x00, 0x22,
|
||||||
|
0xff, 0x00, 0x00, 0xee];
|
||||||
|
|
||||||
|
send_fbu_msg([{ x: hotx, y: hoty,
|
||||||
|
width: w, height: h,
|
||||||
|
encoding: 0x574d5664}],
|
||||||
|
[rect], client);
|
||||||
|
|
||||||
|
expect(client._cursor.change)
|
||||||
|
.to.have.been.calledOnce;
|
||||||
|
expect(client._cursor.change)
|
||||||
|
.to.have.been.calledWith(expected_rgba,
|
||||||
|
hotx, hoty,
|
||||||
|
w, h);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not update cursor when incorrect cursor type given', function () {
|
||||||
|
let rect = [];
|
||||||
|
push8(rect, 3); // invalid cursor type
|
||||||
|
push8(rect, 0); // padding
|
||||||
|
|
||||||
|
client._cursor.change.resetHistory();
|
||||||
|
send_fbu_msg([{ x: 0, y: 0, width: 2, height: 2,
|
||||||
|
encoding: 0x574d5664}],
|
||||||
|
[rect], client);
|
||||||
|
|
||||||
|
expect(client._cursor.change)
|
||||||
|
.to.not.have.been.called;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should handle the last_rect pseudo-encoding', function () {
|
it('should handle the last_rect pseudo-encoding', function () {
|
||||||
send_fbu_msg([{ x: 0, y: 0, width: 0, height: 0, encoding: -224}], [[]], client, 100);
|
send_fbu_msg([{ x: 0, y: 0, width: 0, height: 0, encoding: -224}], [[]], client, 100);
|
||||||
expect(client._FBU.rects).to.equal(0);
|
expect(client._FBU.rects).to.equal(0);
|
||||||
|
|
Loading…
Reference in New Issue