Merge a7f4d708ca into 2f1e11b54a
This commit is contained in:
commit
c0dc4d0bce
|
|
@ -0,0 +1,78 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="25"
|
||||
height="25"
|
||||
viewBox="0 0 25 25"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)"
|
||||
sodipodi:docname="pointer.svg"
|
||||
inkscape:export-filename="/home/ossman/devel/noVNC/images/keyboard.png"
|
||||
inkscape:export-xdpi="90"
|
||||
inkscape:export-ydpi="90">
|
||||
<defs
|
||||
id="defs4" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#717171"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="22.627417"
|
||||
inkscape:cx="6.9841519"
|
||||
inkscape:cy="18.584699"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
units="px"
|
||||
inkscape:snap-bbox="true"
|
||||
inkscape:bbox-paths="true"
|
||||
inkscape:bbox-nodes="true"
|
||||
inkscape:snap-bbox-edge-midpoints="true"
|
||||
inkscape:snap-bbox-midpoints="false"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1403"
|
||||
inkscape:window-x="2560"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:object-paths="true"
|
||||
inkscape:snap-intersection-paths="true"
|
||||
inkscape:object-nodes="true"
|
||||
inkscape:snap-midpoints="true"
|
||||
inkscape:snap-smooth-nodes="true"
|
||||
inkscape:document-rotation="0">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid4136" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-1027.3622)">
|
||||
<path
|
||||
style="fill:#ffffff;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 6.3910823,1030.3965 v 17.497 l 3.5465954,-2.6671 1.5862113,4.1015 4.336661,-1.5752 -1.59331,-4.2624 4.341678,-0.3562 z"
|
||||
id="path879" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
51
app/ui.js
51
app/ui.js
|
|
@ -232,6 +232,10 @@ const UI = {
|
|||
document.getElementById("noVNC_view_drag_button")
|
||||
.addEventListener('click', UI.toggleViewDrag);
|
||||
|
||||
document
|
||||
.getElementById("noVNC_pointer_lock_button")
|
||||
.addEventListener("click", UI.requestPointerLock);
|
||||
|
||||
document.getElementById("noVNC_control_bar_handle")
|
||||
.addEventListener('mousedown', UI.controlbarHandleMouseDown);
|
||||
document.getElementById("noVNC_control_bar_handle")
|
||||
|
|
@ -453,6 +457,7 @@ const UI = {
|
|||
UI.updatePowerButton();
|
||||
UI.keepControlbar();
|
||||
}
|
||||
UI.updatePointerLockButton();
|
||||
|
||||
// State change closes dialogs as they may not be relevant
|
||||
// anymore
|
||||
|
|
@ -1051,6 +1056,7 @@ const UI = {
|
|||
UI.rfb.addEventListener("clipboard", UI.clipboardReceive);
|
||||
UI.rfb.addEventListener("bell", UI.bell);
|
||||
UI.rfb.addEventListener("desktopname", UI.updateDesktopName);
|
||||
UI.rfb.addEventListener("inputlock", UI.inputLockChanged);
|
||||
UI.rfb.clipViewport = UI.getSetting('view_clip');
|
||||
UI.rfb.scaleViewport = UI.getSetting('resize') === 'scale';
|
||||
UI.rfb.resizeSession = UI.getSetting('resize') === 'remote';
|
||||
|
|
@ -1293,6 +1299,7 @@ const UI = {
|
|||
document.getElementById('noVNC_fullscreen_button')
|
||||
.classList.remove("noVNC_selected");
|
||||
}
|
||||
UI.updatePointerLockButton();
|
||||
},
|
||||
|
||||
/* ------^-------
|
||||
|
|
@ -1345,6 +1352,38 @@ const UI = {
|
|||
/* ------^-------
|
||||
* /VIEW CLIPPING
|
||||
* ==============
|
||||
* POINTER LOCK
|
||||
* ------v------*/
|
||||
|
||||
updatePointerLockButton() {
|
||||
// Only show the button if the pointer lock API is properly supported
|
||||
// AND in fullscreen.
|
||||
if (
|
||||
UI.connected &&
|
||||
(document.fullscreenElement || // alternative standard method
|
||||
document.mozFullScreenElement || // currently working methods
|
||||
document.webkitFullscreenElement ||
|
||||
document.msFullscreenElement) &&
|
||||
(document.pointerLockElement !== undefined ||
|
||||
document.mozPointerLockElement !== undefined)
|
||||
) {
|
||||
document
|
||||
.getElementById("noVNC_pointer_lock_button")
|
||||
.classList.remove("noVNC_hidden");
|
||||
} else {
|
||||
document
|
||||
.getElementById("noVNC_pointer_lock_button")
|
||||
.classList.add("noVNC_hidden");
|
||||
}
|
||||
},
|
||||
|
||||
requestPointerLock() {
|
||||
UI.rfb.requestInputLock({ pointer: true });
|
||||
},
|
||||
|
||||
/* ------^-------
|
||||
* /POINTER LOCK
|
||||
* ==============
|
||||
* VIEWDRAG
|
||||
* ------v------*/
|
||||
|
||||
|
|
@ -1710,6 +1749,18 @@ const UI = {
|
|||
document.title = e.detail.name + " - " + PAGE_TITLE;
|
||||
},
|
||||
|
||||
inputLockChanged(e) {
|
||||
if (e.detail.pointer) {
|
||||
document
|
||||
.getElementById("noVNC_pointer_lock_button")
|
||||
.classList.add("noVNC_selected");
|
||||
} else {
|
||||
document
|
||||
.getElementById("noVNC_pointer_lock_button")
|
||||
.classList.remove("noVNC_selected");
|
||||
}
|
||||
},
|
||||
|
||||
bell(e) {
|
||||
if (WebUtil.getConfigVar('bell', 'on') === 'on') {
|
||||
const promise = document.getElementById('noVNC_bell').play();
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ export const encodings = {
|
|||
pseudoEncodingCompressLevel9: -247,
|
||||
pseudoEncodingCompressLevel0: -256,
|
||||
pseudoEncodingVMwareCursor: 0x574d5664,
|
||||
pseudoEncodingVMwareCursorPosition: 0x574d5666,
|
||||
pseudoEncodingExtendedClipboard: 0xc0a1e5ce
|
||||
};
|
||||
|
||||
|
|
|
|||
97
core/rfb.js
97
core/rfb.js
|
|
@ -183,6 +183,7 @@ export default class RFB extends EventTargetMixin {
|
|||
this._mousePos = {};
|
||||
this._mouseButtonMask = 0;
|
||||
this._mouseLastMoveTime = 0;
|
||||
this._pointerLock = false;
|
||||
this._viewportDragging = false;
|
||||
this._viewportDragPos = {};
|
||||
this._viewportHasMoved = false;
|
||||
|
|
@ -200,6 +201,8 @@ export default class RFB extends EventTargetMixin {
|
|||
focusCanvas: this._focusCanvas.bind(this),
|
||||
handleResize: this._handleResize.bind(this),
|
||||
handleMouse: this._handleMouse.bind(this),
|
||||
handlePointerLockChange: this._handlePointerLockChange.bind(this),
|
||||
handlePointerLockError: this._handlePointerLockError.bind(this),
|
||||
handleWheel: this._handleWheel.bind(this),
|
||||
handleGesture: this._handleGesture.bind(this),
|
||||
handleRSAAESCredentialsRequired: this._handleRSAAESCredentialsRequired.bind(this),
|
||||
|
|
@ -481,6 +484,24 @@ export default class RFB extends EventTargetMixin {
|
|||
this._canvas.blur();
|
||||
}
|
||||
|
||||
requestInputLock(locks) {
|
||||
if (locks.pointer) {
|
||||
if (this._canvas.requestPointerLock) {
|
||||
this._canvas.requestPointerLock();
|
||||
return;
|
||||
}
|
||||
if (this._canvas.mozRequestPointerLock) {
|
||||
this._canvas.mozRequestPointerLock();
|
||||
return;
|
||||
}
|
||||
}
|
||||
// If we were not able to request any lock, still let the user know
|
||||
// about the result.
|
||||
this.dispatchEvent(new CustomEvent(
|
||||
"inputlock",
|
||||
{ detail: { pointer: this._pointerLock }, }));
|
||||
}
|
||||
|
||||
clipboardPasteFrom(text) {
|
||||
if (this._rfbConnectionState !== 'connected' || this._viewOnly) { return; }
|
||||
|
||||
|
|
@ -548,6 +569,14 @@ export default class RFB extends EventTargetMixin {
|
|||
// preventDefault() on mousedown doesn't stop this event for some
|
||||
// reason so we have to explicitly block it
|
||||
this._canvas.addEventListener('contextmenu', this._eventHandlers.handleMouse);
|
||||
// Pointer Lock listeners need to be installed in document instead of the canvas.
|
||||
if (document.onpointerlockchange !== undefined) {
|
||||
document.addEventListener('pointerlockchange', this._eventHandlers.handlePointerLockChange, false);
|
||||
document.addEventListener('pointerlockerror', this._eventHandlers.handlePointerLockError, false);
|
||||
} else if (document.onmozpointerlockchange !== undefined) {
|
||||
document.addEventListener('mozpointerlockchange', this._eventHandlers.handlePointerLockChange, false);
|
||||
document.addEventListener('mozpointerlockerror', this._eventHandlers.handlePointerLockError, false);
|
||||
}
|
||||
|
||||
// Wheel events
|
||||
this._canvas.addEventListener("wheel", this._eventHandlers.handleWheel);
|
||||
|
|
@ -572,6 +601,13 @@ export default class RFB extends EventTargetMixin {
|
|||
this._canvas.removeEventListener('mousemove', this._eventHandlers.handleMouse);
|
||||
this._canvas.removeEventListener('click', this._eventHandlers.handleMouse);
|
||||
this._canvas.removeEventListener('contextmenu', this._eventHandlers.handleMouse);
|
||||
if (document.onpointerlockchange !== undefined) {
|
||||
document.removeEventListener('pointerlockchange', this._eventHandlers.handlePointerLockChange);
|
||||
document.removeEventListener('pointerlockerror', this._eventHandlers.handlePointerLockError);
|
||||
} else if (document.onmozpointerlockchange !== undefined) {
|
||||
document.removeEventListener('mozpointerlockchange', this._eventHandlers.handlePointerLockChange);
|
||||
document.removeEventListener('mozpointerlockerror', this._eventHandlers.handlePointerLockError);
|
||||
}
|
||||
this._canvas.removeEventListener("mousedown", this._eventHandlers.focusCanvas);
|
||||
this._canvas.removeEventListener("touchstart", this._eventHandlers.focusCanvas);
|
||||
this._resizeObserver.disconnect();
|
||||
|
|
@ -980,8 +1016,27 @@ export default class RFB extends EventTargetMixin {
|
|||
return;
|
||||
}
|
||||
|
||||
let pos = clientToElement(ev.clientX, ev.clientY,
|
||||
let pos;
|
||||
if (this._pointerLock) {
|
||||
pos = {
|
||||
x: this._mousePos.x + ev.movementX,
|
||||
y: this._mousePos.y + ev.movementY,
|
||||
};
|
||||
if (pos.x < 0) {
|
||||
pos.x = 0;
|
||||
} else if (pos.x > this._fbWidth) {
|
||||
pos.x = this._fbWidth;
|
||||
}
|
||||
if (pos.y < 0) {
|
||||
pos.y = 0;
|
||||
} else if (pos.y > this._fbHeight) {
|
||||
pos.y = this._fbHeight;
|
||||
}
|
||||
this._cursor.move(pos.x, pos.y);
|
||||
} else {
|
||||
pos = clientToElement(ev.clientX, ev.clientY,
|
||||
this._canvas);
|
||||
}
|
||||
|
||||
switch (ev.type) {
|
||||
case 'mousedown':
|
||||
|
|
@ -1082,6 +1137,28 @@ export default class RFB extends EventTargetMixin {
|
|||
this._mouseLastMoveTime = Date.now();
|
||||
}
|
||||
|
||||
_handlePointerLockChange() {
|
||||
if (
|
||||
document.pointerLockElement === this._canvas ||
|
||||
document.mozPointerLockElement === this._canvas
|
||||
) {
|
||||
this._pointerLock = true;
|
||||
this._cursor.setEmulateCursor(true);
|
||||
} else {
|
||||
this._pointerLock = false;
|
||||
this._cursor.setEmulateCursor(false);
|
||||
}
|
||||
this.dispatchEvent(new CustomEvent(
|
||||
"inputlock",
|
||||
{ detail: { pointer: this._pointerLock }, }));
|
||||
}
|
||||
|
||||
_handlePointerLockError() {
|
||||
this.dispatchEvent(new CustomEvent(
|
||||
"inputlock",
|
||||
{ detail: { pointer: this._pointerLock }, }));
|
||||
}
|
||||
|
||||
_sendMouse(x, y, mask) {
|
||||
if (this._rfbConnectionState !== 'connected') { return; }
|
||||
if (this._viewOnly) { return; } // View only, skip mouse events
|
||||
|
|
@ -2065,6 +2142,8 @@ export default class RFB extends EventTargetMixin {
|
|||
encs.push(encodings.pseudoEncodingCursor);
|
||||
}
|
||||
|
||||
encs.push(encodings.pseudoEncodingVMwareCursorPosition);
|
||||
|
||||
RFB.messages.clientEncodings(this._sock, encs);
|
||||
}
|
||||
|
||||
|
|
@ -2471,6 +2550,9 @@ export default class RFB extends EventTargetMixin {
|
|||
case encodings.pseudoEncodingVMwareCursor:
|
||||
return this._handleVMwareCursor();
|
||||
|
||||
case encodings.pseudoEncodingVMwareCursorPosition:
|
||||
return this._handleVMwareCursorPosition();
|
||||
|
||||
case encodings.pseudoEncodingCursor:
|
||||
return this._handleCursor();
|
||||
|
||||
|
|
@ -2609,6 +2691,19 @@ export default class RFB extends EventTargetMixin {
|
|||
return true;
|
||||
}
|
||||
|
||||
_handleVMwareCursorPosition() {
|
||||
const x = this._FBU.x;
|
||||
const y = this._FBU.y;
|
||||
|
||||
if (this._pointerLock) {
|
||||
// Only attempt to match the server's pointer position if we are in
|
||||
// pointer lock mode.
|
||||
this._mousePos = { x: x, y: y };
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
_handleCursor() {
|
||||
const hotx = this._FBU.x; // hotspot-x
|
||||
const hoty = this._FBU.y; // hotspot-y
|
||||
|
|
|
|||
|
|
@ -6,21 +6,20 @@
|
|||
|
||||
import { supportsCursorURIs, isTouchDevice } from './browser.js';
|
||||
|
||||
const useFallback = !supportsCursorURIs || isTouchDevice;
|
||||
const needsFallback = !supportsCursorURIs || isTouchDevice;
|
||||
|
||||
export default class Cursor {
|
||||
constructor() {
|
||||
this._target = null;
|
||||
|
||||
this._canvas = document.createElement('canvas');
|
||||
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';
|
||||
|
||||
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';
|
||||
}
|
||||
this._useFallback = needsFallback;
|
||||
|
||||
this._position = { x: 0, y: 0 };
|
||||
this._hotSpot = { x: 0, y: 0 };
|
||||
|
|
@ -40,9 +39,14 @@ export default class Cursor {
|
|||
|
||||
this._target = target;
|
||||
|
||||
if (useFallback) {
|
||||
document.body.appendChild(this._canvas);
|
||||
document.body.appendChild(this._canvas);
|
||||
|
||||
if (needsFallback) {
|
||||
// Only add the event listeners if this will be responsible for
|
||||
// rendering the cursor all the time. Otherwise, the cursor will
|
||||
// only be rendered then the forced emulation is turned on, and
|
||||
// that doesn't require this class to be adjusting the cursor
|
||||
// position.
|
||||
const options = { capture: true, passive: true };
|
||||
this._target.addEventListener('mouseover', this._eventHandlers.mouseover, options);
|
||||
this._target.addEventListener('mouseleave', this._eventHandlers.mouseleave, options);
|
||||
|
|
@ -58,16 +62,16 @@ export default class Cursor {
|
|||
return;
|
||||
}
|
||||
|
||||
if (useFallback) {
|
||||
if (needsFallback) {
|
||||
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);
|
||||
|
||||
document.body.removeChild(this._canvas);
|
||||
}
|
||||
|
||||
document.body.removeChild(this._canvas);
|
||||
|
||||
this._target = null;
|
||||
}
|
||||
|
||||
|
|
@ -91,9 +95,10 @@ export default class Cursor {
|
|||
ctx.clearRect(0, 0, w, h);
|
||||
ctx.putImageData(img, 0, 0);
|
||||
|
||||
if (useFallback) {
|
||||
if (this._useFallback || needsFallback) {
|
||||
this._updatePosition();
|
||||
} else {
|
||||
}
|
||||
if (!needsFallback) {
|
||||
let url = this._canvas.toDataURL();
|
||||
this._target.style.cursor = 'url(' + url + ')' + hotx + ' ' + hoty + ', default';
|
||||
}
|
||||
|
|
@ -112,7 +117,7 @@ export default class Cursor {
|
|||
// Mouse events might be emulated, this allows
|
||||
// moving the cursor in such cases
|
||||
move(clientX, clientY) {
|
||||
if (!useFallback) {
|
||||
if (!this._useFallback) {
|
||||
return;
|
||||
}
|
||||
// clientX/clientY are relative the _visual viewport_,
|
||||
|
|
@ -130,6 +135,22 @@ export default class Cursor {
|
|||
this._updateVisibility(target);
|
||||
}
|
||||
|
||||
// Force the use of cursor emulation. This is needed when the pointer lock
|
||||
// is in use, since the browser will not render the cursor.
|
||||
setEmulateCursor(emulate) {
|
||||
if (needsFallback) {
|
||||
// We need to use the fallback all the time, so we shouldn't update
|
||||
// the fallback flag.
|
||||
return;
|
||||
}
|
||||
this._useFallback = emulate;
|
||||
if (this._useFallback) {
|
||||
this._showCursor();
|
||||
} else {
|
||||
this._hideCursor();
|
||||
}
|
||||
}
|
||||
|
||||
_handleMouseOver(event) {
|
||||
// This event could be because we're entering the target, or
|
||||
// moving around amongst its sub elements. Let the move handler
|
||||
|
|
|
|||
37
docs/API.md
37
docs/API.md
|
|
@ -117,6 +117,10 @@ protocol stream.
|
|||
- The `capabilities` event is fired when `RFB.capabilities` is
|
||||
updated.
|
||||
|
||||
[`inputlock`](#inputlock)
|
||||
- The `inputlock` event is fired when an input lock is acquired (or released)
|
||||
by the canvas.
|
||||
|
||||
### Methods
|
||||
|
||||
[`RFB.disconnect()`](#rfbdisconnect)
|
||||
|
|
@ -155,6 +159,9 @@ protocol stream.
|
|||
[`RFB.clipboardPasteFrom()`](#rfbclipboardpastefrom)
|
||||
- Send clipboard contents to server.
|
||||
|
||||
[`RFB.requestInputLock()`](#rfbrequestInputLock)
|
||||
- Requests that the RFB canvas acquire an input lock.
|
||||
|
||||
### Details
|
||||
|
||||
#### RFB()
|
||||
|
|
@ -285,6 +292,15 @@ The `capabilities` event is fired whenever an entry is added or removed
|
|||
from `RFB.capabilities`. The `detail` property is an `Object` with the
|
||||
property `capabilities` containing the new value of `RFB.capabilities`.
|
||||
|
||||
#### inputlock
|
||||
|
||||
The `inputlock` event is fired after a request to acquire an input lock or
|
||||
whenever the state of the canvas' input lock has changed, the latter typically
|
||||
occurs because the lock was released by the user pressing the ESC key or
|
||||
performing a browser-specific gesture. The `detail` property is an `Object`
|
||||
with the property `pointer` containing whether the Pointer Lock is currently
|
||||
held or not.
|
||||
|
||||
#### RFB.disconnect()
|
||||
|
||||
The `RFB.disconnect()` method is used to disconnect from the currently
|
||||
|
|
@ -423,3 +439,24 @@ to the remote server.
|
|||
|
||||
**`text`**
|
||||
- A `DOMString` specifying the clipboard data to send.
|
||||
|
||||
#### RFB.requestInputLock()
|
||||
|
||||
The `RFB.requestInputLock()` method is used to request that the RFB canvas hold
|
||||
an input lock. An `inputlock` event will be fired with the result of the
|
||||
acquisition of the requested locks.
|
||||
|
||||
##### Syntax
|
||||
|
||||
RFB.requestInputLock( { pointer: true } );
|
||||
|
||||
###### Parameters
|
||||
|
||||
**`pointer`**
|
||||
- Requests to acquire a [Pointer
|
||||
Lock](https://developer.mozilla.org/en-US/docs/Web/API/Pointer_Lock_API),
|
||||
which hides the local mouse cursor and provides relative motion events.
|
||||
This must be called directly from an event handler where a user has
|
||||
directly interacted with an element through an [engagement
|
||||
gesture](https://w3c.github.io/pointerlock/#dfn-engagement-gesture) (e.g. a
|
||||
click or touch event) for the browser to allow this.
|
||||
|
|
|
|||
|
|
@ -2725,6 +2725,27 @@ describe('Remote Frame Buffer Protocol Client', function () {
|
|||
client._canvas.dispatchEvent(ev);
|
||||
}
|
||||
|
||||
function supportsSendMouseMovementEvent() {
|
||||
// Some browsers (like Safari) support the movementX /
|
||||
// movementY properties of MouseEvent, but do not allow creation
|
||||
// of non-trusted events with those properties.
|
||||
let ev;
|
||||
|
||||
ev = new MouseEvent('mousemove',
|
||||
{ 'movementX': 100,
|
||||
'movementY': 100 });
|
||||
return ev.movementX === 100 && ev.movementY === 100;
|
||||
}
|
||||
|
||||
function sendMouseMovementEvent(dx, dy) {
|
||||
let ev;
|
||||
|
||||
ev = new MouseEvent('mousemove',
|
||||
{ 'movementX': dx,
|
||||
'movementY': dy });
|
||||
client._canvas.dispatchEvent(ev);
|
||||
}
|
||||
|
||||
function sendMouseButtonEvent(x, y, down, button) {
|
||||
let pos = elementToClient(x, y);
|
||||
let ev;
|
||||
|
|
@ -2838,6 +2859,62 @@ describe('Remote Frame Buffer Protocol Client', function () {
|
|||
50, 70, 0x0);
|
||||
});
|
||||
|
||||
it('should ignore remote cursor position updates', function () {
|
||||
if (!supportsSendMouseMovementEvent()) {
|
||||
this.skip();
|
||||
return;
|
||||
}
|
||||
// Simple VMware Cursor Position FBU message with pointer coordinates
|
||||
// (50, 50).
|
||||
const incoming = [ 0x00, 0x00, 0x00, 0x01, 0x00, 0x32, 0x00, 0x32,
|
||||
0x00, 0x00, 0x00, 0x00, 0x57, 0x4d, 0x56, 0x66 ];
|
||||
client._resize(100, 100);
|
||||
|
||||
const cursorSpy = sinon.spy(client, '_handleVMwareCursorPosition');
|
||||
client._sock._websocket._receiveData(new Uint8Array(incoming));
|
||||
expect(cursorSpy).to.have.been.calledOnceWith();
|
||||
cursorSpy.restore();
|
||||
|
||||
expect(client._mousePos).to.deep.equal({ });
|
||||
sendMouseMoveEvent(10, 10);
|
||||
clock.tick(100);
|
||||
expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
|
||||
10, 10, 0x0);
|
||||
});
|
||||
|
||||
it('should handle remote mouse position updates in pointer lock mode', function () {
|
||||
if (!supportsSendMouseMovementEvent()) {
|
||||
this.skip();
|
||||
return;
|
||||
}
|
||||
// Simple VMware Cursor Position FBU message with pointer coordinates
|
||||
// (50, 50).
|
||||
const incoming = [ 0x00, 0x00, 0x00, 0x01, 0x00, 0x32, 0x00, 0x32,
|
||||
0x00, 0x00, 0x00, 0x00, 0x57, 0x4d, 0x56, 0x66 ];
|
||||
client._resize(100, 100);
|
||||
|
||||
const spy = sinon.spy();
|
||||
client.addEventListener("inputlock", spy);
|
||||
let stub = sinon.stub(document, 'pointerLockElement');
|
||||
stub.get(function () { return client._canvas; });
|
||||
client._handlePointerLockChange();
|
||||
stub.restore();
|
||||
client._sock._websocket._receiveData(new Uint8Array([0x02, 0x02]));
|
||||
expect(spy).to.have.been.calledOnce;
|
||||
expect(spy.args[0][0].detail.pointer).to.be.true;
|
||||
|
||||
const cursorSpy = sinon.spy(client, '_handleVMwareCursorPosition');
|
||||
client._sock._websocket._receiveData(new Uint8Array(incoming));
|
||||
expect(cursorSpy).to.have.been.calledOnceWith();
|
||||
cursorSpy.restore();
|
||||
|
||||
expect(client._mousePos).to.deep.equal({ x: 50, y: 50 });
|
||||
sendMouseMovementEvent(10, 10);
|
||||
clock.tick(100);
|
||||
expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
|
||||
60, 60, 0x0);
|
||||
});
|
||||
|
||||
describe('Event Aggregation', function () {
|
||||
it('should send a single pointer event on mouse movement', function () {
|
||||
sendMouseMoveEvent(50, 70);
|
||||
|
|
|
|||
5
vnc.html
5
vnc.html
|
|
@ -84,6 +84,11 @@
|
|||
id="noVNC_view_drag_button" class="noVNC_button noVNC_hidden"
|
||||
title="Move/Drag Viewport">
|
||||
|
||||
<!-- Lock pointer events -->
|
||||
<input type="image" alt="Lock pointer" src="app/images/pointer.svg"
|
||||
id="noVNC_pointer_lock_button" class="noVNC_button noVNC_hidden"
|
||||
title="Lock pointer">
|
||||
|
||||
<!--noVNC Touch Device only buttons-->
|
||||
<div id="noVNC_mobile_buttons">
|
||||
<input type="image" alt="Keyboard" src="app/images/keyboard.svg"
|
||||
|
|
|
|||
Loading…
Reference in New Issue