1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-08 19:26:48 +00:00

refactor(console): one keys menu module for both VNC console pages

The lite and the full console page each carried the same 150 lines that
send key combinations (Ctrl+Alt+Del, Ctrl+Alt+F1..F12, Alt+Tab, ...). They
now import static/js/console_keys.js, which binds the menu to the page's
RFB connection.
This commit is contained in:
catborise 2026-10-26 20:29:46 +03:00
parent f6d4886d1b
commit a0b203749a
3 changed files with 55 additions and 234 deletions

View file

@ -56,84 +56,7 @@
<!-- actual script modules -->
<script type="module" crossorigin="anonymous">
import UI from '{% static "js/novnc/app/ui.js" %}';
import KeyTable from '{% static "js/novnc/core/input/keysym.js" %}';
const fnKeys = [
{ keysym: KeyTable.XK_F1, code: 'F1' },
{ keysym: KeyTable.XK_F2, code: 'F2' },
{ keysym: KeyTable.XK_F3, code: 'F3' },
{ keysym: KeyTable.XK_F4, code: 'F4' },
{ keysym: KeyTable.XK_F5, code: 'F5' },
{ keysym: KeyTable.XK_F6, code: 'F6' },
{ keysym: KeyTable.XK_F7, code: 'F7' },
{ keysym: KeyTable.XK_F8, code: 'F8' },
{ keysym: KeyTable.XK_F9, code: 'F9' },
{ keysym: KeyTable.XK_F10, code: 'F10' },
{ keysym: KeyTable.XK_F11, code: 'F11' },
{ keysym: KeyTable.XK_F12, code: 'F12' }
];
function isConnected() {
return UI.rfb && (!UI.rfb._rfbConnectionState || UI.rfb._rfbConnectionState === 'connected');
}
function sendSingleKey(keysym, code) {
if (!isConnected()) { return false; }
UI.rfb.sendKey(keysym, code, true);
setTimeout(() => {
if (isConnected()) {
UI.rfb.sendKey(keysym, code, false);
}
}, 20);
return false;
}
function sendKeyCombo(keys) {
if (!isConnected()) { return false; }
for (let i = 0; i < keys.length; i++) {
UI.rfb.sendKey(keys[i].keysym, keys[i].code, true);
}
setTimeout(() => {
if (isConnected()) {
for (let i = keys.length - 1; i >= 0; i--) {
UI.rfb.sendKey(keys[i].keysym, keys[i].code, false);
}
}
}, 20);
return false;
}
function sendCtrlAltDel() {
if (isConnected() && typeof UI.rfb.sendCtrlAltDel === 'function') {
UI.rfb.sendCtrlAltDel();
} else {
sendKeyCombo([
{ keysym: KeyTable.XK_Control_L, code: 'ControlLeft' },
{ keysym: KeyTable.XK_Alt_L, code: 'AltLeft' },
{ keysym: KeyTable.XK_Delete, code: 'Delete' }
]);
}
return false;
}
function sendCtrlAltFN(f) {
if (f < 0 || f >= fnKeys.length) { return false; }
return sendKeyCombo([
{ keysym: KeyTable.XK_Control_L, code: 'ControlLeft' },
{ keysym: KeyTable.XK_Alt_L, code: 'AltLeft' },
fnKeys[f]
]);
}
function bindKeyAction(id, handler) {
const el = document.getElementById(id);
if (el) {
el.addEventListener('click', (e) => {
e.preventDefault();
handler();
});
}
}
import { bindKeyAction, bindSendKeys } from '{% static "js/console_keys.js" %}';
bindKeyAction('fullscreen_button', () => {
if (typeof UI.fullscreen === 'function') {
@ -141,40 +64,7 @@
}
});
bindKeyAction('ctrlaltdel', sendCtrlAltDel);
bindKeyAction('ctrlaltbackspace', () => sendKeyCombo([
{ keysym: KeyTable.XK_Control_L, code: 'ControlLeft' },
{ keysym: KeyTable.XK_Alt_L, code: 'AltLeft' },
{ keysym: KeyTable.XK_BackSpace, code: 'Backspace' }
]));
bindKeyAction('alttab', () => sendKeyCombo([
{ keysym: KeyTable.XK_Alt_L, code: 'AltLeft' },
{ keysym: KeyTable.XK_Tab, code: 'Tab' }
]));
bindKeyAction('superkey', () => sendSingleKey(KeyTable.XK_Super_L, 'MetaLeft'));
bindKeyAction('sendesc', () => sendSingleKey(KeyTable.XK_Escape, 'Escape'));
bindKeyAction('sendtab', () => sendSingleKey(KeyTable.XK_Tab, 'Tab'));
bindKeyAction('ctrlc', () => sendKeyCombo([
{ keysym: KeyTable.XK_Control_L, code: 'ControlLeft' },
{ keysym: KeyTable.XK_c, code: 'KeyC' }
]));
bindKeyAction('ctrlz', () => sendKeyCombo([
{ keysym: KeyTable.XK_Control_L, code: 'ControlLeft' },
{ keysym: KeyTable.XK_z, code: 'KeyZ' }
]));
bindKeyAction('ctrld', () => sendKeyCombo([
{ keysym: KeyTable.XK_Control_L, code: 'ControlLeft' },
{ keysym: KeyTable.XK_d, code: 'KeyD' }
]));
bindKeyAction('ctrlbackslash', () => sendKeyCombo([
{ keysym: KeyTable.XK_Control_L, code: 'ControlLeft' },
{ keysym: KeyTable.XK_backslash, code: 'Backslash' }
]));
for (let i = 0; i < 12; i++) {
bindKeyAction('ctrlaltf' + (i + 1), () => sendCtrlAltFN(i));
}
bindSendKeys(() => UI.rfb);
// novncd behind the panel's own address (nginx's /novncd/); a VM with a
// QEMU websocket port is reached on that port of the panel's host name

View file

@ -27,7 +27,7 @@
<script type="module" crossorigin="anonymous">
// RFB holds the API to connect and communicate with a VNC server
import RFB from '{% static "js/novnc/core/rfb.js" %}';
import KeyTable from '{% static "js/novnc/core/input/keysym.js" %}';
import { bindKeyAction, bindSendKeys } from '{% static "js/console_keys.js" %}';
let rfb;
let desktopName;
@ -60,76 +60,6 @@
desktopName = e.detail.name;
}
function isConnected() {
return rfb && (!rfb._rfbConnectionState || rfb._rfbConnectionState === 'connected');
}
function sendSingleKey(keysym, code) {
if (!isConnected()) { return false; }
rfb.sendKey(keysym, code, true);
setTimeout(() => {
if (isConnected()) {
rfb.sendKey(keysym, code, false);
}
}, 20);
return false;
}
function sendKeyCombo(keys) {
if (!isConnected()) { return false; }
for (let i = 0; i < keys.length; i++) {
rfb.sendKey(keys[i].keysym, keys[i].code, true);
}
setTimeout(() => {
if (isConnected()) {
for (let i = keys.length - 1; i >= 0; i--) {
rfb.sendKey(keys[i].keysym, keys[i].code, false);
}
}
}, 20);
return false;
}
// Since most operating systems will catch Ctrl+Alt+Del
// before they get a chance to be intercepted by the browser,
// we provide a way to emulate this key sequence.
function sendCtrlAltDel() {
if (isConnected() && typeof rfb.sendCtrlAltDel === 'function') {
rfb.sendCtrlAltDel();
} else {
sendKeyCombo([
{ keysym: KeyTable.XK_Control_L, code: 'ControlLeft' },
{ keysym: KeyTable.XK_Alt_L, code: 'AltLeft' },
{ keysym: KeyTable.XK_Delete, code: 'Delete' }
]);
}
return false;
}
const fnKeys = [
{ keysym: KeyTable.XK_F1, code: 'F1' },
{ keysym: KeyTable.XK_F2, code: 'F2' },
{ keysym: KeyTable.XK_F3, code: 'F3' },
{ keysym: KeyTable.XK_F4, code: 'F4' },
{ keysym: KeyTable.XK_F5, code: 'F5' },
{ keysym: KeyTable.XK_F6, code: 'F6' },
{ keysym: KeyTable.XK_F7, code: 'F7' },
{ keysym: KeyTable.XK_F8, code: 'F8' },
{ keysym: KeyTable.XK_F9, code: 'F9' },
{ keysym: KeyTable.XK_F10, code: 'F10' },
{ keysym: KeyTable.XK_F11, code: 'F11' },
{ keysym: KeyTable.XK_F12, code: 'F12' }
];
function sendCtrlAltFN(f) {
if (f < 0 || f >= fnKeys.length) { return false; }
return sendKeyCombo([
{ keysym: KeyTable.XK_Control_L, code: 'ControlLeft' },
{ keysym: KeyTable.XK_Alt_L, code: 'AltLeft' },
fnKeys[f]
]);
}
function fullscreen() {
if (document.fullscreenElement || // alternative standard method
document.mozFullScreenElement || // currently working methods
@ -170,16 +100,13 @@
});
function machineShutdown() {
if (isConnected()) { rfb.machineShutdown(); }
return false;
rfb.machineShutdown();
}
function machineReboot() {
if (isConnected()) { rfb.machineReboot(); }
return false;
rfb.machineReboot();
}
function machineReset() {
if (isConnected()) { rfb.machineReset(); }
return false;
rfb.machineReset();
}
// Show a status text in the top bar
@ -187,16 +114,6 @@
document.getElementById('noVNC_status').textContent = text;
}
function bindKeyAction(id, handler) {
const el = document.getElementById(id);
if (el) {
el.addEventListener('click', (e) => {
e.preventDefault();
handler();
});
}
}
// This function extracts the value of one variable from the
// query string. If the variable isn't defined in the URL
// it returns the default value instead.
@ -217,7 +134,6 @@
return defaultValue;
}
bindKeyAction('sendCtrlAltDelButton', sendCtrlAltDel);
bindKeyAction('machineShutdownButton', machineShutdown);
bindKeyAction('machineRebootButton', machineReboot);
bindKeyAction('machineResetButton', machineReset);
@ -230,40 +146,7 @@
link.setAttribute('aria-pressed', rfb.scaleViewport);
});
bindKeyAction('ctrlaltdel', sendCtrlAltDel);
bindKeyAction('ctrlaltbackspace', () => sendKeyCombo([
{ keysym: KeyTable.XK_Control_L, code: 'ControlLeft' },
{ keysym: KeyTable.XK_Alt_L, code: 'AltLeft' },
{ keysym: KeyTable.XK_BackSpace, code: 'Backspace' }
]));
bindKeyAction('alttab', () => sendKeyCombo([
{ keysym: KeyTable.XK_Alt_L, code: 'AltLeft' },
{ keysym: KeyTable.XK_Tab, code: 'Tab' }
]));
bindKeyAction('superkey', () => sendSingleKey(KeyTable.XK_Super_L, 'MetaLeft'));
bindKeyAction('sendesc', () => sendSingleKey(KeyTable.XK_Escape, 'Escape'));
bindKeyAction('sendtab', () => sendSingleKey(KeyTable.XK_Tab, 'Tab'));
bindKeyAction('ctrlc', () => sendKeyCombo([
{ keysym: KeyTable.XK_Control_L, code: 'ControlLeft' },
{ keysym: KeyTable.XK_c, code: 'KeyC' }
]));
bindKeyAction('ctrlz', () => sendKeyCombo([
{ keysym: KeyTable.XK_Control_L, code: 'ControlLeft' },
{ keysym: KeyTable.XK_z, code: 'KeyZ' }
]));
bindKeyAction('ctrld', () => sendKeyCombo([
{ keysym: KeyTable.XK_Control_L, code: 'ControlLeft' },
{ keysym: KeyTable.XK_d, code: 'KeyD' }
]));
bindKeyAction('ctrlbackslash', () => sendKeyCombo([
{ keysym: KeyTable.XK_Control_L, code: 'ControlLeft' },
{ keysym: KeyTable.XK_backslash, code: 'Backslash' }
]));
for (let i = 0; i < 12; i++) {
bindKeyAction('ctrlaltf' + (i + 1), () => sendCtrlAltFN(i));
}
bindSendKeys(() => rfb);
// novncd behind the panel's own address (nginx's /novncd/), never a host
// from the link: the page sends it a console token. A VM with a QEMU

48
static/js/console_keys.js Normal file
View file

@ -0,0 +1,48 @@
// The keys menu of the VNC console pages (lite and full): key combinations
// the browser or the operating system would otherwise catch.
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' };
// press the keys in order, release them in reverse
function sendKeys(rfb, keys) {
if (!rfb || (rfb._rfbConnectionState && rfb._rfbConnectionState !== 'connected')) { return; }
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();
});
}
}
// getRfb: the page's RFB object, which the full page creates on connect
export function bindSendKeys(getRfb) {
const send = (...keys) => () => sendKeys(getRfb(), keys);
const ctrlAltDel = () => {
const rfb = getRfb();
if (rfb && typeof rfb.sendCtrlAltDel === 'function') {
rfb.sendCtrlAltDel();
}
};
bindKeyAction('ctrlaltdel', ctrlAltDel);
bindKeyAction('sendCtrlAltDelButton', ctrlAltDel);
bindKeyAction('ctrlaltbackspace', send(CTRL, ALT, { keysym: KeyTable.XK_BackSpace, code: 'Backspace' }));
bindKeyAction('alttab', send(ALT, { keysym: KeyTable.XK_Tab, code: 'Tab' }));
bindKeyAction('superkey', send({ keysym: KeyTable.XK_Super_L, code: 'MetaLeft' }));
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 }));
}
}