From 96001175a6a1f49550adfef7d0b5ed185e7d71a8 Mon Sep 17 00:00:00 2001 From: mattmcclaskey Date: Tue, 12 Sep 2023 14:16:19 -0400 Subject: [PATCH] WIP - computer acting odd, committing code --- app/ui_screen.js | 206 +++++++++++++++++++++++++- core/display.js | 322 ++++++++++++++++++++++++++++++----------- core/input/keyboard.js | 7 +- core/rfb.js | 94 +++++++----- 4 files changed, 497 insertions(+), 132 deletions(-) diff --git a/app/ui_screen.js b/app/ui_screen.js index 9ba4ff9a..b772e39f 100644 --- a/app/ui_screen.js +++ b/app/ui_screen.js @@ -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 = { connected: false, //Initial Loading of the UI prime() { - + this.start(); }, //Render default UI @@ -20,19 +22,209 @@ const UI = { UI.addDefaultHandlers(); + UI.updateVisualState('disconnected'); }, 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() { - + 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() { - } + }, + + 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(); diff --git a/core/display.js b/core/display.js index 8b9c43a6..fa003a66 100644 --- a/core/display.js +++ b/core/display.js @@ -14,7 +14,7 @@ import { isWindows } from './util/browser.js'; import { uuidv4 } from './util/strings.js' export default class Display { - constructor(target) { + constructor(target, isPrimaryDisplay) { Log.Debug(">> Display.constructor"); /* @@ -85,8 +85,10 @@ export default class Display { this._clipViewport = false; this._antiAliasing = 0; this._fps = 0; + this._isPrimaryDisplay = isPrimaryDisplay; + this._screenID = uuidv4(); this._screens = [{ - screenID: uuidv4(), + screenID: this._screenID, screenIndex: 0, width: this._target.width, //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 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"); } @@ -166,6 +173,8 @@ export default class Display { //recalculate primary display container size this._screens[0].containerHeight = this._target.parentNode.offsetHeight; 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 for (let i=0; i 1) { const primary_screen = this._screens[0]; const secondary_screen = this._screens[1]; + let total_width = 0; + let total_height = 0; + switch (this._screens[1].relativePosition) { case 0: //primary screen is to left @@ -257,50 +269,59 @@ export default class Display { } } - data.screens = structuredClone(this._screens); + data.screens = this._screens; return data; } addScreen(screenID, width, height, relativePosition, pixelRatio, containerHeight, containerWidth) { - //currently only support one secondary screen - if (this._screens.length > 1) { - this._screens[1].channel.close(); - this._screens.pop() - } - screenIdx = this.screens.length; - new_screen = { - screenID: screenID, - screenIndex: screenIdx, - width: width, //client - height: height, //client - serverWidth: 0, //calculated - serverHeight: 0, //calculated - x: 0, - y: 0, - relativePosition: relativePosition, - pixelRatio: pixelRatio, - containerHeight: containerHeight, - containerWidth: containerWidth, - channel: null, - scale: 0 - } + if (this._isPrimaryDisplay) { + //currently only support one secondary screen + if (this._screens.length > 1) { + this._screens[1].channel.close(); + this._screens.pop() + } - new_screen.channel = new BroadcastChannel(`screen_${screenID}_channel`); - //new_screen.channel.message = this._handleSecondaryDisplayMessage().bind(this); + var new_screen = { + screenID: screenID, + screenIndex: this.screens.length, + width: width, //client + height: height, //client + serverWidth: 0, //calculated + serverHeight: 0, //calculated + x: 0, + y: 0, + relativePosition: relativePosition, + pixelRatio: pixelRatio, + containerHeight: containerHeight, + containerWidth: containerWidth, + channel: null, + scale: 0 + } - this._screens.push(new_screen); - new_screen.channel.postMessage({ eventType: "registered", screenIndex: screenIdx }); + 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.") + } + } removeScreen(screenID) { - for (let i=1; i 0) { + width = this._screens[0].serverWidth; + height = this._screens[0].serverHeight; + } if (canvas.width !== width || canvas.height !== height) { // We have to save the canvas data since changing the size will clear it @@ -434,7 +459,8 @@ export default class Display { this._asyncRenderQPush({ 'type': 'flip', '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) { if (!fromQueue) { - this._asyncRenderQPush({ - 'type': 'fill', - 'x': x, - 'y': y, - 'width': width, - 'height': height, - 'color': color, - 'frame_id': frame_id - }); + let rect = { + type: 'fill', + x: x, + y: y, + width: width, + height: height, + color: color, + frame_id: frame_id + } + this._processRectScreens(rect); + this._asyncRenderQPush(rect); } else { this._setFillColor(color); 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) { if (!fromQueue) { - this._asyncRenderQPush({ + let rect = { 'type': 'copy', 'oldX': oldX, 'oldY': oldY, @@ -506,7 +534,9 @@ export default class Display { 'width': w, 'height': h, 'frame_id': frame_id - }); + } + this._processRectScreens(rect); + this._asyncRenderQPush(rect); } else { // Due to this bug among others [1] we need to disable the image-smoothing to // avoid getting a blur effect when copying data. @@ -531,18 +561,31 @@ export default class Display { if ((width === 0) || (height === 0)) { return; } - const img = new Image(); - img.src = "data: " + mime + ";base64," + Base64.encode(arr); - - this._asyncRenderQPush({ + + let rect = { 'type': 'img', - 'img': img, + 'img': null, 'x': x, 'y': y, 'width': width, 'height': height, '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) { @@ -560,12 +603,18 @@ export default class Display { 'height': height, 'frame_id': frame_id } + this._processRectScreens(rect); - let imageBmpPromise = createImageBitmap(img); - imageBmpPromise.then( function(img) { - rect.img = img; - rect.img.complete = true; - }.bind(rect) ); + if (rect.inPrimary) { + let imageBmpPromise = createImageBitmap(img); + imageBmpPromise.then( function(img) { + rect.img = img; + rect.img.complete = true; + }.bind(rect) ); + } + if (rect.inSecondary) { + rect.arr = img; + } this._asyncRenderQPush(rect); } @@ -577,7 +626,7 @@ export default class Display { // this probably isn't getting called *nearly* as much const newArr = new Uint8Array(width * height * 4); newArr.set(new Uint8Array(arr.buffer, 0, newArr.length)); - this._asyncRenderQPush({ + let rect = { 'type': 'blit', 'data': newArr, 'x': x, @@ -585,7 +634,9 @@ export default class Display { 'width': width, 'height': height, 'frame_id': frame_id - }); + } + this._processRectScreens(rect); + this._asyncRenderQPush(rect); } else { // NB(directxman12): arr must be an Type Array view let data = new Uint8ClampedArray(arr.buffer, @@ -598,7 +649,7 @@ export default class Display { blitQoi(x, y, width, height, arr, offset, frame_id, fromQueue) { if (!fromQueue) { - this._asyncRenderQPush({ + let rect = { 'type': 'blitQ', 'data': arr, 'x': x, @@ -606,7 +657,9 @@ export default class Display { 'width': width, 'height': height, 'frame_id': frame_id - }); + } + this._processRectScreens(rect); + this._asyncRenderQPush(rect); } else { this._targetCtx.putImageData(arr, x, y); } @@ -647,6 +700,53 @@ export default class Display { // ===== 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 */ @@ -772,34 +872,53 @@ export default class Display { for (let i = 0; i < frame.length; i++) { const a = frame[i]; - switch (a.type) { - case 'copy': - this.copyImage(a.oldX, a.oldY, a.x, a.y, a.width, a.height, a.frame_id, true); - break; - case 'fill': - this.fillRect(a.x, a.y, a.width, a.height, a.color, a.frame_id, true); - break; - case 'blit': - this.blitImage(a.x, a.y, a.width, a.height, a.data, 0, a.frame_id, true); - break; - case 'blitQ': - this.blitQoi(a.x, a.y, a.width, a.height, a.data, 0, a.frame_id, true); - break; - case 'img': - this.drawImage(a.img, a.x, a.y, a.width, a.height); - break; - case 'transparent': - transparent_rects.push(a); - break; + + for (let sI = 0; sI < a.screenLocations.length; sI++) { + let screenLocation = a.screenLocations[sI]; + if (screenLocation.screenIndex == 0) { + switch (a.type) { + case 'copy': + this.copyImage(screenLocation.oldX, screenLocation.oldY, screenLocation.x, screenLocation.y, a.width, a.height, a.frame_id, true); + break; + case 'fill': + this.fillRect(screenLocation.x, screenLocation.y, a.width, a.height, a.color, a.frame_id, true); + break; + case 'blit': + this.blitImage(screenLocation.x, screenLocation.y, a.width, a.height, a.data, 0, a.frame_id, true); + break; + case 'blitQ': + this.blitQoi(screenLocation.x, screenLocation.y, a.width, a.height, a.data, 0, a.frame_id, true); + break; + case 'img': + 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 for (let i = 0; i < transparent_rects.length; i++) { const a = transparent_rects[i]; - - if (a.img) { - this.drawImage(a.img, a.x, a.y, a.width, a.height); + let screenIndexes = this._getRectScreenIndexes(a); + + for (let sI = 0; sI < screenLocations.length; sI++) { + 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) { this._scale = factor; const vp = this._screens[0]; diff --git a/core/input/keyboard.js b/core/input/keyboard.js index db3c97ab..169c59b6 100644 --- a/core/input/keyboard.js +++ b/core/input/keyboard.js @@ -11,7 +11,6 @@ import KeyTable from "./keysym.js"; import keysyms from "./keysymdef.js"; import imekeys from "./imekeys.js"; import * as browser from "../util/browser.js"; -import UI from '../../app/ui.js'; import { isChromiumBased } from '../util/browser.js'; // @@ -46,6 +45,7 @@ export default class Keyboard { this._lastKeyboardInput = null; this._defaultKeyboardInputLen = 100; this._keyboardInputReset(); + this._translateShortcuts = true; } // ===== PUBLIC METHODS ===== @@ -56,6 +56,9 @@ export default class Keyboard { this.focus(); } + get translateShortcuts() { return this._translateShortcuts; } + set translateShortcuts(value) { this._translateShortcuts = value; } + // ===== PRIVATE METHODS ===== clearKeysDown(event) { @@ -319,7 +322,7 @@ export default class Keyboard { // Translate MacOs CMD based shortcuts to their CTRL based counterpart if ( browser.isMac() && - UI.rfb && UI.rfb.translateShortcuts && + this._translateShortcuts && code !== "MetaLeft" && code !== "MetaRight" && e.metaKey && !e.ctrlKey && !e.altKey ) { diff --git a/core/rfb.js b/core/rfb.js index bb6813a6..24c50818 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -80,7 +80,7 @@ export default class RFB extends EventTargetMixin { if (!target) { throw new Error("Must specify target"); } - if (!urlOrChannel) { + if (!urlOrChannel && isPrimaryDisplay) { throw new Error("Must specify URL, WebSocket or RTCDataChannel"); } @@ -219,7 +219,7 @@ export default class RFB extends EventTargetMixin { this._supportsBroadcastChannel = (typeof BroadcastChannel !== "undefined"); if (this._supportsBroadcastChannel) { 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.") } @@ -277,7 +277,7 @@ export default class RFB extends EventTargetMixin { // NB: nothing that needs explicit teardown should be done // before this point, since this can throw an exception try { - this._display = new Display(this._canvas); + this._display = new Display(this._canvas, this._isPrimaryDisplay); } catch (exc) { Log.Error("Display exception: " + exc); throw exc; @@ -300,6 +300,10 @@ export default class RFB extends EventTargetMixin { if (this._isPrimaryDisplay) { this._setupWebSocket(); + } else { + this._updateConnectionState('connecting'); + this._registerSecondaryDisplay(); + this._updateConnectionState('connected'); } Log.Debug("<< RFB.constructor"); @@ -327,6 +331,11 @@ export default class RFB extends EventTargetMixin { // ===== PROPERTIES ===== + get translateShortcuts() { return this._keyboard.translateShortcuts; } + set translateShortcuts(value) { + this._keyboard.translateShortcuts = value; + } + get pointerLock() { return this._pointerLock; } set pointerLock(value) { if (!this._pointerLock) { @@ -1065,8 +1074,6 @@ export default class RFB extends EventTargetMixin { } catch (e) { this._fail("Error attaching channel (" + e + ")"); } - } else { - this._registerSecondaryDisplay(); } // Make our elements part of the page @@ -1608,30 +1615,8 @@ export default class RFB extends EventTargetMixin { { 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) { - message = { + let message = { messageType: messageType, data: data } @@ -1641,19 +1626,50 @@ export default class RFB extends EventTargetMixin { _handleControlMessage(event) { console.log(event); - switch (event.eventType) { - case 'register': - this._display.addScreen(event.screenID, event.width, event.height, event.relativePosition, event.pixelRatio, event.containerHeight, event.containerWidth); - Log.Info(`Secondary monitor (${event.screenID}) has been registered.`); - break; - case 'unregister': - if (this._display.removeScreen(event.screenID)) { - Log.Info(`Secondary monitor (${event.screenID}) has been removed.`); - } else { - Log.Info(`Secondary monitor (${event.screenID}) not found.`); - } + if (this._isPrimaryDisplay) { + switch (event.data.eventType) { + case 'register': + this._display.addScreen(event.data.screenID, event.data.width, event.data.height, event.data.relativePosition, event.data.pixelRatio, event.data.containerHeight, event.data.containerWidth); + const size = this._screenSize(); + RFB.messages.setDesktopSize(this._sock, size, this._screenFlags); + Log.Info(`Secondary monitor (${event.data.screenID}) has been registered.`); + break; + case 'unregister': + 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); + } }