mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-08 11:16:47 +00:00
The VM page offered a lite and a full VNC console. Lite lacked a clipboard, display settings and a way to reconnect; full put noVNC's own side bar under the panel's bar, with a Fullscreen item that called a function noVNC does not have, status messages hidden behind the bar, saved noVNC settings that overrode the Access tab's, connection settings that cannot apply, and a Connect button that reused the page's two-minute token and failed. console-vnc.html and static/js/console_vnc.js replace both, on noVNC's RFB with Bootstrap controls, and keep what the full view did: - Status in the bar (Connecting, Connected, Disconnected, Connection lost, Could not connect, Authentication failed with the server's reason on hover); Disconnect, then Reconnect, which loads the page again for a new token. - The VNC password in a dialog (with a username when the server asks for one), not a browser prompt; without passwordless console it is not in the page. - Send key(s) gains Hold Ctrl / Alt / Windows (noVNC's extra keys). - Clipboard: what the VM copies, sending text to the VM's clipboard, and typing it key by key (new: works in firmware, installers and login prompts; Shift for the characters a US keyboard shifts, 25 ms a key as firmware drops faster keys, verified in a UEFI shell). - View: scaling (none, fit, resize the VM's screen), clip, drag a clipped screen, view only, dot cursor, quality, compression; it starts as Settings > Console says (a touch screen starts fitted). - A Keyboard item on touch devices (noVNC's on-screen keyboard input, key events and Android's text-only input), Fullscreen with keyboard lock, the bell. Dropped with the full view: noVNC's websocket host/port/path, repeater ID and log level (the panel fixes them), automatic reconnect (the token is single use in time), XVP power buttons (QEMU has no XVP) and shared-mode off. The bar is compact (about 45 px in the 850x600 window). The Access tab has one Console button instead of the Lite/Full split and four switches, and it and the VM list open one window per VM (opening it again reloads it).
99 lines
3.9 KiB
JavaScript
99 lines
3.9 KiB
JavaScript
// The keys menu of the VNC console page: key combinations the browser or the
|
|
// operating system would otherwise catch, and modifiers a device lacks.
|
|
import KeyTable from './novnc/core/input/keysym.js';
|
|
|
|
const CTRL = { keysym: KeyTable.XK_Control_L, code: 'ControlLeft' };
|
|
const ALT = { keysym: KeyTable.XK_Alt_L, code: 'AltLeft' };
|
|
const SUPER = { keysym: KeyTable.XK_Super_L, code: 'MetaLeft' };
|
|
const HOLDS = [['holdctrl', CTRL], ['holdalt', ALT], ['holdsuper', SUPER]];
|
|
|
|
function hold(rfb, id, key, down) {
|
|
const item = document.getElementById(id);
|
|
rfb.sendKey(key.keysym, key.code, down);
|
|
item.setAttribute('aria-pressed', down);
|
|
item.classList.toggle('active', down);
|
|
}
|
|
|
|
// the held modifiers up: before a combination from the menu (which presses
|
|
// and releases its own), and before the page stops sending keys
|
|
export function releaseHeld(rfb) {
|
|
for (const [id, key] of HOLDS) {
|
|
if (document.getElementById(id).getAttribute('aria-pressed') === 'true') {
|
|
hold(rfb, id, key, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
// press the keys in order, release them in reverse
|
|
function sendKeys(rfb, keys) {
|
|
releaseHeld(rfb);
|
|
keys.forEach((key) => rfb.sendKey(key.keysym, key.code, true));
|
|
setTimeout(() => [...keys].reverse().forEach((key) => rfb.sendKey(key.keysym, key.code, false)), 20);
|
|
}
|
|
|
|
export function bindKeyAction(id, handler) {
|
|
const el = document.getElementById(id);
|
|
if (el) {
|
|
el.addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
handler();
|
|
});
|
|
}
|
|
}
|
|
|
|
export function bindSendKeys(rfb) {
|
|
const send = (...keys) => () => sendKeys(rfb, keys);
|
|
bindKeyAction('ctrlaltdel', () => {
|
|
releaseHeld(rfb);
|
|
rfb.sendCtrlAltDel();
|
|
});
|
|
bindKeyAction('ctrlaltbackspace', send(CTRL, ALT, { keysym: KeyTable.XK_BackSpace, code: 'Backspace' }));
|
|
bindKeyAction('alttab', send(ALT, { keysym: KeyTable.XK_Tab, code: 'Tab' }));
|
|
bindKeyAction('superkey', send(SUPER));
|
|
bindKeyAction('sendesc', send({ keysym: KeyTable.XK_Escape, code: 'Escape' }));
|
|
bindKeyAction('sendtab', send({ keysym: KeyTable.XK_Tab, code: 'Tab' }));
|
|
bindKeyAction('ctrlc', send(CTRL, { keysym: KeyTable.XK_c, code: 'KeyC' }));
|
|
bindKeyAction('ctrlz', send(CTRL, { keysym: KeyTable.XK_z, code: 'KeyZ' }));
|
|
bindKeyAction('ctrld', send(CTRL, { keysym: KeyTable.XK_d, code: 'KeyD' }));
|
|
bindKeyAction('ctrlbackslash', send(CTRL, { keysym: KeyTable.XK_backslash, code: 'Backslash' }));
|
|
for (let i = 1; i <= 12; i++) {
|
|
bindKeyAction('ctrlaltf' + i, send(CTRL, ALT, { keysym: KeyTable['XK_F' + i], code: 'F' + i }));
|
|
}
|
|
// a held modifier stays down until it is chosen again: with the next key
|
|
// typed it makes a combination the menu does not list
|
|
for (const [id, key] of HOLDS) {
|
|
bindKeyAction(id, () => {
|
|
// a view-only page sends no keys: nothing to hold
|
|
if (!rfb.viewOnly) {
|
|
hold(rfb, id, key, document.getElementById(id).getAttribute('aria-pressed') !== 'true');
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
// the Fullscreen item; in full screen the page also takes the keys the
|
|
// browser would keep (Alt, Tab, Escape) where the browser lets it
|
|
export function bindFullscreen() {
|
|
const item = document.getElementById('fullscreen_button');
|
|
if (!document.fullscreenEnabled) {
|
|
item.hidden = true;
|
|
return;
|
|
}
|
|
bindKeyAction('fullscreen_button', () => {
|
|
if (document.fullscreenElement) {
|
|
document.exitFullscreen();
|
|
} else {
|
|
document.documentElement.requestFullscreen().catch(() => {});
|
|
}
|
|
});
|
|
document.addEventListener('fullscreenchange', () => {
|
|
if (!navigator.keyboard) {
|
|
return;
|
|
}
|
|
if (document.fullscreenElement) {
|
|
navigator.keyboard.lock(['AltLeft', 'AltRight', 'Tab', 'Escape']).catch(() => {});
|
|
} else {
|
|
navigator.keyboard.unlock();
|
|
}
|
|
});
|
|
}
|