KASM-5078 Multi monitor display ui
This commit is contained in:
parent
aef462ce62
commit
1873ec0c91
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 576 512"><path fill="#fff" d="M512 48H64c-8.8 0-16 7.2-16 16V256H528V64c0-8.8-7.2-16-16-16zm64 208v48 48c0 35.3-28.7 64-64 64H364.3l8 48H424c13.3 0 24 10.7 24 24s-10.7 24-24 24H352 224 152c-13.3 0-24-10.7-24-24s10.7-24 24-24h51.7l8-48H64c-35.3 0-64-28.7-64-64V304 256 64C0 28.7 28.7 0 64 0H512c35.3 0 64 28.7 64 64V256zM48 304v48c0 8.8 7.2 16 16 16H239.5c.3 0 .6 0 .8 0h95.2c.3 0 .6 0 .8 0H512c8.8 0 16-7.2 16-16V304H48zM252.3 464h71.3l-8-48H260.3l-8 48z"/></svg>
|
After Width: | Height: | Size: 529 B |
|
@ -1191,3 +1191,122 @@ a:link {
|
|||
a:visited {
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* ----------------------------------------
|
||||
* Multi Display Arrangement
|
||||
* ----------------------------------------
|
||||
*/
|
||||
|
||||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
.flex-col {
|
||||
flex-direction: column;
|
||||
}
|
||||
.canvas {
|
||||
background: rgb(225,225,226);
|
||||
background: linear-gradient(0deg, rgba(225,225,226,1) 0%, rgba(237,237,238,1) 100%);
|
||||
}
|
||||
.row {
|
||||
background: #e9e9ea;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #dcdcdd;
|
||||
padding: 6px 15px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.row input {
|
||||
border: 1px solid #dcdcdd;
|
||||
padding: 8px 2px;
|
||||
text-align: right;
|
||||
background-color: #ededee;
|
||||
}
|
||||
|
||||
#noVNC_displays {
|
||||
position: fixed;
|
||||
display: none;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 100;
|
||||
background: rgba(0,0,0,0.7);
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
transition: 0.5s ease-in-out;
|
||||
|
||||
|
||||
padding: 5px;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
line-height: 25px;
|
||||
word-wrap: break-word;
|
||||
|
||||
}
|
||||
#noVNC_displays.noVNC_open {
|
||||
display: flex;
|
||||
transform: translateY(0);
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
#noVNC_displays .canvas {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-radius: 15px;
|
||||
background: #fdfdfd;
|
||||
padding: 15px 20px;
|
||||
box-shadow: 0 0 15px rgba(0,0,0,0.4);
|
||||
position: relative;
|
||||
}
|
||||
#noVNC_displays .canvas-title {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
padding: 0 10px;
|
||||
line-height: 1;
|
||||
line-height: 1.4;
|
||||
}
|
||||
#noVNC_displays .canvas-text {
|
||||
font-size: 12px;
|
||||
padding: 0 10px;
|
||||
opacity: 0.6;
|
||||
line-height: 1.4;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
#noVNC_displays .arrange-buttons {
|
||||
margin-top: 15px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
#noVNC_displays .arrange-buttons button {
|
||||
border: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 4px 7px;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
#noVNC_refreshMonitors {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 25px;
|
||||
}
|
||||
#noVNC_refreshMonitors_icon {
|
||||
transition: all 0.3s;
|
||||
transform-origin: center;
|
||||
}
|
||||
#noVNC_addMonitor {
|
||||
background-color: #2196F3;
|
||||
color: white;
|
||||
}
|
||||
#noVNC_addMonitor svg {
|
||||
margin-right: 5px;
|
||||
}
|
||||
#noVNC_displays .canvas canvas {
|
||||
background: #f7f7f7;
|
||||
border: 1px solid #ececec;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
|
303
app/ui.js
303
app/ui.js
|
@ -68,6 +68,9 @@ const UI = {
|
|||
inhibitReconnect: true,
|
||||
reconnectCallback: null,
|
||||
reconnectPassword: null,
|
||||
monitors: [],
|
||||
selectedMonitor: null,
|
||||
refreshRotation: 0,
|
||||
|
||||
supportsBroadcastChannel: (typeof BroadcastChannel !== "undefined"),
|
||||
|
||||
|
@ -132,7 +135,8 @@ const UI = {
|
|||
UI.addMachineHandlers();
|
||||
UI.addClipboardHandlers();
|
||||
UI.addSettingsHandlers();
|
||||
UI.addMultiMonitorAddHandler();
|
||||
UI.addDisplaysHandler();
|
||||
// UI.addMultiMonitorAddHandler();
|
||||
document.getElementById("noVNC_status")
|
||||
.addEventListener('click', UI.hideStatus);
|
||||
UI.openControlbar();
|
||||
|
@ -574,13 +578,23 @@ const UI = {
|
|||
window.addEventListener('msfullscreenchange', UI.updateFullscreenButton);
|
||||
},
|
||||
|
||||
addMultiMonitorAddHandler() {
|
||||
addDisplaysHandler() {
|
||||
if (UI.supportsBroadcastChannel) {
|
||||
UI.showControlInput("noVNC_addmonitor_button");
|
||||
UI.addClickHandle('noVNC_addmonitor_button', UI.addSecondaryMonitor);
|
||||
UI.showControlInput("noVNC_displays_button");
|
||||
UI.addClickHandle('noVNC_displays_button', UI.openDisplays);
|
||||
UI.addClickHandle('noVNC_close_displays', UI.closeDisplays);
|
||||
UI.addClickHandle('noVNC_addMonitor', UI.addSecondaryMonitor);
|
||||
UI.addClickHandle('noVNC_refreshMonitors', UI.displaysRefresh);
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
/*addMultiMonitorAddHandler() {
|
||||
if (UI.supportsBroadcastChannel) {
|
||||
UI.addClickHandle('noVNC_addmonitor_button', UI.addSecondaryMonitor);
|
||||
}
|
||||
},*/
|
||||
|
||||
/* ------^-------
|
||||
* /EVENT HANDLERS
|
||||
* ==============
|
||||
|
@ -1815,6 +1829,27 @@ const UI = {
|
|||
* /MULTI-MONITOR SUPPORT
|
||||
* ==============*/
|
||||
|
||||
openDisplays() {
|
||||
document.getElementById('noVNC_displays').classList.add("noVNC_open");
|
||||
let screenPlan = UI.rfb.getScreenPlan();
|
||||
UI.initMonitors(screenPlan)
|
||||
UI.displayMonitors()
|
||||
},
|
||||
|
||||
closeDisplays() {
|
||||
document.getElementById('noVNC_displays').classList.remove("noVNC_open");
|
||||
},
|
||||
|
||||
displaysRefresh() {
|
||||
const rotation = UI.refreshRotation + 180;
|
||||
let screenPlan = UI.rfb.getScreenPlan();
|
||||
document.getElementById('noVNC_refreshMonitors_icon').style.transform = "rotate(" + rotation + "deg)"
|
||||
UI.refreshRotation = rotation
|
||||
UI.updateMonitors(screenPlan)
|
||||
UI.recenter()
|
||||
UI.draw()
|
||||
},
|
||||
|
||||
addSecondaryMonitor() {
|
||||
let new_display_path = window.location.pathname.replace(/[^/]*$/, '')
|
||||
let new_display_url = `${window.location.protocol}//${window.location.host}${new_display_path}screen.html`;
|
||||
|
@ -1823,6 +1858,261 @@ const UI = {
|
|||
window.open(new_display_url);
|
||||
},
|
||||
|
||||
initMonitors(screenPlan) {
|
||||
const { scale } = UI.multiMonitorSettings()
|
||||
let monitors = []
|
||||
screenPlan.screens.forEach(screen => {
|
||||
monitors.push({
|
||||
id: screen.screenID,
|
||||
x: screen.x / scale,
|
||||
y: screen.y / scale,
|
||||
w: screen.serverWidth / scale,
|
||||
h: screen.serverHeight / scale,
|
||||
scale: 1,
|
||||
fill: '#eeeeeecc',
|
||||
isDragging: false
|
||||
})
|
||||
})
|
||||
UI.monitors = monitors
|
||||
},
|
||||
|
||||
updateMonitors(screenPlan) {
|
||||
UI.initMonitors(screenPlan)
|
||||
UI.recenter()
|
||||
UI.draw()
|
||||
},
|
||||
|
||||
multiMonitorSettings() {
|
||||
const canvas = document.getElementById("noVNC_multiMonitorWidget")
|
||||
return {
|
||||
canvas,
|
||||
ctx: canvas.getContext("2d"),
|
||||
bb: canvas.getBoundingClientRect(),
|
||||
scale: 12,
|
||||
canvasWidth: 560,
|
||||
canvasHeight: 230,
|
||||
}
|
||||
},
|
||||
|
||||
recenter() {
|
||||
const monitors = UI.monitors
|
||||
UI.removeSpaces()
|
||||
const { startLeft, startTop } = UI.getSizes(monitors)
|
||||
|
||||
for (var i = 0; i < monitors.length; i++) {
|
||||
var m = monitors[i];
|
||||
m.x += startLeft
|
||||
m.y += startTop
|
||||
}
|
||||
},
|
||||
|
||||
removeSpaces() {
|
||||
const monitors = UI.monitors
|
||||
let prev = monitors[0]
|
||||
if (monitors.length > 1) {
|
||||
for (var i = 1; i < monitors.length; i++) {
|
||||
var a = monitors[i];
|
||||
let prevStart = prev.x + prev.w
|
||||
let prevStartTop = prev.y + prev.h
|
||||
if (a.x > prevStart) {
|
||||
a.x = prevStart
|
||||
}
|
||||
if (a.x < prevStart) {
|
||||
if (a.y <= prevStartTop) {
|
||||
a.x = prevStart
|
||||
}
|
||||
}
|
||||
if (a.y > prevStartTop) {
|
||||
if (a.x <= prevStart) {
|
||||
a.y = prevStartTop
|
||||
}
|
||||
}
|
||||
prev = monitors[i]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
rect(ctx, x, y, w, h) {
|
||||
ctx.beginPath();
|
||||
ctx.roundRect(x, y, w, h, 5);
|
||||
ctx.stroke();
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
},
|
||||
|
||||
draw() {
|
||||
const { ctx, canvasWidth, canvasHeight, scale } = UI.multiMonitorSettings()
|
||||
const monitors = UI.monitors
|
||||
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
|
||||
|
||||
ctx.rect(0, 0, canvasWidth, canvasHeight);
|
||||
|
||||
for (var i = 0; i < monitors.length; i++) {
|
||||
var m = monitors[i];
|
||||
ctx.fillStyle = m.fill;
|
||||
ctx.lineWidth = 1;
|
||||
ctx.lineJoin = "round";
|
||||
ctx.strokeStyle = m === UI.selectedMonitor ? "#2196F3" : "#aaa";
|
||||
UI.rect(ctx, m.x, m.y, (m.w / m.scale), (m.h / m.scale));
|
||||
ctx.font = "13px sans-serif";
|
||||
ctx.textAlign = "right";
|
||||
ctx.textBaseline = "top";
|
||||
ctx.fillStyle = "#000";
|
||||
ctx.fillText((i + 1), (m.x + m.w) - 4, m.y + 4);
|
||||
ctx.font = "200 11px sans-serif";
|
||||
ctx.textAlign = "center";
|
||||
ctx.textBaseline = "middle";
|
||||
ctx.fillText(m.w * scale + ' x ' + m.h * scale, m.x + (m.w / 2), m.y + (m.h / 2));
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
getSizes(monitors) {
|
||||
const { canvasWidth, canvasHeight } = UI.multiMonitorSettings()
|
||||
let top = monitors[0].y
|
||||
let left = monitors[0].x
|
||||
let width = monitors[0].w
|
||||
let height = monitors[0].h
|
||||
for (var i = 0; i < monitors.length; i++) {
|
||||
var m = monitors[i];
|
||||
if (m.x < left) {
|
||||
left = m.x
|
||||
}
|
||||
if (m.y < top) {
|
||||
top = m.y
|
||||
}
|
||||
if(m.x + m.w > width) {
|
||||
width = m.x + m.w
|
||||
}
|
||||
if(m.y + m.h > height) {
|
||||
height = m.y + m.h
|
||||
}
|
||||
}
|
||||
const startLeft = ((canvasWidth - width - left) / 2);
|
||||
const startTop = ((canvasHeight - height - top) / 2);
|
||||
|
||||
return { top, left, width, height, startLeft, startTop }
|
||||
},
|
||||
|
||||
|
||||
displayMonitors() {
|
||||
const { canvas, ctx, bb, canvasWidth, canvasHeight, scale } = UI.multiMonitorSettings()
|
||||
let offsetX
|
||||
let offsetY
|
||||
let dragok = false
|
||||
let startX;
|
||||
let startY;
|
||||
|
||||
offsetX = bb.left
|
||||
offsetY = bb.top
|
||||
|
||||
canvas.addEventListener("mousedown", myDown, false);
|
||||
canvas.addEventListener("mouseup", myUp, false);
|
||||
canvas.addEventListener("mousemove", myMove, false);
|
||||
UI.recenter()
|
||||
UI.draw()
|
||||
|
||||
function myDown(e) {
|
||||
let monitors = UI.monitors
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
let mx = parseInt(e.clientX - offsetX);
|
||||
let my = parseInt(e.clientY - offsetY);
|
||||
for (var i = 0; i < monitors.length; i++) {
|
||||
var mon = monitors[i];
|
||||
var monw = mon.w / mon.scale
|
||||
var monh = mon.h / mon.scale
|
||||
// Find the closest rect to drag
|
||||
if (mx > mon.x && mx < mon.x + monw && my > mon.y && my < mon.y + monh) {
|
||||
dragok = true;
|
||||
mon.isDragging = true;
|
||||
UI.selectedMonitor = mon
|
||||
break // get out of the loop rather than dragging multiple
|
||||
}
|
||||
}
|
||||
startX = mx;
|
||||
startY = my;
|
||||
UI.draw()
|
||||
}
|
||||
function myUp(e) {
|
||||
let monitors = UI.monitors
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
// clear all the dragging flags
|
||||
dragok = false;
|
||||
for (var i = 0; i < monitors.length; i++) {
|
||||
monitors[i].isDragging = false;
|
||||
}
|
||||
const screenplan = setScreenPlan()
|
||||
UI.recenter()
|
||||
UI.draw()
|
||||
}
|
||||
function myMove(e) {
|
||||
|
||||
if (dragok) {
|
||||
let monitors = UI.monitors
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
// get the current mouse position
|
||||
var mx = parseInt(e.clientX - offsetX);
|
||||
var my = parseInt(e.clientY - offsetY);
|
||||
|
||||
// calculate the distance the mouse has moved
|
||||
// since the last mousemove
|
||||
var dx = mx - startX;
|
||||
var dy = my - startY;
|
||||
|
||||
// move each rect that isDragging
|
||||
// by the distance the mouse has moved
|
||||
// since the last mousemove
|
||||
for (var i = 0; i < monitors.length; i++) {
|
||||
var m = monitors[i];
|
||||
if (m.isDragging) {
|
||||
m.x += dx;
|
||||
m.y += dy;
|
||||
if (m.x) { // don't move into another monitor
|
||||
// if (m.y )
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// redraw the scene with the new rect positions
|
||||
UI.draw();
|
||||
|
||||
// reset the starting mouse position for the next mousemove
|
||||
startX = mx;
|
||||
startY = my;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function setScreenPlan() {
|
||||
let monitors = UI.monitors
|
||||
const { top, left, width, height } = UI.getSizes(monitors)
|
||||
const screens = []
|
||||
for (var i = 0; i < monitors.length; i++) {
|
||||
var a = monitors[i];
|
||||
screens.push({
|
||||
screenID: a.id,
|
||||
serverHeight: a.h * scale,
|
||||
serverWidth: a.w * scale,
|
||||
x: (a.x - left) * scale,
|
||||
y: (a.y - top) * scale
|
||||
})
|
||||
}
|
||||
const screenPlan = {
|
||||
serverHeight: height * scale,
|
||||
serverWidth: width * scale,
|
||||
screens
|
||||
}
|
||||
UI.rfb.applyScreenPlan(screenPlan);
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
|
||||
|
||||
/* ------^-------
|
||||
|
@ -2478,11 +2768,12 @@ const UI = {
|
|||
let screenPlan = UI.rfb.getScreenPlan();
|
||||
|
||||
// Now make adjustments to the screen plan, this is just an example
|
||||
screenPlan.screens[1].y = 100;
|
||||
// screenPlan.screens[1].y = 0;
|
||||
|
||||
// Finally apply the screen plan
|
||||
|
||||
UI.rfb.applyScreenPlan(screenPlan);
|
||||
console.log(screenPlan);
|
||||
UI.updateMonitors(screenPlan)
|
||||
},
|
||||
|
||||
//Helper to add options to dropdown.
|
||||
|
|
26
vnc.html
26
vnc.html
|
@ -187,10 +187,11 @@
|
|||
|
||||
<!-- Add Second Screen -->
|
||||
<div class="noVNC_button_div noVNC_hidden" >
|
||||
<input type="image" alt="Fullscreen" src="app/images/fullscreen.svg"
|
||||
id="noVNC_addmonitor_button" class="noVNC_button"
|
||||
title="Add Monitor">
|
||||
Add Monitor
|
||||
<input type="image" alt="Fullscreen" src="app/images/desktop-regular.svg"
|
||||
id="noVNC_displays_button" class="noVNC_button"
|
||||
style="margin: 10px 3px;"
|
||||
title="Show Displays">
|
||||
Displays
|
||||
</div>
|
||||
|
||||
<!-- Toggle game mode -->
|
||||
|
@ -545,6 +546,23 @@
|
|||
<!-- Status Dialog -->
|
||||
<div id="noVNC_status"></div>
|
||||
|
||||
<!-- Status Dialog -->
|
||||
<div id="noVNC_displays">
|
||||
<div class="canvas">
|
||||
<div class="canvas-title">Arrange Displays</div>
|
||||
<div class="canvas-text">Drag and drop to arrange displays</div>
|
||||
<div id="noVNC_refreshMonitors"><svg id="noVNC_refreshMonitors_icon" xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 512 512"><path class="fa-primary" d="M105.1 202.6c7.7-21.8 20.2-42.3 37.8-59.8c62.5-62.5 163.8-62.5 226.3 0L386.3 160H336c-17.7 0-32 14.3-32 32s14.3 32 32 32H463.5c0 0 0 0 0 0h.4c17.7 0 32-14.3 32-32V64c0-17.7-14.3-32-32-32s-32 14.3-32 32v51.2L414.4 97.6c-87.5-87.5-229.3-87.5-316.8 0C73.2 122 55.6 150.7 44.8 181.4c-5.9 16.7 2.9 34.9 19.5 40.8s34.9-2.9 40.8-19.5z"/><path class="fa-secondary" d="M80 396.9V448c0 17.7-14.3 32-32 32s-32-14.3-32-32V320c0-17.7 14.3-32 32-32H176c17.7 0 32 14.3 32 32s-14.3 32-32 32H125.6l17.2 17.1c62.5 62.5 163.8 62.5 226.3 0c17.5-17.5 30.1-38 37.8-59.8c5.9-16.7 24.2-25.4 40.8-19.5s25.4 24.2 19.5 40.8c-10.8 30.6-28.4 59.3-52.9 83.8c-87.5 87.5-229.3 87.5-316.7 0L80 396.9z"/></svg></div>
|
||||
<canvas id="noVNC_multiMonitorWidget" class="" width="560" height="230"></canvas>
|
||||
<div class="arrange-buttons">
|
||||
<button id="noVNC_addMonitor">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 448 512"><path fill="currentColor" d="M240 64c0-8.8-7.2-16-16-16s-16 7.2-16 16V240H32c-8.8 0-16 7.2-16 16s7.2 16 16 16H208V448c0 8.8 7.2 16 16 16s16-7.2 16-16V272H416c8.8 0 16-7.2 16-16s-7.2-16-16-16H240V64z"/></svg>
|
||||
Add Monitor
|
||||
</button>
|
||||
<button id="noVNC_close_displays">Done</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Connect button -->
|
||||
<div class="noVNC_center">
|
||||
<div id="noVNC_connect_dlg">
|
||||
|
|
Loading…
Reference in New Issue