Additionally make thumbnail encoding threadpooled as well, just so it (probably) doesn't block as much.
148 lines
3.3 KiB
TypeScript
148 lines
3.3 KiB
TypeScript
import { VncClient } from '@computernewb/nodejs-rfb';
|
|
import { EventEmitter } from 'node:events';
|
|
import { BatchRects } from './QemuUtil.js';
|
|
import { Size, Rect, Clamp } from '@cvmts/shared';
|
|
|
|
const kQemuFps = 60;
|
|
|
|
export type VncRect = {
|
|
x: number;
|
|
y: number;
|
|
width: number;
|
|
height: number;
|
|
};
|
|
|
|
// events:
|
|
//
|
|
// 'resize' -> (w, h) -> done when resize occurs
|
|
// 'rect' -> (x, y, ImageData) -> framebuffer
|
|
// 'frame' -> () -> done at end of frame
|
|
|
|
export class QemuDisplay extends EventEmitter {
|
|
private displayVnc = new VncClient({
|
|
debug: false,
|
|
fps: kQemuFps,
|
|
|
|
encodings: [
|
|
VncClient.consts.encodings.raw,
|
|
|
|
//VncClient.consts.encodings.pseudoQemuAudio,
|
|
VncClient.consts.encodings.pseudoDesktopSize
|
|
// For now?
|
|
//VncClient.consts.encodings.pseudoCursor
|
|
]
|
|
});
|
|
|
|
private vncShouldReconnect: boolean = false;
|
|
private vncSocketPath: string;
|
|
|
|
constructor(socketPath: string) {
|
|
super();
|
|
|
|
this.vncSocketPath = socketPath;
|
|
|
|
this.displayVnc.on('connectTimeout', () => {
|
|
this.Reconnect();
|
|
});
|
|
|
|
this.displayVnc.on('authError', () => {
|
|
this.Reconnect();
|
|
});
|
|
|
|
this.displayVnc.on('disconnect', () => {
|
|
this.Reconnect();
|
|
});
|
|
|
|
this.displayVnc.on('closed', () => {
|
|
this.Reconnect();
|
|
});
|
|
|
|
this.displayVnc.on('firstFrameUpdate', () => {
|
|
// apparently this library is this good.
|
|
// at least it's better than the two others which exist.
|
|
this.displayVnc.changeFps(kQemuFps);
|
|
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');
|
|
});
|
|
}
|
|
|
|
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({
|
|
path: this.vncSocketPath
|
|
});
|
|
}
|
|
|
|
Connect() {
|
|
this.vncShouldReconnect = true;
|
|
this.Reconnect();
|
|
}
|
|
|
|
Disconnect() {
|
|
this.vncShouldReconnect = false;
|
|
this.displayVnc.disconnect();
|
|
}
|
|
|
|
Connected() {
|
|
return this.displayVnc.connected;
|
|
}
|
|
|
|
Buffer(): Buffer {
|
|
return this.displayVnc.fb;
|
|
}
|
|
|
|
Size(): Size {
|
|
if (!this.displayVnc.connected)
|
|
return {
|
|
width: 0,
|
|
height: 0
|
|
};
|
|
|
|
return {
|
|
width: this.displayVnc.clientWidth,
|
|
height: this.displayVnc.clientHeight
|
|
};
|
|
}
|
|
|
|
MouseEvent(x: number, y: number, buttons: number) {
|
|
if (this.displayVnc.connected) this.displayVnc.sendPointerEvent(Clamp(x, 0, this.displayVnc.clientWidth), Clamp(y, 0, this.displayVnc.clientHeight), buttons);
|
|
}
|
|
|
|
KeyboardEvent(keysym: number, pressed: boolean) {
|
|
if (this.displayVnc.connected) this.displayVnc.sendKeyEvent(keysym, pressed);
|
|
}
|
|
}
|