summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPierre Ossman <ossman@cendio.se>2018-07-11 13:39:37 +0200
committerPierre Ossman <ossman@cendio.se>2018-07-11 13:39:37 +0200
commit67fefcf184c1f291292027a785b333ba1a16f0c9 (patch)
tree27216bdc250fe01b5fa8c40e42160d4ea74a1e56
parent1073b60155ae834369fd044a13a2a90a963516eb (diff)
parentbaa4f23ee520a41db0d793c0e2b39f8c9bccf517 (diff)
downloadnovnc-67fefcf184c1f291292027a785b333ba1a16f0c9.tar.gz
Merge branch 'cursor' of https://github.com/CendioOssman/noVNC
-rw-r--r--core/display.js52
-rw-r--r--core/rfb.js20
-rw-r--r--core/util/cursor.js230
-rw-r--r--docs/API-internal.md3
4 files changed, 240 insertions, 65 deletions
diff --git a/core/display.js b/core/display.js
index eb7eec2..057b12f 100644
--- a/core/display.js
+++ b/core/display.js
@@ -496,18 +496,6 @@ Display.prototype = {
this._damage(x, y, img.width, img.height);
},
- changeCursor: function (pixels, mask, hotx, hoty, w, h) {
- Display.changeCursor(this._target, pixels, mask, hotx, hoty, w, h);
- },
-
- defaultCursor: function () {
- this._target.style.cursor = "default";
- },
-
- disableLocalCursor: function () {
- this._target.style.cursor = "none";
- },
-
autoscale: function (containerWidth, containerHeight) {
const vp = this._viewportLoc;
const targetAspectRatio = containerWidth / containerHeight;
@@ -653,43 +641,3 @@ Display.prototype = {
}
},
};
-
-// Class Methods
-Display.changeCursor = function (target, pixels, mask, hotx, hoty, w, h) {
- if ((w === 0) || (h === 0)) {
- target.style.cursor = 'none';
- return;
- }
-
- const cur = []
- for (let y = 0; y < h; y++) {
- for (let x = 0; x < w; x++) {
- let idx = y * Math.ceil(w / 8) + Math.floor(x / 8);
- const alpha = (mask[idx] << (x % 8)) & 0x80 ? 255 : 0;
- idx = ((w * y) + x) * 4;
- cur.push(pixels[idx + 2]); // red
- cur.push(pixels[idx + 1]); // green
- cur.push(pixels[idx]); // blue
- cur.push(alpha); // alpha
- }
- }
-
- const canvas = document.createElement('canvas');
- const ctx = canvas.getContext('2d');
-
- canvas.width = w;
- canvas.height = h;
-
- let img;
- if (SUPPORTS_IMAGEDATA_CONSTRUCTOR) {
- img = new ImageData(new Uint8ClampedArray(cur), w, h);
- } else {
- img = ctx.createImageData(w, h);
- img.data.set(new Uint8ClampedArray(cur));
- }
- ctx.clearRect(0, 0, w, h);
- ctx.putImageData(img, 0, 0);
-
- const url = canvas.toDataURL();
- target.style.cursor = 'url(' + url + ')' + hotx + ' ' + hoty + ', default';
-};
diff --git a/core/rfb.js b/core/rfb.js
index 3150082..a94542f 100644
--- a/core/rfb.js
+++ b/core/rfb.js
@@ -12,11 +12,11 @@
import * as Log from './util/logging.js';
import { decodeUTF8 } from './util/strings.js';
-import { supportsCursorURIs, isTouchDevice } from './util/browser.js';
import EventTargetMixin from './util/eventtarget.js';
import Display from "./display.js";
import Keyboard from "./input/keyboard.js";
import Mouse from "./input/mouse.js";
+import Cursor from "./util/cursor.js";
import Websock from "./websock.js";
import DES from "./des.js";
import KeyTable from "./input/keysym.js";
@@ -161,6 +161,8 @@ export default function RFB(target, url, options) {
this._canvas.tabIndex = -1;
this._screen.appendChild(this._canvas);
+ this._cursor = new Cursor();
+
// populate encHandlers with bound versions
this._encHandlers[encodings.encodingRaw] = RFB.encodingHandlers.RAW.bind(this);
this._encHandlers[encodings.encodingCopyRect] = RFB.encodingHandlers.COPYRECT.bind(this);
@@ -410,6 +412,8 @@ RFB.prototype = {
// Make our elements part of the page
this._target.appendChild(this._screen);
+ this._cursor.attach(this._canvas);
+
// Monitor size changes of the screen
// FIXME: Use ResizeObserver, or hidden overflow
window.addEventListener('resize', this._eventHandlers.windowResize);
@@ -423,6 +427,7 @@ RFB.prototype = {
_disconnect: function () {
Log.Debug(">> RFB.disconnect");
+ this._cursor.detach();
this._canvas.removeEventListener("mousedown", this._eventHandlers.focusCanvas);
this._canvas.removeEventListener("touchstart", this._eventHandlers.focusCanvas);
window.removeEventListener('resize', this._eventHandlers.windowResize);
@@ -1247,10 +1252,6 @@ RFB.prototype = {
this._timing.fbu_rt_start = (new Date()).getTime();
this._timing.pixels = 0;
- // Cursor will be server side until the server decides to honor
- // our request and send over the cursor image
- this._display.disableLocalCursor();
-
this._updateConnectionState('connected');
return true;
},
@@ -1281,8 +1282,7 @@ RFB.prototype = {
encs.push(encodings.pseudoEncodingFence);
encs.push(encodings.pseudoEncodingContinuousUpdates);
- if (supportsCursorURIs() &&
- !isTouchDevice && this._fb_depth == 24) {
+ if (this._fb_depth == 24) {
encs.push(encodings.pseudoEncodingCursor);
}
@@ -2535,9 +2535,9 @@ RFB.encodingHandlers = {
this._FBU.bytes = pixelslength + masklength;
if (this._sock.rQwait("cursor encoding", this._FBU.bytes)) { return false; }
- this._display.changeCursor(this._sock.rQshiftBytes(pixelslength),
- this._sock.rQshiftBytes(masklength),
- x, y, w, h);
+ this._cursor.change(this._sock.rQshiftBytes(pixelslength),
+ this._sock.rQshiftBytes(masklength),
+ x, y, w, h);
this._FBU.bytes = 0;
this._FBU.rects--;
diff --git a/core/util/cursor.js b/core/util/cursor.js
new file mode 100644
index 0000000..da72723
--- /dev/null
+++ b/core/util/cursor.js
@@ -0,0 +1,230 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright 2018 Pierre Ossman for noVNC
+ * Licensed under MPL 2.0 or any later version (see LICENSE.txt)
+ */
+
+import { supportsCursorURIs, isTouchDevice } from './browser.js';
+
+const useFallback = !supportsCursorURIs() || isTouchDevice;
+
+function Cursor(container) {
+ this._target = null;
+
+ this._canvas = document.createElement('canvas');
+
+ if (useFallback) {
+ this._canvas.style.position = 'fixed';
+ this._canvas.style.zIndex = '65535';
+ this._canvas.style.pointerEvents = 'none';
+ // Can't use "display" because of Firefox bug #1445997
+ this._canvas.style.visibility = 'hidden';
+ document.body.appendChild(this._canvas);
+ }
+
+ this._position = { x: 0, y: 0 };
+ this._hotSpot = { x: 0, y: 0 };
+
+ this._eventHandlers = {
+ 'mouseover': this._handleMouseOver.bind(this),
+ 'mouseleave': this._handleMouseLeave.bind(this),
+ 'mousemove': this._handleMouseMove.bind(this),
+ 'mouseup': this._handleMouseUp.bind(this),
+ 'touchstart': this._handleTouchStart.bind(this),
+ 'touchmove': this._handleTouchMove.bind(this),
+ 'touchend': this._handleTouchEnd.bind(this),
+ };
+}
+
+Cursor.prototype = {
+ attach: function (target) {
+ if (this._target) {
+ this.detach();
+ }
+
+ this._target = target;
+
+ if (useFallback) {
+ // FIXME: These don't fire properly except for mouse
+ /// movement in IE. We want to also capture element
+ // movement, size changes, visibility, etc.
+ const options = { capture: true, passive: true };
+ this._target.addEventListener('mouseover', this._eventHandlers.mouseover, options);
+ this._target.addEventListener('mouseleave', this._eventHandlers.mouseleave, options);
+ this._target.addEventListener('mousemove', this._eventHandlers.mousemove, options);
+ this._target.addEventListener('mouseup', this._eventHandlers.mouseup, options);
+
+ // There is no "touchleave" so we monitor touchstart globally
+ window.addEventListener('touchstart', this._eventHandlers.touchstart, options);
+ this._target.addEventListener('touchmove', this._eventHandlers.touchmove, options);
+ this._target.addEventListener('touchend', this._eventHandlers.touchend, options);
+ }
+
+ this.clear();
+ },
+
+ detach: function () {
+ if (useFallback) {
+ const options = { capture: true, passive: true };
+ this._target.removeEventListener('mouseover', this._eventHandlers.mouseover, options);
+ this._target.removeEventListener('mouseleave', this._eventHandlers.mouseleave, options);
+ this._target.removeEventListener('mousemove', this._eventHandlers.mousemove, options);
+ this._target.removeEventListener('mouseup', this._eventHandlers.mouseup, options);
+
+ window.removeEventListener('touchstart', this._eventHandlers.touchstart, options);
+ this._target.removeEventListener('touchmove', this._eventHandlers.touchmove, options);
+ this._target.removeEventListener('touchend', this._eventHandlers.touchend, options);
+ }
+
+ this._target = null;
+ },
+
+ change: function (pixels, mask, hotx, hoty, w, h) {
+ if ((w === 0) || (h === 0)) {
+ this.clear();
+ return;
+ }
+
+ let cur = []
+ for (let y = 0; y < h; y++) {
+ for (let x = 0; x < w; x++) {
+ let idx = y * Math.ceil(w / 8) + Math.floor(x / 8);
+ let alpha = (mask[idx] << (x % 8)) & 0x80 ? 255 : 0;
+ idx = ((w * y) + x) * 4;
+ cur.push(pixels[idx + 2]); // red
+ cur.push(pixels[idx + 1]); // green
+ cur.push(pixels[idx]); // blue
+ cur.push(alpha); // alpha
+ }
+ }
+
+ this._position.x = this._position.x + this._hotSpot.x - hotx;
+ this._position.y = this._position.y + this._hotSpot.y - hoty;
+ this._hotSpot.x = hotx;
+ this._hotSpot.y = hoty;
+
+ let ctx = this._canvas.getContext('2d');
+
+ this._canvas.width = w;
+ this._canvas.height = h;
+
+ let img;
+ try {
+ // IE doesn't support this
+ img = new ImageData(new Uint8ClampedArray(cur), w, h);
+ } catch (ex) {
+ img = ctx.createImageData(w, h);
+ img.data.set(new Uint8ClampedArray(cur));
+ }
+ ctx.clearRect(0, 0, w, h);
+ ctx.putImageData(img, 0, 0);
+
+ if (useFallback) {
+ this._updatePosition();
+ } else {
+ let url = this._canvas.toDataURL();
+ this._target.style.cursor = 'url(' + url + ')' + hotx + ' ' + hoty + ', default';
+ }
+ },
+
+ clear: function () {
+ this._target.style.cursor = 'none';
+ this._canvas.width = 0;
+ this._canvas.height = 0;
+ this._position.x = this._position.x + this._hotSpot.x;
+ this._position.y = this._position.y + this._hotSpot.y;
+ this._hotSpot.x = 0;
+ this._hotSpot.y = 0;
+ },
+
+ _handleMouseOver: function (event) {
+ // This event could be because we're entering the target, or
+ // moving around amongst its sub elements. Let the move handler
+ // sort things out.
+ this._handleMouseMove(event);
+ },
+
+ _handleMouseLeave: function (event) {
+ this._hideCursor();
+ },
+
+ _handleMouseMove: function (event) {
+ this._updateVisibility(event.target);
+
+ this._position.x = event.clientX - this._hotSpot.x;
+ this._position.y = event.clientY - this._hotSpot.y;
+
+ this._updatePosition();
+ },
+
+ _handleMouseUp: function (event) {
+ // We might get this event because of a drag operation that
+ // moved outside of the target. Check what's under the cursor
+ // now and adjust visibility based on that.
+ let target = document.elementFromPoint(event.clientX, event.clientY);
+ this._updateVisibility(target);
+ },
+
+ _handleTouchStart: function (event) {
+ // Just as for mouseover, we let the move handler deal with it
+ this._handleTouchMove(event);
+ },
+
+ _handleTouchMove: function (event) {
+ this._updateVisibility(event.target);
+
+ this._position.x = event.changedTouches[0].clientX - this._hotSpot.x;
+ this._position.y = event.changedTouches[0].clientY - this._hotSpot.y;
+
+ this._updatePosition();
+ },
+
+ _handleTouchEnd: function (event) {
+ // Same principle as for mouseup
+ let target = document.elementFromPoint(event.changedTouches[0].clientX,
+ event.changedTouches[0].clientY);
+ this._updateVisibility(target);
+ },
+
+ _showCursor: function () {
+ if (this._canvas.style.visibility === 'hidden')
+ this._canvas.style.visibility = '';
+ },
+
+ _hideCursor: function () {
+ if (this._canvas.style.visibility !== 'hidden')
+ this._canvas.style.visibility = 'hidden';
+ },
+
+ // Should we currently display the cursor?
+ // (i.e. are we over the target, or a child of the target without a
+ // different cursor set)
+ _shouldShowCursor: function (target) {
+ // Easy case
+ if (target === this._target)
+ return true;
+ // Other part of the DOM?
+ if (!this._target.contains(target))
+ return false;
+ // Has the child its own cursor?
+ // FIXME: How can we tell that a sub element has an
+ // explicit "cursor: none;"?
+ if (window.getComputedStyle(target).cursor !== 'none')
+ return false;
+ return true;
+ },
+
+ _updateVisibility: function (target) {
+ if (this._shouldShowCursor(target))
+ this._showCursor();
+ else
+ this._hideCursor();
+ },
+
+ _updatePosition: function () {
+ this._canvas.style.left = this._position.x + "px";
+ this._canvas.style.top = this._position.y + "px";
+ },
+};
+
+export default Cursor;
diff --git a/docs/API-internal.md b/docs/API-internal.md
index 4943c1a..0b29afb 100644
--- a/docs/API-internal.md
+++ b/docs/API-internal.md
@@ -113,9 +113,6 @@ None
| blitRgbImage | (x, y, width, height, arr, offset, from_queue) | Blit RGB encoded image to display
| blitRgbxImage | (x, y, width, height, arr, offset, from_queue) | Blit RGBX encoded image to display
| drawImage | (img, x, y) | Draw image and track damage
-| changeCursor | (pixels, mask, hotx, hoty, w, h) | Change cursor appearance
-| defaultCursor | () | Restore default cursor appearance
-| disableLocalCursor | () | Disable local (client-side) cursor
| autoscale | (containerWidth, containerHeight) | Scale the display
### 2.3.3 Callbacks