KASM-5411 Use windows placement api (#86)
Automatic placement of new displays using the Windows API if available. Control panel for secondary displays.
This commit is contained in:
parent
246dbd4999
commit
4dac080460
|
@ -1371,6 +1371,12 @@ a:visited {
|
||||||
#noVNC_setting_enable_hidpi_option.show {
|
#noVNC_setting_enable_hidpi_option.show {
|
||||||
display: flex!important;
|
display: flex!important;
|
||||||
}
|
}
|
||||||
|
#noVNC_auto_placement_option {
|
||||||
|
display: none!important;
|
||||||
|
}
|
||||||
|
#noVNC_auto_placement_option.show {
|
||||||
|
display: flex!important;
|
||||||
|
}
|
||||||
#noVNC_refreshMonitors {
|
#noVNC_refreshMonitors {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 20px;
|
top: 20px;
|
||||||
|
|
|
@ -0,0 +1,213 @@
|
||||||
|
:root {
|
||||||
|
--text-color: rgb(147, 166, 188);
|
||||||
|
--bg: rgb(33 39 63 / 0.98);
|
||||||
|
--gray-500: 107 114 128;
|
||||||
|
}
|
||||||
|
.light {
|
||||||
|
--text-color: rgb(22, 45, 72);
|
||||||
|
--bg: rgba(234, 235, 240, 0.98);
|
||||||
|
--gray-500: 209 213 219;
|
||||||
|
}
|
||||||
|
#mySidenav {
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
margin-top: auto;
|
||||||
|
margin-bottom: auto;
|
||||||
|
position: fixed;
|
||||||
|
z-index: 999999;
|
||||||
|
transition: 0.5s;
|
||||||
|
border-radius: 0;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 400px;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
transform: translateX(calc(-100% - 30px));
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
#mySidenav * {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
#mySidenav button {
|
||||||
|
border: 0;
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
.sidenavnew {
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
margin-top: auto;
|
||||||
|
margin-bottom: auto;
|
||||||
|
position: fixed;
|
||||||
|
z-index: 999999;
|
||||||
|
background-color: var(--bg);
|
||||||
|
overflow-x: hidden;
|
||||||
|
transition: 0.5s;
|
||||||
|
border-radius: 0;
|
||||||
|
width: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
padding: 30px;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
#mySidenav .sidebar-icons-list .sidebar-icons {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px 12px;
|
||||||
|
background-color: transparent;
|
||||||
|
margin: 0;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-bottom: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
height: auto;
|
||||||
|
gap: 12px;
|
||||||
|
text-align: initial;
|
||||||
|
}
|
||||||
|
#mySidenav.loadin {
|
||||||
|
transform: translateX(-100%);
|
||||||
|
}
|
||||||
|
#mySidenav .innertab {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 15px 5px;
|
||||||
|
}
|
||||||
|
#mySidenav.loadin.show_nav {
|
||||||
|
left: 0;
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
#mySidenav .tab {
|
||||||
|
position: absolute;
|
||||||
|
right: -30px;
|
||||||
|
top: calc(50% - 30px);
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
#menuTab.dragging {
|
||||||
|
padding-top: 5rem/* 80px */;
|
||||||
|
padding-bottom: 5rem/* 80px */;
|
||||||
|
margin-top: -5rem/* -80px */;
|
||||||
|
padding-right: 2rem/* 32px */;
|
||||||
|
margin-right: -2rem/* -32px */;
|
||||||
|
}
|
||||||
|
.sidebar-icon-container {
|
||||||
|
border-radius: 5px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
flex: 0 0 36px;
|
||||||
|
}
|
||||||
|
.text-white {
|
||||||
|
--text-opacity: 1;
|
||||||
|
color: rgb(255 255 255 / var(--text-opacity));
|
||||||
|
}
|
||||||
|
.bg-gray-500 {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(var(--gray-500) / var(--tw-bg-opacity));
|
||||||
|
}
|
||||||
|
svg:not(:root).svg-inline--fa, svg:not(:host).svg-inline--fa {
|
||||||
|
overflow: visible;
|
||||||
|
box-sizing: content-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.svg-inline--fa.fa-lg {
|
||||||
|
vertical-align: -0.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fa-lg {
|
||||||
|
font-size: 1.25em;
|
||||||
|
line-height: 0.05em;
|
||||||
|
vertical-align: -0.075em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.svg-inline--fa {
|
||||||
|
display: var(--fa-display, inline-block);
|
||||||
|
height: 1em;
|
||||||
|
overflow: visible;
|
||||||
|
vertical-align: -0.125em;
|
||||||
|
}
|
||||||
|
.bg-emerald-500 {
|
||||||
|
--bg-opacity: 1;
|
||||||
|
background-color: rgb(16 185 129 / var(--bg-opacity));
|
||||||
|
}
|
||||||
|
.bg-transparent {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
.touch-none {
|
||||||
|
touch-action: none;
|
||||||
|
}
|
||||||
|
.rotate-180 {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
#mySidenav .tabinner {
|
||||||
|
background: var(--bg);
|
||||||
|
width: 30px;
|
||||||
|
}
|
||||||
|
.justify-between {
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
.rounded-r-lg {
|
||||||
|
border-top-right-radius: 0.5rem;
|
||||||
|
border-bottom-right-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
.absolute {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
.overflow-hidden {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.flex-col {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.flex {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.py-1 {
|
||||||
|
padding-top: 0.25rem;
|
||||||
|
padding-bottom: 0.25rem;
|
||||||
|
}
|
||||||
|
.bg-black\/5 {
|
||||||
|
background-color: rgb(0 0 0 / 0.05);
|
||||||
|
}
|
||||||
|
.rounded-tr-lg {
|
||||||
|
border-top-right-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
.justify-center {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.items-center {
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.cursor-move {
|
||||||
|
cursor: move;
|
||||||
|
}
|
||||||
|
.cursor-pointer {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.py-5 {
|
||||||
|
padding-top: 1.25rem;
|
||||||
|
padding-bottom: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-cols-4 {
|
||||||
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
.grid {
|
||||||
|
display: grid;
|
||||||
|
}
|
||||||
|
.font-bold {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-xl {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
line-height: 1.75rem;
|
||||||
|
}
|
||||||
|
.text-xs {
|
||||||
|
font-size: 0.75rem/* 12px */;
|
||||||
|
line-height: 1rem/* 16px */;
|
||||||
|
}
|
84
app/ui.js
84
app/ui.js
|
@ -66,6 +66,7 @@ const UI = {
|
||||||
sortedMonitors: [],
|
sortedMonitors: [],
|
||||||
selectedMonitor: null,
|
selectedMonitor: null,
|
||||||
refreshRotation: 0,
|
refreshRotation: 0,
|
||||||
|
currentDisplay: null,
|
||||||
|
|
||||||
supportsBroadcastChannel: (typeof BroadcastChannel !== "undefined"),
|
supportsBroadcastChannel: (typeof BroadcastChannel !== "undefined"),
|
||||||
|
|
||||||
|
@ -196,6 +197,15 @@ const UI = {
|
||||||
UI.addOption(document.getElementById('noVNC_setting_logging'), llevels[i], llevels[i]);
|
UI.addOption(document.getElementById('noVNC_setting_logging'), llevels[i], llevels[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ('getScreenDetails' in window) {
|
||||||
|
document.getElementById('noVNC_auto_placement_option').classList.add("show");
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialAutoPlacementValue = window.localStorage.getItem('autoPlacement')
|
||||||
|
if (initialAutoPlacementValue === null) {
|
||||||
|
document.getElementById("noVNC_auto_placement").checked = true
|
||||||
|
}
|
||||||
|
|
||||||
// Settings with immediate effects
|
// Settings with immediate effects
|
||||||
UI.initSetting('logging', 'warn');
|
UI.initSetting('logging', 'warn');
|
||||||
UI.updateLogging();
|
UI.updateLogging();
|
||||||
|
@ -507,6 +517,7 @@ const UI = {
|
||||||
UI.addClickHandle('noVNC_settings_button', UI.toggleSettingsPanel);
|
UI.addClickHandle('noVNC_settings_button', UI.toggleSettingsPanel);
|
||||||
|
|
||||||
document.getElementById("noVNC_setting_enable_perf_stats").addEventListener('click', UI.showStats);
|
document.getElementById("noVNC_setting_enable_perf_stats").addEventListener('click', UI.showStats);
|
||||||
|
document.getElementById("noVNC_auto_placement").addEventListener('change', UI.setAutoPlacement);
|
||||||
|
|
||||||
UI.addSettingChangeHandler('encrypt');
|
UI.addSettingChangeHandler('encrypt');
|
||||||
UI.addSettingChangeHandler('resize');
|
UI.addSettingChangeHandler('resize');
|
||||||
|
@ -597,6 +608,14 @@ const UI = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
setAutoPlacement(e) {
|
||||||
|
if (e.target.checked === false) {
|
||||||
|
window.localStorage.setItem('autoPlacement', false)
|
||||||
|
} else {
|
||||||
|
window.localStorage.removeItem('autoPlacement')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
/*addMultiMonitorAddHandler() {
|
/*addMultiMonitorAddHandler() {
|
||||||
if (UI.supportsBroadcastChannel) {
|
if (UI.supportsBroadcastChannel) {
|
||||||
UI.addClickHandle('noVNC_addmonitor_button', UI.addSecondaryMonitor);
|
UI.addClickHandle('noVNC_addmonitor_button', UI.addSecondaryMonitor);
|
||||||
|
@ -1891,10 +1910,53 @@ const UI = {
|
||||||
UI.draw()
|
UI.draw()
|
||||||
},
|
},
|
||||||
|
|
||||||
addSecondaryMonitor() {
|
normalizePlacementValues(details) {
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
increaseCurrentDisplay(details) {
|
||||||
|
const max = details.screens.length
|
||||||
|
const thisIndex = details.screens.findIndex(el => el === details.currentScreen)
|
||||||
|
if (max === 1) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if (UI.currentDisplay === null) {
|
||||||
|
UI.currentDisplay = thisIndex
|
||||||
|
}
|
||||||
|
UI.currentDisplay += 1
|
||||||
|
if (UI.currentDisplay === thisIndex) {
|
||||||
|
UI.currentDisplay += 1
|
||||||
|
}
|
||||||
|
if (UI.currentDisplay >= max) {
|
||||||
|
UI.currentDisplay = 0
|
||||||
|
}
|
||||||
|
return UI.currentDisplay
|
||||||
|
},
|
||||||
|
|
||||||
|
async addSecondaryMonitor() {
|
||||||
let new_display_path = window.location.pathname.replace(/[^/]*$/, '')
|
let new_display_path = window.location.pathname.replace(/[^/]*$/, '')
|
||||||
let new_display_url = `${window.location.protocol}//${window.location.host}${new_display_path}screen.html`;
|
let new_display_url = `${window.location.protocol}//${window.location.host}${new_display_path}screen.html`;
|
||||||
|
|
||||||
|
const auto_placement = document.getElementById('noVNC_auto_placement').checked
|
||||||
|
if (auto_placement && 'getScreenDetails' in window) {
|
||||||
|
let permission = false;
|
||||||
|
try {
|
||||||
|
const { state } = await navigator.permissions.query({ name: 'window-management' });
|
||||||
|
permission = (state === 'granted' || state === 'prompt');
|
||||||
|
if (permission && window.screen.isExtended) {
|
||||||
|
const details = await window.getScreenDetails()
|
||||||
|
const current = UI.increaseCurrentDisplay(details)
|
||||||
|
let screen = details.screens[current]
|
||||||
|
const options = 'left='+screen.availLeft+',top='+screen.availTop+',width='+screen.availWidth+',height='+screen.availHeight+',fullscreen'
|
||||||
|
window.open(new_display_url, '_blank', options);
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e)
|
||||||
|
// Nothing.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Log.Debug(`Opening a secondary display ${new_display_url}`)
|
Log.Debug(`Opening a secondary display ${new_display_url}`)
|
||||||
window.open(new_display_url, '_blank', 'toolbar=0,location=0,menubar=0');
|
window.open(new_display_url, '_blank', 'toolbar=0,location=0,menubar=0');
|
||||||
},
|
},
|
||||||
|
@ -1997,7 +2059,12 @@ const UI = {
|
||||||
|
|
||||||
rect(ctx, x, y, w, h) {
|
rect(ctx, x, y, w, h) {
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.roundRect(x, y, w, h, 5);
|
if (typeof ctx.roundRect !== 'undefined') {
|
||||||
|
ctx.roundRect(x, y, w, h, 5);
|
||||||
|
} else {
|
||||||
|
// fallback for old browsers
|
||||||
|
ctx.rect(x, y, w, h);
|
||||||
|
}
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
ctx.closePath();
|
ctx.closePath();
|
||||||
ctx.fill();
|
ctx.fill();
|
||||||
|
@ -2066,7 +2133,6 @@ const UI = {
|
||||||
for (var i = 0; i < monitors.length; i++) {
|
for (var i = 0; i < monitors.length; i++) {
|
||||||
var monitor = monitors[i];
|
var monitor = monitors[i];
|
||||||
var a = sortedMonitors.find(el => el.id === monitor.id)
|
var a = sortedMonitors.find(el => el.id === monitor.id)
|
||||||
console.log(a)
|
|
||||||
screens.push({
|
screens.push({
|
||||||
screenID: a.id,
|
screenID: a.id,
|
||||||
serverHeight: Math.round(a.h * scale),
|
serverHeight: Math.round(a.h * scale),
|
||||||
|
@ -2080,8 +2146,6 @@ const UI = {
|
||||||
serverWidth: Math.round(width * scale),
|
serverWidth: Math.round(width * scale),
|
||||||
screens
|
screens
|
||||||
}
|
}
|
||||||
console.log('setScreenPlan')
|
|
||||||
console.log(screenPlan)
|
|
||||||
UI.rfb.applyScreenPlan(screenPlan);
|
UI.rfb.applyScreenPlan(screenPlan);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -2839,11 +2903,19 @@ const UI = {
|
||||||
|
|
||||||
screenRegistered(e) {
|
screenRegistered(e) {
|
||||||
console.log('screen registered')
|
console.log('screen registered')
|
||||||
|
|
||||||
// Get the current screen plan
|
// Get the current screen plan
|
||||||
// When a new display is added, it is defaulted to be placed to the far right relative to existing displays and to the top
|
// When a new display is added, it is defaulted to be placed to the far right relative to existing displays and to the top
|
||||||
if (UI.rfb) {
|
if (UI.rfb) {
|
||||||
let screenPlan = UI.rfb.getScreenPlan();
|
let screenPlan = UI.rfb.getScreenPlan();
|
||||||
console.log(screenPlan)
|
if (e && e.detail) {
|
||||||
|
const { left, top, screenID } = e.detail
|
||||||
|
const current = screenPlan.screens.findIndex(el => el.screenID === screenID)
|
||||||
|
if (current) {
|
||||||
|
screenPlan.screens[current].x = left
|
||||||
|
screenPlan.screens[current].y = top
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
UI.updateMonitors(screenPlan)
|
UI.updateMonitors(screenPlan)
|
||||||
UI._identify(UI.monitors)
|
UI._identify(UI.monitors)
|
||||||
|
|
106
app/ui_screen.js
106
app/ui_screen.js
|
@ -12,15 +12,14 @@ const UI = {
|
||||||
screens: [],
|
screens: [],
|
||||||
supportsBroadcastChannel: (typeof BroadcastChannel !== "undefined"),
|
supportsBroadcastChannel: (typeof BroadcastChannel !== "undefined"),
|
||||||
controlChannel: null,
|
controlChannel: null,
|
||||||
|
draggingTab: false,
|
||||||
//Initial Loading of the UI
|
//Initial Loading of the UI
|
||||||
prime() {
|
prime() {
|
||||||
console.log('prime')
|
|
||||||
this.start();
|
this.start();
|
||||||
},
|
},
|
||||||
|
|
||||||
//Render default UI
|
//Render default UI
|
||||||
start() {
|
start() {
|
||||||
console.log('start')
|
|
||||||
window.addEventListener("unload", (e) => {
|
window.addEventListener("unload", (e) => {
|
||||||
if (UI.rfb) {
|
if (UI.rfb) {
|
||||||
UI.disconnect();
|
UI.disconnect();
|
||||||
|
@ -33,10 +32,88 @@ const UI = {
|
||||||
|
|
||||||
UI.addDefaultHandlers();
|
UI.addDefaultHandlers();
|
||||||
UI.updateVisualState('disconnected');
|
UI.updateVisualState('disconnected');
|
||||||
|
|
||||||
|
const webui_mode = window.localStorage.getItem('theme')?.toLowerCase() || 'dark'
|
||||||
|
document.getElementById('screen').classList.add(webui_mode);
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
addDefaultHandlers() {
|
addDefaultHandlers() {
|
||||||
document.getElementById('noVNC_connect_button').addEventListener('click', UI.connect);
|
document.getElementById('noVNC_connect_button').addEventListener('click', UI.connect);
|
||||||
|
// Control panel events
|
||||||
|
document.getElementById('toggleMenu').addEventListener('click', UI.toggleMenu);
|
||||||
|
document.getElementById('closeMenu').addEventListener('click', UI.toggleMenu);
|
||||||
|
document.getElementById('fullscreenTrigger').addEventListener('click', UI.fullscreenTrigger);
|
||||||
|
document.getElementById('menuTab').addEventListener('mousemove', UI.dragTab);
|
||||||
|
document.getElementById('menuTab').addEventListener('mouseup', UI.dragEnd);
|
||||||
|
document.getElementById('menuTab').addEventListener('touchmove', UI.touchDragTab);
|
||||||
|
document.getElementById('dragHandler').addEventListener('mousedown', UI.dragStart);
|
||||||
|
document.getElementById('dragHandler').addEventListener('touchstart', UI.dragStart);
|
||||||
|
document.getElementById('dragHandler').addEventListener('mouseup', UI.dragEnd);
|
||||||
|
document.getElementById('dragHandler').addEventListener('touchend', UI.dragEnd);
|
||||||
|
document.getElementById('menuTab').addEventListener('mouseleave', UI.dragEnd);
|
||||||
|
// End control panel events
|
||||||
|
},
|
||||||
|
|
||||||
|
dragStart(e) {
|
||||||
|
UI.draggingTab = true
|
||||||
|
},
|
||||||
|
dragEnd(e) {
|
||||||
|
document.getElementById('menuTab').classList.remove('dragging')
|
||||||
|
UI.draggingTab = false
|
||||||
|
},
|
||||||
|
|
||||||
|
dragTab(e) {
|
||||||
|
if (UI.draggingTab) {
|
||||||
|
document.getElementById('menuTab').style.top = (e.clientY - 10) + 'px'
|
||||||
|
document.getElementById('menuTab').classList.add('dragging')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
touchDragTab(e) {
|
||||||
|
if (UI.draggingTab) {
|
||||||
|
e.preventDefault()
|
||||||
|
const touch = e.touches[0]
|
||||||
|
document.getElementById('menuTab').style.top = (touch.clientY - 10) + 'px'
|
||||||
|
document.getElementById('menuTab').classList.add('dragging')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
fullscreenTrigger() {
|
||||||
|
if (document.fullscreenElement || // alternative standard method
|
||||||
|
document.mozFullScreenElement || // currently working methods
|
||||||
|
document.webkitFullscreenElement ||
|
||||||
|
document.msFullscreenElement) {
|
||||||
|
if (document.exitFullscreen) {
|
||||||
|
document.exitFullscreen();
|
||||||
|
} else if (document.mozCancelFullScreen) {
|
||||||
|
document.mozCancelFullScreen();
|
||||||
|
} else if (document.webkitExitFullscreen) {
|
||||||
|
document.webkitExitFullscreen();
|
||||||
|
} else if (document.msExitFullscreen) {
|
||||||
|
document.msExitFullscreen();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (document.documentElement.requestFullscreen) {
|
||||||
|
document.documentElement.requestFullscreen();
|
||||||
|
} else if (document.documentElement.mozRequestFullScreen) {
|
||||||
|
document.documentElement.mozRequestFullScreen();
|
||||||
|
} else if (document.documentElement.webkitRequestFullscreen) {
|
||||||
|
document.documentElement.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
|
||||||
|
} else if (document.body.msRequestFullscreen) {
|
||||||
|
document.body.msRequestFullscreen();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
toggleMenu() {
|
||||||
|
document.getElementById('mySidenav').classList.toggle('show_nav')
|
||||||
|
const show = document.getElementById('mySidenav').classList.contains('show_nav')
|
||||||
|
if (show) {
|
||||||
|
document.getElementById('toggleMenuIcon').classList.add('rotate-180')
|
||||||
|
} else {
|
||||||
|
document.getElementById('toggleMenuIcon').classList.remove('rotate-180')
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
getSetting(name, isBool, default_value) {
|
getSetting(name, isBool, default_value) {
|
||||||
|
@ -55,7 +132,16 @@ const UI = {
|
||||||
},
|
},
|
||||||
|
|
||||||
connect() {
|
connect() {
|
||||||
console.log('connect')
|
|
||||||
|
let details = null
|
||||||
|
const initialAutoPlacementValue = window.localStorage.getItem('autoPlacement')
|
||||||
|
if (initialAutoPlacementValue === null) {
|
||||||
|
details = {
|
||||||
|
left: window.screenLeft,
|
||||||
|
top: window.screenTop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
UI.rfb = new RFB(document.getElementById('noVNC_container'),
|
UI.rfb = new RFB(document.getElementById('noVNC_container'),
|
||||||
document.getElementById('noVNC_keyboardinput'),
|
document.getElementById('noVNC_keyboardinput'),
|
||||||
"", //URL
|
"", //URL
|
||||||
|
@ -103,20 +189,17 @@ const UI = {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (UI.supportsBroadcastChannel) {
|
if (UI.supportsBroadcastChannel) {
|
||||||
console.log('add event listener')
|
|
||||||
UI.controlChannel = new BroadcastChannel(UI.rfb.connectionID);
|
UI.controlChannel = new BroadcastChannel(UI.rfb.connectionID);
|
||||||
UI.controlChannel.addEventListener('message', UI.handleControlMessage)
|
UI.controlChannel.addEventListener('message', UI.handleControlMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
//attach this secondary display to the primary display
|
//attach this secondary display to the primary display
|
||||||
if (UI.screenID === null) {
|
if (UI.screenID === null) {
|
||||||
const screen = UI.rfb.attachSecondaryDisplay();
|
const screen = UI.rfb.attachSecondaryDisplay(details);
|
||||||
UI.screenID = screen.screenID
|
UI.screenID = screen.screenID
|
||||||
UI.screen = screen
|
UI.screen = screen
|
||||||
} else {
|
} else {
|
||||||
console.log('else reattach screens')
|
UI.rfb.reattachSecondaryDisplay(UI.screen, details);
|
||||||
console.log(UI.screen)
|
|
||||||
UI.rfb.reattachSecondaryDisplay(UI.screen);
|
|
||||||
}
|
}
|
||||||
document.querySelector('title').textContent = 'Display ' + UI.screenID
|
document.querySelector('title').textContent = 'Display ' + UI.screenID
|
||||||
|
|
||||||
|
@ -171,7 +254,6 @@ const UI = {
|
||||||
document.documentElement.classList.add("noVNC_disconnecting");
|
document.documentElement.classList.add("noVNC_disconnecting");
|
||||||
break;
|
break;
|
||||||
case 'disconnected':
|
case 'disconnected':
|
||||||
console.log('disconnected')
|
|
||||||
document.documentElement.classList.add("noVNC_disconnected");
|
document.documentElement.classList.add("noVNC_disconnected");
|
||||||
if (connect_el.classList.contains("noVNC_hidden")) {
|
if (connect_el.classList.contains("noVNC_hidden")) {
|
||||||
connect_el.classList.remove('noVNC_hidden');
|
connect_el.classList.remove('noVNC_hidden');
|
||||||
|
@ -191,7 +273,6 @@ const UI = {
|
||||||
|
|
||||||
identify(data) {
|
identify(data) {
|
||||||
UI.screens = data.screens
|
UI.screens = data.screens
|
||||||
console.log('identify')
|
|
||||||
const screen = data.screens.find(el => el.id === UI.screenID)
|
const screen = data.screens.find(el => el.id === UI.screenID)
|
||||||
if (screen) {
|
if (screen) {
|
||||||
document.getElementById('noVNC_identify_monitor').innerHTML = screen.num
|
document.getElementById('noVNC_identify_monitor').innerHTML = screen.num
|
||||||
|
@ -274,7 +355,6 @@ const UI = {
|
||||||
if (UI.rfb) {
|
if (UI.rfb) {
|
||||||
UI.rfb.disconnect();
|
UI.rfb.disconnect();
|
||||||
if (UI.supportsBroadcastChannel) {
|
if (UI.supportsBroadcastChannel) {
|
||||||
console.log('remove event listeners')
|
|
||||||
UI.controlChannel.removeEventListener('message', UI.handleControlMessage);
|
UI.controlChannel.removeEventListener('message', UI.handleControlMessage);
|
||||||
UI.rfb.removeEventListener("connect", UI.connectFinished);
|
UI.rfb.removeEventListener("connect", UI.connectFinished);
|
||||||
}
|
}
|
||||||
|
@ -329,5 +409,9 @@ const UI = {
|
||||||
}
|
}
|
||||||
|
|
||||||
UI.prime();
|
UI.prime();
|
||||||
|
const initialAutoPlacementValue = window.localStorage.getItem('autoPlacement')
|
||||||
|
|
||||||
|
if ('getScreenDetails' in window && initialAutoPlacementValue === null) {
|
||||||
|
UI.connect();
|
||||||
|
}
|
||||||
export default UI;
|
export default UI;
|
||||||
|
|
29
core/rfb.js
29
core/rfb.js
|
@ -743,16 +743,16 @@ export default class RFB extends EventTargetMixin {
|
||||||
|
|
||||||
// ===== PUBLIC METHODS =====
|
// ===== PUBLIC METHODS =====
|
||||||
|
|
||||||
attachSecondaryDisplay() {
|
attachSecondaryDisplay(details) {
|
||||||
this._updateConnectionState('connecting');
|
this._updateConnectionState('connecting');
|
||||||
const screen = this._registerSecondaryDisplay();
|
const screen = this._registerSecondaryDisplay(false, details);
|
||||||
this._updateConnectionState('connected');
|
this._updateConnectionState('connected');
|
||||||
return screen
|
return screen
|
||||||
}
|
}
|
||||||
|
|
||||||
reattachSecondaryDisplay(screen) {
|
reattachSecondaryDisplay(screen, details) {
|
||||||
this._updateConnectionState('connecting');
|
this._updateConnectionState('connecting');
|
||||||
this._registerSecondaryDisplay(screen);
|
this._registerSecondaryDisplay(screen, details);
|
||||||
this._updateConnectionState('connected');
|
this._updateConnectionState('connected');
|
||||||
return screen
|
return screen
|
||||||
}
|
}
|
||||||
|
@ -1543,7 +1543,15 @@ export default class RFB extends EventTargetMixin {
|
||||||
size.serverWidth + 'x' + size.serverHeight);
|
size.serverWidth + 'x' + size.serverHeight);
|
||||||
} else if (this._display.screenIndex > 0) {
|
} else if (this._display.screenIndex > 0) {
|
||||||
//re-register the secondary display with new resolution
|
//re-register the secondary display with new resolution
|
||||||
this._registerSecondaryDisplay();
|
let details = null
|
||||||
|
const initialAutoPlacementValue = window.localStorage.getItem('autoPlacement')
|
||||||
|
if (initialAutoPlacementValue === null) {
|
||||||
|
details = {
|
||||||
|
left: window.screenLeft,
|
||||||
|
top: window.screenTop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this._registerSecondaryDisplay(false, details);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._display.screens.length > 1) {
|
if (this._display.screens.length > 1) {
|
||||||
|
@ -1729,12 +1737,16 @@ export default class RFB extends EventTargetMixin {
|
||||||
let coords;
|
let coords;
|
||||||
switch (event.data.eventType) {
|
switch (event.data.eventType) {
|
||||||
case 'register':
|
case 'register':
|
||||||
|
const details = {
|
||||||
|
...event.data.details,
|
||||||
|
screenID: event.data.screenID
|
||||||
|
}
|
||||||
this._display.addScreen(event.data.screenID, event.data.width, event.data.height, event.data.pixelRatio, event.data.containerHeight, event.data.containerWidth);
|
this._display.addScreen(event.data.screenID, event.data.width, event.data.height, event.data.pixelRatio, event.data.containerHeight, event.data.containerWidth);
|
||||||
size = this._screenSize();
|
size = this._screenSize();
|
||||||
RFB.messages.setDesktopSize(this._sock, size, this._screenFlags);
|
RFB.messages.setDesktopSize(this._sock, size, this._screenFlags);
|
||||||
this._sendEncodings();
|
this._sendEncodings();
|
||||||
this._updateContinuousUpdates();
|
this._updateContinuousUpdates();
|
||||||
this.dispatchEvent(new CustomEvent("screenregistered", {}));
|
this.dispatchEvent(new CustomEvent("screenregistered", { detail: details }));
|
||||||
Log.Info(`Secondary monitor (${event.data.screenID}) has been registered.`);
|
Log.Info(`Secondary monitor (${event.data.screenID}) has been registered.`);
|
||||||
break;
|
break;
|
||||||
case 'reattach':
|
case 'reattach':
|
||||||
|
@ -1843,7 +1855,7 @@ export default class RFB extends EventTargetMixin {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_registerSecondaryDisplay(currentScreen = false) {
|
_registerSecondaryDisplay(currentScreen = false, details = null) {
|
||||||
if (!this._isPrimaryDisplay) {
|
if (!this._isPrimaryDisplay) {
|
||||||
//let screen = this._screenSize().screens[0];
|
//let screen = this._screenSize().screens[0];
|
||||||
//
|
//
|
||||||
|
@ -1864,7 +1876,8 @@ export default class RFB extends EventTargetMixin {
|
||||||
pixelRatio: screen.pixelRatio,
|
pixelRatio: screen.pixelRatio,
|
||||||
containerWidth: screen.containerWidth,
|
containerWidth: screen.containerWidth,
|
||||||
containerHeight: screen.containerHeight,
|
containerHeight: screen.containerHeight,
|
||||||
channel: null
|
channel: null,
|
||||||
|
details
|
||||||
}
|
}
|
||||||
this._controlChannel.postMessage(message);
|
this._controlChannel.postMessage(message);
|
||||||
|
|
||||||
|
|
80
screen.html
80
screen.html
|
@ -44,6 +44,7 @@
|
||||||
|
|
||||||
<!-- Stylesheets -->
|
<!-- Stylesheets -->
|
||||||
<link rel="stylesheet" href="app/styles/base.css">
|
<link rel="stylesheet" href="app/styles/base.css">
|
||||||
|
<link rel="stylesheet" href="app/styles/screen.css">
|
||||||
|
|
||||||
<script src="app/error-handler.js"></script>
|
<script src="app/error-handler.js"></script>
|
||||||
|
|
||||||
|
@ -58,7 +59,7 @@
|
||||||
|
|
||||||
<script type="module" crossorigin="use-credentials" src="app/ui_screen.js"></script>
|
<script type="module" crossorigin="use-credentials" src="app/ui_screen.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body id="screen">
|
||||||
<div id="noVNC_fallback_error" class="noVNC_center">
|
<div id="noVNC_fallback_error" class="noVNC_center">
|
||||||
<div>
|
<div>
|
||||||
<div id="noVNC_close_error" onclick="document.getElementById('noVNC_fallback_error').remove()"></div>
|
<div id="noVNC_close_error" onclick="document.getElementById('noVNC_fallback_error').remove()"></div>
|
||||||
|
@ -107,4 +108,81 @@
|
||||||
autocomplete="off" spellcheck="false" tabindex="-1"></textarea>
|
autocomplete="off" spellcheck="false" tabindex="-1"></textarea>
|
||||||
</div>
|
</div>
|
||||||
<div id="noVNC_identify_monitor">0</div>
|
<div id="noVNC_identify_monitor">0</div>
|
||||||
|
|
||||||
|
<div id="mySidenav" class="loadin">
|
||||||
|
<div id="menuTab" class="tab touch-none" style="top: 49px;">
|
||||||
|
<div class="flex flex-col tabinner rounded-r-lg">
|
||||||
|
<div id="dragHandler" class="flex justify-center cursor-move items-center bg-black/5 rounded-tr-lg py-1">
|
||||||
|
<svg aria-hidden="true" focusable="false" data-prefix="fal" data-icon="grip-dots"
|
||||||
|
class="svg-inline--fa fa-grip-dots " role="img" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 448 512">
|
||||||
|
<path fill="currentColor"
|
||||||
|
d="M384 192a16 16 0 1 0 0-32 16 16 0 1 0 0 32zm0-64a48 48 0 1 1 0 96 48 48 0 1 1 0-96zM224 192a16 16 0 1 0 0-32 16 16 0 1 0 0 32zm0-64a48 48 0 1 1 0 96 48 48 0 1 1 0-96zM48 176a16 16 0 1 0 32 0 16 16 0 1 0 -32 0zm64 0a48 48 0 1 1 -96 0 48 48 0 1 1 96 0zM384 352a16 16 0 1 0 0-32 16 16 0 1 0 0 32zm0-64a48 48 0 1 1 0 96 48 48 0 1 1 0-96zM208 336a16 16 0 1 0 32 0 16 16 0 1 0 -32 0zm64 0a48 48 0 1 1 -96 0 48 48 0 1 1 96 0zM64 352a16 16 0 1 0 0-32 16 16 0 1 0 0 32zm0-64a48 48 0 1 1 0 96 48 48 0 1 1 0-96z">
|
||||||
|
</path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div id="toggleMenu" class="cursor-pointer relative rounded-br-lg innertab">
|
||||||
|
<div id="toggleMenuIcon" class="flex absolute">
|
||||||
|
<svg aria-hidden="true" focusable="false" data-prefix="fal" data-icon="chevron-right"
|
||||||
|
class="svg-inline--fa fa-chevron-right " role="img" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 320 512">
|
||||||
|
<path fill="currentColor"
|
||||||
|
d="M299.3 244.7c6.2 6.2 6.2 16.4 0 22.6l-192 192c-6.2 6.2-16.4 6.2-22.6 0s-6.2-16.4 0-22.6L265.4 256 84.7 75.3c-6.2-6.2-6.2-16.4 0-22.6s16.4-6.2 22.6 0l192 192z">
|
||||||
|
</path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="sidenavnew">
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span class="text-xl font-bold">Control Panel</span>
|
||||||
|
<button id="closeMenu" class="bg-transparent">
|
||||||
|
<svg aria-hidden="true" focusable="false" data-prefix="fal" data-icon="xmark"
|
||||||
|
class="svg-inline--fa fa-xmark text-xl" role="img" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 384 512">
|
||||||
|
<path fill="currentColor"
|
||||||
|
d="M324.5 411.1c6.2 6.2 16.4 6.2 22.6 0s6.2-16.4 0-22.6L214.6 256 347.1 123.5c6.2-6.2 6.2-16.4 0-22.6s-16.4-6.2-22.6 0L192 233.4 59.5 100.9c-6.2-6.2-16.4-6.2-22.6 0s-6.2 16.4 0 22.6L169.4 256 36.9 388.5c-6.2 6.2-6.2 16.4 0 22.6s16.4 6.2 22.6 0L192 278.6 324.5 411.1z">
|
||||||
|
</path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="sidebar-icons-list">
|
||||||
|
<div class="grid grid-cols-4 py-5">
|
||||||
|
<button id="fullscreenTrigger" class="sidebar-icons !bg-transparent flex flex-col">
|
||||||
|
<div id="fullscreenBg" class="sidebar-icon-container text-white bg-gray-500"><svg aria-hidden="true" focusable="false"
|
||||||
|
data-prefix="fal" data-icon="expand" class="svg-inline--fa fa-expand fa-lg " role="img"
|
||||||
|
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
|
||||||
|
<path fill="currentColor"
|
||||||
|
d="M144 32c8.8 0 16 7.2 16 16s-7.2 16-16 16H32V176c0 8.8-7.2 16-16 16s-16-7.2-16-16V48c0-8.8 7.2-16 16-16H144zM0 336c0-8.8 7.2-16 16-16s16 7.2 16 16V448H144c8.8 0 16 7.2 16 16s-7.2 16-16 16H16c-8.8 0-16-7.2-16-16V336zM432 32c8.8 0 16 7.2 16 16V176c0 8.8-7.2 16-16 16s-16-7.2-16-16V64H304c-8.8 0-16-7.2-16-16s7.2-16 16-16H432zM416 336c0-8.8 7.2-16 16-16s16 7.2 16 16V464c0 8.8-7.2 16-16 16H304c-8.8 0-16-7.2-16-16s7.2-16 16-16H416V336z">
|
||||||
|
</path>
|
||||||
|
</svg></div>
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<div class="font-bold text-xs">Fullscreen</div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function fullscreenchanged(event) {
|
||||||
|
if (document.fullscreenElement) {
|
||||||
|
if ('keyboard' in navigator) {
|
||||||
|
navigator.keyboard.lock(["Escape"]);
|
||||||
|
}
|
||||||
|
document.getElementById('fullscreenBg').classList.remove('bg-gray-500')
|
||||||
|
document.getElementById('fullscreenBg').classList.add('bg-emerald-500')
|
||||||
|
} else {
|
||||||
|
if ('keyboard' in navigator) {
|
||||||
|
navigator.keyboard.unlock(["Escape"]);
|
||||||
|
}
|
||||||
|
document.getElementById('fullscreenBg').classList.remove('bg-emerald-500')
|
||||||
|
document.getElementById('fullscreenBg').classList.add('bg-gray-500')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.addEventListener("fullscreenchange", fullscreenchanged);
|
||||||
|
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
6
vnc.html
6
vnc.html
|
@ -553,6 +553,12 @@
|
||||||
<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>
|
<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
|
Add Monitor
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<label id="noVNC_auto_placement_option" class="button">
|
||||||
|
<input style="margin: 0 10px 0 3px;" id="noVNC_auto_placement" type="checkbox" />
|
||||||
|
Auto placement
|
||||||
|
</label>
|
||||||
|
|
||||||
<label id="noVNC_setting_enable_hidpi_option" class="button">
|
<label id="noVNC_setting_enable_hidpi_option" class="button">
|
||||||
<input style="margin: 0 10px 0 3px;" id="noVNC_setting_enable_hidpi" type="checkbox" />
|
<input style="margin: 0 10px 0 3px;" id="noVNC_setting_enable_hidpi" type="checkbox" />
|
||||||
Native Resolution
|
Native Resolution
|
||||||
|
|
Loading…
Reference in New Issue