diff --git a/core/encodings.js b/core/encodings.js index bf25ac917..7afcb17fc 100644 --- a/core/encodings.js +++ b/core/encodings.js @@ -30,6 +30,7 @@ export const encodings = { pseudoEncodingXvp: -309, pseudoEncodingFence: -312, pseudoEncodingContinuousUpdates: -313, + pseudoEncodingExtendedMouseButtons: -316, pseudoEncodingCompressLevel9: -247, pseudoEncodingCompressLevel0: -256, pseudoEncodingVMwareCursor: 0x574d5664, diff --git a/core/rfb.js b/core/rfb.js index 89e9197d2..57f025814 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -152,6 +152,8 @@ export default class RFB extends EventTargetMixin { this._qemuExtKeyEventSupported = false; + this._extendedPointerEventSupported = false; + this._clipboardText = null; this._clipboardServerCapabilitiesActions = {}; this._clipboardServerCapabilitiesFormats = {}; @@ -1051,10 +1053,11 @@ export default class RFB extends EventTargetMixin { 1: 1 << 2, // Right 2: 1 << 1, // Middle 3: 1 << 7, // Back + 4: 1 << 8, // Forward }; let bmask = 0; - for (let i = 0; i < 4; i++) { + for (let i = 0; i < 5; i++) { if (buttons & (1 << i)) { bmask |= buttonMaskMap[i]; } @@ -1189,8 +1192,20 @@ export default class RFB extends EventTargetMixin { if (this._rfbConnectionState !== 'connected') { return; } if (this._viewOnly) { return; } // View only, skip mouse events - RFB.messages.pointerEvent(this._sock, this._display.absX(x), - this._display.absY(y), mask); + // Highest bit in mask is never sent to the server + if (mask & 0x8000) { + throw new Error("Illegal mouse button mask (mask: " + mask + ")"); + } + + let extendedMouseButtons = mask & 0x7f80; + + if (this._extendedPointerEventSupported && extendedMouseButtons) { + RFB.messages.extendedPointerEvent(this._sock, this._display.absX(x), + this._display.absY(y), mask); + } else { + RFB.messages.pointerEvent(this._sock, this._display.absX(x), + this._display.absY(y), mask); + } } _handleWheel(ev) { @@ -2229,6 +2244,7 @@ export default class RFB extends EventTargetMixin { encs.push(encodings.pseudoEncodingContinuousUpdates); encs.push(encodings.pseudoEncodingDesktopName); encs.push(encodings.pseudoEncodingExtendedClipboard); + encs.push(encodings.pseudoEncodingExtendedMouseButtons); if (this._fbDepth == 24) { encs.push(encodings.pseudoEncodingVMwareCursor); @@ -2658,6 +2674,10 @@ export default class RFB extends EventTargetMixin { case encodings.pseudoEncodingExtendedDesktopSize: return this._handleExtendedDesktopSize(); + case encodings.pseudoEncodingExtendedMouseButtons: + this._extendedPointerEventSupported = true; + return true; + case encodings.pseudoEncodingQEMULedEvent: return this._handleLedEvent(); @@ -3067,6 +3087,10 @@ RFB.messages = { pointerEvent(sock, x, y, mask) { sock.sQpush8(5); // msg-type + // Marker bit must be set to 0, otherwise the server might + // confuse the marker bit with the highest bit in a normal + // PointerEvent message. + mask = mask & 0x7f; sock.sQpush8(mask); sock.sQpush16(x); @@ -3075,6 +3099,27 @@ RFB.messages = { sock.flush(); }, + extendedPointerEvent(sock, x, y, mask) { + sock.sQpush8(5); // msg-type + + let higherBits = (mask >> 7) & 0xff; + + // Bits 2-7 are reserved + if (higherBits & 0xfc) { + throw new Error("Invalid mouse button mask: " + mask); + } + + let lowerBits = mask & 0x7f; + lowerBits |= 0x80; // Set marker bit to 1 + + sock.sQpush8(lowerBits); + sock.sQpush16(x); + sock.sQpush16(y); + sock.sQpush8(higherBits); + + sock.flush(); + }, + // Used to build Notify and Request data. _buildExtendedClipboardFlags(actions, formats) { let data = new Uint8Array(4); diff --git a/tests/test.rfb.js b/tests/test.rfb.js index 62f2a6498..8cdd2e36b 100644 --- a/tests/test.rfb.js +++ b/tests/test.rfb.js @@ -233,6 +233,30 @@ describe('Remote Frame Buffer protocol client', function () { client._canvas.dispatchEvent(ev); } + function sendFbuMsg(rectInfo, rectData, client, rectCnt) { + let data = []; + + if (!rectCnt || rectCnt > -1) { + // header + data.push(0); // msg type + data.push(0); // padding + push16(data, rectCnt || rectData.length); + } + + for (let i = 0; i < rectData.length; i++) { + if (rectInfo[i]) { + push16(data, rectInfo[i].x); + push16(data, rectInfo[i].y); + push16(data, rectInfo[i].width); + push16(data, rectInfo[i].height); + push32(data, rectInfo[i].encoding); + } + data = data.concat(rectData[i]); + } + + client._sock._websocket._receiveData(new Uint8Array(data)); + } + describe('Connecting/Disconnecting', function () { describe('#RFB (constructor)', function () { let open, attach; @@ -2757,30 +2781,6 @@ describe('Remote Frame Buffer protocol client', function () { }); describe('Framebuffer update handling', function () { - function sendFbuMsg(rectInfo, rectData, client, rectCnt) { - let data = []; - - if (!rectCnt || rectCnt > -1) { - // header - data.push(0); // msg type - data.push(0); // padding - push16(data, rectCnt || rectData.length); - } - - for (let i = 0; i < rectData.length; i++) { - if (rectInfo[i]) { - push16(data, rectInfo[i].x); - push16(data, rectInfo[i].y); - push16(data, rectInfo[i].width); - push16(data, rectInfo[i].height); - push32(data, rectInfo[i].encoding); - } - data = data.concat(rectData[i]); - } - - client._sock._websocket._receiveData(new Uint8Array(data)); - } - it('should send an update request if there is sufficient data', function () { let esock = new Websock(); let ews = new FakeWebSocket(); @@ -3265,6 +3265,7 @@ describe('Remote Frame Buffer protocol client', function () { expect(spy).to.have.been.calledOnce; expect(spy.args[0][0].detail.name).to.equal('som€ nam€'); }); + }); describe('Caps Lock and Num Lock remote fixup', function () { @@ -3757,6 +3758,7 @@ describe('Remote Frame Buffer protocol client', function () { describe('Asynchronous events', function () { let client; let pointerEvent; + let extendedPointerEvent; let keyEvent; let qemuKeyEvent; @@ -3770,12 +3772,14 @@ describe('Remote Frame Buffer protocol client', function () { client.focusOnClick = false; pointerEvent = sinon.spy(RFB.messages, 'pointerEvent'); + extendedPointerEvent = sinon.spy(RFB.messages, 'extendedPointerEvent'); keyEvent = sinon.spy(RFB.messages, 'keyEvent'); qemuKeyEvent = sinon.spy(RFB.messages, 'QEMUExtendedKeyEvent'); }); afterEach(function () { pointerEvent.restore(); + extendedPointerEvent.restore(); keyEvent.restore(); qemuKeyEvent.restore(); }); @@ -3884,6 +3888,23 @@ describe('Remote Frame Buffer protocol client', function () { 50, 70, 0x0); }); + it('should send extended pointer event when server supports extended pointer events', function () { + // Enable extended pointer events + sendFbuMsg([{ x: 0, y: 0, width: 0, height: 0, encoding: -316 }], [[]], client); + + sendMouseButtonEvent(50, 70, true, 0x10, client); + + expect(extendedPointerEvent).to.have.been.calledOnceWith(client._sock, + 50, 70, 0x100); + }); + + it('should send normal pointer event when server does not support extended pointer events', function () { + sendMouseButtonEvent(50, 70, true, 0x10, client); + + expect(pointerEvent).to.have.been.calledOnceWith(client._sock, + 50, 70, 0x100); + }); + describe('Event aggregation', function () { it('should send a single pointer event on mouse movement', function () { sendMouseMoveEvent(50, 70, 0x0, client); @@ -5135,11 +5156,36 @@ describe('RFB messages', function () { }); it('should send correct data for pointer events', function () { + RFB.messages.pointerEvent(sock, 12345, 54321, 0x2b); + let expected = + [ 5, 0x2b, 0x30, 0x39, 0xd4, 0x31]; + expect(sock).to.have.sent(new Uint8Array(expected)); + }); + + it('should send correct data for pointer events with marker bit set', function () { RFB.messages.pointerEvent(sock, 12345, 54321, 0xab); let expected = - [ 5, 0xab, 0x30, 0x39, 0xd4, 0x31]; + [ 5, 0x2b, 0x30, 0x39, 0xd4, 0x31]; expect(sock).to.have.sent(new Uint8Array(expected)); }); + + it('should send correct data for pointer events with extended button bits set', function () { + RFB.messages.pointerEvent(sock, 12345, 54321, 0x3ab); + let expected = + [ 5, 0x2b, 0x30, 0x39, 0xd4, 0x31]; + expect(sock).to.have.sent(new Uint8Array(expected)); + }); + + it('should send correct data for extended pointer events', function () { + RFB.messages.extendedPointerEvent(sock, 12345, 54321, 0xab); + let expected = + [ 5, 0xab, 0x30, 0x39, 0xd4, 0x31, 0x1]; + expect(sock).to.have.sent(new Uint8Array(expected)); + }); + + it('should not send invalid data for extended pointer events', function () { + expect(() => RFB.messages.extendedPointerEvent(sock, 12345, 54321, 0x3ab)).to.throw(Error); + }); }); describe('Clipboard events', function () {