WIP - computer acting odd, committing code

This commit is contained in:
mattmcclaskey 2023-09-12 14:16:19 -04:00
parent 419a3a70e9
commit 96001175a6
No known key found for this signature in database
4 changed files with 497 additions and 132 deletions

View File

@ -1,13 +1,15 @@
import RFB from "../core/rfb.js";
import * as WebUtil from "./webutil.js";
import { isTouchDevice, isSafari, hasScrollbarGutter, dragThreshold, supportsBinaryClipboard, isFirefox, isWindows, isIOS, supportsPointerLock }
from '../core/util/browser.js';
import { MouseButtonMapper, XVNC_BUTTONS } from "../core/mousebuttonmapper.js";
const UI = { const UI = {
connected: false, connected: false,
//Initial Loading of the UI //Initial Loading of the UI
prime() { prime() {
this.start();
}, },
//Render default UI //Render default UI
@ -20,19 +22,209 @@ const UI = {
UI.addDefaultHandlers(); UI.addDefaultHandlers();
UI.updateVisualState('disconnected');
}, },
addDefaultHandlers() { addDefaultHandlers() {
document.getElementById('noVNC_connect_button', UI.connect); document.getElementById('noVNC_connect_button').addEventListener('click', UI.connect);;
},
getSetting(name, isBool) {
const ctrl = document.getElementById('noVNC_setting_' + name);
let val = WebUtil.readSetting(name);
if (typeof val !== 'undefined' && val !== null && isBool) {
if (val.toString().toLowerCase() in {'0': 1, 'no': 1, 'false': 1}) {
val = false;
} else {
val = true;
}
}
return val;
}, },
connect() { connect() {
UI.rfb = new RFB(document.getElementById('noVNC_container'),
document.getElementById('noVNC_keyboardinput'),
"", //URL
{
shared: UI.getSetting('shared', true),
repeaterID: UI.getSetting('repeaterID', false),
credentials: { password: null }
},
false // Not a primary display
);
UI.rfb.addEventListener("connect", UI.connectFinished);
//UI.rfb.addEventListener("disconnect", UI.disconnectFinished);
UI.rfb.clipViewport = UI.getSetting('view_clip');
UI.rfb.scaleViewport = UI.getSetting('resize') === 'scale';
UI.rfb.resizeSession = UI.getSetting('resize') === 'remote';
UI.rfb.qualityLevel = parseInt(UI.getSetting('quality'));
UI.rfb.dynamicQualityMin = parseInt(UI.getSetting('dynamic_quality_min'));
UI.rfb.dynamicQualityMax = parseInt(UI.getSetting('dynamic_quality_max'));
UI.rfb.jpegVideoQuality = parseInt(UI.getSetting('jpeg_video_quality'));
UI.rfb.webpVideoQuality = parseInt(UI.getSetting('webp_video_quality'));
UI.rfb.videoArea = parseInt(UI.getSetting('video_area'));
UI.rfb.videoTime = parseInt(UI.getSetting('video_time'));
UI.rfb.videoOutTime = parseInt(UI.getSetting('video_out_time'));
UI.rfb.videoScaling = parseInt(UI.getSetting('video_scaling'));
UI.rfb.treatLossless = parseInt(UI.getSetting('treat_lossless'));
UI.rfb.maxVideoResolutionX = parseInt(UI.getSetting('max_video_resolution_x'));
UI.rfb.maxVideoResolutionY = parseInt(UI.getSetting('max_video_resolution_y'));
UI.rfb.frameRate = parseInt(UI.getSetting('framerate'));
UI.rfb.compressionLevel = parseInt(UI.getSetting('compression'));
UI.rfb.showDotCursor = UI.getSetting('show_dot');
UI.rfb.idleDisconnect = UI.getSetting('idle_disconnect');
UI.rfb.pointerRelative = UI.getSetting('pointer_relative');
UI.rfb.videoQuality = parseInt(UI.getSetting('video_quality'));
UI.rfb.antiAliasing = UI.getSetting('anti_aliasing');
UI.rfb.clipboardUp = UI.getSetting('clipboard_up');
UI.rfb.clipboardDown = UI.getSetting('clipboard_down');
UI.rfb.clipboardSeamless = UI.getSetting('clipboard_seamless');
UI.rfb.keyboard.enableIME = UI.getSetting('enable_ime');
UI.rfb.clipboardBinary = supportsBinaryClipboard() && UI.rfb.clipboardSeamless;
UI.rfb.enableWebRTC = UI.getSetting('enable_webrtc');
UI.rfb.enableHiDpi = UI.getSetting('enable_hidpi');
UI.rfb.mouseButtonMapper = UI.initMouseButtonMapper();
if (UI.rfb.videoQuality === 5) {
UI.rfb.enableQOI = true;
}
},
updateVisualState(state) {
document.documentElement.classList.remove("noVNC_connecting");
document.documentElement.classList.remove("noVNC_connected");
document.documentElement.classList.remove("noVNC_disconnecting");
document.documentElement.classList.remove("noVNC_reconnecting");
document.documentElement.classList.remove("noVNC_disconnected");
const transitionElem = document.getElementById("noVNC_transition_text");
if (WebUtil.isInsideKasmVDI())
{
parent.postMessage({ action: 'connection_state', value: state}, '*' );
}
switch (state) {
case 'init':
break;
case 'connecting':
transitionElem.textContent = _("Connecting...");
document.documentElement.classList.add("noVNC_connecting");
break;
case 'connected':
document.documentElement.classList.add("noVNC_connected");
break;
case 'disconnecting':
transitionElem.textContent = _("Disconnecting...");
document.documentElement.classList.add("noVNC_disconnecting");
break;
case 'disconnected':
document.documentElement.classList.add("noVNC_disconnected");
break;
case 'reconnecting':
transitionElem.textContent = _("Reconnecting...");
document.documentElement.classList.add("noVNC_reconnecting");
break;
default:
Log.Error("Invalid visual state: " + state);
UI.showStatus(_("Internal error"), 'error');
return;
}
},
showStatus(text, statusType, time, kasm = false) {
// If inside the full Kasm CDI framework, don't show messages unless explicitly told to
if (WebUtil.isInsideKasmVDI() && !kasm) {
return;
}
const statusElem = document.getElementById('noVNC_status');
if (typeof statusType === 'undefined') {
statusType = 'normal';
}
// Don't overwrite more severe visible statuses and never
// errors. Only shows the first error.
if (statusElem.classList.contains("noVNC_open")) {
if (statusElem.classList.contains("noVNC_status_error")) {
return;
}
if (statusElem.classList.contains("noVNC_status_warn") &&
statusType === 'normal') {
return;
}
}
clearTimeout(UI.statusTimeout);
switch (statusType) {
case 'error':
statusElem.classList.remove("noVNC_status_warn");
statusElem.classList.remove("noVNC_status_normal");
statusElem.classList.add("noVNC_status_error");
break;
case 'warning':
case 'warn':
statusElem.classList.remove("noVNC_status_error");
statusElem.classList.remove("noVNC_status_normal");
statusElem.classList.add("noVNC_status_warn");
break;
case 'normal':
case 'info':
default:
statusElem.classList.remove("noVNC_status_error");
statusElem.classList.remove("noVNC_status_warn");
statusElem.classList.add("noVNC_status_normal");
break;
}
statusElem.textContent = text;
statusElem.classList.add("noVNC_open");
// If no time was specified, show the status for 1.5 seconds
if (typeof time === 'undefined') {
time = 1500;
}
// Error messages do not timeout
if (statusType !== 'error') {
UI.statusTimeout = window.setTimeout(UI.hideStatus, time);
}
}, },
disconnect() { disconnect() {
} },
connectFinished(e) {
UI.connected = true;
UI.inhibitReconnect = false;
UI.showStatus("Secondary Screen Connected");
UI.updateVisualState('connected');
// Do this last because it can only be used on rendered elements
UI.rfb.focus();
},
initMouseButtonMapper() {
const mouseButtonMapper = new MouseButtonMapper();
const settings = WebUtil.readSetting("mouseButtonMapper");
if (settings) {
mouseButtonMapper.load(settings);
return mouseButtonMapper;
}
mouseButtonMapper.set(0, XVNC_BUTTONS.LEFT_BUTTON);
mouseButtonMapper.set(1, XVNC_BUTTONS.MIDDLE_BUTTON);
mouseButtonMapper.set(2, XVNC_BUTTONS.RIGHT_BUTTON);
mouseButtonMapper.set(3, XVNC_BUTTONS.BACK_BUTTON);
mouseButtonMapper.set(4, XVNC_BUTTONS.FORWARD_BUTTON);
WebUtil.writeSetting("mouseButtonMapper", mouseButtonMapper.dump());
return mouseButtonMapper;
},
} }
UI.prime(); UI.prime();

View File

@ -14,7 +14,7 @@ import { isWindows } from './util/browser.js';
import { uuidv4 } from './util/strings.js' import { uuidv4 } from './util/strings.js'
export default class Display { export default class Display {
constructor(target) { constructor(target, isPrimaryDisplay) {
Log.Debug(">> Display.constructor"); Log.Debug(">> Display.constructor");
/* /*
@ -85,8 +85,10 @@ export default class Display {
this._clipViewport = false; this._clipViewport = false;
this._antiAliasing = 0; this._antiAliasing = 0;
this._fps = 0; this._fps = 0;
this._isPrimaryDisplay = isPrimaryDisplay;
this._screenID = uuidv4();
this._screens = [{ this._screens = [{
screenID: uuidv4(), screenID: this._screenID,
screenIndex: 0, screenIndex: 0,
width: this._target.width, //client width: this._target.width, //client
height: this._target.height, //client height: this._target.height, //client
@ -108,6 +110,11 @@ export default class Display {
// Use requestAnimationFrame to write to canvas, to match display refresh rate // Use requestAnimationFrame to write to canvas, to match display refresh rate
this._animationFrameID = window.requestAnimationFrame( () => { this._pushAsyncFrame(); }); this._animationFrameID = window.requestAnimationFrame( () => { this._pushAsyncFrame(); });
if (!this._isPrimaryDisplay) {
this._screens[0].channel = new BroadcastChannel(`screen_${this._screenID}_channel`);
this._screens[0].channel.addEventListener('message', this._handleSecondaryDisplayMessage.bind(this));
}
Log.Debug("<< Display.constructor"); Log.Debug("<< Display.constructor");
} }
@ -166,6 +173,8 @@ export default class Display {
//recalculate primary display container size //recalculate primary display container size
this._screens[0].containerHeight = this._target.parentNode.offsetHeight; this._screens[0].containerHeight = this._target.parentNode.offsetHeight;
this._screens[0].containerWidth = this._target.parentNode.offsetWidth; this._screens[0].containerWidth = this._target.parentNode.offsetWidth;
this._screens[0].width = this._target.width;
this._screens[0].height = this._target.height;
//calculate server-side resolution of each screen //calculate server-side resolution of each screen
for (let i=0; i<this._screens.length; i++) { for (let i=0; i<this._screens.length; i++) {
@ -218,6 +227,9 @@ export default class Display {
if (this._screens.length > 1) { if (this._screens.length > 1) {
const primary_screen = this._screens[0]; const primary_screen = this._screens[0];
const secondary_screen = this._screens[1]; const secondary_screen = this._screens[1];
let total_width = 0;
let total_height = 0;
switch (this._screens[1].relativePosition) { switch (this._screens[1].relativePosition) {
case 0: case 0:
//primary screen is to left //primary screen is to left
@ -257,50 +269,59 @@ export default class Display {
} }
} }
data.screens = structuredClone(this._screens); data.screens = this._screens;
return data; return data;
} }
addScreen(screenID, width, height, relativePosition, pixelRatio, containerHeight, containerWidth) { addScreen(screenID, width, height, relativePosition, pixelRatio, containerHeight, containerWidth) {
//currently only support one secondary screen if (this._isPrimaryDisplay) {
if (this._screens.length > 1) { //currently only support one secondary screen
this._screens[1].channel.close(); if (this._screens.length > 1) {
this._screens.pop() this._screens[1].channel.close();
} this._screens.pop()
screenIdx = this.screens.length; }
new_screen = {
screenID: screenID, var new_screen = {
screenIndex: screenIdx, screenID: screenID,
width: width, //client screenIndex: this.screens.length,
height: height, //client width: width, //client
serverWidth: 0, //calculated height: height, //client
serverHeight: 0, //calculated serverWidth: 0, //calculated
x: 0, serverHeight: 0, //calculated
y: 0, x: 0,
relativePosition: relativePosition, y: 0,
pixelRatio: pixelRatio, relativePosition: relativePosition,
containerHeight: containerHeight, pixelRatio: pixelRatio,
containerWidth: containerWidth, containerHeight: containerHeight,
channel: null, containerWidth: containerWidth,
scale: 0 channel: null,
scale: 0
}
new_screen.channel = new BroadcastChannel(`screen_${screenID}_channel`);
//new_screen.channel.message = this._handleSecondaryDisplayMessage().bind(this);
this._screens.push(new_screen);
new_screen.channel.postMessage({ eventType: "registered", screenIndex: new_screen.screenIndex });
} else {
throw new Error("Cannot add a screen to a secondary display.")
} }
new_screen.channel = new BroadcastChannel(`screen_${screenID}_channel`);
//new_screen.channel.message = this._handleSecondaryDisplayMessage().bind(this);
this._screens.push(new_screen);
new_screen.channel.postMessage({ eventType: "registered", screenIndex: screenIdx });
} }
removeScreen(screenID) { removeScreen(screenID) {
for (let i=1; i<this._screens.length; i++) { if (this.isPrimaryDisplay) {
if (this._screens[i].screenID == screenID) { for (let i=1; i<this._screens.length; i++) {
this._screens.splice(i, 1); if (this._screens[i].screenID == screenID) {
return true; this._screens.splice(i, 1);
return true;
}
} }
return false;
} else {
throw new Error("Secondary screens only allowed on primary display.")
} }
return false;
} }
viewportChangePos(deltaX, deltaY) { viewportChangePos(deltaX, deltaY) {
@ -340,7 +361,7 @@ export default class Display {
viewportChangeSize(width, height) { viewportChangeSize(width, height) {
if (!this._clipViewport || if ((!this._clipViewport && this._screens.length === 1 ) ||
typeof(width) === "undefined" || typeof(width) === "undefined" ||
typeof(height) === "undefined") { typeof(height) === "undefined") {
@ -398,6 +419,10 @@ export default class Display {
const canvas = this._target; const canvas = this._target;
if (canvas == undefined) { return; } if (canvas == undefined) { return; }
if (this._screens.length > 0) {
width = this._screens[0].serverWidth;
height = this._screens[0].serverHeight;
}
if (canvas.width !== width || canvas.height !== height) { if (canvas.width !== width || canvas.height !== height) {
// We have to save the canvas data since changing the size will clear it // We have to save the canvas data since changing the size will clear it
@ -434,7 +459,8 @@ export default class Display {
this._asyncRenderQPush({ this._asyncRenderQPush({
'type': 'flip', 'type': 'flip',
'frame_id': frame_id, 'frame_id': frame_id,
'rect_cnt': rect_cnt 'rect_cnt': rect_cnt,
'screenLocations': [ { screenIndex: 0, x: 0, y: 0 } ]
}); });
} }
@ -480,15 +506,17 @@ export default class Display {
fillRect(x, y, width, height, color, frame_id, fromQueue) { fillRect(x, y, width, height, color, frame_id, fromQueue) {
if (!fromQueue) { if (!fromQueue) {
this._asyncRenderQPush({ let rect = {
'type': 'fill', type: 'fill',
'x': x, x: x,
'y': y, y: y,
'width': width, width: width,
'height': height, height: height,
'color': color, color: color,
'frame_id': frame_id frame_id: frame_id
}); }
this._processRectScreens(rect);
this._asyncRenderQPush(rect);
} else { } else {
this._setFillColor(color); this._setFillColor(color);
this._targetCtx.fillRect(x, y, width, height); this._targetCtx.fillRect(x, y, width, height);
@ -497,7 +525,7 @@ export default class Display {
copyImage(oldX, oldY, newX, newY, w, h, frame_id, fromQueue) { copyImage(oldX, oldY, newX, newY, w, h, frame_id, fromQueue) {
if (!fromQueue) { if (!fromQueue) {
this._asyncRenderQPush({ let rect = {
'type': 'copy', 'type': 'copy',
'oldX': oldX, 'oldX': oldX,
'oldY': oldY, 'oldY': oldY,
@ -506,7 +534,9 @@ export default class Display {
'width': w, 'width': w,
'height': h, 'height': h,
'frame_id': frame_id 'frame_id': frame_id
}); }
this._processRectScreens(rect);
this._asyncRenderQPush(rect);
} else { } else {
// Due to this bug among others [1] we need to disable the image-smoothing to // Due to this bug among others [1] we need to disable the image-smoothing to
// avoid getting a blur effect when copying data. // avoid getting a blur effect when copying data.
@ -531,18 +561,31 @@ export default class Display {
if ((width === 0) || (height === 0)) { if ((width === 0) || (height === 0)) {
return; return;
} }
const img = new Image();
img.src = "data: " + mime + ";base64," + Base64.encode(arr);
this._asyncRenderQPush({ let rect = {
'type': 'img', 'type': 'img',
'img': img, 'img': null,
'x': x, 'x': x,
'y': y, 'y': y,
'width': width, 'width': width,
'height': height, 'height': height,
'frame_id': frame_id 'frame_id': frame_id
}); }
this._processRectScreens(rect);
if (rect.inPrimary) {
const img = new Image();
img.src = "data: " + mime + ";base64," + Base64.encode(arr);
rect.img = img;
} else {
rect.type = 'img_array'
}
if (rect.inSecondary) {
rect.mime = mime;
rect.arr = arr;
}
this._asyncRenderQPush(rect);
} }
transparentRect(x, y, width, height, img, frame_id) { transparentRect(x, y, width, height, img, frame_id) {
@ -560,12 +603,18 @@ export default class Display {
'height': height, 'height': height,
'frame_id': frame_id 'frame_id': frame_id
} }
this._processRectScreens(rect);
let imageBmpPromise = createImageBitmap(img); if (rect.inPrimary) {
imageBmpPromise.then( function(img) { let imageBmpPromise = createImageBitmap(img);
rect.img = img; imageBmpPromise.then( function(img) {
rect.img.complete = true; rect.img = img;
}.bind(rect) ); rect.img.complete = true;
}.bind(rect) );
}
if (rect.inSecondary) {
rect.arr = img;
}
this._asyncRenderQPush(rect); this._asyncRenderQPush(rect);
} }
@ -577,7 +626,7 @@ export default class Display {
// this probably isn't getting called *nearly* as much // this probably isn't getting called *nearly* as much
const newArr = new Uint8Array(width * height * 4); const newArr = new Uint8Array(width * height * 4);
newArr.set(new Uint8Array(arr.buffer, 0, newArr.length)); newArr.set(new Uint8Array(arr.buffer, 0, newArr.length));
this._asyncRenderQPush({ let rect = {
'type': 'blit', 'type': 'blit',
'data': newArr, 'data': newArr,
'x': x, 'x': x,
@ -585,7 +634,9 @@ export default class Display {
'width': width, 'width': width,
'height': height, 'height': height,
'frame_id': frame_id 'frame_id': frame_id
}); }
this._processRectScreens(rect);
this._asyncRenderQPush(rect);
} else { } else {
// NB(directxman12): arr must be an Type Array view // NB(directxman12): arr must be an Type Array view
let data = new Uint8ClampedArray(arr.buffer, let data = new Uint8ClampedArray(arr.buffer,
@ -598,7 +649,7 @@ export default class Display {
blitQoi(x, y, width, height, arr, offset, frame_id, fromQueue) { blitQoi(x, y, width, height, arr, offset, frame_id, fromQueue) {
if (!fromQueue) { if (!fromQueue) {
this._asyncRenderQPush({ let rect = {
'type': 'blitQ', 'type': 'blitQ',
'data': arr, 'data': arr,
'x': x, 'x': x,
@ -606,7 +657,9 @@ export default class Display {
'width': width, 'width': width,
'height': height, 'height': height,
'frame_id': frame_id 'frame_id': frame_id
}); }
this._processRectScreens(rect);
this._asyncRenderQPush(rect);
} else { } else {
this._targetCtx.putImageData(arr, x, y); this._targetCtx.putImageData(arr, x, y);
} }
@ -647,6 +700,53 @@ export default class Display {
// ===== PRIVATE METHODS ===== // ===== PRIVATE METHODS =====
_handleSecondaryDisplayMessage(event) {
if (!this._isPrimaryDisplay && event.data) {
switch (event.data.eventType) {
case 'rect':
let rect = event.data.rect;
let pos = rect.screenLocations[event.data.screenLocationIndex];
if (!pos) {
console.log('wtf');
}
switch (rect.type) {
case 'copy':
this.copyImage(rect.oldX, rect.oldY, pos.x, pos.y, rect.width, rect.height, rect.frame_id, true);
break;
case 'fill':
this.fillRect(pos.x, pos.y, rect.width, rect.height, rect.color, rect.frame_id, true);
break;
case 'blit':
this.blitImage(pos.x, pos.y, rect.width, rect.height, rect.data, 0, rect.frame_id, true);
break;
case 'blitQ':
this.blitQoi(pos.x, pos.y, rect.width, rect.height, rect.data, 0, rect.frame_id, true);
break;
case 'img':
case 'img_arr':
rect.img = new Image();
rect.img.src = "data: " + rect.mime + ";base64," + Base64.encode(rect.arr);
if (!rect.img.complete) {
rect.img.addEventListener('load', function (rect) {
this.drawImage(rect.img, pos.x, pos.y, rect.width, rect.height);
}.bind(this, rect));
} else {
this.drawImage(rect.img, pos.x, pos.y, rect.width, rect.height);
}
break;
case 'transparent':
let imageBmpPromise = createImageBitmap(rect.arr);
imageBmpPromise.then(function(rect, img) {
this.drawImage(img, pos.x, pos.y, rect.width, rect.height);
}).bind(this, rect);
break;
}
break;
}
}
}
/* /*
Process incoming rects into a frame buffer, assume rects are out of order due to either UDP or parallel processing of decoding Process incoming rects into a frame buffer, assume rects are out of order due to either UDP or parallel processing of decoding
*/ */
@ -772,34 +872,53 @@ export default class Display {
for (let i = 0; i < frame.length; i++) { for (let i = 0; i < frame.length; i++) {
const a = frame[i]; const a = frame[i];
switch (a.type) {
case 'copy': for (let sI = 0; sI < a.screenLocations.length; sI++) {
this.copyImage(a.oldX, a.oldY, a.x, a.y, a.width, a.height, a.frame_id, true); let screenLocation = a.screenLocations[sI];
break; if (screenLocation.screenIndex == 0) {
case 'fill': switch (a.type) {
this.fillRect(a.x, a.y, a.width, a.height, a.color, a.frame_id, true); case 'copy':
break; this.copyImage(screenLocation.oldX, screenLocation.oldY, screenLocation.x, screenLocation.y, a.width, a.height, a.frame_id, true);
case 'blit': break;
this.blitImage(a.x, a.y, a.width, a.height, a.data, 0, a.frame_id, true); case 'fill':
break; this.fillRect(screenLocation.x, screenLocation.y, a.width, a.height, a.color, a.frame_id, true);
case 'blitQ': break;
this.blitQoi(a.x, a.y, a.width, a.height, a.data, 0, a.frame_id, true); case 'blit':
break; this.blitImage(screenLocation.x, screenLocation.y, a.width, a.height, a.data, 0, a.frame_id, true);
case 'img': break;
this.drawImage(a.img, a.x, a.y, a.width, a.height); case 'blitQ':
break; this.blitQoi(screenLocation.x, screenLocation.y, a.width, a.height, a.data, 0, a.frame_id, true);
case 'transparent': break;
transparent_rects.push(a); case 'img':
break; this.drawImage(a.img, screenLocation.x, screenLocation.y, a.width, a.height);
break;
case 'transparent':
transparent_rects.push(a);
break;
}
} else {
if (a.img) {
a.img = null;
}
this._screens[screenLocation.screenIndex].channel.postMessage({ eventType: 'rect', rect: a, screenLocationIndex: sI });
}
} }
} }
//rects with transparency get applied last //rects with transparency get applied last
for (let i = 0; i < transparent_rects.length; i++) { for (let i = 0; i < transparent_rects.length; i++) {
const a = transparent_rects[i]; const a = transparent_rects[i];
let screenIndexes = this._getRectScreenIndexes(a);
if (a.img) { for (let sI = 0; sI < screenLocations.length; sI++) {
this.drawImage(a.img, a.x, a.y, a.width, a.height); let screenLocation = a.screenLocations[sI];
if (sI == 0) {
if (a.img) {
this.drawImage(a.img, a.x, a.y, a.width, a.height);
}
} else {
this._screens[screenLocation.screenIndex].channel.postMessage({ eventType: 'rect', rect: a, screenLocationIndex: sI });
}
} }
} }
@ -823,6 +942,41 @@ export default class Display {
} }
} }
_processRectScreens(rect) {
//find which screen this rect belongs to and adjust its x and y to be relative to the destination
let indexes = [];
rect.inPrimary = false;
rect.inSecondary = false;
for (let i=0; i < this._screens.length; i++) {
let screen = this._screens[i];
if (
((rect.x >= screen.x && rect.x < screen.x + screen.width) &&
(rect.y >= screen.y && rect.y < screen.y + screen.height)) ||
((rect.x+rect.width >= screen.x && rect.x+rect.width < screen.x + screen.width) &&
(rect.y+rect.height >= screen.y && rect.y+rect.height < screen.y + screen.height))
) {
let screenPosition = {
x: 0 - (screen.x - rect.x), //rect.x - screen.x,
y: 0 - (screen.y - rect.y), //rect.y - screen.y,
screenIndex: i
}
if (rect.type === 'copy') {
screenPosition.oldX = 0 - (screen.x - rect.oldX); //rect.oldX - screen.x;
screenPosition.oldY = 0 - (screen.y - rect.oldY); //rect.oldY - screen.y;
}
indexes.push(screenPosition);
if (i == 0) {
rect.inPrimary = true;
} else {
rect.inSecondary = true;
}
}
}
rect.screenLocations = indexes;
}
_rescale(factor) { _rescale(factor) {
this._scale = factor; this._scale = factor;
const vp = this._screens[0]; const vp = this._screens[0];

View File

@ -11,7 +11,6 @@ import KeyTable from "./keysym.js";
import keysyms from "./keysymdef.js"; import keysyms from "./keysymdef.js";
import imekeys from "./imekeys.js"; import imekeys from "./imekeys.js";
import * as browser from "../util/browser.js"; import * as browser from "../util/browser.js";
import UI from '../../app/ui.js';
import { isChromiumBased } from '../util/browser.js'; import { isChromiumBased } from '../util/browser.js';
// //
@ -46,6 +45,7 @@ export default class Keyboard {
this._lastKeyboardInput = null; this._lastKeyboardInput = null;
this._defaultKeyboardInputLen = 100; this._defaultKeyboardInputLen = 100;
this._keyboardInputReset(); this._keyboardInputReset();
this._translateShortcuts = true;
} }
// ===== PUBLIC METHODS ===== // ===== PUBLIC METHODS =====
@ -56,6 +56,9 @@ export default class Keyboard {
this.focus(); this.focus();
} }
get translateShortcuts() { return this._translateShortcuts; }
set translateShortcuts(value) { this._translateShortcuts = value; }
// ===== PRIVATE METHODS ===== // ===== PRIVATE METHODS =====
clearKeysDown(event) { clearKeysDown(event) {
@ -319,7 +322,7 @@ export default class Keyboard {
// Translate MacOs CMD based shortcuts to their CTRL based counterpart // Translate MacOs CMD based shortcuts to their CTRL based counterpart
if ( if (
browser.isMac() && browser.isMac() &&
UI.rfb && UI.rfb.translateShortcuts && this._translateShortcuts &&
code !== "MetaLeft" && code !== "MetaRight" && code !== "MetaLeft" && code !== "MetaRight" &&
e.metaKey && !e.ctrlKey && !e.altKey e.metaKey && !e.ctrlKey && !e.altKey
) { ) {

View File

@ -80,7 +80,7 @@ export default class RFB extends EventTargetMixin {
if (!target) { if (!target) {
throw new Error("Must specify target"); throw new Error("Must specify target");
} }
if (!urlOrChannel) { if (!urlOrChannel && isPrimaryDisplay) {
throw new Error("Must specify URL, WebSocket or RTCDataChannel"); throw new Error("Must specify URL, WebSocket or RTCDataChannel");
} }
@ -219,7 +219,7 @@ export default class RFB extends EventTargetMixin {
this._supportsBroadcastChannel = (typeof BroadcastChannel !== "undefined"); this._supportsBroadcastChannel = (typeof BroadcastChannel !== "undefined");
if (this._supportsBroadcastChannel) { if (this._supportsBroadcastChannel) {
this._controlChannel = new BroadcastChannel("registrationChannel"); this._controlChannel = new BroadcastChannel("registrationChannel");
this._controlChannel.message = this._handleControlMessage.bind(this); this._controlChannel.addEventListener('message', this._handleControlMessage.bind(this));
Log.Debug("Attached to registrationChannel for secondary displays.") Log.Debug("Attached to registrationChannel for secondary displays.")
} }
@ -277,7 +277,7 @@ export default class RFB extends EventTargetMixin {
// NB: nothing that needs explicit teardown should be done // NB: nothing that needs explicit teardown should be done
// before this point, since this can throw an exception // before this point, since this can throw an exception
try { try {
this._display = new Display(this._canvas); this._display = new Display(this._canvas, this._isPrimaryDisplay);
} catch (exc) { } catch (exc) {
Log.Error("Display exception: " + exc); Log.Error("Display exception: " + exc);
throw exc; throw exc;
@ -300,6 +300,10 @@ export default class RFB extends EventTargetMixin {
if (this._isPrimaryDisplay) { if (this._isPrimaryDisplay) {
this._setupWebSocket(); this._setupWebSocket();
} else {
this._updateConnectionState('connecting');
this._registerSecondaryDisplay();
this._updateConnectionState('connected');
} }
Log.Debug("<< RFB.constructor"); Log.Debug("<< RFB.constructor");
@ -327,6 +331,11 @@ export default class RFB extends EventTargetMixin {
// ===== PROPERTIES ===== // ===== PROPERTIES =====
get translateShortcuts() { return this._keyboard.translateShortcuts; }
set translateShortcuts(value) {
this._keyboard.translateShortcuts = value;
}
get pointerLock() { return this._pointerLock; } get pointerLock() { return this._pointerLock; }
set pointerLock(value) { set pointerLock(value) {
if (!this._pointerLock) { if (!this._pointerLock) {
@ -1065,8 +1074,6 @@ export default class RFB extends EventTargetMixin {
} catch (e) { } catch (e) {
this._fail("Error attaching channel (" + e + ")"); this._fail("Error attaching channel (" + e + ")");
} }
} else {
this._registerSecondaryDisplay();
} }
// Make our elements part of the page // Make our elements part of the page
@ -1608,30 +1615,8 @@ export default class RFB extends EventTargetMixin {
{ detail: { capabilities: this._capabilities } })); { detail: { capabilities: this._capabilities } }));
} }
_registerSecondaryDisplay() {
this._primaryDisplayChannel = new BroadcastChannel(`screen_${this._screenID}_channel`);
this._primaryDisplayChannel.message = this._handleSecondaryDisplayMessage.bind(this);
const size = this._screenSize();
message = {
eventType: 'register',
screenID: this._screenID,
screenIndex: this._screenIndex,
width: size.w,
height: size.h,
x: 0,
y: 0,
relativePosition: 0,
pixelRatio: window.devicePixelRatio,
containerWidth: this._screen.offsetWidth,
containerHeight: this._screen.offsetWidth,
channel: null
}
this._controlChannel.postMessage(message)
}
_proxyRFBMessage(messageType, data) { _proxyRFBMessage(messageType, data) {
message = { let message = {
messageType: messageType, messageType: messageType,
data: data data: data
} }
@ -1641,19 +1626,50 @@ export default class RFB extends EventTargetMixin {
_handleControlMessage(event) { _handleControlMessage(event) {
console.log(event); console.log(event);
switch (event.eventType) { if (this._isPrimaryDisplay) {
case 'register': switch (event.data.eventType) {
this._display.addScreen(event.screenID, event.width, event.height, event.relativePosition, event.pixelRatio, event.containerHeight, event.containerWidth); case 'register':
Log.Info(`Secondary monitor (${event.screenID}) has been registered.`); this._display.addScreen(event.data.screenID, event.data.width, event.data.height, event.data.relativePosition, event.data.pixelRatio, event.data.containerHeight, event.data.containerWidth);
break; const size = this._screenSize();
case 'unregister': RFB.messages.setDesktopSize(this._sock, size, this._screenFlags);
if (this._display.removeScreen(event.screenID)) { Log.Info(`Secondary monitor (${event.data.screenID}) has been registered.`);
Log.Info(`Secondary monitor (${event.screenID}) has been removed.`); break;
} else { case 'unregister':
Log.Info(`Secondary monitor (${event.screenID}) not found.`); if (this._display.removeScreen(event.data.screenID)) {
} Log.Info(`Secondary monitor (${event.data.screenID}) has been removed.`);
const size = this._screenSize();
RFB.messages.setDesktopSize(this._sock, size, this._screenFlags);
} else {
Log.Info(`Secondary monitor (${event.data.screenID}) not found.`);
}
}
} }
}
_registerSecondaryDisplay() {
if (!this._isPrimaryDisplay) {
let screen = this._screenSize().screens[0];
this._display.resize(screen.containerWidth, screen.containerWidth);
screen = this._screenSize().screens[0];
let message = {
eventType: 'register',
screenID: screen.screenID,
screenIndex: 1,
width: screen.width,
height: screen.height,
x: 0,
y: 0,
relativePosition: 0,
pixelRatio: screen.pixelRatio,
containerWidth: screen.containerWidth,
containerHeight: screen.containerHeight,
channel: null
}
this._controlChannel.postMessage(message);
}
} }