Separate mouse and touch events

This commit is contained in:
Juanjo Diaz 2019-12-17 14:14:55 +02:00
parent 776cda5dc4
commit 30c1485ad8
7 changed files with 380 additions and 262 deletions

View File

@ -5,8 +5,7 @@
*/
import * as Log from '../util/logging.js';
import { isTouchDevice } from '../util/browser.js';
import { setCapture, stopEvent, getPointerEvent } from '../util/events.js';
import { setCapture, stopEvent } from '../util/events.js';
const WHEEL_STEP = 10; // Delta threshold for a mouse wheel step
const WHEEL_STEP_TIMEOUT = 50; // ms
@ -17,10 +16,6 @@ export default class Mouse {
constructor(target) {
this._target = target || document;
this._doubleClickTimer = null;
this._lastTouchPos = null;
this._pos = null;
this._wheelStepXTimer = null;
this._wheelStepYTimer = null;
this._oldMouseMoveTime = 0;
@ -35,11 +30,6 @@ export default class Mouse {
'mousedisable': this._handleMouseDisable.bind(this)
};
// ===== PROPERTIES =====
this.touchButton = 1; // Button mask (1, 2, 4) for touch devices
// (0 means ignore clicks)
// ===== EVENT HANDLERS =====
this.onmousebutton = () => {}; // Handler for mouse button press/release
@ -48,48 +38,11 @@ export default class Mouse {
// ===== PRIVATE METHODS =====
_resetDoubleClickTimer() {
this._doubleClickTimer = null;
}
_handleMouseButton(e, down) {
this._updateMousePosition(e);
let pos = this._pos;
const position = this._getMousePosition(e);
let bmask;
if (e.touches || e.changedTouches) {
// Touch device
// When two touches occur within 500 ms of each other and are
// close enough together a double click is triggered.
if (down == 1) {
if (this._doubleClickTimer === null) {
this._lastTouchPos = pos;
} else {
clearTimeout(this._doubleClickTimer);
// When the distance between the two touches is small enough
// force the position of the latter touch to the position of
// the first.
const xs = this._lastTouchPos.x - pos.x;
const ys = this._lastTouchPos.y - pos.y;
const d = Math.sqrt((xs * xs) + (ys * ys));
// The goal is to trigger on a certain physical width,
// the devicePixelRatio brings us a bit closer but is
// not optimal.
const threshold = 20 * (window.devicePixelRatio || 1);
if (d < threshold) {
pos = this._lastTouchPos;
}
}
this._doubleClickTimer =
setTimeout(this._resetDoubleClickTimer.bind(this), 500);
}
bmask = this.touchButton;
// If bmask is set
} else if (e.which) {
if (e.which) {
/* everything except IE */
bmask = 1 << e.button;
} else {
@ -100,18 +53,14 @@ export default class Mouse {
}
Log.Debug("onmousebutton " + (down ? "down" : "up") +
", x: " + pos.x + ", y: " + pos.y + ", bmask: " + bmask);
this.onmousebutton(pos.x, pos.y, down, bmask);
", x: " + position.x + ", y: " + position.y + ", bmask: " + bmask);
this.onmousebutton(position.x, position.y, down, bmask);
stopEvent(e);
}
_handleMouseDown(e) {
// Touch events have implicit capture
if (e.type === "mousedown") {
setCapture(this._target);
}
setCapture(this._target);
this._handleMouseButton(e, 1);
}
@ -122,27 +71,25 @@ export default class Mouse {
// 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() {
_generateWheelStepX(position) {
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);
this.onmousebutton(position.x, position.y, 1, 1 << 5);
this.onmousebutton(position.x, position.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.onmousebutton(position.x, position.y, 1, 1 << 6);
this.onmousebutton(position.x, position.y, 0, 1 << 6);
}
this._accumulatedWheelDeltaX = 0;
}
_generateWheelStepY() {
_generateWheelStepY(position) {
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);
this.onmousebutton(position.x, position.y, 1, 1 << 3);
this.onmousebutton(position.x, position.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.onmousebutton(position.x, position.y, 1, 1 << 4);
this.onmousebutton(position.x, position.y, 0, 1 << 4);
}
this._accumulatedWheelDeltaY = 0;
@ -158,7 +105,7 @@ export default class Mouse {
_handleMouseWheel(e) {
this._resetWheelStepTimers();
this._updateMousePosition(e);
const position = this._getMousePosition(e);
let dX = e.deltaX;
let dY = e.deltaY;
@ -181,17 +128,17 @@ export default class Mouse {
// Small delta events that do not pass the threshold get sent
// after a timeout.
if (Math.abs(this._accumulatedWheelDeltaX) > WHEEL_STEP) {
this._generateWheelStepX();
this._generateWheelStepX(position);
} else {
this._wheelStepXTimer =
window.setTimeout(this._generateWheelStepX.bind(this),
window.setTimeout(() => this._generateWheelStepX(position),
WHEEL_STEP_TIMEOUT);
}
if (Math.abs(this._accumulatedWheelDeltaY) > WHEEL_STEP) {
this._generateWheelStepY();
this._generateWheelStepY(position);
} else {
this._wheelStepYTimer =
window.setTimeout(this._generateWheelStepY.bind(this),
window.setTimeout(() => this._generateWheelStepY(position),
WHEEL_STEP_TIMEOUT);
}
@ -199,7 +146,7 @@ export default class Mouse {
}
_handleMouseMove(e) {
this._updateMousePosition(e);
const position = this._getMousePosition(e);
// Limit mouse move events to one every MOUSE_MOVE_DELAY ms
clearTimeout(this.mouseMoveTimer);
@ -207,9 +154,9 @@ export default class Mouse {
if (newMouseMoveTime < this._oldMouseMoveTime + MOUSE_MOVE_DELAY) {
this.mouseMoveTimer = setTimeout(this.onmousemove.bind(this),
MOUSE_MOVE_DELAY,
this._pos.x, this._pos.y);
position.x, position.y);
} else {
this.onmousemove(this._pos.x, this._pos.y);
this.onmousemove(position.x, position.y);
}
this._oldMouseMoveTime = newMouseMoveTime;
@ -228,9 +175,8 @@ export default class Mouse {
}
}
// Update coordinates relative to target
_updateMousePosition(e) {
e = getPointerEvent(e);
// Get coordinates relative to target
_getMousePosition(e) {
const bounds = this._target.getBoundingClientRect();
let x;
let y;
@ -249,18 +195,13 @@ export default class Mouse {
} else {
y = e.clientY - bounds.top;
}
this._pos = {x: x, y: y};
return { x, y };
}
// ===== PUBLIC METHODS =====
grab() {
const t = this._target;
if (isTouchDevice) {
t.addEventListener('touchstart', this._eventHandlers.mousedown);
t.addEventListener('touchend', this._eventHandlers.mouseup);
t.addEventListener('touchmove', this._eventHandlers.mousemove);
}
t.addEventListener('mousedown', this._eventHandlers.mousedown);
t.addEventListener('mouseup', this._eventHandlers.mouseup);
t.addEventListener('mousemove', this._eventHandlers.mousemove);
@ -279,11 +220,6 @@ export default class Mouse {
this._resetWheelStepTimers();
if (isTouchDevice) {
t.removeEventListener('touchstart', this._eventHandlers.mousedown);
t.removeEventListener('touchend', this._eventHandlers.mouseup);
t.removeEventListener('touchmove', this._eventHandlers.mousemove);
}
t.removeEventListener('mousedown', this._eventHandlers.mousedown);
t.removeEventListener('mouseup', this._eventHandlers.mouseup);
t.removeEventListener('mousemove', this._eventHandlers.mousemove);

140
core/input/touch.js Normal file
View File

@ -0,0 +1,140 @@
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2020 The noVNC Authors
* Licensed under MPL 2.0 or any later version (see LICENSE.txt)
*/
import * as Log from '../util/logging.js';
import { stopEvent, getPointerEvent } from '../util/events.js';
const TOUCH_MOVE_DELAY = 17; // Minimum wait (ms) between two touch moves
export default class Touch {
constructor(target) {
this._target = target || document;
this._doubleClickTimer = null;
this._lastTouchPos = null;
this._oldTouchMoveTime = 0;
this._eventHandlers = {
'touchstart': this._handleTouchStart.bind(this),
'touchend': this._handleTouchEnd.bind(this),
'touchmove': this._handleTouchMove.bind(this)
};
// ===== PROPERTIES =====
this.touchButton = 1; // Button mask (1, 2, 4) for touch devices (0 means ignore clicks)
// ===== EVENT HANDLERS =====
this.ontouch = () => {}; // Handler for mouse button click/release
this.ontouchmove = () => {}; // Handler for mouse movement
}
// ===== PRIVATE METHODS =====
_handleTouchStart(e) {
this._handleTouchAsMouseButton(e, 1);
}
_handleTouchEnd(e) {
this._handleTouchAsMouseButton(e, 0);
}
_handleTouchMove(e) {
const position = this._getTouchPosition(e);
// Limit touch move events to one every TOUCH_MOVE_DELAY ms
clearTimeout(this.touchMoveTimer);
const newTouchMoveTime = Date.now();
if (newTouchMoveTime < this._oldTouchMoveTime + TOUCH_MOVE_DELAY) {
this.touchMoveTimer = setTimeout(this.ontouchmove.bind(this),
TOUCH_MOVE_DELAY,
position.x, position.y);
} else {
this.ontouchmove(position.x, position.y);
}
this._oldTouchMoveTime = newTouchMoveTime;
stopEvent(e);
}
_handleTouchAsMouseButton(e, down) {
let position = this._getTouchPosition(e);
// When two touches occur within 500 ms of each other and are
// close enough together a double click is triggered.
if (down == 1) {
if (this._doubleClickTimer === null) {
this._lastTouchPos = position;
} else {
clearTimeout(this._doubleClickTimer);
// When the distance between the two touches is small enough
// force the position of the latter touch to the position of
// the first.
const xs = this._lastTouchPos.x - position.x;
const ys = this._lastTouchPos.y - position.y;
const d = Math.sqrt((xs * xs) + (ys * ys));
// The goal is to trigger on a certain physical width,
// the devicePixelRatio brings us a bit closer but is
// not optimal.
const threshold = 20 * (window.devicePixelRatio || 1);
if (d < threshold) {
position = this._lastTouchPos;
}
}
this._doubleClickTimer = setTimeout(() => (this._doubleClickTimer = null), 500);
}
const bmask = this.touchButton;
Log.Debug("onmousebutton " + (down ? "down" : "up") +
", x: " + position.x + ", y: " + position.y + ", bmask: " + bmask);
this.ontouch(position.x, position.y, down, bmask);
stopEvent(e);
}
// Get coordinates relative to target
_getTouchPosition(e) {
const pointerEvent = getPointerEvent(e);
const bounds = this._target.getBoundingClientRect();
let x;
let y;
// Clip to target bounds
if (pointerEvent.clientX < bounds.left) {
x = 0;
} else if (pointerEvent.clientX >= bounds.right) {
x = bounds.width - 1;
} else {
x = pointerEvent.clientX - bounds.left;
}
if (pointerEvent.clientY < bounds.top) {
y = 0;
} else if (pointerEvent.clientY >= bounds.bottom) {
y = bounds.height - 1;
} else {
y = pointerEvent.clientY - bounds.top;
}
return { x, y };
}
// ===== PUBLIC METHODS =====
grab() {
this._target.addEventListener('touchstart', this._eventHandlers.touchstart);
this._target.addEventListener('touchend', this._eventHandlers.touchend);
this._target.addEventListener('touchmove', this._eventHandlers.touchmove);
}
ungrab() {
this._target.removeEventListener('touchstart', this._eventHandlers.touchstart);
this._target.removeEventListener('touchend', this._eventHandlers.touchend);
this._target.removeEventListener('touchmove', this._eventHandlers.touchmove);
}
}

View File

@ -10,13 +10,14 @@
import { toUnsigned32bit, toSigned32bit } from './util/int.js';
import * as Log from './util/logging.js';
import { encodeUTF8, decodeUTF8 } from './util/strings.js';
import { dragThreshold } from './util/browser.js';
import { dragThreshold, isTouchDevice } from './util/browser.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 Touch from "./input/touch.js";
import Cursor from "./util/cursor.js";
import Websock from "./websock.js";
import DES from "./des.js";
@ -115,6 +116,7 @@ export default class RFB extends EventTargetMixin {
this._flushing = false; // Display flushing state
this._keyboard = null; // Keyboard input handler object
this._mouse = null; // Mouse input handler object
this._touch = null; // Touch input handler object
// Timers
this._disconnTimer = null; // disconnection timer
@ -202,8 +204,14 @@ export default class RFB extends EventTargetMixin {
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._mouse.onmousebutton = this._handlePointerPress.bind(this);
this._mouse.onmousemove = this._handlePointerMove.bind(this);
if (isTouchDevice) {
this._touch = new Touch(this._canvas);
this._touch.ontouch = this._handlePointerPress.bind(this);
this._touch.ontouchmove = this._handlePointerMove.bind(this);
}
this._sock = new Websock();
this._sock.on('message', () => {
@ -292,17 +300,19 @@ export default class RFB extends EventTargetMixin {
if (viewOnly) {
this._keyboard.ungrab();
this._mouse.ungrab();
if (this._touch) { this._touch.ungrab(); }
} else {
this._keyboard.grab();
this._mouse.grab();
if (this._touch) { this._touch.grab(); }
}
}
}
get capabilities() { return this._capabilities; }
get touchButton() { return this._mouse.touchButton; }
set touchButton(button) { this._mouse.touchButton = button; }
get touchButton() { return this._touch.touchButton; }
set touchButton(button) { this._touch.touchButton = button; }
get clipViewport() { return this._clipViewport; }
set clipViewport(viewport) {
@ -813,7 +823,7 @@ export default class RFB extends EventTargetMixin {
this.sendKey(keysym, code, down);
}
_handleMouseButton(x, y, down, bmask) {
_handlePointerPress(x, y, down, bmask) {
if (down) {
this._mouse_buttonMask |= bmask;
} else {
@ -853,7 +863,7 @@ export default class RFB extends EventTargetMixin {
RFB.messages.pointerEvent(this._sock, this._display.absX(x), this._display.absY(y), this._mouse_buttonMask);
}
_handleMouseMove(x, y) {
_handlePointerMove(x, y) {
if (this._viewportDragging) {
const deltaX = this._viewportDragPos.x - x;
const deltaY = this._viewportDragPos.y - y;

View File

@ -11,8 +11,10 @@ official external API.
## 1.1 Module List
* __Mouse__ (core/input/mouse.js): Mouse input event handler with
limited touch support.
* __Mouse__ (core/input/mouse.js): Mouse input event handler.
* __Touch__ (core/input/touch.js): Touch input event handler that converts
touch events to mouse events.
* __Keyboard__ (core/input/keyboard.js): Keyboard input event handler with
non-US keyboard support. Translates keyDown and keyUp events to X11
@ -39,9 +41,7 @@ callback event name, and the callback function.
### 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.
None
### 2.1.2 Methods
@ -58,29 +58,52 @@ callback event name, and the callback function.
| onmousemove | (x, y) | Handler for mouse movement
## 2.2 Keyboard Module
## 2.2 Touch Module
### 2.2.1 Configuration Attributes
None
| 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.2.2 Methods
| name | parameters | description
| ------ | ---------- | ------------
| grab | () | Begin capturing touch events
| ungrab | () | Stop capturing touch events
### 2.2.2 Callbacks
| name | parameters | description
| ------------- | ------------------- | ------------
| ontouch | (x, y, down, bmask) | Handler for touch event (as button click/release)
| ontouchmove | (x, y) | Handler for touch movement
## 2.3 Keyboard Module
### 2.3.1 Configuration Attributes
None
### 2.3.2 Methods
| name | parameters | description
| ------ | ---------- | ------------
| grab | () | Begin capturing keyboard events
| ungrab | () | Stop capturing keyboard events
### 2.2.3 Callbacks
### 2.3.4 Callbacks
| name | parameters | description
| ---------- | -------------------- | ------------
| onkeypress | (keysym, code, down) | Handler for key press/release
## 2.3 Display Module
## 2.4 Display Module
### 2.3.1 Configuration Attributes
### 2.4.1 Configuration Attributes
| name | type | mode | default | description
| ------------ | ----- | ---- | ------- | ------------
@ -89,7 +112,7 @@ None
| width | int | RO | | Display area width
| height | int | RO | | Display area height
### 2.3.2 Methods
### 2.4.2 Methods
| name | parameters | description
| ------------------ | ------------------------------------------------------- | ------------
@ -113,7 +136,7 @@ None
| drawImage | (img, x, y) | Draw image and track damage
| autoscale | (containerWidth, containerHeight) | Scale the display
### 2.3.3 Callbacks
### 2.4.3 Callbacks
| name | parameters | description
| ------- | ---------- | ------------

View File

@ -34,7 +34,6 @@ describe('Mouse Event Handling', function () {
e.preventDefault = sinon.spy();
return e;
};
const touchevent = mouseevent;
describe('Decode Mouse Events', function () {
it('should decode mousedown events', function (done) {
@ -89,131 +88,6 @@ describe('Mouse Event Handling', function () {
});
});
describe('Double-click for Touch', function () {
beforeEach(function () { this.clock = sinon.useFakeTimers(); });
afterEach(function () { this.clock.restore(); });
it('should use same pos for 2nd tap if close enough', function (done) {
let calls = 0;
const mouse = new Mouse(target);
mouse.onmousebutton = (x, y, down, bmask) => {
calls++;
if (calls === 1) {
expect(down).to.be.equal(1);
expect(x).to.be.equal(68);
expect(y).to.be.equal(36);
} else if (calls === 3) {
expect(down).to.be.equal(1);
expect(x).to.be.equal(68);
expect(y).to.be.equal(36);
done();
}
};
// touch events are sent in an array of events
// with one item for each touch point
mouse._handleMouseDown(touchevent(
'touchstart', { touches: [{ clientX: 78, clientY: 46 }]}));
this.clock.tick(10);
mouse._handleMouseUp(touchevent(
'touchend', { touches: [{ clientX: 79, clientY: 45 }]}));
this.clock.tick(200);
mouse._handleMouseDown(touchevent(
'touchstart', { touches: [{ clientX: 67, clientY: 35 }]}));
this.clock.tick(10);
mouse._handleMouseUp(touchevent(
'touchend', { touches: [{ clientX: 66, clientY: 36 }]}));
});
it('should not modify 2nd tap pos if far apart', function (done) {
let calls = 0;
const mouse = new Mouse(target);
mouse.onmousebutton = (x, y, down, bmask) => {
calls++;
if (calls === 1) {
expect(down).to.be.equal(1);
expect(x).to.be.equal(68);
expect(y).to.be.equal(36);
} else if (calls === 3) {
expect(down).to.be.equal(1);
expect(x).to.not.be.equal(68);
expect(y).to.not.be.equal(36);
done();
}
};
mouse._handleMouseDown(touchevent(
'touchstart', { touches: [{ clientX: 78, clientY: 46 }]}));
this.clock.tick(10);
mouse._handleMouseUp(touchevent(
'touchend', { touches: [{ clientX: 79, clientY: 45 }]}));
this.clock.tick(200);
mouse._handleMouseDown(touchevent(
'touchstart', { touches: [{ clientX: 57, clientY: 35 }]}));
this.clock.tick(10);
mouse._handleMouseUp(touchevent(
'touchend', { touches: [{ clientX: 56, clientY: 36 }]}));
});
it('should not modify 2nd tap pos if not soon enough', function (done) {
let calls = 0;
const mouse = new Mouse(target);
mouse.onmousebutton = (x, y, down, bmask) => {
calls++;
if (calls === 1) {
expect(down).to.be.equal(1);
expect(x).to.be.equal(68);
expect(y).to.be.equal(36);
} else if (calls === 3) {
expect(down).to.be.equal(1);
expect(x).to.not.be.equal(68);
expect(y).to.not.be.equal(36);
done();
}
};
mouse._handleMouseDown(touchevent(
'touchstart', { touches: [{ clientX: 78, clientY: 46 }]}));
this.clock.tick(10);
mouse._handleMouseUp(touchevent(
'touchend', { touches: [{ clientX: 79, clientY: 45 }]}));
this.clock.tick(500);
mouse._handleMouseDown(touchevent(
'touchstart', { touches: [{ clientX: 67, clientY: 35 }]}));
this.clock.tick(10);
mouse._handleMouseUp(touchevent(
'touchend', { touches: [{ clientX: 66, clientY: 36 }]}));
});
it('should not modify 2nd tap pos if not touch', function (done) {
let calls = 0;
const mouse = new Mouse(target);
mouse.onmousebutton = (x, y, down, bmask) => {
calls++;
if (calls === 1) {
expect(down).to.be.equal(1);
expect(x).to.be.equal(68);
expect(y).to.be.equal(36);
} else if (calls === 3) {
expect(down).to.be.equal(1);
expect(x).to.not.be.equal(68);
expect(y).to.not.be.equal(36);
done();
}
};
mouse._handleMouseDown(mouseevent(
'mousedown', { button: '0x01', clientX: 78, clientY: 46 }));
this.clock.tick(10);
mouse._handleMouseUp(mouseevent(
'mouseup', { button: '0x01', clientX: 79, clientY: 45 }));
this.clock.tick(200);
mouse._handleMouseDown(mouseevent(
'mousedown', { button: '0x01', clientX: 67, clientY: 35 }));
this.clock.tick(10);
mouse._handleMouseUp(mouseevent(
'mouseup', { button: '0x01', clientX: 66, clientY: 36 }));
});
});
describe('Accumulate mouse wheel events with small delta', function () {
beforeEach(function () { this.clock = sinon.useFakeTimers(); });

View File

@ -495,28 +495,28 @@ describe('Remote Frame Buffer Protocol Client', function () {
});
it('should not send button messages when initiating viewport dragging', function () {
client._handleMouseButton(13, 9, 0x001);
client._handlePointerPress(13, 9, 0x001);
expect(RFB.messages.pointerEvent).to.not.have.been.called;
});
it('should send button messages when release without movement', function () {
// Just up and down
client._handleMouseButton(13, 9, 0x001);
client._handleMouseButton(13, 9, 0x000);
client._handlePointerPress(13, 9, 0x001);
client._handlePointerPress(13, 9, 0x000);
expect(RFB.messages.pointerEvent).to.have.been.calledTwice;
RFB.messages.pointerEvent.resetHistory();
// Small movement
client._handleMouseButton(13, 9, 0x001);
client._handleMouseMove(15, 14);
client._handleMouseButton(15, 14, 0x000);
client._handlePointerPress(13, 9, 0x001);
client._handlePointerMove(15, 14);
client._handlePointerPress(15, 14, 0x000);
expect(RFB.messages.pointerEvent).to.have.been.calledTwice;
});
it('should send button message directly when drag is disabled', function () {
client.dragViewport = false;
client._handleMouseButton(13, 9, 0x001);
client._handlePointerPress(13, 9, 0x001);
expect(RFB.messages.pointerEvent).to.have.been.calledOnce;
});
@ -525,15 +525,15 @@ describe('Remote Frame Buffer Protocol Client', function () {
// Too small movement
client._handleMouseButton(13, 9, 0x001);
client._handleMouseMove(18, 9);
client._handlePointerPress(13, 9, 0x001);
client._handlePointerMove(18, 9);
expect(RFB.messages.pointerEvent).to.not.have.been.called;
expect(client._display.viewportChangePos).to.not.have.been.called;
// Sufficient movement
client._handleMouseMove(43, 9);
client._handlePointerMove(43, 9);
expect(RFB.messages.pointerEvent).to.not.have.been.called;
expect(client._display.viewportChangePos).to.have.been.calledOnce;
@ -543,7 +543,7 @@ describe('Remote Frame Buffer Protocol Client', function () {
// Now a small movement should move right away
client._handleMouseMove(43, 14);
client._handlePointerMove(43, 14);
expect(RFB.messages.pointerEvent).to.not.have.been.called;
expect(client._display.viewportChangePos).to.have.been.calledOnce;
@ -553,9 +553,9 @@ describe('Remote Frame Buffer Protocol Client', function () {
it('should not send button messages when dragging ends', function () {
// First the movement
client._handleMouseButton(13, 9, 0x001);
client._handleMouseMove(43, 9);
client._handleMouseButton(43, 9, 0x000);
client._handlePointerPress(13, 9, 0x001);
client._handlePointerMove(43, 9);
client._handlePointerPress(43, 9, 0x000);
expect(RFB.messages.pointerEvent).to.not.have.been.called;
});
@ -563,15 +563,15 @@ describe('Remote Frame Buffer Protocol Client', function () {
it('should terminate viewport dragging on a button up event', function () {
// First the dragging movement
client._handleMouseButton(13, 9, 0x001);
client._handleMouseMove(43, 9);
client._handleMouseButton(43, 9, 0x000);
client._handlePointerPress(13, 9, 0x001);
client._handlePointerMove(43, 9);
client._handlePointerPress(43, 9, 0x000);
// Another movement now should not move the viewport
sinon.spy(client._display, "viewportChangePos");
client._handleMouseMove(43, 59);
client._handlePointerMove(43, 59);
expect(client._display.viewportChangePos).to.not.have.been.called;
});
@ -2727,26 +2727,26 @@ describe('Remote Frame Buffer Protocol Client', function () {
it('should not send button messages in view-only mode', function () {
client._viewOnly = true;
sinon.spy(client._sock, 'flush');
client._handleMouseButton(0, 0, 1, 0x001);
client._handlePointerPress(0, 0, 1, 0x001);
expect(client._sock.flush).to.not.have.been.called;
});
it('should not send movement messages in view-only mode', function () {
client._viewOnly = true;
sinon.spy(client._sock, 'flush');
client._handleMouseMove(0, 0);
client._handlePointerMove(0, 0);
expect(client._sock.flush).to.not.have.been.called;
});
it('should send a pointer event on mouse button presses', function () {
client._handleMouseButton(10, 12, 1, 0x001);
client._handlePointerPress(10, 12, 1, 0x001);
const pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0, flush: () => {}};
RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x001);
expect(client._sock).to.have.sent(pointer_msg._sQ);
});
it('should send a mask of 1 on mousedown', function () {
client._handleMouseButton(10, 12, 1, 0x001);
client._handlePointerPress(10, 12, 1, 0x001);
const pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0, flush: () => {}};
RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x001);
expect(client._sock).to.have.sent(pointer_msg._sQ);
@ -2754,22 +2754,22 @@ describe('Remote Frame Buffer Protocol Client', function () {
it('should send a mask of 0 on mouseup', function () {
client._mouse_buttonMask = 0x001;
client._handleMouseButton(10, 12, 0, 0x001);
client._handlePointerPress(10, 12, 0, 0x001);
const pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0, flush: () => {}};
RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x000);
expect(client._sock).to.have.sent(pointer_msg._sQ);
});
it('should send a pointer event on mouse movement', function () {
client._handleMouseMove(10, 12);
client._handlePointerMove(10, 12);
const pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0, flush: () => {}};
RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x000);
expect(client._sock).to.have.sent(pointer_msg._sQ);
});
it('should set the button mask so that future mouse movements use it', function () {
client._handleMouseButton(10, 12, 1, 0x010);
client._handleMouseMove(13, 9);
client._handlePointerPress(10, 12, 1, 0x010);
client._handlePointerMove(13, 9);
const pointer_msg = {_sQ: new Uint8Array(12), _sQlen: 0, flush: () => {}};
RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x010);
RFB.messages.pointerEvent(pointer_msg, 13, 9, 0x010);

135
tests/test.touch.js Normal file
View File

@ -0,0 +1,135 @@
const expect = chai.expect;
import Touch from '../core/input/touch.js';
describe('Touch 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;
};
const touchevent = mouseevent;
describe('Double-click for Touch', function () {
beforeEach(function () { this.clock = sinon.useFakeTimers(); });
afterEach(function () { this.clock.restore(); });
it('should use same pos for 2nd tap if close enough', function (done) {
let calls = 0;
const touch = new Touch(target);
touch.ontouch = (x, y, down, bmask) => {
calls++;
if (calls === 1) {
expect(down).to.be.equal(1);
expect(x).to.be.equal(68);
expect(y).to.be.equal(36);
} else if (calls === 3) {
expect(down).to.be.equal(1);
expect(x).to.be.equal(68);
expect(y).to.be.equal(36);
done();
}
};
// touch events are sent in an array of events
// with one item for each touch point
touch._handleTouchStart(touchevent(
'touchstart', { touches: [{ clientX: 78, clientY: 46 }]}));
this.clock.tick(10);
touch._handleTouchEnd(touchevent(
'touchend', { touches: [{ clientX: 79, clientY: 45 }]}));
this.clock.tick(200);
touch._handleTouchStart(touchevent(
'touchstart', { touches: [{ clientX: 67, clientY: 35 }]}));
this.clock.tick(10);
touch._handleTouchEnd(touchevent(
'touchend', { touches: [{ clientX: 66, clientY: 36 }]}));
});
it('should not modify 2nd tap pos if far apart', function (done) {
let calls = 0;
const touch = new Touch(target);
touch.ontouch = (x, y, down, bmask) => {
calls++;
if (calls === 1) {
expect(down).to.be.equal(1);
expect(x).to.be.equal(68);
expect(y).to.be.equal(36);
} else if (calls === 3) {
expect(down).to.be.equal(1);
expect(x).to.not.be.equal(68);
expect(y).to.not.be.equal(36);
done();
}
};
touch._handleTouchStart(touchevent(
'touchstart', { touches: [{ clientX: 78, clientY: 46 }]}));
this.clock.tick(10);
touch._handleTouchEnd(touchevent(
'touchend', { touches: [{ clientX: 79, clientY: 45 }]}));
this.clock.tick(200);
touch._handleTouchStart(touchevent(
'touchstart', { touches: [{ clientX: 57, clientY: 35 }]}));
this.clock.tick(10);
touch._handleTouchEnd(touchevent(
'touchend', { touches: [{ clientX: 56, clientY: 36 }]}));
});
it('should not modify 2nd tap pos if not soon enough', function (done) {
let calls = 0;
const touch = new Touch(target);
touch.ontouch = (x, y, down, bmask) => {
calls++;
if (calls === 1) {
expect(down).to.be.equal(1);
expect(x).to.be.equal(68);
expect(y).to.be.equal(36);
} else if (calls === 3) {
expect(down).to.be.equal(1);
expect(x).to.not.be.equal(68);
expect(y).to.not.be.equal(36);
done();
}
};
touch._handleTouchStart(touchevent(
'touchstart', { touches: [{ clientX: 78, clientY: 46 }]}));
this.clock.tick(10);
touch._handleTouchEnd(touchevent(
'touchend', { touches: [{ clientX: 79, clientY: 45 }]}));
this.clock.tick(500);
touch._handleTouchStart(touchevent(
'touchstart', { touches: [{ clientX: 67, clientY: 35 }]}));
this.clock.tick(10);
touch._handleTouchEnd(touchevent(
'touchend', { touches: [{ clientX: 66, clientY: 36 }]}));
});
});
});