From 50cde2faab54e1bfcf411ab3fb5c2950f444e81f Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Wed, 10 Jun 2020 16:13:03 +0200 Subject: [PATCH] Move mouse event handling to RFB class Move the last remaining bits to the RFB class to keep things simple, as the Mouse class no longer provides any real value. --- core/input/mouse.js | 127 --------------- core/rfb.js | 72 +++++++-- docs/API-internal.md | 40 +---- tests/test.mouse.js | 73 --------- tests/test.rfb.js | 364 ++++++++++++++++++++++++++++++------------- 5 files changed, 324 insertions(+), 352 deletions(-) delete mode 100644 core/input/mouse.js delete mode 100644 tests/test.mouse.js diff --git a/core/input/mouse.js b/core/input/mouse.js deleted file mode 100644 index 794adfe2..00000000 --- a/core/input/mouse.js +++ /dev/null @@ -1,127 +0,0 @@ -/* - * noVNC: HTML5 VNC client - * Copyright (C) 2019 The noVNC Authors - * Licensed under MPL 2.0 or any later version (see LICENSE.txt) - */ - -import * as Log from '../util/logging.js'; -import { setCapture, stopEvent, getPointerEvent } from '../util/events.js'; - -export default class Mouse { - constructor(target) { - this._target = target || document; - - this._pos = null; - - this._eventHandlers = { - 'mousedown': this._handleMouseDown.bind(this), - 'mouseup': this._handleMouseUp.bind(this), - 'mousemove': this._handleMouseMove.bind(this), - 'mousedisable': this._handleMouseDisable.bind(this) - }; - - // ===== EVENT HANDLERS ===== - - this.onmousebutton = () => {}; // Handler for mouse button press/release - this.onmousemove = () => {}; // Handler for mouse movement - } - - // ===== PRIVATE METHODS ===== - - _resetDoubleClickTimer() { - this._doubleClickTimer = null; - } - - _handleMouseButton(e, down) { - this._updateMousePosition(e); - let pos = this._pos; - - let bmask = 1 << e.button; - - Log.Debug("onmousebutton " + (down ? "down" : "up") + - ", x: " + pos.x + ", y: " + pos.y + ", bmask: " + bmask); - this.onmousebutton(pos.x, pos.y, down, bmask); - - stopEvent(e); - } - - _handleMouseDown(e) { - setCapture(this._target); - - this._handleMouseButton(e, 1); - } - - _handleMouseUp(e) { - this._handleMouseButton(e, 0); - } - - _handleMouseMove(e) { - this._updateMousePosition(e); - this.onmousemove(this._pos.x, this._pos.y); - stopEvent(e); - } - - _handleMouseDisable(e) { - /* - * Stop propagation if inside canvas area - * Note: This is only needed for the 'click' event as it fails - * to fire properly for the target element so we have - * to listen on the document element instead. - */ - if (e.target == this._target) { - stopEvent(e); - } - } - - // Update coordinates relative to target - _updateMousePosition(e) { - e = getPointerEvent(e); - const bounds = this._target.getBoundingClientRect(); - let x; - let y; - // Clip to target bounds - if (e.clientX < bounds.left) { - x = 0; - } else if (e.clientX >= bounds.right) { - x = bounds.width - 1; - } else { - x = e.clientX - bounds.left; - } - if (e.clientY < bounds.top) { - y = 0; - } else if (e.clientY >= bounds.bottom) { - y = bounds.height - 1; - } else { - y = e.clientY - bounds.top; - } - this._pos = {x: x, y: y}; - } - - // ===== PUBLIC METHODS ===== - - grab() { - const t = this._target; - t.addEventListener('mousedown', this._eventHandlers.mousedown); - t.addEventListener('mouseup', this._eventHandlers.mouseup); - t.addEventListener('mousemove', this._eventHandlers.mousemove); - - // Prevent middle-click pasting (see above for why we bind to document) - document.addEventListener('click', this._eventHandlers.mousedisable); - - // preventDefault() on mousedown doesn't stop this event for some - // reason so we have to explicitly block it - t.addEventListener('contextmenu', this._eventHandlers.mousedisable); - } - - ungrab() { - const t = this._target; - - t.removeEventListener('mousedown', this._eventHandlers.mousedown); - t.removeEventListener('mouseup', this._eventHandlers.mouseup); - t.removeEventListener('mousemove', this._eventHandlers.mousemove); - - document.removeEventListener('click', this._eventHandlers.mousedisable); - - t.removeEventListener('contextmenu', this._eventHandlers.mousedisable); - } -} diff --git a/core/rfb.js b/core/rfb.js index c5b60160..c56bdc98 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -12,12 +12,12 @@ import * as Log from './util/logging.js'; import { encodeUTF8, decodeUTF8 } from './util/strings.js'; import { dragThreshold } from './util/browser.js'; import { clientToElement } from './util/element.js'; +import { setCapture } from './util/events.js'; import EventTargetMixin from './util/eventtarget.js'; import Display from "./display.js"; import Inflator from "./inflator.js"; import Deflator from "./deflator.js"; import Keyboard from "./input/keyboard.js"; -import Mouse from "./input/mouse.js"; import GestureHandler from "./input/gesturehandler.js"; import Cursor from "./util/cursor.js"; import Websock from "./websock.js"; @@ -129,7 +129,6 @@ export default class RFB extends EventTargetMixin { this._display = null; // Display object this._flushing = false; // Display flushing state this._keyboard = null; // Keyboard input handler object - this._mouse = null; // Mouse input handler object this._gestures = null; // Gesture input handler object // Timers @@ -169,6 +168,7 @@ export default class RFB extends EventTargetMixin { this._eventHandlers = { focusCanvas: this._focusCanvas.bind(this), windowResize: this._windowResize.bind(this), + handleMouse: this._handleMouse.bind(this), handleWheel: this._handleWheel.bind(this), handleGesture: this._handleGesture.bind(this), }; @@ -229,10 +229,6 @@ export default class RFB extends EventTargetMixin { this._keyboard = new Keyboard(this._canvas); this._keyboard.onkeyevent = this._handleKeyEvent.bind(this); - this._mouse = new Mouse(this._canvas); - this._mouse.onmousebutton = this._handleMouseButton.bind(this); - this._mouse.onmousemove = this._handleMouseMove.bind(this); - this._gestures = new GestureHandler(); this._sock = new Websock(); @@ -321,10 +317,8 @@ export default class RFB extends EventTargetMixin { this._rfbConnectionState === "connected") { if (viewOnly) { this._keyboard.ungrab(); - this._mouse.ungrab(); } else { this._keyboard.grab(); - this._mouse.grab(); } } } @@ -539,6 +533,16 @@ export default class RFB extends EventTargetMixin { this._canvas.addEventListener("mousedown", this._eventHandlers.focusCanvas); this._canvas.addEventListener("touchstart", this._eventHandlers.focusCanvas); + // Mouse events + this._canvas.addEventListener('mousedown', this._eventHandlers.handleMouse); + this._canvas.addEventListener('mouseup', this._eventHandlers.handleMouse); + this._canvas.addEventListener('mousemove', this._eventHandlers.handleMouse); + // Prevent middle-click pasting (see handler for why we bind to document) + this._canvas.addEventListener('click', this._eventHandlers.handleMouse); + // preventDefault() on mousedown doesn't stop this event for some + // reason so we have to explicitly block it + this._canvas.addEventListener('contextmenu', this._eventHandlers.handleMouse); + // Wheel events this._canvas.addEventListener("wheel", this._eventHandlers.handleWheel); @@ -557,11 +561,15 @@ export default class RFB extends EventTargetMixin { 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.handleMouse); + this._canvas.removeEventListener('mouseup', this._eventHandlers.handleMouse); + this._canvas.removeEventListener('mousemove', this._eventHandlers.handleMouse); + this._canvas.removeEventListener('click', this._eventHandlers.handleMouse); + this._canvas.removeEventListener('contextmenu', this._eventHandlers.handleMouse); this._canvas.removeEventListener("mousedown", this._eventHandlers.focusCanvas); this._canvas.removeEventListener("touchstart", this._eventHandlers.focusCanvas); window.removeEventListener('resize', this._eventHandlers.windowResize); this._keyboard.ungrab(); - this._mouse.ungrab(); this._gestures.detach(); this._sock.close(); try { @@ -859,6 +867,51 @@ export default class RFB extends EventTargetMixin { this.sendKey(keysym, code, down); } + _handleMouse(ev) { + /* + * We don't check connection status or viewOnly here as the + * mouse events might be used to control the viewport + */ + + if (ev.type === 'click') { + /* + * Note: This is only needed for the 'click' event as it fails + * to fire properly for the target element so we have + * to listen on the document element instead. + */ + if (ev.target !== this._canvas) { + return; + } + } + + // FIXME: if we're in view-only and not dragging, + // should we stop events? + ev.stopPropagation(); + ev.preventDefault(); + + if ((ev.type === 'click') || (ev.type === 'contextmenu')) { + return; + } + + let pos = clientToElement(ev.clientX, ev.clientY, + this._canvas); + + switch (ev.type) { + case 'mousedown': + setCapture(this._canvas); + this._handleMouseButton(pos.x, pos.y, + true, 1 << ev.button); + break; + case 'mouseup': + this._handleMouseButton(pos.x, pos.y, + false, 1 << ev.button); + break; + case 'mousemove': + this._handleMouseMove(pos.x, pos.y); + break; + } + } + _handleMouseButton(x, y, down, bmask) { if (this.dragViewport) { if (down && !this._viewportDragging) { @@ -1678,7 +1731,6 @@ export default class RFB extends EventTargetMixin { this._resize(width, height); if (!this._viewOnly) { this._keyboard.grab(); } - if (!this._viewOnly) { this._mouse.grab(); } this._fbDepth = 24; diff --git a/docs/API-internal.md b/docs/API-internal.md index f1519422..cb4cc396 100644 --- a/docs/API-internal.md +++ b/docs/API-internal.md @@ -11,9 +11,6 @@ official external API. ## 1.1 Module List -* __Mouse__ (core/input/mouse.js): Mouse input event handler with -limited touch support. - * __Keyboard__ (core/input/keyboard.js): Keyboard input event handler with non-US keyboard support. Translates keyDown and keyUp events to X11 keysym values. @@ -35,52 +32,29 @@ callback event name, and the callback function. ## 2. Modules -## 2.1 Mouse Module +## 2.1 Keyboard Module ### 2.1.1 Configuration Attributes -| name | type | mode | default | description -| ----------- | ---- | ---- | -------- | ------------ -| touchButton | int | RW | 1 | Button mask (1, 2, 4) for which click to send on touch devices. 0 means ignore clicks. - -### 2.1.2 Methods - -| name | parameters | description -| ------ | ---------- | ------------ -| grab | () | Begin capturing mouse events -| ungrab | () | Stop capturing mouse events - -### 2.1.2 Callbacks - -| name | parameters | description -| ------------- | ------------------- | ------------ -| onmousebutton | (x, y, down, bmask) | Handler for mouse button click/release -| onmousemove | (x, y) | Handler for mouse movement - - -## 2.2 Keyboard Module - -### 2.2.1 Configuration Attributes - None -### 2.2.2 Methods +### 2.1.2 Methods | name | parameters | description | ------ | ---------- | ------------ | grab | () | Begin capturing keyboard events | ungrab | () | Stop capturing keyboard events -### 2.2.3 Callbacks +### 2.1.3 Callbacks | name | parameters | description | ---------- | -------------------- | ------------ | onkeypress | (keysym, code, down) | Handler for key press/release -## 2.3 Display Module +## 2.2 Display Module -### 2.3.1 Configuration Attributes +### 2.2.1 Configuration Attributes | name | type | mode | default | description | ------------ | ----- | ---- | ------- | ------------ @@ -89,7 +63,7 @@ None | width | int | RO | | Display area width | height | int | RO | | Display area height -### 2.3.2 Methods +### 2.2.2 Methods | name | parameters | description | ------------------ | ------------------------------------------------------- | ------------ @@ -113,7 +87,7 @@ None | drawImage | (img, x, y) | Draw image and track damage | autoscale | (containerWidth, containerHeight) | Scale the display -### 2.3.3 Callbacks +### 2.2.3 Callbacks | name | parameters | description | ------- | ---------- | ------------ diff --git a/tests/test.mouse.js b/tests/test.mouse.js deleted file mode 100644 index 25d52195..00000000 --- a/tests/test.mouse.js +++ /dev/null @@ -1,73 +0,0 @@ -const expect = chai.expect; - -import Mouse from '../core/input/mouse.js'; - -describe('Mouse Event Handling', function () { - "use strict"; - - let target; - - beforeEach(function () { - // For these tests we can assume that the canvas is 100x100 - // located at coordinates 10x10 - target = document.createElement('canvas'); - target.style.position = "absolute"; - target.style.top = "10px"; - target.style.left = "10px"; - target.style.width = "100px"; - target.style.height = "100px"; - document.body.appendChild(target); - }); - afterEach(function () { - document.body.removeChild(target); - target = null; - }); - - // The real constructors might not work everywhere we - // want to run these tests - const mouseevent = (typeArg, MouseEventInit) => { - const e = { type: typeArg }; - for (let key in MouseEventInit) { - e[key] = MouseEventInit[key]; - } - e.stopPropagation = sinon.spy(); - e.preventDefault = sinon.spy(); - return e; - }; - - describe('Decode Mouse Events', function () { - it('should decode mousedown events', function (done) { - const mouse = new Mouse(target); - mouse.onmousebutton = (x, y, down, bmask) => { - expect(bmask).to.be.equal(0x01); - expect(down).to.be.equal(1); - done(); - }; - mouse._handleMouseDown(mouseevent('mousedown', { button: 0 })); - }); - it('should decode mouseup events', function (done) { - let calls = 0; - const mouse = new Mouse(target); - mouse.onmousebutton = (x, y, down, bmask) => { - expect(bmask).to.be.equal(0x01); - if (calls++ === 1) { - expect(down).to.not.be.equal(1); - done(); - } - }; - mouse._handleMouseDown(mouseevent('mousedown', { button: 0 })); - mouse._handleMouseUp(mouseevent('mouseup', { button: 0 })); - }); - it('should decode mousemove events', function (done) { - const mouse = new Mouse(target); - mouse.onmousemove = (x, y) => { - // Note that target relative coordinates are sent - expect(x).to.be.equal(40); - expect(y).to.be.equal(10); - done(); - }; - mouse._handleMouseMove(mouseevent('mousemove', - { clientX: 50, clientY: 20 })); - }); - }); -}); diff --git a/tests/test.rfb.js b/tests/test.rfb.js index cc450cd9..db9340ab 100644 --- a/tests/test.rfb.js +++ b/tests/test.rfb.js @@ -1579,12 +1579,10 @@ describe('Remote Frame Buffer Protocol Client', function () { expect(client._display.resize).to.have.been.calledWith(27, 32); }); - it('should grab the mouse and keyboard', function () { + it('should grab the keyboard', function () { sinon.spy(client._keyboard, 'grab'); - sinon.spy(client._mouse, 'grab'); sendServerInit({}, client); expect(client._keyboard.grab).to.have.been.calledOnce; - expect(client._mouse.grab).to.have.been.calledOnce; }); describe('Initial Update Request', function () { @@ -2739,6 +2737,11 @@ describe('Remote Frame Buffer Protocol Client', function () { client = makeRFB(); client._display.resize(100, 100); + // We need to disable this as focusing the canvas will + // cause the browser to scoll to it, messing up our + // client coordinate calculations + client.focusOnClick = false; + pointerEvent = sinon.spy(RFB.messages, 'pointerEvent'); keyEvent = sinon.spy(RFB.messages, 'keyEvent'); qemuKeyEvent = sinon.spy(RFB.messages, 'QEMUExtendedKeyEvent'); @@ -2769,136 +2772,279 @@ describe('Remote Frame Buffer Protocol Client', function () { } describe('Mouse Events', function () { + function sendMouseMoveEvent(x, y) { + let pos = elementToClient(x, y); + let ev; + + try { + ev = new MouseEvent('mousemove', + { 'screenX': pos.x + window.screenX, + 'screenY': pos.y + window.screenY, + 'clientX': pos.x, + 'clientY': pos.y }); + } catch (e) { + ev = document.createEvent('MouseEvent'); + ev.initMouseEvent('mousemove', + true, true, window, 0, + pos.x + window.screenX, + pos.y + window.screenY, + pos.x, pos.y, + false, false, false, false, + 0, null); + } + + client._canvas.dispatchEvent(ev); + } + + function sendMouseButtonEvent(x, y, down, button) { + let pos = elementToClient(x, y); + let ev; + + try { + ev = new MouseEvent(down ? 'mousedown' : 'mouseup', + { 'screenX': pos.x + window.screenX, + 'screenY': pos.y + window.screenY, + 'clientX': pos.x, + 'clientY': pos.y, + 'button': button, + 'buttons': 1 << button }); + } catch (e) { + ev = document.createEvent('MouseEvent'); + ev.initMouseEvent(down ? 'mousedown' : 'mouseup', + true, true, window, 0, + pos.x + window.screenX, + pos.y + window.screenY, + pos.x, pos.y, + false, false, false, false, + button, null); + } + + client._canvas.dispatchEvent(ev); + } + it('should not send button messages in view-only mode', function () { client._viewOnly = true; - client._handleMouseButton(0, 0, 1, 0x001); - expect(RFB.messages.pointerEvent).to.not.have.been.called; + sendMouseButtonEvent(10, 10, true, 0); + clock.tick(50); + expect(pointerEvent).to.not.have.been.called; }); it('should not send movement messages in view-only mode', function () { client._viewOnly = true; - client._handleMouseMove(0, 0); - expect(RFB.messages.pointerEvent).to.not.have.been.called; + sendMouseMoveEvent(10, 10); + clock.tick(50); + expect(pointerEvent).to.not.have.been.called; }); - it('should send a pointer event on mouse button presses', function () { - client._handleMouseButton(10, 12, 1, 0x001); - expect(RFB.messages.pointerEvent).to.have.been.calledOnce; + it('should handle left mouse button', function () { + sendMouseButtonEvent(10, 10, true, 0); + + expect(pointerEvent).to.have.been.calledOnceWith(client._sock, + 10, 10, 0x1); + pointerEvent.resetHistory(); + + sendMouseButtonEvent(10, 10, false, 0); + + expect(pointerEvent).to.have.been.calledOnceWith(client._sock, + 10, 10, 0x0); }); - it('should send a mask of 1 on mousedown', function () { - client._handleMouseButton(11, 13, 1, 0x001); - expect(RFB.messages.pointerEvent).to.have.been.calledWith( - client._sock, 11, 13, 0x001); + it('should handle middle mouse button', function () { + sendMouseButtonEvent(10, 10, true, 1); + + expect(pointerEvent).to.have.been.calledOnceWith(client._sock, + 10, 10, 0x2); + pointerEvent.resetHistory(); + + sendMouseButtonEvent(10, 10, false, 1); + + expect(pointerEvent).to.have.been.calledOnceWith(client._sock, + 10, 10, 0x0); }); - it('should send a mask of 0 on mouseup', function () { - client._mouseButtonMask = 0x001; - client._handleMouseButton(105, 120, 0, 0x001); - expect(RFB.messages.pointerEvent).to.have.been.calledWith( - client._sock, 105, 120, 0x000); + it('should handle right mouse button', function () { + sendMouseButtonEvent(10, 10, true, 2); + + expect(pointerEvent).to.have.been.calledOnceWith(client._sock, + 10, 10, 0x4); + pointerEvent.resetHistory(); + + sendMouseButtonEvent(10, 10, false, 2); + + expect(pointerEvent).to.have.been.calledOnceWith(client._sock, + 10, 10, 0x0); }); - it('should send a mask of 0 on mousemove', function () { - client._handleMouseMove(100, 200); - expect(RFB.messages.pointerEvent).to.have.been.calledWith( - client._sock, 100, 200, 0x000); + it('should handle multiple mouse buttons', function () { + sendMouseButtonEvent(10, 10, true, 0); + sendMouseButtonEvent(10, 10, true, 2); + + expect(pointerEvent).to.have.been.calledTwice; + expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock, + 10, 10, 0x1); + expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock, + 10, 10, 0x5); + + pointerEvent.resetHistory(); + + sendMouseButtonEvent(10, 10, false, 0); + sendMouseButtonEvent(10, 10, false, 2); + + expect(pointerEvent).to.have.been.calledTwice; + expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock, + 10, 10, 0x4); + expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock, + 10, 10, 0x0); }); - it('should set the button mask so that future mouse movements use it', function () { - client._handleMouseButton(10, 12, 1, 0x010); - client._handleMouseMove(13, 9); - expect(RFB.messages.pointerEvent).to.have.been.calledTwice; - expect(RFB.messages.pointerEvent).to.have.been.calledWith( - client._sock, 13, 9, 0x010); + it('should handle mouse movement', function () { + sendMouseMoveEvent(50, 70); + expect(pointerEvent).to.have.been.calledOnceWith(client._sock, + 50, 70, 0x0); }); - it('should send a single pointer event on mouse movement', function () { - client._handleMouseMove(100, 200); - this.clock.tick(100); - expect(RFB.messages.pointerEvent).to.have.been.calledOnce; + it('should handle click and drag', function () { + sendMouseButtonEvent(10, 10, true, 0); + sendMouseMoveEvent(50, 70); + + expect(pointerEvent).to.have.been.calledTwice; + expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock, + 10, 10, 0x1); + expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock, + 50, 70, 0x1); + + pointerEvent.resetHistory(); + + sendMouseButtonEvent(50, 70, false, 0); + + expect(pointerEvent).to.have.been.calledOnceWith(client._sock, + 50, 70, 0x0); }); - it('should delay one move if two events are too close', function () { - client._handleMouseMove(18, 30); - client._handleMouseMove(20, 50); - expect(RFB.messages.pointerEvent).to.have.been.calledOnce; - this.clock.tick(100); - expect(RFB.messages.pointerEvent).to.have.been.calledTwice; + describe('Event Aggregation', function () { + it('should send a single pointer event on mouse movement', function () { + sendMouseMoveEvent(50, 70); + clock.tick(100); + expect(pointerEvent).to.have.been.calledOnceWith(client._sock, + 50, 70, 0x0); + }); + + it('should delay one move if two events are too close', function () { + sendMouseMoveEvent(18, 30); + sendMouseMoveEvent(20, 50); + + expect(pointerEvent).to.have.been.calledOnceWith(client._sock, + 18, 30, 0x0); + pointerEvent.resetHistory(); + + clock.tick(100); + + expect(pointerEvent).to.have.been.calledOnceWith(client._sock, + 20, 50, 0x0); + }); + + it('should only send first and last move of many close events', function () { + sendMouseMoveEvent(18, 30); + sendMouseMoveEvent(20, 50); + sendMouseMoveEvent(21, 55); + + expect(pointerEvent).to.have.been.calledOnceWith(client._sock, + 18, 30, 0x0); + pointerEvent.resetHistory(); + + clock.tick(100); + + expect(pointerEvent).to.have.been.calledOnceWith(client._sock, + 21, 55, 0x0); + }); + + // We selected the 17ms since that is ~60 FPS + it('should send move events every 17 ms', function () { + sendMouseMoveEvent(1, 10); // instant send + clock.tick(10); + + expect(pointerEvent).to.have.been.calledOnceWith(client._sock, + 1, 10, 0x0); + pointerEvent.resetHistory(); + + sendMouseMoveEvent(2, 20); // delayed + clock.tick(10); // timeout send + + expect(pointerEvent).to.have.been.calledOnceWith(client._sock, + 2, 20, 0x0); + pointerEvent.resetHistory(); + + sendMouseMoveEvent(3, 30); // delayed + clock.tick(10); + sendMouseMoveEvent(4, 40); // delayed + clock.tick(10); // timeout send + + expect(pointerEvent).to.have.been.calledOnceWith(client._sock, + 4, 40, 0x0); + pointerEvent.resetHistory(); + + sendMouseMoveEvent(5, 50); // delayed + + expect(pointerEvent).to.not.have.been.called; + }); + + it('should send waiting move events before a button press', function () { + sendMouseMoveEvent(13, 9); + + expect(pointerEvent).to.have.been.calledOnceWith(client._sock, + 13, 9, 0x0); + pointerEvent.resetHistory(); + + sendMouseMoveEvent(20, 70); + + expect(pointerEvent).to.not.have.been.called; + + sendMouseButtonEvent(20, 70, true, 0); + + expect(pointerEvent).to.have.been.calledTwice; + expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock, + 20, 70, 0x0); + expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock, + 20, 70, 0x1); + }); + + it('should send move events with enough time apart normally', function () { + sendMouseMoveEvent(58, 60); + + expect(pointerEvent).to.have.been.calledOnceWith(client._sock, + 58, 60, 0x0); + pointerEvent.resetHistory(); + + clock.tick(20); + + sendMouseMoveEvent(25, 60); + + expect(pointerEvent).to.have.been.calledOnceWith(client._sock, + 25, 60, 0x0); + pointerEvent.resetHistory(); + }); + + it('should not send waiting move events if disconnected', function () { + sendMouseMoveEvent(88, 99); + + expect(pointerEvent).to.have.been.calledOnceWith(client._sock, + 88, 99, 0x0); + pointerEvent.resetHistory(); + + sendMouseMoveEvent(66, 77); + client.disconnect(); + clock.tick(20); + + expect(pointerEvent).to.not.have.been.called; + }); }); - it('should only send first and last move of many close events', function () { - client._handleMouseMove(18, 40); - client._handleMouseMove(20, 50); - client._handleMouseMove(21, 55); - - expect(RFB.messages.pointerEvent).to.have.been.calledOnce; - this.clock.tick(60); - - expect(RFB.messages.pointerEvent).to.have.been.calledTwice; - expect(RFB.messages.pointerEvent.firstCall).to.have.been.calledWith( - client._sock, 18, 40, 0x000); - expect(RFB.messages.pointerEvent.secondCall).to.have.been.calledWith( - client._sock, 21, 55, 0x000); + it.skip('should block click events', function () { + /* FIXME */ }); - // We selected the 17ms since that is ~60 FPS - it('should send move events every 17 ms', function () { - client._handleMouseMove(1, 10); // instant send - this.clock.tick(10); - client._handleMouseMove(2, 20); // delayed - this.clock.tick(10); // timeout send - client._handleMouseMove(3, 30); // delayed - this.clock.tick(10); - client._handleMouseMove(4, 40); // delayed - this.clock.tick(10); // timeout send - client._handleMouseMove(5, 50); // delayed - - expect(RFB.messages.pointerEvent).to.have.callCount(3); - expect(RFB.messages.pointerEvent.firstCall).to.have.been.calledWith( - client._sock, 1, 10, 0x000); - expect(RFB.messages.pointerEvent.secondCall).to.have.been.calledWith( - client._sock, 2, 20, 0x000); - expect(RFB.messages.pointerEvent.thirdCall).to.have.been.calledWith( - client._sock, 4, 40, 0x000); - }); - - it('should send waiting move events before a button press', function () { - client._handleMouseMove(13, 9); - client._handleMouseMove(20, 70); - client._handleMouseButton(10, 12, 1, 0x100); - expect(RFB.messages.pointerEvent).to.have.been.calledThrice; - expect(RFB.messages.pointerEvent.firstCall).to.have.been.calledWith( - client._sock, 13, 9, 0x000); - expect(RFB.messages.pointerEvent.secondCall).to.have.been.calledWith( - client._sock, 10, 12, 0x000); - expect(RFB.messages.pointerEvent.thirdCall).to.have.been.calledWith( - client._sock, 10, 12, 0x100); - }); - - it('should not delay events when button mask changes', function () { - client._handleMouseMove(13, 9); // instant - client._handleMouseMove(11, 10); // delayed - client._handleMouseButton(10, 12, 1, 0x010); // flush delayed - expect(RFB.messages.pointerEvent).to.have.been.calledThrice; - }); - - it('should send move events with enough time apart normally', function () { - client._handleMouseMove(58, 60); - expect(RFB.messages.pointerEvent).to.have.been.calledOnce; - - this.clock.tick(20); - - client._handleMouseMove(25, 60); - expect(RFB.messages.pointerEvent).to.have.been.calledTwice; - }); - - it('should not send waiting move events if disconnected', function () { - client._handleMouseMove(88, 99); - client._handleMouseMove(66, 77); - client.disconnect(); - this.clock.tick(20); - expect(RFB.messages.pointerEvent).to.have.been.calledOnce; + it.skip('should block contextmenu events', function () { + /* FIXME */ }); });