KASM-5359 Multiscreen Misc (#82)
* add auto left click on focus * remove filter of cursor updates * terminate session option * Detect user switching windows vs tabs * Get cross display dragging working * Selectively prevent default on mouse events * correct mouse coordinates from secondary screens --------- Co-authored-by: matt <matt@kasmweb.com> Co-authored-by: Chris Hunt <chris.hunt@kasmweb.com>
This commit is contained in:
parent
eafb24583d
commit
91d579bd7a
|
@ -1750,6 +1750,12 @@ const UI = {
|
||||||
case 'control_displays':
|
case 'control_displays':
|
||||||
parent.postMessage({ action: 'can_control_displays', value: true}, '*' );
|
parent.postMessage({ action: 'can_control_displays', value: true}, '*' );
|
||||||
break;
|
break;
|
||||||
|
case 'terminate':
|
||||||
|
//terminate a session, different then disconnect in that it is assumed KasmVNC will be shutdown
|
||||||
|
if (UI.rfb) {
|
||||||
|
UI.rfb.terminate();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
157
core/rfb.js
157
core/rfb.js
|
@ -208,6 +208,7 @@ export default class RFB extends EventTargetMixin {
|
||||||
this._accumulatedWheelDeltaY = 0;
|
this._accumulatedWheelDeltaY = 0;
|
||||||
this.mouseButtonMapper = null;
|
this.mouseButtonMapper = null;
|
||||||
this._mouseLastScreenIndex = -1;
|
this._mouseLastScreenIndex = -1;
|
||||||
|
this._sendLeftClickonNextMove = false;
|
||||||
|
|
||||||
// Gesture state
|
// Gesture state
|
||||||
this._gestureLastTapTime = null;
|
this._gestureLastTapTime = null;
|
||||||
|
@ -240,6 +241,7 @@ export default class RFB extends EventTargetMixin {
|
||||||
handleGesture: this._handleGesture.bind(this),
|
handleGesture: this._handleGesture.bind(this),
|
||||||
handleFocusChange: this._handleFocusChange.bind(this),
|
handleFocusChange: this._handleFocusChange.bind(this),
|
||||||
handleMouseOut: this._handleMouseOut.bind(this),
|
handleMouseOut: this._handleMouseOut.bind(this),
|
||||||
|
handleVisibilityChange: this._handleVisibilityChange.bind(this),
|
||||||
};
|
};
|
||||||
|
|
||||||
// main setup
|
// main setup
|
||||||
|
@ -315,6 +317,7 @@ export default class RFB extends EventTargetMixin {
|
||||||
this._clipViewport = false;
|
this._clipViewport = false;
|
||||||
this._scaleViewport = false;
|
this._scaleViewport = false;
|
||||||
this._resizeSession = false;
|
this._resizeSession = false;
|
||||||
|
this._lastVisibilityState = "visible";
|
||||||
|
|
||||||
this._showDotCursor = false;
|
this._showDotCursor = false;
|
||||||
if (options.showDotCursor !== undefined) {
|
if (options.showDotCursor !== undefined) {
|
||||||
|
@ -850,6 +853,18 @@ export default class RFB extends EventTargetMixin {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
terminate() {
|
||||||
|
if (this._isPrimaryDisplay) {
|
||||||
|
//disconnect the rfb connection
|
||||||
|
this._updateConnectionState('disconnecting');
|
||||||
|
this._sock.off('error');
|
||||||
|
this._sock.off('message');
|
||||||
|
this._sock.off('open');
|
||||||
|
//close secondary display windows
|
||||||
|
this._proxyRFBMessage('terminate');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sendCtrlAltDel() {
|
sendCtrlAltDel() {
|
||||||
if (this._rfbConnectionState !== 'connected' || this._viewOnly) { return; }
|
if (this._rfbConnectionState !== 'connected' || this._viewOnly) { return; }
|
||||||
Log.Info("Sending Ctrl-Alt-Del");
|
Log.Info("Sending Ctrl-Alt-Del");
|
||||||
|
@ -1172,6 +1187,7 @@ export default class RFB extends EventTargetMixin {
|
||||||
this._canvas.addEventListener("focus", this._eventHandlers.handleFocusChange);
|
this._canvas.addEventListener("focus", this._eventHandlers.handleFocusChange);
|
||||||
window.addEventListener("focus", this._eventHandlers.handleFocusChange);
|
window.addEventListener("focus", this._eventHandlers.handleFocusChange);
|
||||||
window.addEventListener("blur", this._eventHandlers.handleFocusChange);
|
window.addEventListener("blur", this._eventHandlers.handleFocusChange);
|
||||||
|
document.addEventListener("visibilitychange", this._eventHandlers.handleVisibilityChange);
|
||||||
|
|
||||||
//User cursor moves outside of the window
|
//User cursor moves outside of the window
|
||||||
window.addEventListener("mouseover", this._eventHandlers.handleMouseOut);
|
window.addEventListener("mouseover", this._eventHandlers.handleMouseOut);
|
||||||
|
@ -1351,7 +1367,8 @@ export default class RFB extends EventTargetMixin {
|
||||||
this._canvas.removeEventListener("focus", this._eventHandlers.handleFocusChange);
|
this._canvas.removeEventListener("focus", this._eventHandlers.handleFocusChange);
|
||||||
window.removeEventListener('resize', this._eventHandlers.windowResize);
|
window.removeEventListener('resize', this._eventHandlers.windowResize);
|
||||||
window.removeEventListener('focus', this._eventHandlers.handleFocusChange);
|
window.removeEventListener('focus', this._eventHandlers.handleFocusChange);
|
||||||
window.removeEventListener('focus', this._eventHandlers.handleFocusChange);
|
document.removeEventListener('visibilitychange', this._eventHandlers.handleVisibilityChange);
|
||||||
|
|
||||||
this._keyboard.ungrab();
|
this._keyboard.ungrab();
|
||||||
this._gestures.detach();
|
this._gestures.detach();
|
||||||
if (this._isPrimaryDisplay) {
|
if (this._isPrimaryDisplay) {
|
||||||
|
@ -1389,6 +1406,32 @@ export default class RFB extends EventTargetMixin {
|
||||||
|
|
||||||
_handleFocusChange(event) {
|
_handleFocusChange(event) {
|
||||||
this._resendClipboardNextUserDrivenEvent = true;
|
this._resendClipboardNextUserDrivenEvent = true;
|
||||||
|
|
||||||
|
if (event.type == 'focus' && event.currentTarget instanceof Window) {
|
||||||
|
|
||||||
|
if (this._lastVisibilityState === 'visible') {
|
||||||
|
Log.Debug("Window focused while user switched between windows.");
|
||||||
|
// added for multi-montiors
|
||||||
|
// as user moves from window to window, focus change loses a click, this marks the next mouse
|
||||||
|
// move to simulate a left click. We wait for the next mouse move because we need accurate x,y coords
|
||||||
|
this._sendLeftClickonNextMove = true;
|
||||||
|
} else {
|
||||||
|
Log.Debug("Window focused while user switched between tabs.");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.visibilityState === "visible" && this._lastVisibilityState === "hidden") {
|
||||||
|
Log.Debug("Window is now visible.");
|
||||||
|
this._lastVisibilityState = document.visibilityState;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_handleVisibilityChange(event) {
|
||||||
|
if (document.visibilityState === "hidden") {
|
||||||
|
this._lastVisibilityState = document.visibilityState;
|
||||||
|
Log.Debug("Window is not visible.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_focusCanvas(event) {
|
_focusCanvas(event) {
|
||||||
|
@ -1681,6 +1724,7 @@ export default class RFB extends EventTargetMixin {
|
||||||
if (this._isPrimaryDisplay) {
|
if (this._isPrimaryDisplay) {
|
||||||
// Secondary to Primary screen message
|
// Secondary to Primary screen message
|
||||||
let size;
|
let size;
|
||||||
|
let coords;
|
||||||
switch (event.data.eventType) {
|
switch (event.data.eventType) {
|
||||||
case 'register':
|
case 'register':
|
||||||
this._display.addScreen(event.data.screenID, event.data.width, event.data.height, event.data.pixelRatio, event.data.containerHeight, event.data.containerWidth);
|
this._display.addScreen(event.data.screenID, event.data.width, event.data.height, event.data.pixelRatio, event.data.containerHeight, event.data.containerWidth);
|
||||||
|
@ -1715,12 +1759,37 @@ export default class RFB extends EventTargetMixin {
|
||||||
Log.Info(`Secondary monitor (${event.data.screenID}) not found.`);
|
Log.Info(`Secondary monitor (${event.data.screenID}) not found.`);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'pointerEvent':
|
case 'mousemove':
|
||||||
let coords = this._display.getServerRelativeCoordinates(event.data.screenIndex, event.data.args[0], event.data.args[1]);
|
coords = this._display.getServerRelativeCoordinates(event.data.screenIndex, event.data.args[0], event.data.args[1]);
|
||||||
this._mouseLastScreenIndex = event.data.screenIndex;
|
this._mouseLastScreenIndex = event.data.screenIndex;
|
||||||
event.data.args[0] = coords[0];
|
this._mousePos = { 'x': coords[0], 'y': coords[1] };
|
||||||
event.data.args[1] = coords[1];
|
if (this._mouseButtonMask !== 0 && !event.data.args[2]) {
|
||||||
RFB.messages.pointerEvent(this._sock, ...event.data.args);
|
this._mouseButtonMask = 0;
|
||||||
|
}
|
||||||
|
RFB.messages.pointerEvent(this._sock, this._mousePos.x, this._mousePos.y, this._mouseButtonMask);
|
||||||
|
|
||||||
|
//simulate a left click
|
||||||
|
if (event.data.args[3]) {
|
||||||
|
this._mouseButtonMask |= 0x1;
|
||||||
|
RFB.messages.pointerEvent(this._sock, this._mousePos.x, this._mousePos.y, this._mouseButtonMask);
|
||||||
|
this._mouseButtonMask &= ~0x1;
|
||||||
|
RFB.messages.pointerEvent(this._sock, this._mousePos.x, this._mousePos.y, this._mouseButtonMask);
|
||||||
|
Log.Debug('Simulated Left Click on secondary display.');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'mousedown':
|
||||||
|
coords = this._display.getServerRelativeCoordinates(event.data.screenIndex, event.data.args[0], event.data.args[1]);
|
||||||
|
this._mouseLastScreenIndex = event.data.screenIndex;
|
||||||
|
this._mousePos = { 'x': coords[0], 'y': coords[1] };
|
||||||
|
this._mouseButtonMask |= event.data.args[2];
|
||||||
|
RFB.messages.pointerEvent(this._sock, this._mousePos.x, this._mousePos.y, this._mouseButtonMask);
|
||||||
|
break;
|
||||||
|
case 'mouseup':
|
||||||
|
coords = this._display.getServerRelativeCoordinates(event.data.screenIndex, event.data.args[0], event.data.args[1]);
|
||||||
|
this._mouseLastScreenIndex = event.data.screenIndex;
|
||||||
|
this._mousePos = { 'x': coords[0], 'y': coords[1] };
|
||||||
|
this._mouseButtonMask &= event.data.args[2];
|
||||||
|
RFB.messages.pointerEvent(this._sock, this._mousePos.x, this._mousePos.y, this._mouseButtonMask);
|
||||||
break;
|
break;
|
||||||
case 'keyEvent':
|
case 'keyEvent':
|
||||||
RFB.messages.keyEvent(this._sock, ...event.data.args);
|
RFB.messages.keyEvent(this._sock, ...event.data.args);
|
||||||
|
@ -1736,10 +1805,8 @@ export default class RFB extends EventTargetMixin {
|
||||||
// Primary to secondary screen message
|
// Primary to secondary screen message
|
||||||
switch (event.data.eventType) {
|
switch (event.data.eventType) {
|
||||||
case 'updateCursor':
|
case 'updateCursor':
|
||||||
if (event.data.mouseLastScreenIndex === this._display.screenIndex || this._mouseLastScreenIndex === -1) {
|
this._updateCursor(...event.data.args);
|
||||||
this._updateCursor(...event.data.args);
|
this._mouseLastScreenIndex = event.data.mouseLastScreenIndex;
|
||||||
this._mouseLastScreenIndex = event.data.mouseLastScreenIndex;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case 'receivedClipboard':
|
case 'receivedClipboard':
|
||||||
if (event.data.mouseLastScreenIndex === this._display.screenIndex) {
|
if (event.data.mouseLastScreenIndex === this._display.screenIndex) {
|
||||||
|
@ -1749,6 +1816,10 @@ export default class RFB extends EventTargetMixin {
|
||||||
case 'disconnect':
|
case 'disconnect':
|
||||||
this.disconnect();
|
this.disconnect();
|
||||||
break;
|
break;
|
||||||
|
case 'terminate':
|
||||||
|
this.disconnect();
|
||||||
|
window.close();
|
||||||
|
break;
|
||||||
case 'forceResize':
|
case 'forceResize':
|
||||||
this._hiDpi = event.data.args[0];
|
this._hiDpi = event.data.args[0];
|
||||||
this._updateScale();
|
this._updateScale();
|
||||||
|
@ -1850,14 +1921,19 @@ export default class RFB extends EventTargetMixin {
|
||||||
}
|
}
|
||||||
|
|
||||||
_handleMouseOut(ev) {
|
_handleMouseOut(ev) {
|
||||||
if (ev.toElement !== null && ev.relatedTarget === null) {
|
if (ev.toElement !== null && ev.relatedTarget === null && ev.fromElement === null) {
|
||||||
//mouse was outside of the window and just came in, this is our chance to do things
|
//mouse was outside of the window and just came in, this is our chance to do things
|
||||||
|
Log.Debug("Mouse came into Window");
|
||||||
|
Log.Debug(ev);
|
||||||
|
|
||||||
//Ensure the window was not moved to a different screen with a different pixel ratio
|
//Ensure the window was not moved to a different screen with a different pixel ratio
|
||||||
if (this._display.screens[0].pixelRatio !== window.devicePixelRatio) {
|
if (this._display.screens[0].pixelRatio !== window.devicePixelRatio) {
|
||||||
Log.Debug("Window moved to another screen with different pixel ratio, sending resize request.");
|
Log.Debug("Window moved to another screen with different pixel ratio, sending resize request.");
|
||||||
this._requestRemoteResize();
|
this._requestRemoteResize();
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
Log.Debug("Mouse left Window");
|
||||||
|
Log.Debug(ev);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1881,9 +1957,9 @@ export default class RFB extends EventTargetMixin {
|
||||||
// FIXME: if we're in view-only and not dragging,
|
// FIXME: if we're in view-only and not dragging,
|
||||||
// should we stop events?
|
// should we stop events?
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
ev.preventDefault();
|
|
||||||
|
|
||||||
if ((ev.type === 'click') || (ev.type === 'contextmenu')) {
|
if ((ev.type === 'click') || (ev.type === 'contextmenu')) {
|
||||||
|
ev.preventDefault();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1921,6 +1997,9 @@ export default class RFB extends EventTargetMixin {
|
||||||
const mappedButton = this.mouseButtonMapper.get(ev.button);
|
const mappedButton = this.mouseButtonMapper.get(ev.button);
|
||||||
switch (ev.type) {
|
switch (ev.type) {
|
||||||
case 'mousedown':
|
case 'mousedown':
|
||||||
|
if (this._display.screens.length === 0 || window.self === window.top) {
|
||||||
|
ev.preventDefault();
|
||||||
|
}
|
||||||
setCapture(this._canvas);
|
setCapture(this._canvas);
|
||||||
|
|
||||||
// Translate CMD+Click into CTRL+click on MacOs
|
// Translate CMD+Click into CTRL+click on MacOs
|
||||||
|
@ -1937,18 +2016,35 @@ export default class RFB extends EventTargetMixin {
|
||||||
// Ensure keys down are synced between client and server
|
// Ensure keys down are synced between client and server
|
||||||
this._keyboard.clearKeysDown(ev);
|
this._keyboard.clearKeysDown(ev);
|
||||||
|
|
||||||
this._handleMouseButton(pos.x, pos.y,
|
if (this._isPrimaryDisplay) {
|
||||||
true, xvncButtonToMask(mappedButton));
|
this._handleMouseButton(pos.x, pos.y, true, xvncButtonToMask(mappedButton));
|
||||||
|
} else {
|
||||||
|
this._proxyRFBMessage('mousedown', [ pos.x, pos.y, xvncButtonToMask(mappedButton) ]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Debug('Mouse Down');
|
||||||
break;
|
break;
|
||||||
case 'mouseup':
|
case 'mouseup':
|
||||||
this._handleMouseButton(pos.x, pos.y,
|
ev.preventDefault();
|
||||||
false, xvncButtonToMask(mappedButton));
|
if (this._isPrimaryDisplay) {
|
||||||
|
this._handleMouseButton(pos.x, pos.y, false, xvncButtonToMask(mappedButton));
|
||||||
|
} else {
|
||||||
|
this._proxyRFBMessage('mouseup', [ pos.x, pos.y, xvncButtonToMask(mappedButton) ]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Debug('Mouse Up');
|
||||||
break;
|
break;
|
||||||
case 'mousemove':
|
case 'mousemove':
|
||||||
//when there are multiple screens
|
ev.preventDefault();
|
||||||
//This window can get mouse move events when the cursor is outside of the window, if the mouse is down
|
if (this._isPrimaryDisplay) {
|
||||||
//when the cursor crosses the threshold of the window
|
this._handleMouseMove(pos.x, pos.y, (ev.buttons > 0));
|
||||||
this._handleMouseMove(pos.x, pos.y);
|
} else {
|
||||||
|
this._proxyRFBMessage('mousemove', [ pos.x, pos.y, (ev.buttons > 0), this._sendLeftClickonNextMove ]);
|
||||||
|
this._sendLeftClickonNextMove = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ev.preventDefault();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1992,9 +2088,12 @@ export default class RFB extends EventTargetMixin {
|
||||||
}
|
}
|
||||||
|
|
||||||
this._sendMouse(x, y, this._mouseButtonMask);
|
this._sendMouse(x, y, this._mouseButtonMask);
|
||||||
|
|
||||||
|
//marked true on canvas going into focus
|
||||||
|
this._sendLeftClickonNextMove = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
_handleMouseMove(x, y) {
|
_handleMouseMove(x, y, down) {
|
||||||
if (this._viewportDragging) {
|
if (this._viewportDragging) {
|
||||||
const deltaX = this._viewportDragPos.x - x;
|
const deltaX = this._viewportDragPos.x - x;
|
||||||
const deltaY = this._viewportDragPos.y - y;
|
const deltaY = this._viewportDragPos.y - y;
|
||||||
|
@ -2011,6 +2110,12 @@ export default class RFB extends EventTargetMixin {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// With multiple displays, it is possible to end up in a state where we lost the mouseup event
|
||||||
|
// If a mouse move indicates no buttons are down but the current state shows something down, lets clear the plate
|
||||||
|
if (this._mouseButtonMask !== 0 && !down) {
|
||||||
|
this._mouseButtonMask = 0;
|
||||||
|
}
|
||||||
|
|
||||||
this._mousePos = { 'x': x, 'y': y };
|
this._mousePos = { 'x': x, 'y': y };
|
||||||
|
|
||||||
// Limit many mouse move events to one every MOUSE_MOVE_DELAY ms
|
// Limit many mouse move events to one every MOUSE_MOVE_DELAY ms
|
||||||
|
@ -2027,6 +2132,14 @@ export default class RFB extends EventTargetMixin {
|
||||||
}, MOUSE_MOVE_DELAY - timeSinceLastMove);
|
}, MOUSE_MOVE_DELAY - timeSinceLastMove);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Simulate a left click on focus change
|
||||||
|
//this was added to aid multi-display, not requiring two clicks when switching between displays
|
||||||
|
if (this._sendLeftClickonNextMove) {
|
||||||
|
this._sendLeftClickonNextMove = false;
|
||||||
|
this._handleMouseButton(this._mousePos.x, this._mousePos.y, true, 0x1);
|
||||||
|
this._handleMouseButton(this._mousePos.x, this._mousePos.y, false, 0x1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_handleDelayedMouseMove() {
|
_handleDelayedMouseMove() {
|
||||||
|
@ -2158,7 +2271,7 @@ export default class RFB extends EventTargetMixin {
|
||||||
}
|
}
|
||||||
|
|
||||||
_fakeMouseMove(ev, elementX, elementY) {
|
_fakeMouseMove(ev, elementX, elementY) {
|
||||||
this._handleMouseMove(elementX, elementY);
|
this._handleMouseMove(elementX, elementY, false);
|
||||||
this._cursor.move(ev.detail.clientX, ev.detail.clientY);
|
this._cursor.move(ev.detail.clientX, ev.detail.clientY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue