summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPierre Ossman <ossman@cendio.se>2020-06-10 13:59:10 +0200
committerSamuel Mannehed <samuel@cendio.se>2020-06-12 09:18:46 +0200
commitf84bc57bdaf6ed67804fee7d9caadd94c65d1bac (patch)
tree6ad770ea23d54e14770c881097d49f9aff55ff2e
parent4a87038080ee8d9ad8eb9f276fc6acde6ef87467 (diff)
downloadnovnc-f84bc57bdaf6ed67804fee7d9caadd94c65d1bac.tar.gz
Move wheel event handling to RFB class
The Mouse class does very little now so it mostly just obfuscate things. Move everything directly in to the RFB class instead.
-rw-r--r--core/input/mouse.js70
-rw-r--r--core/rfb.js66
-rw-r--r--tests/test.mouse.js82
-rw-r--r--tests/test.rfb.js212
4 files changed, 221 insertions, 209 deletions
diff --git a/core/input/mouse.js b/core/input/mouse.js
index 8c917d0..794adfe 100644
--- a/core/input/mouse.js
+++ b/core/input/mouse.js
@@ -7,22 +7,16 @@
import * as Log from '../util/logging.js';
import { setCapture, stopEvent, getPointerEvent } from '../util/events.js';
-const WHEEL_STEP = 10; // Delta threshold for a mouse wheel step
-const WHEEL_LINE_HEIGHT = 19;
-
export default class Mouse {
constructor(target) {
this._target = target || document;
this._pos = null;
- this._accumulatedWheelDeltaX = 0;
- this._accumulatedWheelDeltaY = 0;
this._eventHandlers = {
'mousedown': this._handleMouseDown.bind(this),
'mouseup': this._handleMouseUp.bind(this),
'mousemove': this._handleMouseMove.bind(this),
- 'mousewheel': this._handleMouseWheel.bind(this),
'mousedisable': this._handleMouseDisable.bind(this)
};
@@ -61,68 +55,6 @@ export default class Mouse {
this._handleMouseButton(e, 0);
}
- // Mouse wheel events are sent in steps over VNC. This means that the VNC
- // protocol can't handle a wheel event with specific distance or speed.
- // Therefor, if we get a lot of small mouse wheel events we combine them.
- _generateWheelStepX() {
-
- if (this._accumulatedWheelDeltaX < 0) {
- this.onmousebutton(this._pos.x, this._pos.y, 1, 1 << 5);
- this.onmousebutton(this._pos.x, this._pos.y, 0, 1 << 5);
- } else if (this._accumulatedWheelDeltaX > 0) {
- this.onmousebutton(this._pos.x, this._pos.y, 1, 1 << 6);
- this.onmousebutton(this._pos.x, this._pos.y, 0, 1 << 6);
- }
-
- this._accumulatedWheelDeltaX = 0;
- }
-
- _generateWheelStepY() {
-
- if (this._accumulatedWheelDeltaY < 0) {
- this.onmousebutton(this._pos.x, this._pos.y, 1, 1 << 3);
- this.onmousebutton(this._pos.x, this._pos.y, 0, 1 << 3);
- } else if (this._accumulatedWheelDeltaY > 0) {
- this.onmousebutton(this._pos.x, this._pos.y, 1, 1 << 4);
- this.onmousebutton(this._pos.x, this._pos.y, 0, 1 << 4);
- }
-
- this._accumulatedWheelDeltaY = 0;
- }
-
- _handleMouseWheel(e) {
- this._updateMousePosition(e);
-
- let dX = e.deltaX;
- let dY = e.deltaY;
-
- // Pixel units unless it's non-zero.
- // Note that if deltamode is line or page won't matter since we aren't
- // sending the mouse wheel delta to the server anyway.
- // The difference between pixel and line can be important however since
- // we have a threshold that can be smaller than the line height.
- if (e.deltaMode !== 0) {
- dX *= WHEEL_LINE_HEIGHT;
- dY *= WHEEL_LINE_HEIGHT;
- }
-
- this._accumulatedWheelDeltaX += dX;
- this._accumulatedWheelDeltaY += dY;
-
- // Generate a mouse wheel step event when the accumulated delta
- // for one of the axes is large enough.
- // Small delta events that do not pass the threshold get sent
- // after a timeout.
- if (Math.abs(this._accumulatedWheelDeltaX) > WHEEL_STEP) {
- this._generateWheelStepX();
- }
- if (Math.abs(this._accumulatedWheelDeltaY) > WHEEL_STEP) {
- this._generateWheelStepY();
- }
-
- stopEvent(e);
- }
-
_handleMouseMove(e) {
this._updateMousePosition(e);
this.onmousemove(this._pos.x, this._pos.y);
@@ -172,7 +104,6 @@ export default class Mouse {
t.addEventListener('mousedown', this._eventHandlers.mousedown);
t.addEventListener('mouseup', this._eventHandlers.mouseup);
t.addEventListener('mousemove', this._eventHandlers.mousemove);
- t.addEventListener('wheel', this._eventHandlers.mousewheel);
// Prevent middle-click pasting (see above for why we bind to document)
document.addEventListener('click', this._eventHandlers.mousedisable);
@@ -188,7 +119,6 @@ export default class Mouse {
t.removeEventListener('mousedown', this._eventHandlers.mousedown);
t.removeEventListener('mouseup', this._eventHandlers.mouseup);
t.removeEventListener('mousemove', this._eventHandlers.mousemove);
- t.removeEventListener('wheel', this._eventHandlers.mousewheel);
document.removeEventListener('click', this._eventHandlers.mousedisable);
diff --git a/core/rfb.js b/core/rfb.js
index 9db375f..4d1dcf1 100644
--- a/core/rfb.js
+++ b/core/rfb.js
@@ -41,6 +41,10 @@ const DEFAULT_BACKGROUND = 'rgb(40, 40, 40)';
// Minimum wait (ms) between two mouse moves
const MOUSE_MOVE_DELAY = 17;
+// Wheel thresholds
+const WHEEL_STEP = 10; // Pixels needed for one step
+const WHEEL_LINE_HEIGHT = 19; // Assumed pixels for one line step
+
// Gesture thresholds
const GESTURE_ZOOMSENS = 75;
const GESTURE_SCRLSENS = 50;
@@ -152,6 +156,8 @@ export default class RFB extends EventTargetMixin {
this._viewportDragging = false;
this._viewportDragPos = {};
this._viewportHasMoved = false;
+ this._accumulatedWheelDeltaX = 0;
+ this._accumulatedWheelDeltaY = 0;
// Gesture state
this._gestureLastTapTime = null;
@@ -163,6 +169,7 @@ export default class RFB extends EventTargetMixin {
this._eventHandlers = {
focusCanvas: this._focusCanvas.bind(this),
windowResize: this._windowResize.bind(this),
+ handleWheel: this._handleWheel.bind(this),
handleGesture: this._handleGesture.bind(this),
};
@@ -532,6 +539,9 @@ export default class RFB extends EventTargetMixin {
this._canvas.addEventListener("mousedown", this._eventHandlers.focusCanvas);
this._canvas.addEventListener("touchstart", this._eventHandlers.focusCanvas);
+ // Wheel events
+ this._canvas.addEventListener("wheel", this._eventHandlers.handleWheel);
+
// Gesture events
this._canvas.addEventListener("gesturestart", this._eventHandlers.handleGesture);
this._canvas.addEventListener("gesturemove", this._eventHandlers.handleGesture);
@@ -546,6 +556,7 @@ export default class RFB extends EventTargetMixin {
this._canvas.removeEventListener("gesturestart", this._eventHandlers.handleGesture);
this._canvas.removeEventListener("gesturemove", this._eventHandlers.handleGesture);
this._canvas.removeEventListener("gestureend", this._eventHandlers.handleGesture);
+ this._canvas.removeEventListener("wheel", this._eventHandlers.handleWheel);
this._canvas.removeEventListener("mousedown", this._eventHandlers.focusCanvas);
this._canvas.removeEventListener("touchstart", this._eventHandlers.focusCanvas);
window.removeEventListener('resize', this._eventHandlers.windowResize);
@@ -939,6 +950,61 @@ export default class RFB extends EventTargetMixin {
this._display.absY(y), mask);
}
+ _handleWheel(ev) {
+ if (this._rfbConnectionState !== 'connected') { return; }
+ if (this._viewOnly) { return; } // View only, skip mouse events
+
+ ev.stopPropagation();
+ ev.preventDefault();
+
+ let pos = clientToElement(ev.clientX, ev.clientY,
+ this._canvas);
+
+ let dX = ev.deltaX;
+ let dY = ev.deltaY;
+
+ // Pixel units unless it's non-zero.
+ // Note that if deltamode is line or page won't matter since we aren't
+ // sending the mouse wheel delta to the server anyway.
+ // The difference between pixel and line can be important however since
+ // we have a threshold that can be smaller than the line height.
+ if (ev.deltaMode !== 0) {
+ dX *= WHEEL_LINE_HEIGHT;
+ dY *= WHEEL_LINE_HEIGHT;
+ }
+
+ // Mouse wheel events are sent in steps over VNC. This means that the VNC
+ // protocol can't handle a wheel event with specific distance or speed.
+ // Therefor, if we get a lot of small mouse wheel events we combine them.
+ this._accumulatedWheelDeltaX += dX;
+ this._accumulatedWheelDeltaY += dY;
+
+ // Generate a mouse wheel step event when the accumulated delta
+ // for one of the axes is large enough.
+ if (Math.abs(this._accumulatedWheelDeltaX) > WHEEL_STEP) {
+ if (this._accumulatedWheelDeltaX < 0) {
+ this._handleMouseButton(pos.x, pos.y, true, 1 << 5);
+ this._handleMouseButton(pos.x, pos.y, false, 1 << 5);
+ } else if (this._accumulatedWheelDeltaX > 0) {
+ this._handleMouseButton(pos.x, pos.y, true, 1 << 6);
+ this._handleMouseButton(pos.x, pos.y, false, 1 << 6);
+ }
+
+ this._accumulatedWheelDeltaX = 0;
+ }
+ if (Math.abs(this._accumulatedWheelDeltaY) > WHEEL_STEP) {
+ if (this._accumulatedWheelDeltaY < 0) {
+ this._handleMouseButton(pos.x, pos.y, true, 1 << 3);
+ this._handleMouseButton(pos.x, pos.y, false, 1 << 3);
+ } else if (this._accumulatedWheelDeltaY > 0) {
+ this._handleMouseButton(pos.x, pos.y, true, 1 << 4);
+ this._handleMouseButton(pos.x, pos.y, false, 1 << 4);
+ }
+
+ this._accumulatedWheelDeltaY = 0;
+ }
+ }
+
_handleTapEvent(ev, bmask) {
let pos = clientToElement(ev.detail.clientX, ev.detail.clientY,
this._canvas);
diff --git a/tests/test.mouse.js b/tests/test.mouse.js
index 13bd0c6..25d5219 100644
--- a/tests/test.mouse.js
+++ b/tests/test.mouse.js
@@ -69,87 +69,5 @@ describe('Mouse Event Handling', function () {
mouse._handleMouseMove(mouseevent('mousemove',
{ clientX: 50, clientY: 20 }));
});
- it('should decode mousewheel events', function (done) {
- let calls = 0;
- const mouse = new Mouse(target);
- mouse.onmousebutton = (x, y, down, bmask) => {
- calls++;
- expect(bmask).to.be.equal(1<<6);
- if (calls === 1) {
- expect(down).to.be.equal(1);
- } else if (calls === 2) {
- expect(down).to.not.be.equal(1);
- done();
- }
- };
- mouse._handleMouseWheel(mouseevent('mousewheel',
- { deltaX: 50, deltaY: 0,
- deltaMode: 0}));
- });
- });
-
- describe('Accumulate mouse wheel events with small delta', function () {
-
- it('should accumulate wheel events if small enough', function () {
- const mouse = new Mouse(target);
- mouse.onmousebutton = sinon.spy();
-
- mouse._handleMouseWheel(mouseevent(
- 'mousewheel', { clientX: 18, clientY: 40,
- deltaX: 4, deltaY: 0, deltaMode: 0 }));
- mouse._handleMouseWheel(mouseevent(
- 'mousewheel', { clientX: 18, clientY: 40,
- deltaX: 4, deltaY: 0, deltaMode: 0 }));
-
- // threshold is 10
- expect(mouse._accumulatedWheelDeltaX).to.be.equal(8);
-
- mouse._handleMouseWheel(mouseevent(
- 'mousewheel', { clientX: 18, clientY: 40,
- deltaX: 4, deltaY: 0, deltaMode: 0 }));
-
- expect(mouse.onmousebutton).to.have.callCount(2); // mouse down and up
-
- mouse._handleMouseWheel(mouseevent(
- 'mousewheel', { clientX: 18, clientY: 40,
- deltaX: 4, deltaY: 9, deltaMode: 0 }));
-
- expect(mouse._accumulatedWheelDeltaX).to.be.equal(4);
- expect(mouse._accumulatedWheelDeltaY).to.be.equal(9);
-
- expect(mouse.onmousebutton).to.have.callCount(2); // still
- });
-
- it('should not accumulate large wheel events', function () {
- const mouse = new Mouse(target);
- mouse.onmousebutton = sinon.spy();
-
- mouse._handleMouseWheel(mouseevent(
- 'mousewheel', { clientX: 18, clientY: 40,
- deltaX: 11, deltaY: 0, deltaMode: 0 }));
- mouse._handleMouseWheel(mouseevent(
- 'mousewheel', { clientX: 18, clientY: 40,
- deltaX: 0, deltaY: 70, deltaMode: 0 }));
- mouse._handleMouseWheel(mouseevent(
- 'mousewheel', { clientX: 18, clientY: 40,
- deltaX: 400, deltaY: 400, deltaMode: 0 }));
-
- expect(mouse.onmousebutton).to.have.callCount(8); // mouse down and up
- });
-
- it('should account for non-zero deltaMode', function () {
- const mouse = new Mouse(target);
- mouse.onmousebutton = sinon.spy();
-
- mouse._handleMouseWheel(mouseevent(
- 'mousewheel', { clientX: 18, clientY: 40,
- deltaX: 0, deltaY: 2, deltaMode: 1 }));
-
- mouse._handleMouseWheel(mouseevent(
- 'mousewheel', { clientX: 18, clientY: 40,
- deltaX: 1, deltaY: 0, deltaMode: 2 }));
-
- expect(mouse.onmousebutton).to.have.callCount(4); // mouse down and up
- });
});
});
diff --git a/tests/test.rfb.js b/tests/test.rfb.js
index e46f40c..909b300 100644
--- a/tests/test.rfb.js
+++ b/tests/test.rfb.js
@@ -2731,20 +2731,44 @@ describe('Remote Frame Buffer Protocol Client', function () {
describe('Asynchronous Events', function () {
let client;
+ let pointerEvent;
+ let keyEvent;
+ let qemuKeyEvent;
+
beforeEach(function () {
client = makeRFB();
+ client._display.resize(100, 100);
+
+ pointerEvent = sinon.spy(RFB.messages, 'pointerEvent');
+ keyEvent = sinon.spy(RFB.messages, 'keyEvent');
+ qemuKeyEvent = sinon.spy(RFB.messages, 'QEMUExtendedKeyEvent');
});
- describe('Mouse event handlers', function () {
- beforeEach(function () {
- this.clock = sinon.useFakeTimers(Date.now());
- sinon.spy(RFB.messages, 'pointerEvent');
- });
- afterEach(function () {
- this.clock.restore();
- RFB.messages.pointerEvent.restore();
- });
+ afterEach(function () {
+ pointerEvent.restore();
+ keyEvent.restore();
+ qemuKeyEvent.restore();
+ });
+ function elementToClient(x, y) {
+ let res = { x: 0, y: 0 };
+
+ let bounds = client._canvas.getBoundingClientRect();
+
+ /*
+ * If the canvas is on a fractional position we will calculate
+ * a fractional mouse position. But that gets truncated when we
+ * send the event, AND the same thing happens in RFB when it
+ * generates the PointerEvent message. To compensate for that
+ * fact we round the value upwards here.
+ */
+ res.x = Math.ceil(bounds.left + x);
+ res.y = Math.ceil(bounds.top + y);
+
+ return res;
+ }
+
+ describe('Mouse Events', function () {
it('should not send button messages in view-only mode', function () {
client._viewOnly = true;
client._handleMouseButton(0, 0, 1, 0x001);
@@ -2878,7 +2902,128 @@ describe('Remote Frame Buffer Protocol Client', function () {
});
});
- describe('Keyboard Event Handlers', function () {
+ describe('Wheel Events', function () {
+ function sendWheelEvent(x, y, dx, dy, mode=0) {
+ let pos = elementToClient(x, y);
+ let ev;
+
+ try {
+ ev = new WheelEvent('wheel',
+ { 'screenX': pos.x + window.screenX,
+ 'screenY': pos.y + window.screenY,
+ 'clientX': pos.x,
+ 'clientY': pos.y,
+ 'deltaX': dx,
+ 'deltaY': dy,
+ 'deltaMode': mode });
+ } catch (e) {
+ ev = document.createEvent('WheelEvent');
+ ev.initWheelEvent('wheel', true, true, window, 0,
+ pos.x + window.screenX,
+ pos.y + window.screenY,
+ pos.x, pos.y,
+ 0, null, "",
+ dx, dy, 0, mode);
+ }
+
+ client._canvas.dispatchEvent(ev);
+ }
+
+ it('should handle wheel up event', function () {
+ sendWheelEvent(10, 10, 0, -50);
+
+ expect(pointerEvent).to.have.been.calledTwice;
+ expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
+ 10, 10, 1<<3);
+ expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
+ 10, 10, 0);
+ });
+
+ it('should handle wheel down event', function () {
+ sendWheelEvent(10, 10, 0, 50);
+
+ expect(pointerEvent).to.have.been.calledTwice;
+ expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
+ 10, 10, 1<<4);
+ expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
+ 10, 10, 0);
+ });
+
+ it('should handle wheel left event', function () {
+ sendWheelEvent(10, 10, -50, 0);
+
+ expect(pointerEvent).to.have.been.calledTwice;
+ expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
+ 10, 10, 1<<5);
+ expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
+ 10, 10, 0);
+ });
+
+ it('should handle wheel right event', function () {
+ sendWheelEvent(10, 10, 50, 0);
+
+ expect(pointerEvent).to.have.been.calledTwice;
+ expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
+ 10, 10, 1<<6);
+ expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
+ 10, 10, 0);
+ });
+
+ it('should ignore wheel when in view only', function () {
+ client._viewOnly = true;
+
+ sendWheelEvent(10, 10, 50, 0);
+
+ expect(pointerEvent).to.not.have.been.called;
+ });
+
+ it('should accumulate wheel events if small enough', function () {
+ sendWheelEvent(10, 10, 0, 4);
+ sendWheelEvent(10, 10, 0, 4);
+
+ expect(pointerEvent).to.not.have.been.called;
+
+ sendWheelEvent(10, 10, 0, 4);
+
+ expect(pointerEvent).to.have.been.calledTwice;
+ expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
+ 10, 10, 1<<4);
+ expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
+ 10, 10, 0);
+ });
+
+ it('should not accumulate large wheel events', function () {
+ sendWheelEvent(10, 10, 0, 400);
+
+ expect(pointerEvent).to.have.been.calledTwice;
+ expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
+ 10, 10, 1<<4);
+ expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
+ 10, 10, 0);
+ });
+
+ it('should handle line based wheel event', function () {
+ sendWheelEvent(10, 10, 0, 1, 1);
+
+ expect(pointerEvent).to.have.been.calledTwice;
+ expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
+ 10, 10, 1<<4);
+ expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
+ 10, 10, 0);
+ });
+
+ it('should handle page based wheel event', function () {
+ sendWheelEvent(10, 10, 0, 1, 2);
+
+ expect(pointerEvent).to.have.been.calledTwice;
+ expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
+ 10, 10, 1<<4);
+ expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
+ 10, 10, 0);
+ });
+ });
+
+ describe('Keyboard Events', function () {
it('should send a key message on a key press', function () {
client._handleKeyEvent(0x41, 'KeyA', true);
const keyMsg = {_sQ: new Uint8Array(8), _sQlen: 0, flush: () => {}};
@@ -2895,42 +3040,14 @@ describe('Remote Frame Buffer Protocol Client', function () {
});
describe('Gesture event handlers', function () {
- let pointerEvent;
-
beforeEach(function () {
// Touch events and gestures are not supported on IE
if (browser.isIE()) {
this.skip();
return;
}
-
- pointerEvent = sinon.spy(RFB.messages, 'pointerEvent');
-
- client._display.resize(100, 100);
});
- afterEach(function () {
- pointerEvent.restore();
- });
-
- function elementToClient(x, y) {
- let res = { x: 0, y: 0 };
-
- let bounds = client._canvas.getBoundingClientRect();
-
- /*
- * If the canvas is on a fractional position we will calculate
- * a fractional mouse position. But that gets truncated when we
- * send the event, AND the same thing happens in RFB when it
- * generates the PointerEvent message. To compensate for that
- * fact we round the value upwards here.
- */
- res.x = Math.ceil(bounds.left + x);
- res.y = Math.ceil(bounds.top + y);
-
- return res;
- }
-
function gestureStart(gestureType, x, y,
magnitudeX = 0, magnitudeY = 0) {
let pos = elementToClient(x, y);
@@ -3392,25 +3509,6 @@ describe('Remote Frame Buffer Protocol Client', function () {
});
describe('Gesture pinch', function () {
- let keyEvent;
- let qemuKeyEvent;
-
- beforeEach(function () {
- // Touch events and gestures are not supported on IE
- if (browser.isIE()) {
- this.skip();
- return;
- }
-
- keyEvent = sinon.spy(RFB.messages, 'keyEvent');
- qemuKeyEvent = sinon.spy(RFB.messages, 'QEMUExtendedKeyEvent');
- });
-
- afterEach(function () {
- keyEvent.restore();
- qemuKeyEvent.restore();
- });
-
it('should handle gesture pinch in events', function () {
let keysym = KeyTable.XK_Control_L;
let bmask = 0x10; // Button mask for scroll down