SPB Git forge

spb/admin-ka

Public
41commits 1branches 0releases
172.9 MBsize
maindefault branch
19 days agolast push
JavaScript 65.5% Python 17.8% CSS 13% HTML 3.7%
8.2 KB · 250 lines javascript
Raw Blame History
1/*2 * noVNC: HTML5 VNC client3 * Copyright (C) 2019 The noVNC authors4 * Licensed under MPL 2.0 or any later version (see LICENSE.txt)5 */67import { supportsCursorURIs, isTouchDevice } from './browser.js';89const useFallback = !supportsCursorURIs || isTouchDevice;1011export default class Cursor {12    constructor() {13        this._target = null;1415        this._canvas = document.createElement('canvas');1617        if (useFallback) {18            this._canvas.style.position = 'fixed';19            this._canvas.style.zIndex = '65535';20            this._canvas.style.pointerEvents = 'none';21            // Safari on iOS can select the cursor image22            // https://bugs.webkit.org/show_bug.cgi?id=24922323            this._canvas.style.userSelect = 'none';24            this._canvas.style.WebkitUserSelect = 'none';25            // Can't use "display" because of Firefox bug #144599726            this._canvas.style.visibility = 'hidden';27        }2829        this._position = { x: 0, y: 0 };30        this._hotSpot = { x: 0, y: 0 };3132        this._eventHandlers = {33            'mouseover': this._handleMouseOver.bind(this),34            'mouseleave': this._handleMouseLeave.bind(this),35            'mousemove': this._handleMouseMove.bind(this),36            'mouseup': this._handleMouseUp.bind(this),37        };38    }3940    attach(target) {41        if (this._target) {42            this.detach();43        }4445        this._target = target;4647        if (useFallback) {48            document.body.appendChild(this._canvas);4950            const options = { capture: true, passive: true };51            this._target.addEventListener('mouseover', this._eventHandlers.mouseover, options);52            this._target.addEventListener('mouseleave', this._eventHandlers.mouseleave, options);53            this._target.addEventListener('mousemove', this._eventHandlers.mousemove, options);54            this._target.addEventListener('mouseup', this._eventHandlers.mouseup, options);55        }5657        this.clear();58    }5960    detach() {61        if (!this._target) {62            return;63        }6465        if (useFallback) {66            const options = { capture: true, passive: true };67            this._target.removeEventListener('mouseover', this._eventHandlers.mouseover, options);68            this._target.removeEventListener('mouseleave', this._eventHandlers.mouseleave, options);69            this._target.removeEventListener('mousemove', this._eventHandlers.mousemove, options);70            this._target.removeEventListener('mouseup', this._eventHandlers.mouseup, options);7172            if (document.contains(this._canvas)) {73                document.body.removeChild(this._canvas);74            }75        }7677        this._target = null;78    }7980    change(rgba, hotx, hoty, w, h) {81        if ((w === 0) || (h === 0)) {82            this.clear();83            return;84        }8586        this._position.x = this._position.x + this._hotSpot.x - hotx;87        this._position.y = this._position.y + this._hotSpot.y - hoty;88        this._hotSpot.x = hotx;89        this._hotSpot.y = hoty;9091        let ctx = this._canvas.getContext('2d');9293        this._canvas.width = w;94        this._canvas.height = h;9596        let img = new ImageData(new Uint8ClampedArray(rgba), w, h);97        ctx.clearRect(0, 0, w, h);98        ctx.putImageData(img, 0, 0);99100        if (useFallback) {101            this._updatePosition();102        } else {103            let url = this._canvas.toDataURL();104            this._target.style.cursor = 'url(' + url + ')' + hotx + ' ' + hoty + ', default';105        }106    }107108    clear() {109        this._target.style.cursor = 'none';110        this._canvas.width = 0;111        this._canvas.height = 0;112        this._position.x = this._position.x + this._hotSpot.x;113        this._position.y = this._position.y + this._hotSpot.y;114        this._hotSpot.x = 0;115        this._hotSpot.y = 0;116    }117118    // Mouse events might be emulated, this allows119    // moving the cursor in such cases120    move(clientX, clientY) {121        if (!useFallback) {122            return;123        }124        // clientX/clientY are relative the _visual viewport_,125        // but our position is relative the _layout viewport_,126        // so try to compensate when we can127        if (window.visualViewport) {128            this._position.x = clientX + window.visualViewport.offsetLeft;129            this._position.y = clientY + window.visualViewport.offsetTop;130        } else {131            this._position.x = clientX;132            this._position.y = clientY;133        }134        this._updatePosition();135        let target = document.elementFromPoint(clientX, clientY);136        this._updateVisibility(target);137    }138139    _handleMouseOver(event) {140        // This event could be because we're entering the target, or141        // moving around amongst its sub elements. Let the move handler142        // sort things out.143        this._handleMouseMove(event);144    }145146    _handleMouseLeave(event) {147        // Check if we should show the cursor on the element we are leaving to148        this._updateVisibility(event.relatedTarget);149    }150151    _handleMouseMove(event) {152        this._updateVisibility(event.target);153154        this._position.x = event.clientX - this._hotSpot.x;155        this._position.y = event.clientY - this._hotSpot.y;156157        this._updatePosition();158    }159160    _handleMouseUp(event) {161        // We might get this event because of a drag operation that162        // moved outside of the target. Check what's under the cursor163        // now and adjust visibility based on that.164        let target = document.elementFromPoint(event.clientX, event.clientY);165        this._updateVisibility(target);166167        // Captures end with a mouseup but we can't know the event order of168        // mouseup vs releaseCapture.169        //170        // In the cases when releaseCapture comes first, the code above is171        // enough.172        //173        // In the cases when the mouseup comes first, we need wait for the174        // browser to flush all events and then check again if the cursor175        // should be visible.176        if (this._captureIsActive()) {177            window.setTimeout(() => {178                // We might have detached at this point179                if (!this._target) {180                    return;181                }182                // Refresh the target from elementFromPoint since queued events183                // might have altered the DOM184                target = document.elementFromPoint(event.clientX,185                                                   event.clientY);186                this._updateVisibility(target);187            }, 0);188        }189    }190191    _showCursor() {192        if (this._canvas.style.visibility === 'hidden') {193            this._canvas.style.visibility = '';194        }195    }196197    _hideCursor() {198        if (this._canvas.style.visibility !== 'hidden') {199            this._canvas.style.visibility = 'hidden';200        }201    }202203    // Should we currently display the cursor?204    // (i.e. are we over the target, or a child of the target without a205    // different cursor set)206    _shouldShowCursor(target) {207        if (!target) {208            return false;209        }210        // Easy case211        if (target === this._target) {212            return true;213        }214        // Other part of the DOM?215        if (!this._target.contains(target)) {216            return false;217        }218        // Has the child its own cursor?219        // FIXME: How can we tell that a sub element has an220        //        explicit "cursor: none;"?221        if (window.getComputedStyle(target).cursor !== 'none') {222            return false;223        }224        return true;225    }226227    _updateVisibility(target) {228        // When the cursor target has capture we want to show the cursor.229        // So, if a capture is active - look at the captured element instead.230        if (this._captureIsActive()) {231            target = document.captureElement;232        }233        if (this._shouldShowCursor(target)) {234            this._showCursor();235        } else {236            this._hideCursor();237        }238    }239240    _updatePosition() {241        this._canvas.style.left = this._position.x + "px";242        this._canvas.style.top = this._position.y + "px";243    }244245    _captureIsActive() {246        return document.captureElement &&247            document.documentElement.contains(document.captureElement);248    }249}250