summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSolly Ross <sross@redhat.com>2018-03-05 14:13:21 -0500
committerSolly Ross <sross@redhat.com>2018-03-05 14:17:36 -0500
commit97fbc2bee3a09ec1fac281516435d65369f773e1 (patch)
treeffae71d490d203526fca88799d55826548cb0111
parente62b4ccb5ea4dccc51b7712981e4442dc54171c6 (diff)
downloadnovnc-feature/local-pseudo-cursor.tar.gz
Render cursor manually when neededfeature/local-pseudo-cursor
Some browsers (*cough* IE *cough*) don't support cursors via data URIs, and touch devices sometimes don't have proper cursors, so we had to rely on remote cursor support in some cases. Instead, we can now display local cursors by rendering the cursor to a transparent canvas (with pointer events disabled on that mini-canvas) when we encounter those conditions, meaning we can now advertise local cursor support in all conditions. Fixes #572.
-rw-r--r--.gitignore1
-rw-r--r--app/styles/base.css6
-rw-r--r--core/display.js52
-rw-r--r--core/pseudo-cursor.js41
-rw-r--r--core/rfb.js5
5 files changed, 92 insertions, 13 deletions
diff --git a/.gitignore b/.gitignore
index c178dba..5022550 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,3 +10,4 @@ recordings
*.swp
*~
noVNC-*.tgz
+/package-lock.json
diff --git a/app/styles/base.css b/app/styles/base.css
index 344db9b..cb9fce0 100644
--- a/app/styles/base.css
+++ b/app/styles/base.css
@@ -900,3 +900,9 @@ select:active {
font-size: 90px;
}
}
+
+/* TODO: reorganize */
+.noVNC_pseudo_cursor {
+ position: fixed;
+ pointer-events: none;
+}
diff --git a/core/display.js b/core/display.js
index 9915615..f91ff3b 100644
--- a/core/display.js
+++ b/core/display.js
@@ -9,6 +9,8 @@
import * as Log from './util/logging.js';
import Base64 from "./base64.js";
+import { supportsCursorURIs, isTouchDevice } from './util/browser.js';
+import PseudoCursor from './pseudo-cursor.js';
export default function Display(target) {
this._drawCtx = null;
@@ -66,6 +68,12 @@ export default function Display(target) {
throw new Error("Canvas does not support createImageData");
}
+ // check cursor support to initialize the pseudo-cursor
+ this._psuedo_cursor = null;
+ if (!supportsCursorURIs() || isTouchDevice) {
+ this._pseudo_cursor = new PseudoCursor(this._target, this._target.parentElement);
+ }
+
this._tile16x16 = this._drawCtx.createImageData(16, 16);
Log.Debug("<< Display.constructor");
};
@@ -498,8 +506,38 @@ Display.prototype = {
this._damage(x, y, img.width, img.height);
},
+ moveCursor: function (x, y) {
+ // this is only relevant for pseudo-cursors
+ if (this._pseudo_cursor === null) { return; }
+
+ // convert viewport-local coordinates back to real-space coordinates,
+ // relative to the canvas.
+ var realRelX = (x - this._viewportLoc.x) * this._scale;
+ var realRelY = (y - this._viewportLoc.y) * this._scale;
+
+ this._pseudo_cursor.move(x, y);
+ },
+
changeCursor: function (pixels, mask, hotx, hoty, w, h) {
- Display.changeCursor(this._target, pixels, mask, hotx, hoty, w, h);
+ // in the case of a pseudo-cursor, just render it
+ // to the pseudo-cursor's canvas
+ if (this._pseudo_cursor !== null) {
+ this._pseudo_cursor.change(hotx, hoty, function (canvas) {
+ Display.renderCursor(canvas, pixels, mask, hotx, hoty, w, h);
+ });
+ return;
+ }
+
+ // otherwise, update our canvas's cursor via data URIs
+ if ((w === 0) || (h === 0)) {
+ target.style.cursor = 'none';
+ return;
+ }
+
+ var canvas = document.createElement('canvas');
+ Display.renderCursor(canvas, pixels, mask, hotx, hoty, w, h);
+ var url = canvas.toDataURL();
+ this._target.style.cursor = 'url(' + url + ')' + hotx + ' ' + hoty + ', default';
},
defaultCursor: function () {
@@ -657,9 +695,10 @@ Display.prototype = {
};
// Class Methods
-Display.changeCursor = function (target, pixels, mask, hotx, hoty, w, h) {
+Display.renderCursor = function (canvas, pixels, mask, hotx, hoty, w, h) {
+ canvas.width = w;
+ canvas.height = w;
if ((w === 0) || (h === 0)) {
- target.style.cursor = 'none';
return;
}
@@ -677,12 +716,8 @@ Display.changeCursor = function (target, pixels, mask, hotx, hoty, w, h) {
}
}
- var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
- canvas.width = w;
- canvas.height = h;
-
var img;
if (SUPPORTS_IMAGEDATA_CONSTRUCTOR) {
img = new ImageData(new Uint8ClampedArray(cur), w, h);
@@ -692,7 +727,4 @@ Display.changeCursor = function (target, pixels, mask, hotx, hoty, w, h) {
}
ctx.clearRect(0, 0, w, h);
ctx.putImageData(img, 0, 0);
-
- var url = canvas.toDataURL();
- target.style.cursor = 'url(' + url + ')' + hotx + ' ' + hoty + ', default';
};
diff --git a/core/pseudo-cursor.js b/core/pseudo-cursor.js
new file mode 100644
index 0000000..e9bdc76
--- /dev/null
+++ b/core/pseudo-cursor.js
@@ -0,0 +1,41 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2018 the noVNC authors.
+ * Licensed under MPL 2.0 (see LICENSE.txt)
+ *
+ * See README.md for usage and integration instructions.
+ */
+
+export default function PseudoCursor(boundingTarget, container) {
+ this._boundingTarget = boundingTarget;
+ this._canvas = document.createElement('canvas');
+ this._canvas.classList.add('noVNC_pseudo_cursor');
+
+ this._container = container;
+ this._container.appendChild(this._canvas);
+
+ this._drawCtx = this._canvas.getContext('2d');
+
+ this._hotX = 0;
+ this._hotY = 0;
+}
+
+PseudoCursor.prototype = {
+ change: function(hotX, hotY, renderCallback) {
+ this._drawCtx.clearRect(0, 0, this._canvas.width, this._canvas.height);
+ renderCallback(this._canvas);
+ this._hotX = hotX;
+ this._hotY = hotY;
+ },
+ move: function (relX, relY) {
+ // TODO: do we need to cache this to avoid lots of garbage?
+ var boundingRect = this._boundingTarget.getBoundingClientRect();
+
+ // get the absolute X and Y, factoring in the hotspot
+ var absX = boundingRect.left + relX - this._hotX;
+ var absY = boundingRect.top + relY - this._hotY;
+
+ this._canvas.style.top = Math.floor(absY) + "px";
+ this._canvas.style.left = Math.floor(absX) + "px";
+ }
+};
diff --git a/core/rfb.js b/core/rfb.js
index 81e1e6a..cbffec4 100644
--- a/core/rfb.js
+++ b/core/rfb.js
@@ -12,7 +12,6 @@
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";
@@ -806,6 +805,7 @@ RFB.prototype = {
if (this._rfb_connection_state !== 'connected') { return; }
RFB.messages.pointerEvent(this._sock, this._display.absX(x), this._display.absY(y), this._mouse_buttonMask);
+ this._display.moveCursor(this._display.absX(x), this._display.absY(y));
},
// Message Handlers
@@ -1275,8 +1275,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);
}