Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions app/ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ const UI = {
UI.initSetting('password');
UI.initSetting('autoconnect', false);
UI.initSetting('view_clip', false);
UI.initSetting('view_drag', false);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already have the toolbar button for this. What's the purpose of this change?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a button, but no URL parameter to save this setting as bookmark or enable it automatically when embedding.

https://example.org/vnc.html?autoconnect=1&view_clip=1&view_drag=1

Nevertheless, it wasn't working as intended. Fixed with abc7d63

UI.initSetting('resize', 'off');
UI.initSetting('quality', 6);
UI.initSetting('compression', 2);
Expand Down Expand Up @@ -1096,11 +1097,17 @@ const UI = {
UI.rfb.addEventListener("bell", UI.bell);
UI.rfb.addEventListener("desktopname", UI.updateDesktopName);
UI.rfb.clipViewport = UI.getSetting('view_clip');
UI.rfb.dragViewport = UI.getSetting('view_drag');
UI.rfb.scaleViewport = UI.getSetting('resize') === 'scale';
UI.rfb.resizeSession = UI.getSetting('resize') === 'remote';
UI.rfb.qualityLevel = parseInt(UI.getSetting('quality'));
UI.rfb.compressionLevel = parseInt(UI.getSetting('compression'));
UI.rfb.showDotCursor = UI.getSetting('show_dot');
UI.rfb.showLocalCursor = {
drag: 'grab',
dragging: 'grabbing',
empty: 'default',
};

UI.updateViewOnly(); // requires UI.rfb
},
Expand Down
115 changes: 104 additions & 11 deletions core/rfb.js
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,14 @@ export default class RFB extends EventTargetMixin {

// Cursor
this._cursor = new Cursor();
this._showLocalCursor = false;
this._localCursors = {
dragging: null,
drag: null,
viewOnly: null,
default: null,
empty: null,
};

// XXX: TightVNC 2.8.11 sends no cursor at all until Windows changes
// it. Result: no cursor at all until a window border or an edit field
Expand Down Expand Up @@ -290,12 +298,12 @@ export default class RFB extends EventTargetMixin {

// ===== PROPERTIES =====

this.dragViewport = false;
this.focusOnClick = true;

this._viewOnly = false;
this._clipViewport = false;
this._clippingViewport = false;
this._dragViewport = false;
this._scaleViewport = false;
this._resizeSession = false;

Expand All @@ -315,8 +323,10 @@ export default class RFB extends EventTargetMixin {
this._rfbConnectionState === "connected") {
if (viewOnly) {
this._keyboard.ungrab();
this._refreshCursor();
} else {
this._keyboard.grab();
this._refreshCursor();
}
}
}
Expand All @@ -342,6 +352,12 @@ export default class RFB extends EventTargetMixin {
this._updateClip();
}

get dragViewport() { return this._dragViewport; }
set dragViewport(dragViewport) {
this._dragViewport = dragViewport;
this._refreshCursor();
}

get scaleViewport() { return this._scaleViewport; }
set scaleViewport(scale) {
this._scaleViewport = scale;
Expand Down Expand Up @@ -370,6 +386,29 @@ export default class RFB extends EventTargetMixin {
this._refreshCursor();
}

get showLocalCursor() { return this._showLocalCursor; }
set showLocalCursor(cursors) {
cursors ??= false;
this._showLocalCursor = !!cursors;
const {
default: defaultCursor,
viewOnly: viewOnlyCursor,
drag: dragCursor,
dragging: draggingCursor,
empty: emptyCursor,
} = cursors;
defaultCursor && (this._localCursors.default = defaultCursor);
viewOnlyCursor && (this._localCursors.viewOnly = viewOnlyCursor);
dragCursor && (this._localCursors.drag = dragCursor);
draggingCursor && (this._localCursors.dragging = draggingCursor);
emptyCursor && (this._localCursors.empty = emptyCursor);
this._cursor.detach();
this._cursor.attach(this._canvas, {
showLocalCursor: this._showLocalCursor,
});
this._refreshCursor();
}

get background() { return this._screen.style.background; }
set background(cssValue) { this._screen.style.background = cssValue; }

Expand Down Expand Up @@ -574,7 +613,9 @@ export default class RFB extends EventTargetMixin {

this._gestures.attach(this._canvas);

this._cursor.attach(this._canvas);
this._cursor.attach(this._canvas, {
showLocalCursor: this._showLocalCursor
});
this._refreshCursor();

// Monitor size changes of the screen element
Expand Down Expand Up @@ -1111,16 +1152,23 @@ export default class RFB extends EventTargetMixin {

let bmask = RFB._convertButtonMask(ev.buttons);

let down = ev.type == 'mousedown';
let down = false;
Comment on lines 1114 to +1155
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this improve?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a minor non-functional optimization.

First, the event type is compared against the word mousedown and the result is stored in the local variable down.
Second, in case of the text value of the event type we switch to the responsible code block for'mousedown, mouseup and mousemove.

As the commit message says: Do not compare to 'mousedown' on every 'mousemove'.

I guess mousemove is the most frequently invoked block, so it should be the first case block.

--- 0f48f31e08a70045e4fe5deebcac7d7b04fd816d/core/rfb.js
+++ 57829159839e7a2358f5c22a53414565f5704087/core/rfb.js
@@ -1117,11 +1117,9 @@ export default class RFB extends EventTargetMixin {

         let bmask = RFB._convertButtonMask(ev.buttons);

+        let down = false;
-        let down = ev.type == 'mousedown';
         switch (ev.type) {
             case 'mousedown':
+                down = true;
+            // eslint-disable-next-line no-fallthrough
             case 'mouseup':
                 if (this._dragViewport) {
                     this._cursor.setLocalCursor(down ? 'grabbing' : 'grab');
@@ -1157 +1159 @@
                 break;
             case 'mousemove':

switch (ev.type) {
case 'mousedown':
down = true;
// eslint-disable-next-line no-fallthrough
case 'mouseup':
if (this.dragViewport) {
if (this._dragViewport) {
if (down && !this._viewportDragging) {
this._viewportDragging = true;
this._viewportDragPos = {'x': pos.x, 'y': pos.y};
this._viewportHasMoved = false;

if (this._showLocalCursor) {
this._refreshCursor();
this._cursor.detach();
}

this._flushMouseMoveTimer(pos.x, pos.y);

// Skip sending mouse events, instead save the current
Expand All @@ -1130,6 +1178,13 @@ export default class RFB extends EventTargetMixin {
} else {
this._viewportDragging = false;

if (this._showLocalCursor) {
this._cursor.attach(this._canvas, {
showLocalCursor: this._showLocalCursor,
});
this._refreshCursor();
}

// If we actually performed a drag then we are done
// here and should not send any mouse events
if (this._viewportHasMoved) {
Expand Down Expand Up @@ -1334,7 +1389,7 @@ export default class RFB extends EventTargetMixin {
this._handleTapEvent(ev, 0x2);
break;
case 'drag':
if (this.dragViewport) {
if (this._dragViewport) {
this._viewportHasMoved = false;
this._viewportDragging = true;
this._viewportDragPos = {'x': pos.x, 'y': pos.y};
Expand All @@ -1344,7 +1399,7 @@ export default class RFB extends EventTargetMixin {
}
break;
case 'longpress':
if (this.dragViewport) {
if (this._dragViewport) {
// If dragViewport is true, we need to wait to see
// if we have dragged outside the threshold before
// sending any events to the server.
Expand Down Expand Up @@ -1376,7 +1431,7 @@ export default class RFB extends EventTargetMixin {
break;
case 'drag':
case 'longpress':
if (this.dragViewport) {
if (this._dragViewport) {
this._viewportDragging = true;
const deltaX = this._viewportDragPos.x - pos.x;
const deltaY = this._viewportDragPos.y - pos.y;
Expand Down Expand Up @@ -1451,7 +1506,7 @@ export default class RFB extends EventTargetMixin {
case 'twodrag':
break;
case 'drag':
if (this.dragViewport) {
if (this._dragViewport) {
this._viewportDragging = false;
} else {
this._fakeMouseMove(ev, pos.x, pos.y);
Expand All @@ -1465,7 +1520,7 @@ export default class RFB extends EventTargetMixin {
break;
}

if (this.dragViewport && !this._viewportHasMoved) {
if (this._dragViewport && !this._viewportHasMoved) {
this._fakeMouseMove(ev, pos.x, pos.y);
// If dragViewport is true, we need to wait to see
// if we have dragged outside the threshold before
Expand Down Expand Up @@ -3032,10 +3087,20 @@ export default class RFB extends EventTargetMixin {

_shouldShowDotCursor() {
// Called when this._cursorImage is updated
if (!this._showDotCursor) {
// User does not want to see the dot, so...
if (!this._showDotCursor && !(this._showLocalCursor && this._localCursors.empty)) {
// User does not want to see the dot or has no local cursor, so...
return false;
}
if (this._showLocalCursor) {
// Do not show the dot in states with a local cursor
if (this._viewportDragging) {
if (this._localCursors.dragging) { return false; }
} else if (this._dragViewport) {
if (this._localCursors.drag) { return false; }
} else if (this._viewOnly) {
if (this._localCursors.viewOnly) { return false; }
}
}

// The dot should not be shown if the cursor is already visible,
// i.e. contains at least one not-fully-transparent pixel.
Expand All @@ -3057,13 +3122,41 @@ export default class RFB extends EventTargetMixin {
this._rfbConnectionState !== "connected") {
return;
}
if (this._showLocalCursor) {
this._refreshCursorWithLocalCursors();
return;
}
const image = this._shouldShowDotCursor() ? RFB.cursors.dot : this._cursorImage;
this._cursor.change(image.rgbaPixels,
image.hotx, image.hoty,
image.w, image.h
);
}

_refreshCursorWithLocalCursors() {
let image = this._cursorImage;
let localCursor; // = 'none';
if (this._viewportDragging) {
localCursor = this._localCursors.dragging;
} else if (this._dragViewport) {
localCursor = this._localCursors.drag;
} else if (this._viewOnly) {
localCursor = this._localCursors.viewOnly;
// clear locally rendered cursor when switching to view-only whilst connected
image = RFB.cursors.none;
} else if (this._shouldShowDotCursor()) {
localCursor = this._localCursors.empty;
image = this._showDotCursor ? RFB.cursors.dot : this._cursorImage;
} else {
localCursor = this._localCursors.default;
}
this._cursor.change(image.rgbaPixels,
image.hotx, image.hoty,
image.w, image.h,
{ localCursor }
);
}

static genDES(password, challenge) {
const passwordChars = password.split('').map(c => c.charCodeAt(0));
const key = legacyCrypto.importKey(
Expand Down
54 changes: 32 additions & 22 deletions core/util/cursor.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,29 @@

import { supportsCursorURIs, isTouchDevice } from './browser.js';

const useFallback = !supportsCursorURIs || isTouchDevice;
// Sometimes (at least with Chrome and Firefox on Windows)
// isTouchDevice is true even if there is no touch device, in
// this case useFallback ist also true and cursor URIs are never
// used.
const __FORCE_NO_TOUCH_DEVICE__ = false;
const useFallback = !supportsCursorURIs || (!__FORCE_NO_TOUCH_DEVICE__ && isTouchDevice);

export default class Cursor {
constructor() {
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';
// Safari on iOS can select the cursor image
// https://bugs.webkit.org/show_bug.cgi?id=249223
this._canvas.style.userSelect = 'none';
this._canvas.style.WebkitUserSelect = 'none';
// Can't use "display" because of Firefox bug #1445997
this._canvas.style.visibility = 'hidden';
}
// always initalize canvas.style in case of showing local cursors
this._canvas.style.position = 'fixed';
this._canvas.style.zIndex = '65535';
this._canvas.style.pointerEvents = 'none';
// Safari on iOS can select the cursor image
// https://bugs.webkit.org/show_bug.cgi?id=249223
this._canvas.style.userSelect = 'none';
this._canvas.style.WebkitUserSelect = 'none';
// Can't use "display" because of Firefox bug #1445997
this._canvas.style.visibility = 'hidden';

this._position = { x: 0, y: 0 };
this._hotSpot = { x: 0, y: 0 };
Expand All @@ -37,14 +41,15 @@ export default class Cursor {
};
}

attach(target) {
attach(target, { showLocalCursor } = {}) {
if (this._target) {
this.detach();
}

this._target = target;
this._showLocalCursor = !!showLocalCursor;

if (useFallback) {
if (useFallback || this._showLocalCursor) {
document.body.appendChild(this._canvas);

const options = { capture: true, passive: true };
Expand All @@ -54,15 +59,15 @@ export default class Cursor {
this._target.addEventListener('mouseup', this._eventHandlers.mouseup, options);
}

this.clear();
this.clear({ localCursor: showLocalCursor });
}

detach() {
if (!this._target) {
return;
}

if (useFallback) {
if (useFallback || this._showLocalCursor) {
const options = { capture: true, passive: true };
this._target.removeEventListener('mouseover', this._eventHandlers.mouseover, options);
this._target.removeEventListener('mouseleave', this._eventHandlers.mouseleave, options);
Expand All @@ -77,9 +82,9 @@ export default class Cursor {
this._target = null;
}

change(rgba, hotx, hoty, w, h) {
change(rgba, hotx, hoty, w, h, { localCursor } = {}) {
if ((w === 0) || (h === 0)) {
this.clear();
this.clear({ localCursor });
return;
}

Expand All @@ -97,16 +102,21 @@ export default class Cursor {
ctx.clearRect(0, 0, w, h);
ctx.putImageData(img, 0, 0);

if (useFallback) {
if (useFallback || this._showLocalCursor) {
this._target.style.cursor = localCursor ?? 'none';
this._updatePosition();
} else {
let url = this._canvas.toDataURL();
this._target.style.cursor = 'url(' + url + ')' + hotx + ' ' + hoty + ', default';
}
}

clear() {
this._target.style.cursor = 'none';
clear({ localCursor } = {}) {
// whilst dragging the viewport and changes to the remote cursor
// are made the target might be detached
if (this._target) {
this._target.style.cursor = localCursor ?? 'none';
}
this._canvas.width = 0;
this._canvas.height = 0;
this._position.x = this._position.x + this._hotSpot.x;
Expand All @@ -118,7 +128,7 @@ export default class Cursor {
// Mouse events might be emulated, this allows
// moving the cursor in such cases
move(clientX, clientY) {
if (!useFallback) {
if (!useFallback && !this._showLocalCursor) {
return;
}
// clientX/clientY are relative the _visual viewport_,
Expand Down
Loading