Skip to content

Commit e3195f7

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 52ddb20 commit e3195f7

File tree

3 files changed

+69
-5
lines changed

3 files changed

+69
-5
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: 60 additions & 4 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 = {};
@@ -1060,15 +1062,27 @@ export default class RFB extends EventTargetMixin {
10601062
let pos = clientToElement(ev.clientX, ev.clientY,
10611063
this._canvas);
10621064

1065+
/* Map mouse back and forward mouse buttons (3 and 4) to 7 and 8
1066+
*
1067+
* NOTE: This only works on chromium-based browsers. There is
1068+
* no support for firefox/safari.
1069+
*/
1070+
let button = ev.button;
1071+
if (button == 3 || button == 4) {
1072+
button += 4;
1073+
} else if (button > 4) {
1074+
// Unsupported mouse button
1075+
return;
1076+
}
10631077
switch (ev.type) {
10641078
case 'mousedown':
10651079
setCapture(this._canvas);
10661080
this._handleMouseButton(pos.x, pos.y,
1067-
true, 1 << ev.button);
1081+
true, 1 << button);
10681082
break;
10691083
case 'mouseup':
10701084
this._handleMouseButton(pos.x, pos.y,
1071-
false, 1 << ev.button);
1085+
false, 1 << button);
10721086
break;
10731087
case 'mousemove':
10741088
this._handleMouseMove(pos.x, pos.y);
@@ -1163,8 +1177,20 @@ export default class RFB extends EventTargetMixin {
11631177
if (this._rfbConnectionState !== 'connected') { return; }
11641178
if (this._viewOnly) { return; } // View only, skip mouse events
11651179

1166-
RFB.messages.pointerEvent(this._sock, this._display.absX(x),
1167-
this._display.absY(y), mask);
1180+
// Highest bit in mask is never sent to the server
1181+
if (mask & 0x8000) {
1182+
throw new Error("Illegal mouse button mask (mask: " + mask + ")");
1183+
}
1184+
1185+
let extendedMouseButtons = mask & 0x7f80;
1186+
1187+
if (this._extendedPointerEventSupported && extendedMouseButtons) {
1188+
RFB.messages.extendedPointerEvent(this._sock, this._display.absX(x),
1189+
this._display.absY(y), mask);
1190+
} else {
1191+
RFB.messages.pointerEvent(this._sock, this._display.absX(x),
1192+
this._display.absY(y), mask);
1193+
}
11681194
}
11691195

11701196
_handleWheel(ev) {
@@ -2146,6 +2172,7 @@ export default class RFB extends EventTargetMixin {
21462172
encs.push(encodings.pseudoEncodingContinuousUpdates);
21472173
encs.push(encodings.pseudoEncodingDesktopName);
21482174
encs.push(encodings.pseudoEncodingExtendedClipboard);
2175+
encs.push(encodings.pseudoEncodingExtendedMouseButtons);
21492176

21502177
if (this._fbDepth == 24) {
21512178
encs.push(encodings.pseudoEncodingVMwareCursor);
@@ -2575,6 +2602,10 @@ export default class RFB extends EventTargetMixin {
25752602
case encodings.pseudoEncodingExtendedDesktopSize:
25762603
return this._handleExtendedDesktopSize();
25772604

2605+
case encodings.pseudoEncodingExtendedMouseButtons:
2606+
this._extendedPointerEventSupported = true;
2607+
return true;
2608+
25782609
case encodings.pseudoEncodingQEMULedEvent:
25792610
return this._handleLedEvent();
25802611

@@ -2983,6 +3014,10 @@ RFB.messages = {
29833014
pointerEvent(sock, x, y, mask) {
29843015
sock.sQpush8(5); // msg-type
29853016

3017+
// Marker bit must be set to 0, otherwise the server might
3018+
// confuse the marker bit with the highest bit in a normal
3019+
// PointerEvent message.
3020+
mask = mask & 0x7f;
29863021
sock.sQpush8(mask);
29873022

29883023
sock.sQpush16(x);
@@ -2991,6 +3026,27 @@ RFB.messages = {
29913026
sock.flush();
29923027
},
29933028

3029+
extendedPointerEvent(sock, x, y, mask) {
3030+
sock.sQpush8(5); // msg-type
3031+
3032+
let higherBits = (mask >> 7) & 0xff;
3033+
3034+
// Bits 2-7 are reserved
3035+
if (higherBits & 0xfc) {
3036+
throw new Error("Invalid mouse button mask: " + mask);
3037+
}
3038+
3039+
let lowerBits = mask & 0x7f;
3040+
lowerBits |= 0x80; // Set marker bit to 1
3041+
3042+
sock.sQpush8(lowerBits);
3043+
sock.sQpush16(x);
3044+
sock.sQpush16(y);
3045+
sock.sQpush8(higherBits);
3046+
3047+
sock.flush();
3048+
},
3049+
29943050
// Used to build Notify and Request data.
29953051
_buildExtendedClipboardFlags(actions, formats) {
29963052
let data = new Uint8Array(4);

tests/test.rfb.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4931,7 +4931,14 @@ describe('RFB messages', function () {
49314931
it('should send correct data for pointer events', function () {
49324932
RFB.messages.pointerEvent(sock, 12345, 54321, 0xab);
49334933
let expected =
4934-
[ 5, 0xab, 0x30, 0x39, 0xd4, 0x31];
4934+
[ 5, 0x2b, 0x30, 0x39, 0xd4, 0x31];
4935+
expect(sock).to.have.sent(new Uint8Array(expected));
4936+
});
4937+
4938+
it('should send correct data for extended pointer events', function () {
4939+
RFB.messages.extendedPointerEvent(sock, 12345, 54321, 0xab);
4940+
let expected =
4941+
[ 5, 0xab, 0x30, 0x39, 0xd4, 0x31, 0x01];
49354942
expect(sock).to.have.sent(new Uint8Array(expected));
49364943
});
49374944
});

0 commit comments

Comments
 (0)