]>
git.proxmox.com Git - mirror_novnc.git/blob - core/util/cursor.js
2 * noVNC: HTML5 VNC client
3 * Copyright (C) 2019 The noVNC Authors
4 * Licensed under MPL 2.0 or any later version (see LICENSE.txt)
7 import { supportsCursorURIs
, isTouchDevice
} from './browser.js';
9 const useFallback
= !supportsCursorURIs
|| isTouchDevice
;
11 export default class Cursor
{
15 this._canvas
= document
.createElement('canvas');
18 this._canvas
.style
.position
= 'fixed';
19 this._canvas
.style
.zIndex
= '65535';
20 this._canvas
.style
.pointerEvents
= 'none';
21 // Can't use "display" because of Firefox bug #1445997
22 this._canvas
.style
.visibility
= 'hidden';
23 document
.body
.appendChild(this._canvas
);
26 this._position
= { x
: 0, y
: 0 };
27 this._hotSpot
= { x
: 0, y
: 0 };
29 this._eventHandlers
= {
30 'mouseover': this._handleMouseOver
.bind(this),
31 'mouseleave': this._handleMouseLeave
.bind(this),
32 'mousemove': this._handleMouseMove
.bind(this),
33 'mouseup': this._handleMouseUp
.bind(this),
34 'touchstart': this._handleTouchStart
.bind(this),
35 'touchmove': this._handleTouchMove
.bind(this),
36 'touchend': this._handleTouchEnd
.bind(this),
45 this._target
= target
;
48 // FIXME: These don't fire properly except for mouse
49 /// movement in IE. We want to also capture element
50 // movement, size changes, visibility, etc.
51 const options
= { capture
: true, passive
: true };
52 this._target
.addEventListener('mouseover', this._eventHandlers
.mouseover
, options
);
53 this._target
.addEventListener('mouseleave', this._eventHandlers
.mouseleave
, options
);
54 this._target
.addEventListener('mousemove', this._eventHandlers
.mousemove
, options
);
55 this._target
.addEventListener('mouseup', this._eventHandlers
.mouseup
, options
);
57 // There is no "touchleave" so we monitor touchstart globally
58 window
.addEventListener('touchstart', this._eventHandlers
.touchstart
, options
);
59 this._target
.addEventListener('touchmove', this._eventHandlers
.touchmove
, options
);
60 this._target
.addEventListener('touchend', this._eventHandlers
.touchend
, options
);
72 const options
= { capture
: true, passive
: true };
73 this._target
.removeEventListener('mouseover', this._eventHandlers
.mouseover
, options
);
74 this._target
.removeEventListener('mouseleave', this._eventHandlers
.mouseleave
, options
);
75 this._target
.removeEventListener('mousemove', this._eventHandlers
.mousemove
, options
);
76 this._target
.removeEventListener('mouseup', this._eventHandlers
.mouseup
, options
);
78 window
.removeEventListener('touchstart', this._eventHandlers
.touchstart
, options
);
79 this._target
.removeEventListener('touchmove', this._eventHandlers
.touchmove
, options
);
80 this._target
.removeEventListener('touchend', this._eventHandlers
.touchend
, options
);
86 change(rgba
, hotx
, hoty
, w
, h
) {
87 if ((w
=== 0) || (h
=== 0)) {
92 this._position
.x
= this._position
.x
+ this._hotSpot
.x
- hotx
;
93 this._position
.y
= this._position
.y
+ this._hotSpot
.y
- hoty
;
94 this._hotSpot
.x
= hotx
;
95 this._hotSpot
.y
= hoty
;
97 let ctx
= this._canvas
.getContext('2d');
99 this._canvas
.width
= w
;
100 this._canvas
.height
= h
;
104 // IE doesn't support this
105 img
= new ImageData(new Uint8ClampedArray(rgba
), w
, h
);
107 img
= ctx
.createImageData(w
, h
);
108 img
.data
.set(new Uint8ClampedArray(rgba
));
110 ctx
.clearRect(0, 0, w
, h
);
111 ctx
.putImageData(img
, 0, 0);
114 this._updatePosition();
116 let url
= this._canvas
.toDataURL();
117 this._target
.style
.cursor
= 'url(' + url
+ ')' + hotx
+ ' ' + hoty
+ ', default';
122 this._target
.style
.cursor
= 'none';
123 this._canvas
.width
= 0;
124 this._canvas
.height
= 0;
125 this._position
.x
= this._position
.x
+ this._hotSpot
.x
;
126 this._position
.y
= this._position
.y
+ this._hotSpot
.y
;
131 _handleMouseOver(event
) {
132 // This event could be because we're entering the target, or
133 // moving around amongst its sub elements. Let the move handler
135 this._handleMouseMove(event
);
138 _handleMouseLeave(event
) {
139 // Check if we should show the cursor on the element we are leaving to
140 this._updateVisibility(event
.relatedTarget
);
143 _handleMouseMove(event
) {
144 this._updateVisibility(event
.target
);
146 this._position
.x
= event
.clientX
- this._hotSpot
.x
;
147 this._position
.y
= event
.clientY
- this._hotSpot
.y
;
149 this._updatePosition();
152 _handleMouseUp(event
) {
153 // We might get this event because of a drag operation that
154 // moved outside of the target. Check what's under the cursor
155 // now and adjust visibility based on that.
156 let target
= document
.elementFromPoint(event
.clientX
, event
.clientY
);
157 this._updateVisibility(target
);
159 // Captures end with a mouseup but we can't know the event order of
160 // mouseup vs releaseCapture.
162 // In the cases when releaseCapture comes first, the code above is
165 // In the cases when the mouseup comes first, we need wait for the
166 // browser to flush all events and then check again if the cursor
167 // should be visible.
168 if (this._captureIsActive()) {
169 window
.setTimeout(() => {
170 // Refresh the target from elementFromPoint since queued events
171 // might have altered the DOM
172 target
= document
.elementFromPoint(event
.clientX
,
174 this._updateVisibility(target
);
179 _handleTouchStart(event
) {
180 // Just as for mouseover, we let the move handler deal with it
181 this._handleTouchMove(event
);
184 _handleTouchMove(event
) {
185 this._updateVisibility(event
.target
);
187 this._position
.x
= event
.changedTouches
[0].clientX
- this._hotSpot
.x
;
188 this._position
.y
= event
.changedTouches
[0].clientY
- this._hotSpot
.y
;
190 this._updatePosition();
193 _handleTouchEnd(event
) {
194 // Same principle as for mouseup
195 let target
= document
.elementFromPoint(event
.changedTouches
[0].clientX
,
196 event
.changedTouches
[0].clientY
);
197 this._updateVisibility(target
);
201 if (this._canvas
.style
.visibility
=== 'hidden') {
202 this._canvas
.style
.visibility
= '';
207 if (this._canvas
.style
.visibility
!== 'hidden') {
208 this._canvas
.style
.visibility
= 'hidden';
212 // Should we currently display the cursor?
213 // (i.e. are we over the target, or a child of the target without a
214 // different cursor set)
215 _shouldShowCursor(target
) {
220 if (target
=== this._target
) {
223 // Other part of the DOM?
224 if (!this._target
.contains(target
)) {
227 // Has the child its own cursor?
228 // FIXME: How can we tell that a sub element has an
229 // explicit "cursor: none;"?
230 if (window
.getComputedStyle(target
).cursor
!== 'none') {
236 _updateVisibility(target
) {
237 // When the cursor target has capture we want to show the cursor.
238 // So, if a capture is active - look at the captured element instead.
239 if (this._captureIsActive()) {
240 target
= document
.captureElement
;
242 if (this._shouldShowCursor(target
)) {
250 this._canvas
.style
.left
= this._position
.x
+ "px";
251 this._canvas
.style
.top
= this._position
.y
+ "px";
255 return document
.captureElement
&&
256 document
.documentElement
.contains(document
.captureElement
);