WIP - computer acting odd, committing code
This commit is contained in:
parent
419a3a70e9
commit
96001175a6
204
app/ui_screen.js
204
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();
|
||||
|
|
318
core/display.js
318
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<this._screens.length; i++) {
|
||||
|
@ -218,6 +227,9 @@ export default class Display {
|
|||
if (this._screens.length > 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()
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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) {
|
||||
for (let i=1; i<this._screens.length; i++) {
|
||||
if (this._screens[i].screenID == screenID) {
|
||||
this._screens.splice(i, 1);
|
||||
return true;
|
||||
if (this.isPrimaryDisplay) {
|
||||
for (let i=1; i<this._screens.length; i++) {
|
||||
if (this._screens[i].screenID == screenID) {
|
||||
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) {
|
||||
|
@ -340,7 +361,7 @@ export default class Display {
|
|||
|
||||
viewportChangeSize(width, height) {
|
||||
|
||||
if (!this._clipViewport ||
|
||||
if ((!this._clipViewport && this._screens.length === 1 ) ||
|
||||
typeof(width) === "undefined" ||
|
||||
typeof(height) === "undefined") {
|
||||
|
||||
|
@ -398,6 +419,10 @@ export default class Display {
|
|||
|
||||
const canvas = this._target;
|
||||
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) {
|
||||
|
||||
// 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];
|
||||
let screenIndexes = this._getRectScreenIndexes(a);
|
||||
|
||||
if (a.img) {
|
||||
this.drawImage(a.img, a.x, a.y, a.width, a.height);
|
||||
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];
|
||||
|
|
|
@ -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
|
||||
) {
|
||||
|
|
94
core/rfb.js
94
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue