Skip to content

Commit 31f0bba

Browse files
committed
Add support for forward and back mouse buttons
This commit implements the extendedMouseButtons pseudo-encoding, which makes it possible to use the forward and back mouse buttons.
1 parent 9cdbd28 commit 31f0bba

File tree

3 files changed

+102
-4
lines changed

3 files changed

+102
-4
lines changed

core/encodings.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export const encodings = {
3030
pseudoEncodingXvp: -309,
3131
pseudoEncodingFence: -312,
3232
pseudoEncodingContinuousUpdates: -313,
33+
pseudoEncodingExtendedMouseButtons: -316,
3334
pseudoEncodingCompressLevel9: -247,
3435
pseudoEncodingCompressLevel0: -256,
3536
pseudoEncodingVMwareCursor: 0x574d5664,

core/rfb.js

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,8 @@ export default class RFB extends EventTargetMixin {
152152

153153
this._qemuExtKeyEventSupported = false;
154154

155+
this._extendedPointerEventSupported = false;
156+
155157
this._clipboardText = null;
156158
this._clipboardServerCapabilitiesActions = {};
157159
this._clipboardServerCapabilitiesFormats = {};
@@ -1051,10 +1053,11 @@ export default class RFB extends EventTargetMixin {
10511053
1: 1 << 2, // Right
10521054
2: 1 << 1, // Middle
10531055
3: 1 << 7, // Back
1056+
4: 1 << 8, // Forward
10541057
};
10551058

10561059
let bmask = 0;
1057-
for (let i = 0; i < 4; i++) {
1060+
for (let i = 0; i < 5; i++) {
10581061
if (buttons & (1 << i)) {
10591062
bmask |= buttonMaskMap[i];
10601063
}
@@ -1189,8 +1192,20 @@ export default class RFB extends EventTargetMixin {
11891192
if (this._rfbConnectionState !== 'connected') { return; }
11901193
if (this._viewOnly) { return; } // View only, skip mouse events
11911194

1192-
RFB.messages.pointerEvent(this._sock, this._display.absX(x),
1193-
this._display.absY(y), mask);
1195+
// Highest bit in mask is never sent to the server
1196+
if (mask & 0x8000) {
1197+
throw new Error("Illegal mouse button mask (mask: " + mask + ")");
1198+
}
1199+
1200+
let extendedMouseButtons = mask & 0x7f80;
1201+
1202+
if (this._extendedPointerEventSupported && extendedMouseButtons) {
1203+
RFB.messages.extendedPointerEvent(this._sock, this._display.absX(x),
1204+
this._display.absY(y), mask);
1205+
} else {
1206+
RFB.messages.pointerEvent(this._sock, this._display.absX(x),
1207+
this._display.absY(y), mask);
1208+
}
11941209
}
11951210

11961211
_handleWheel(ev) {
@@ -2229,6 +2244,7 @@ export default class RFB extends EventTargetMixin {
22292244
encs.push(encodings.pseudoEncodingContinuousUpdates);
22302245
encs.push(encodings.pseudoEncodingDesktopName);
22312246
encs.push(encodings.pseudoEncodingExtendedClipboard);
2247+
encs.push(encodings.pseudoEncodingExtendedMouseButtons);
22322248

22332249
if (this._fbDepth == 24) {
22342250
encs.push(encodings.pseudoEncodingVMwareCursor);
@@ -2658,6 +2674,10 @@ export default class RFB extends EventTargetMixin {
26582674
case encodings.pseudoEncodingExtendedDesktopSize:
26592675
return this._handleExtendedDesktopSize();
26602676

2677+
case encodings.pseudoEncodingExtendedMouseButtons:
2678+
this._extendedPointerEventSupported = true;
2679+
return true;
2680+
26612681
case encodings.pseudoEncodingQEMULedEvent:
26622682
return this._handleLedEvent();
26632683

@@ -3067,6 +3087,10 @@ RFB.messages = {
30673087
pointerEvent(sock, x, y, mask) {
30683088
sock.sQpush8(5); // msg-type
30693089

3090+
// Marker bit must be set to 0, otherwise the server might
3091+
// confuse the marker bit with the highest bit in a normal
3092+
// PointerEvent message.
3093+
mask = mask & 0x7f;
30703094
sock.sQpush8(mask);
30713095

30723096
sock.sQpush16(x);
@@ -3075,6 +3099,27 @@ RFB.messages = {
30753099
sock.flush();
30763100
},
30773101

3102+
extendedPointerEvent(sock, x, y, mask) {
3103+
sock.sQpush8(5); // msg-type
3104+
3105+
let higherBits = (mask >> 7) & 0xff;
3106+
3107+
// Bits 2-7 are reserved
3108+
if (higherBits & 0xfc) {
3109+
throw new Error("Invalid mouse button mask: " + mask);
3110+
}
3111+
3112+
let lowerBits = mask & 0x7f;
3113+
lowerBits |= 0x80; // Set marker bit to 1
3114+
3115+
sock.sQpush8(lowerBits);
3116+
sock.sQpush16(x);
3117+
sock.sQpush16(y);
3118+
sock.sQpush8(higherBits);
3119+
3120+
sock.flush();
3121+
},
3122+
30783123
// Used to build Notify and Request data.
30793124
_buildExtendedClipboardFlags(actions, formats) {
30803125
let data = new Uint8Array(4);

tests/test.rfb.js

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3265,6 +3265,11 @@ describe('Remote Frame Buffer protocol client', function () {
32653265
expect(spy).to.have.been.calledOnce;
32663266
expect(spy.args[0][0].detail.name).to.equal('som€ nam€');
32673267
});
3268+
3269+
it('should handle the extendedMouseButtons pseudo-encoding', function () {
3270+
sendFbuMsg([{ x: 0, y: 0, width: 0, height: 0, encoding: -316 }], [[]], client);
3271+
expect(client._extendedPointerEventSupported).to.equals(true);
3272+
});
32683273
});
32693274

32703275
describe('Caps Lock and Num Lock remote fixup', function () {
@@ -3757,6 +3762,7 @@ describe('Remote Frame Buffer protocol client', function () {
37573762
describe('Asynchronous events', function () {
37583763
let client;
37593764
let pointerEvent;
3765+
let extendedPointerEvent;
37603766
let keyEvent;
37613767
let qemuKeyEvent;
37623768

@@ -3770,12 +3776,14 @@ describe('Remote Frame Buffer protocol client', function () {
37703776
client.focusOnClick = false;
37713777

37723778
pointerEvent = sinon.spy(RFB.messages, 'pointerEvent');
3779+
extendedPointerEvent = sinon.spy(RFB.messages, 'extendedPointerEvent');
37733780
keyEvent = sinon.spy(RFB.messages, 'keyEvent');
37743781
qemuKeyEvent = sinon.spy(RFB.messages, 'QEMUExtendedKeyEvent');
37753782
});
37763783

37773784
afterEach(function () {
37783785
pointerEvent.restore();
3786+
extendedPointerEvent.restore();
37793787
keyEvent.restore();
37803788
qemuKeyEvent.restore();
37813789
});
@@ -3884,6 +3892,32 @@ describe('Remote Frame Buffer protocol client', function () {
38843892
50, 70, 0x0);
38853893
});
38863894

3895+
it('should send extended pointer event when supports extended pointer events', function () {
3896+
client._extendedPointerEventSupported = true;
3897+
sendMouseButtonEvent(50, 70, true, 0x10, client);
3898+
3899+
expect(extendedPointerEvent).to.have.been.calledOnceWith(client._sock,
3900+
50, 70, 0x100);
3901+
});
3902+
3903+
it('should send normal pointer event when supports does not extended pointer events', function () {
3904+
client._extendedPointerEventSupported = false;
3905+
sendMouseButtonEvent(50, 70, true, 0x10, client);
3906+
3907+
expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
3908+
50, 70, 0x100);
3909+
});
3910+
3911+
it('should not send pointer event with illegal mask', function () {
3912+
// FIXME: Should we mock convertButtonmask to return 0x7f80 instead of
3913+
// calling sendmouse?
3914+
expect(() => client._sendmouse(50, 70, 0x7f80)).to.throw(Error);
3915+
});
3916+
3917+
it('should not send extended pointer event with illegal mask', function () {
3918+
expect(() => RFB.messages.extendedPointerevent(client._sock, 50, 70, 0xfe00)).to.throw(Error);
3919+
});
3920+
38873921
describe('Event aggregation', function () {
38883922
it('should send a single pointer event on mouse movement', function () {
38893923
sendMouseMoveEvent(50, 70, 0x0, client);
@@ -5135,11 +5169,29 @@ describe('RFB messages', function () {
51355169
});
51365170

51375171
it('should send correct data for pointer events', function () {
5172+
RFB.messages.pointerEvent(sock, 12345, 54321, 0x2b);
5173+
let expected =
5174+
[ 5, 0x2b, 0x30, 0x39, 0xd4, 0x31];
5175+
expect(sock).to.have.sent(new Uint8Array(expected));
5176+
});
5177+
5178+
it('should send correct data for pointer events with marker bit set', function () {
51385179
RFB.messages.pointerEvent(sock, 12345, 54321, 0xab);
51395180
let expected =
5140-
[ 5, 0xab, 0x30, 0x39, 0xd4, 0x31];
5181+
[ 5, 0x2b, 0x30, 0x39, 0xd4, 0x31];
51415182
expect(sock).to.have.sent(new Uint8Array(expected));
51425183
});
5184+
5185+
it('should send correct data for extended pointer events', function () {
5186+
RFB.messages.extendedPointerEvent(sock, 12345, 54321, 0xab);
5187+
let expected =
5188+
[ 5, 0xab, 0x30, 0x39, 0xd4, 0x31, 0x1];
5189+
expect(sock).to.have.sent(new Uint8Array(expected));
5190+
});
5191+
5192+
it('should not send invalid data for extended pointer events', function () {
5193+
expect(() => RFB.messages.extendedPointerEvent(sock, 12345, 54321, 0x3ab)).to.throw(Error);
5194+
});
51435195
});
51445196

51455197
describe('Clipboard events', function () {

0 commit comments

Comments
 (0)