Skip to content

Commit 3055307

Browse files
authored
Merge pull request #1299 from CendioNiko/vmwarecursor
Add support for VMware cursor encoding
2 parents 8dc47f3 + 296ba51 commit 3055307

File tree

3 files changed

+285
-0
lines changed

3 files changed

+285
-0
lines changed

core/encodings.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export const encodings = {
2727
pseudoEncodingContinuousUpdates: -313,
2828
pseudoEncodingCompressLevel9: -247,
2929
pseudoEncodingCompressLevel0: -256,
30+
pseudoEncodingVMwareCursor: 0x574d5664
3031
};
3132

3233
export function encodingName(num) {

core/rfb.js

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1244,6 +1244,7 @@ export default class RFB extends EventTargetMixin {
12441244
encs.push(encodings.pseudoEncodingDesktopName);
12451245

12461246
if (this._fb_depth == 24) {
1247+
encs.push(encodings.pseudoEncodingVMwareCursor);
12471248
encs.push(encodings.pseudoEncodingCursor);
12481249
}
12491250

@@ -1493,6 +1494,9 @@ export default class RFB extends EventTargetMixin {
14931494
this._FBU.rects = 1; // Will be decreased when we return
14941495
return true;
14951496

1497+
case encodings.pseudoEncodingVMwareCursor:
1498+
return this._handleVMwareCursor();
1499+
14961500
case encodings.pseudoEncodingCursor:
14971501
return this._handleCursor();
14981502

@@ -1523,6 +1527,122 @@ export default class RFB extends EventTargetMixin {
15231527
}
15241528
}
15251529

1530+
_handleVMwareCursor() {
1531+
const hotx = this._FBU.x; // hotspot-x
1532+
const hoty = this._FBU.y; // hotspot-y
1533+
const w = this._FBU.width;
1534+
const h = this._FBU.height;
1535+
if (this._sock.rQwait("VMware cursor encoding", 1)) {
1536+
return false;
1537+
}
1538+
1539+
const cursor_type = this._sock.rQshift8();
1540+
1541+
this._sock.rQshift8(); //Padding
1542+
1543+
let rgba;
1544+
const bytesPerPixel = 4;
1545+
1546+
//Classic cursor
1547+
if (cursor_type == 0) {
1548+
//Used to filter away unimportant bits.
1549+
//OR is used for correct conversion in js.
1550+
const PIXEL_MASK = 0xffffff00 | 0;
1551+
rgba = new Array(w * h * bytesPerPixel);
1552+
1553+
if (this._sock.rQwait("VMware cursor classic encoding",
1554+
(w * h * bytesPerPixel) * 2, 2)) {
1555+
return false;
1556+
}
1557+
1558+
let and_mask = new Array(w * h);
1559+
for (let pixel = 0; pixel < (w * h); pixel++) {
1560+
and_mask[pixel] = this._sock.rQshift32();
1561+
}
1562+
1563+
let xor_mask = new Array(w * h);
1564+
for (let pixel = 0; pixel < (w * h); pixel++) {
1565+
xor_mask[pixel] = this._sock.rQshift32();
1566+
}
1567+
1568+
for (let pixel = 0; pixel < (w * h); pixel++) {
1569+
if (and_mask[pixel] == 0) {
1570+
//Fully opaque pixel
1571+
let bgr = xor_mask[pixel];
1572+
let r = bgr >> 8 & 0xff;
1573+
let g = bgr >> 16 & 0xff;
1574+
let b = bgr >> 24 & 0xff;
1575+
1576+
rgba[(pixel * bytesPerPixel) ] = r; //r
1577+
rgba[(pixel * bytesPerPixel) + 1 ] = g; //g
1578+
rgba[(pixel * bytesPerPixel) + 2 ] = b; //b
1579+
rgba[(pixel * bytesPerPixel) + 3 ] = 0xff; //a
1580+
1581+
} else if ((and_mask[pixel] & PIXEL_MASK) ==
1582+
PIXEL_MASK) {
1583+
//Only screen value matters, no mouse colouring
1584+
if (xor_mask[pixel] == 0) {
1585+
//Transparent pixel
1586+
rgba[(pixel * bytesPerPixel) ] = 0x00;
1587+
rgba[(pixel * bytesPerPixel) + 1 ] = 0x00;
1588+
rgba[(pixel * bytesPerPixel) + 2 ] = 0x00;
1589+
rgba[(pixel * bytesPerPixel) + 3 ] = 0x00;
1590+
1591+
} else if ((xor_mask[pixel] & PIXEL_MASK) ==
1592+
PIXEL_MASK) {
1593+
//Inverted pixel, not supported in browsers.
1594+
//Fully opaque instead.
1595+
rgba[(pixel * bytesPerPixel) ] = 0x00;
1596+
rgba[(pixel * bytesPerPixel) + 1 ] = 0x00;
1597+
rgba[(pixel * bytesPerPixel) + 2 ] = 0x00;
1598+
rgba[(pixel * bytesPerPixel) + 3 ] = 0xff;
1599+
1600+
} else {
1601+
//Unhandled xor_mask
1602+
rgba[(pixel * bytesPerPixel) ] = 0x00;
1603+
rgba[(pixel * bytesPerPixel) + 1 ] = 0x00;
1604+
rgba[(pixel * bytesPerPixel) + 2 ] = 0x00;
1605+
rgba[(pixel * bytesPerPixel) + 3 ] = 0xff;
1606+
}
1607+
1608+
} else {
1609+
//Unhandled and_mask
1610+
rgba[(pixel * bytesPerPixel) ] = 0x00;
1611+
rgba[(pixel * bytesPerPixel) + 1 ] = 0x00;
1612+
rgba[(pixel * bytesPerPixel) + 2 ] = 0x00;
1613+
rgba[(pixel * bytesPerPixel) + 3 ] = 0xff;
1614+
}
1615+
}
1616+
1617+
//Alpha cursor.
1618+
} else if (cursor_type == 1) {
1619+
if (this._sock.rQwait("VMware cursor alpha encoding",
1620+
(w * h * 4), 2)) {
1621+
return false;
1622+
}
1623+
1624+
rgba = new Array(w * h * bytesPerPixel);
1625+
1626+
for (let pixel = 0; pixel < (w * h); pixel++) {
1627+
let data = this._sock.rQshift32();
1628+
1629+
rgba[(pixel * 4) ] = data >> 8 & 0xff; //r
1630+
rgba[(pixel * 4) + 1 ] = data >> 16 & 0xff; //g
1631+
rgba[(pixel * 4) + 2 ] = data >> 24 & 0xff; //b
1632+
rgba[(pixel * 4) + 3 ] = data & 0xff; //a
1633+
}
1634+
1635+
} else {
1636+
Log.Warn("The given cursor type is not supported: "
1637+
+ cursor_type + " given.");
1638+
return false;
1639+
}
1640+
1641+
this._updateCursor(rgba, hotx, hoty, w, h);
1642+
1643+
return true;
1644+
}
1645+
15261646
_handleCursor() {
15271647
const hotx = this._FBU.x; // hotspot-x
15281648
const hoty = this._FBU.y; // hotspot-y

tests/test.rfb.js

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2132,6 +2132,170 @@ describe('Remote Frame Buffer Protocol Client', function () {
21322132
});
21332133
});
21342134

2135+
describe('the VMware Cursor pseudo-encoding handler', function () {
2136+
beforeEach(function () {
2137+
sinon.spy(client._cursor, 'change');
2138+
});
2139+
afterEach(function () {
2140+
client._cursor.change.resetHistory();
2141+
});
2142+
2143+
it('should handle the VMware cursor pseudo-encoding', function () {
2144+
let data = [0x00, 0x00, 0xff, 0,
2145+
0x00, 0xff, 0x00, 0,
2146+
0x00, 0xff, 0x00, 0,
2147+
0x00, 0x00, 0xff, 0];
2148+
let rect = [];
2149+
push8(rect, 0);
2150+
push8(rect, 0);
2151+
2152+
//AND-mask
2153+
for (let i = 0; i < data.length; i++) {
2154+
push8(rect, data[i]);
2155+
}
2156+
//XOR-mask
2157+
for (let i = 0; i < data.length; i++) {
2158+
push8(rect, data[i]);
2159+
}
2160+
2161+
send_fbu_msg([{ x: 0, y: 0, width: 2, height: 2,
2162+
encoding: 0x574d5664}],
2163+
[rect], client);
2164+
expect(client._FBU.rects).to.equal(0);
2165+
});
2166+
2167+
it('should handle insufficient cursor pixel data', function () {
2168+
2169+
// Specified 14x23 pixels for the cursor,
2170+
// but only send 2x2 pixels worth of data
2171+
let w = 14;
2172+
let h = 23;
2173+
let data = [0x00, 0x00, 0xff, 0,
2174+
0x00, 0xff, 0x00, 0];
2175+
let rect = [];
2176+
2177+
push8(rect, 0);
2178+
push8(rect, 0);
2179+
2180+
//AND-mask
2181+
for (let i = 0; i < data.length; i++) {
2182+
push8(rect, data[i]);
2183+
}
2184+
//XOR-mask
2185+
for (let i = 0; i < data.length; i++) {
2186+
push8(rect, data[i]);
2187+
}
2188+
2189+
send_fbu_msg([{ x: 0, y: 0, width: w, height: h,
2190+
encoding: 0x574d5664}],
2191+
[rect], client);
2192+
2193+
// expect one FBU to remain unhandled
2194+
expect(client._FBU.rects).to.equal(1);
2195+
});
2196+
2197+
it('should update the cursor when type is classic', function () {
2198+
let and_mask =
2199+
[0xff, 0xff, 0xff, 0xff, //Transparent
2200+
0xff, 0xff, 0xff, 0xff, //Transparent
2201+
0x00, 0x00, 0x00, 0x00, //Opaque
2202+
0xff, 0xff, 0xff, 0xff]; //Inverted
2203+
2204+
let xor_mask =
2205+
[0x00, 0x00, 0x00, 0x00, //Transparent
2206+
0x00, 0x00, 0x00, 0x00, //Transparent
2207+
0x11, 0x22, 0x33, 0x44, //Opaque
2208+
0xff, 0xff, 0xff, 0x44]; //Inverted
2209+
2210+
let rect = [];
2211+
push8(rect, 0); //cursor_type
2212+
push8(rect, 0); //padding
2213+
let hotx = 0;
2214+
let hoty = 0;
2215+
let w = 2;
2216+
let h = 2;
2217+
2218+
//AND-mask
2219+
for (let i = 0; i < and_mask.length; i++) {
2220+
push8(rect, and_mask[i]);
2221+
}
2222+
//XOR-mask
2223+
for (let i = 0; i < xor_mask.length; i++) {
2224+
push8(rect, xor_mask[i]);
2225+
}
2226+
2227+
let expected_rgba = [0x00, 0x00, 0x00, 0x00,
2228+
0x00, 0x00, 0x00, 0x00,
2229+
0x33, 0x22, 0x11, 0xff,
2230+
0x00, 0x00, 0x00, 0xff];
2231+
2232+
send_fbu_msg([{ x: hotx, y: hoty,
2233+
width: w, height: h,
2234+
encoding: 0x574d5664}],
2235+
[rect], client);
2236+
2237+
expect(client._cursor.change)
2238+
.to.have.been.calledOnce;
2239+
expect(client._cursor.change)
2240+
.to.have.been.calledWith(expected_rgba,
2241+
hotx, hoty,
2242+
w, h);
2243+
});
2244+
2245+
it('should update the cursor when type is alpha', function () {
2246+
let data = [0xee, 0x55, 0xff, 0x00, // bgra
2247+
0x00, 0xff, 0x00, 0xff,
2248+
0x00, 0xff, 0x00, 0x22,
2249+
0x00, 0xff, 0x00, 0x22,
2250+
0x00, 0xff, 0x00, 0x22,
2251+
0x00, 0x00, 0xff, 0xee];
2252+
let rect = [];
2253+
push8(rect, 1); //cursor_type
2254+
push8(rect, 0); //padding
2255+
let hotx = 0;
2256+
let hoty = 0;
2257+
let w = 3;
2258+
let h = 2;
2259+
2260+
for (let i = 0; i < data.length; i++) {
2261+
push8(rect, data[i]);
2262+
}
2263+
2264+
let expected_rgba = [0xff, 0x55, 0xee, 0x00,
2265+
0x00, 0xff, 0x00, 0xff,
2266+
0x00, 0xff, 0x00, 0x22,
2267+
0x00, 0xff, 0x00, 0x22,
2268+
0x00, 0xff, 0x00, 0x22,
2269+
0xff, 0x00, 0x00, 0xee];
2270+
2271+
send_fbu_msg([{ x: hotx, y: hoty,
2272+
width: w, height: h,
2273+
encoding: 0x574d5664}],
2274+
[rect], client);
2275+
2276+
expect(client._cursor.change)
2277+
.to.have.been.calledOnce;
2278+
expect(client._cursor.change)
2279+
.to.have.been.calledWith(expected_rgba,
2280+
hotx, hoty,
2281+
w, h);
2282+
});
2283+
2284+
it('should not update cursor when incorrect cursor type given', function () {
2285+
let rect = [];
2286+
push8(rect, 3); // invalid cursor type
2287+
push8(rect, 0); // padding
2288+
2289+
client._cursor.change.resetHistory();
2290+
send_fbu_msg([{ x: 0, y: 0, width: 2, height: 2,
2291+
encoding: 0x574d5664}],
2292+
[rect], client);
2293+
2294+
expect(client._cursor.change)
2295+
.to.not.have.been.called;
2296+
});
2297+
});
2298+
21352299
it('should handle the last_rect pseudo-encoding', function () {
21362300
send_fbu_msg([{ x: 0, y: 0, width: 0, height: 0, encoding: -224}], [[]], client, 100);
21372301
expect(client._FBU.rects).to.equal(0);

0 commit comments

Comments
 (0)