]> git.proxmox.com Git - mirror_novnc.git/commitdiff
Move wheel event handling to RFB class
authorPierre Ossman <ossman@cendio.se>
Wed, 10 Jun 2020 11:59:10 +0000 (13:59 +0200)
committerSamuel Mannehed <samuel@cendio.se>
Fri, 12 Jun 2020 07:18:46 +0000 (09:18 +0200)
The Mouse class does very little now so it mostly just obfuscate things.
Move everything directly in to the RFB class instead.

core/input/mouse.js
core/rfb.js
tests/test.mouse.js
tests/test.rfb.js

index 8c917d098b9a65c1be2b72b26932614b08de3639..794adfe232eaf6222acf4ad98970ac9a83fab19c 100644 (file)
@@ -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);
 
index 9db375fccfb1224a8daa7b14458ee27787cf1302..4d1dcf1e735fa1ecd838de19e4d36b2373908ed5 100644 (file)
@@ -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);
index 13bd0c60582d77f411d02970aa7daa44df72e1cf..25d521951e261f2cb640637bc953b2deb40d531a 100644 (file)
@@ -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
-        });
     });
 });
index e46f40c5ca8700e9a05ffd87bfa7e0d15cdeffb4..909b300a978edf0bd9358fb4aa6f198c6fea09e9 100644 (file)
@@ -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