From a51b39f67ba82d20aaba9851c513ee778f42f76b Mon Sep 17 00:00:00 2001 From: alexkar598 <25136265+alexkar598@users.noreply.github.com> Date: Sun, 23 Jun 2024 01:45:27 -0400 Subject: [PATCH] systemBooted callback + auto resize in guest --- .../components/terminal/terminal.component.ts | 25 ++++++++++---- .../editor-page/editor-page.component.html | 1 + src/vm/commandQueue.service.ts | 1 + src/vm/emulator.service.ts | 34 ++++++++++++++++--- src/vm/emulator.worker.ts | 26 +++++++++----- 5 files changed, 68 insertions(+), 19 deletions(-) diff --git a/src/app/components/terminal/terminal.component.ts b/src/app/components/terminal/terminal.component.ts index ab57a77..69068d4 100644 --- a/src/app/components/terminal/terminal.component.ts +++ b/src/app/components/terminal/terminal.component.ts @@ -9,6 +9,7 @@ import { } from '@angular/core'; import { Terminal } from '@xterm/xterm'; import { FitAddon } from 'xterm-addon-fit'; +import { TerminalDimensions } from '../../../vm/emulator.service'; @Component({ selector: 'app-terminal', @@ -23,26 +24,38 @@ export class TerminalComponent implements AfterViewInit, OnDestroy { @Output() public input = new EventEmitter(); + @Output() + public resize = new EventEmitter(); private terminal = new Terminal(); - private fit = new FitAddon(); - private observer = new ResizeObserver(() => { - this.fit.fit(); - }); + private fitAddon = new FitAddon(); + private observer = new ResizeObserver(() => this.fit()); constructor() { this.terminal.onData((value) => this.input.emit(value)); - this.terminal.loadAddon(this.fit); + this.terminal.loadAddon(this.fitAddon); } public write(value: string) { this.terminal.write(value); } + private fit() { + this.fitAddon.fit(); + const dimensions = this.fitAddon.proposeDimensions(); + console.log(dimensions); + if (!dimensions) { + console.warn('Cannot resolve dimensions'); + return; + } + + this.resize.emit(dimensions); + } + ngAfterViewInit(): void { this.terminal.open(this.container.nativeElement); this.observer.observe(this.container.nativeElement); - this.fit.fit(); + this.fit(); } ngOnDestroy(): void { diff --git a/src/app/pages/editor-page/editor-page.component.html b/src/app/pages/editor-page/editor-page.component.html index 1086673..934b3d7 100644 --- a/src/app/pages/editor-page/editor-page.component.html +++ b/src/app/pages/editor-page/editor-page.component.html @@ -18,6 +18,7 @@ diff --git a/src/vm/commandQueue.service.ts b/src/vm/commandQueue.service.ts index f4a3c91..f98cefd 100644 --- a/src/vm/commandQueue.service.ts +++ b/src/vm/commandQueue.service.ts @@ -209,6 +209,7 @@ export class CommandQueueService { if (this.initialized) throw Error('Controller initialized twice. It probably crashed.'); console.debug('Controller initialized.'); + this.emulator.resolveSystemBooted(); this.initialized = true; this.resultBuffer = ''; this.tickQueue(); diff --git a/src/vm/emulator.service.ts b/src/vm/emulator.service.ts index 0543aa1..ae9fbea 100644 --- a/src/vm/emulator.service.ts +++ b/src/vm/emulator.service.ts @@ -1,5 +1,6 @@ import { EventEmitter, Injectable, Output } from '@angular/core'; import type { + MsgResizePort, MsgSendPort, WorkerMsgWithoutCID, WorkerResponseMsg, @@ -7,6 +8,11 @@ import type { import { environment } from '../environments/environment'; import { Port, vmRemoteUrlSearchParameter } from '../utils/literalConstants'; +export interface TerminalDimensions { + rows: number; + cols: number; +} + const encoder = new TextEncoder(); @Injectable({ @@ -14,15 +20,28 @@ const encoder = new TextEncoder(); }) export class EmulatorService { @Output() - public resetOutputConsole = new EventEmitter(); + public readonly resetOutputConsole = new EventEmitter(); @Output() - public receivedOutput = new EventEmitter<[Port, string]>(); + public readonly receivedOutput = new EventEmitter<[Port, string]>(); + + private readonly worker; + + private readonly asyncCallbacks = new Map(); - private worker; + public resolveSystemBooted!: Function; + public readonly systemBooted = new Promise( + (resolve) => (this.resolveSystemBooted = resolve), + ); - private asyncCallbacks = new Map(); + private readonly savedDimensions = new Map(); constructor() { + this.systemBooted.then(() => { + for (const [port, dims] of this.savedDimensions) { + this.resizePort(port, dims); + } + }); + interface FakeWorker { new (url: URL): Worker; } @@ -73,11 +92,16 @@ export class EmulatorService { public sendPort(...data: MsgSendPort['data']) { this.sendCommand({ command: 'sendPort', data }); } + public resizePort(...data: MsgResizePort['data']) { + this.savedDimensions.set(data[0], data[1]); + this.sendCommand({ command: 'resizePort', data }); + } public pause() { return this.sendCommandAsync({ command: 'pause' }); } public start() { - return this.sendCommandAsync({ command: 'start' }); + this.sendCommand({ command: 'start' }); + return this.systemBooted; } public sendFile(name: string, content: Uint8Array | string) { if (typeof content === 'string') content = encoder.encode(content); diff --git a/src/vm/emulator.worker.ts b/src/vm/emulator.worker.ts index 085a28d..69d03d4 100644 --- a/src/vm/emulator.worker.ts +++ b/src/vm/emulator.worker.ts @@ -1,5 +1,6 @@ /// import { Port, vmRemoteUrlSearchParameter } from '../utils/literalConstants'; +import type { TerminalDimensions } from './emulator.service'; importScripts('./lib/libv86.js'); @@ -7,6 +8,10 @@ export interface MsgSendPort { command: 'sendPort'; data: [Port, string]; } +export interface MsgResizePort { + command: 'resizePort'; + data: [Port, TerminalDimensions]; +} export interface MsgStart { command: 'start'; data?: undefined; @@ -23,6 +28,7 @@ export interface MsgSendFile { export type WorkerMsgWithoutCID = | MsgSendPort + | MsgResizePort | MsgStart | MsgPause | MsgSendFile; @@ -132,19 +138,23 @@ const emulator = new V86({ onmessage = ({ data: e }: MessageEvent) => { switch (e.command) { case 'sendPort': { + const [port, value] = e.data; emulator.bus.send( - `virtio-console${e.data[0]}-input-bytes`, - [...e.data[1]].map((x) => x.charCodeAt(0)), + `virtio-console${port}-input-bytes`, + [...value].map((x) => x.charCodeAt(0)), ); break; } + case 'resizePort': { + const [port, dimensions] = e.data; + emulator.bus.send(`virtio-console${port}-resize`, [ + dimensions.cols, + dimensions.rows, + ]); + break; + } case 'start': { - emulator.run().then(() => { - postMessage({ - command: 'asyncResponse', - commandID: e.commandID, - } satisfies WorkerResponseMsg); - }); + void emulator.run(); break; } case 'pause': {