Files
collabvm-1.2.ts/cvmts/src/VNCVM/VNCVM.ts

185 lines
4.5 KiB
TypeScript
Raw Normal View History

import EventEmitter from 'events';
import VNCVMDef from './VNCVMDef';
import VM from '../VM';
import { Size, Rect, VMDisplay } from '../VMDisplay';
2024-06-11 13:46:24 -04:00
import { VncClient } from '@computernewb/nodejs-rfb';
import { BatchRects, VMState } from '@computernewb/superqemu';
import { execaCommand } from 'execa';
import pino from 'pino';
function Clamp(input: number, min: number, max: number) {
return Math.min(Math.max(input, min), max);
}
async function Sleep(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
2024-06-11 13:46:24 -04:00
export default class VNCVM extends EventEmitter implements VM, VMDisplay {
def: VNCVMDef;
logger;
private displayVnc = new VncClient({
debug: false,
fps: 60,
encodings: [VncClient.consts.encodings.raw, VncClient.consts.encodings.pseudoDesktopSize]
});
private vncShouldReconnect: boolean = false;
constructor(def: VNCVMDef) {
super();
this.def = def;
// TODO: Now that we're using an actual structured logger can we please
this.logger = pino({ name: `CVMTS.VNCVM/${this.def.vncHost}:${this.def.vncPort}` });
this.displayVnc.on('connectTimeout', () => {
2024-06-11 13:46:24 -04:00
this.Reconnect();
});
this.displayVnc.on('authError', () => {
this.Reconnect();
});
this.displayVnc.on('disconnect', () => {
this.logger.info('Disconnected');
2024-06-11 13:46:24 -04:00
this.Reconnect();
});
this.displayVnc.on('closed', () => {
this.Reconnect();
});
this.displayVnc.on('firstFrameUpdate', () => {
this.logger.info('Connected');
2024-06-11 13:46:24 -04:00
// apparently this library is this good.
// at least it's better than the two others which exist.
this.displayVnc.changeFps(60);
this.emit('connected');
this.emit('resize', { width: this.displayVnc.clientWidth, height: this.displayVnc.clientHeight });
//this.emit('rect', { x: 0, y: 0, width: this.displayVnc.clientWidth, height: this.displayVnc.clientHeight });
this.emit('frame');
});
this.displayVnc.on('desktopSizeChanged', (size: Size) => {
this.emit('resize', size);
});
let rects: Rect[] = [];
this.displayVnc.on('rectUpdateProcessed', (rect: Rect) => {
rects.push(rect);
});
this.displayVnc.on('frameUpdated', (fb: Buffer) => {
// use the cvmts batcher
let batched = BatchRects(this.Size(), rects);
this.emit('rect', batched);
// unbatched (watch the performace go now)
//for(let rect of rects)
// this.emit('rect', rect);
rects = [];
this.emit('frame');
});
}
2024-06-11 13:46:24 -04:00
async Reset(): Promise<void> {
if (this.def.restoreCmd) await execaCommand(this.def.restoreCmd, { shell: true });
else {
await this.Stop();
await Sleep(1000);
await this.Start();
}
}
2024-06-11 13:46:24 -04:00
private Reconnect() {
if (this.displayVnc.connected) return;
if (!this.vncShouldReconnect) return;
// TODO: this should also give up after a max tries count
// if we fail after max tries, emit a event
this.displayVnc.connect({
host: this.def.vncHost,
port: this.def.vncPort,
path: null
2024-06-11 13:46:24 -04:00
});
}
async Start(): Promise<void> {
this.logger.info('Connecting');
if (this.def.startCmd) await execaCommand(this.def.startCmd, { shell: true });
this.Connect();
}
2024-06-11 13:46:24 -04:00
async Stop(): Promise<void> {
this.logger.info('Disconnecting');
this.Disconnect();
if (this.def.stopCmd) await execaCommand(this.def.stopCmd, { shell: true });
}
2024-06-11 13:46:24 -04:00
async Reboot(): Promise<void> {
if (this.def.rebootCmd) await execaCommand(this.def.rebootCmd, { shell: true });
}
2024-06-11 13:46:24 -04:00
async MonitorCommand(command: string): Promise<any> {
// TODO: This can maybe run a specified command?
return 'This VM does not support monitor commands.';
}
2024-06-11 13:46:24 -04:00
GetDisplay(): VMDisplay {
return this;
}
2024-06-11 13:46:24 -04:00
GetState(): VMState {
// for now!
return VMState.Started;
}
SnapshotsSupported(): boolean {
return true;
}
2024-06-11 13:46:24 -04:00
Connect(): void {
this.vncShouldReconnect = true;
this.Reconnect();
}
2024-06-11 13:46:24 -04:00
Disconnect(): void {
this.vncShouldReconnect = false;
this.displayVnc.disconnect();
}
2024-06-11 13:46:24 -04:00
Connected(): boolean {
return this.displayVnc.connected;
}
2024-06-11 13:46:24 -04:00
Buffer(): Buffer {
return this.displayVnc.fb;
}
2024-06-11 13:46:24 -04:00
Size(): Size {
2024-06-11 13:46:24 -04:00
if (!this.displayVnc.connected)
return {
width: 0,
height: 0
};
return {
width: this.displayVnc.clientWidth,
height: this.displayVnc.clientHeight
};
}
2024-06-11 13:46:24 -04:00
MouseEvent(x: number, y: number, buttons: number): void {
2024-06-11 13:46:24 -04:00
if (this.displayVnc.connected) this.displayVnc.sendPointerEvent(Clamp(x, 0, this.displayVnc.clientWidth), Clamp(y, 0, this.displayVnc.clientHeight), buttons);
}
2024-06-11 13:46:24 -04:00
KeyboardEvent(keysym: number, pressed: boolean): void {
2024-06-11 13:46:24 -04:00
if (this.displayVnc.connected) this.displayVnc.sendKeyEvent(keysym, pressed);
}
}