This commit is contained in:
Pierre Ossman 2018-07-11 13:39:37 +02:00
commit 67fefcf184
4 changed files with 240 additions and 65 deletions

View File

@ -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';
};

View File

@ -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--;

230
core/util/cursor.js Normal file
View File

@ -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;

View File

@ -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