Skip to content

Commit a51b39f

Browse files
committed
systemBooted callback + auto resize in guest
1 parent 540ab5d commit a51b39f

File tree

5 files changed

+68
-19
lines changed

5 files changed

+68
-19
lines changed

src/app/components/terminal/terminal.component.ts

+19-6
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
} from '@angular/core';
1010
import { Terminal } from '@xterm/xterm';
1111
import { FitAddon } from 'xterm-addon-fit';
12+
import { TerminalDimensions } from '../../../vm/emulator.service';
1213

1314
@Component({
1415
selector: 'app-terminal',
@@ -23,26 +24,38 @@ export class TerminalComponent implements AfterViewInit, OnDestroy {
2324

2425
@Output()
2526
public input = new EventEmitter<string>();
27+
@Output()
28+
public resize = new EventEmitter<TerminalDimensions>();
2629

2730
private terminal = new Terminal();
28-
private fit = new FitAddon();
29-
private observer = new ResizeObserver(() => {
30-
this.fit.fit();
31-
});
31+
private fitAddon = new FitAddon();
32+
private observer = new ResizeObserver(() => this.fit());
3233

3334
constructor() {
3435
this.terminal.onData((value) => this.input.emit(value));
35-
this.terminal.loadAddon(this.fit);
36+
this.terminal.loadAddon(this.fitAddon);
3637
}
3738

3839
public write(value: string) {
3940
this.terminal.write(value);
4041
}
4142

43+
private fit() {
44+
this.fitAddon.fit();
45+
const dimensions = this.fitAddon.proposeDimensions();
46+
console.log(dimensions);
47+
if (!dimensions) {
48+
console.warn('Cannot resolve dimensions');
49+
return;
50+
}
51+
52+
this.resize.emit(dimensions);
53+
}
54+
4255
ngAfterViewInit(): void {
4356
this.terminal.open(this.container.nativeElement);
4457
this.observer.observe(this.container.nativeElement);
45-
this.fit.fit();
58+
this.fit();
4659
}
4760

4861
ngOnDestroy(): void {

src/app/pages/editor-page/editor-page.component.html

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
<app-terminal
1919
#terminal
2020
(input)="emulator.sendPort(Port.Console, $event)"
21+
(resize)="emulator.resizePort(Port.Console, $event)"
2122
/>
2223
</app-panel>
2324
<app-panel title="Screen log" as-split-area>

src/vm/commandQueue.service.ts

+1
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ export class CommandQueueService {
209209
if (this.initialized)
210210
throw Error('Controller initialized twice. It probably crashed.');
211211
console.debug('Controller initialized.');
212+
this.emulator.resolveSystemBooted();
212213
this.initialized = true;
213214
this.resultBuffer = '';
214215
this.tickQueue();

src/vm/emulator.service.ts

+29-5
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,47 @@
11
import { EventEmitter, Injectable, Output } from '@angular/core';
22
import type {
3+
MsgResizePort,
34
MsgSendPort,
45
WorkerMsgWithoutCID,
56
WorkerResponseMsg,
67
} from './emulator.worker';
78
import { environment } from '../environments/environment';
89
import { Port, vmRemoteUrlSearchParameter } from '../utils/literalConstants';
910

11+
export interface TerminalDimensions {
12+
rows: number;
13+
cols: number;
14+
}
15+
1016
const encoder = new TextEncoder();
1117

1218
@Injectable({
1319
providedIn: 'root',
1420
})
1521
export class EmulatorService {
1622
@Output()
17-
public resetOutputConsole = new EventEmitter<void>();
23+
public readonly resetOutputConsole = new EventEmitter<void>();
1824
@Output()
19-
public receivedOutput = new EventEmitter<[Port, string]>();
25+
public readonly receivedOutput = new EventEmitter<[Port, string]>();
26+
27+
private readonly worker;
28+
29+
private readonly asyncCallbacks = new Map<number, Function>();
2030

21-
private worker;
31+
public resolveSystemBooted!: Function;
32+
public readonly systemBooted = new Promise<void>(
33+
(resolve) => (this.resolveSystemBooted = resolve),
34+
);
2235

23-
private asyncCallbacks = new Map<number, Function>();
36+
private readonly savedDimensions = new Map<Port, TerminalDimensions>();
2437

2538
constructor() {
39+
this.systemBooted.then(() => {
40+
for (const [port, dims] of this.savedDimensions) {
41+
this.resizePort(port, dims);
42+
}
43+
});
44+
2645
interface FakeWorker {
2746
new (url: URL): Worker;
2847
}
@@ -73,11 +92,16 @@ export class EmulatorService {
7392
public sendPort(...data: MsgSendPort['data']) {
7493
this.sendCommand({ command: 'sendPort', data });
7594
}
95+
public resizePort(...data: MsgResizePort['data']) {
96+
this.savedDimensions.set(data[0], data[1]);
97+
this.sendCommand({ command: 'resizePort', data });
98+
}
7699
public pause() {
77100
return this.sendCommandAsync({ command: 'pause' });
78101
}
79102
public start() {
80-
return this.sendCommandAsync({ command: 'start' });
103+
this.sendCommand({ command: 'start' });
104+
return this.systemBooted;
81105
}
82106
public sendFile(name: string, content: Uint8Array | string) {
83107
if (typeof content === 'string') content = encoder.encode(content);

src/vm/emulator.worker.ts

+18-8
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
/// <reference lib="webworker" />
22
import { Port, vmRemoteUrlSearchParameter } from '../utils/literalConstants';
3+
import type { TerminalDimensions } from './emulator.service';
34

45
importScripts('./lib/libv86.js');
56

67
export interface MsgSendPort {
78
command: 'sendPort';
89
data: [Port, string];
910
}
11+
export interface MsgResizePort {
12+
command: 'resizePort';
13+
data: [Port, TerminalDimensions];
14+
}
1015
export interface MsgStart {
1116
command: 'start';
1217
data?: undefined;
@@ -23,6 +28,7 @@ export interface MsgSendFile {
2328

2429
export type WorkerMsgWithoutCID =
2530
| MsgSendPort
31+
| MsgResizePort
2632
| MsgStart
2733
| MsgPause
2834
| MsgSendFile;
@@ -132,19 +138,23 @@ const emulator = new V86({
132138
onmessage = ({ data: e }: MessageEvent<WorkerMsg>) => {
133139
switch (e.command) {
134140
case 'sendPort': {
141+
const [port, value] = e.data;
135142
emulator.bus.send(
136-
`virtio-console${e.data[0]}-input-bytes`,
137-
[...e.data[1]].map((x) => x.charCodeAt(0)),
143+
`virtio-console${port}-input-bytes`,
144+
[...value].map((x) => x.charCodeAt(0)),
138145
);
139146
break;
140147
}
148+
case 'resizePort': {
149+
const [port, dimensions] = e.data;
150+
emulator.bus.send(`virtio-console${port}-resize`, [
151+
dimensions.cols,
152+
dimensions.rows,
153+
]);
154+
break;
155+
}
141156
case 'start': {
142-
emulator.run().then(() => {
143-
postMessage({
144-
command: 'asyncResponse',
145-
commandID: e.commandID,
146-
} satisfies WorkerResponseMsg);
147-
});
157+
void emulator.run();
148158
break;
149159
}
150160
case 'pause': {

0 commit comments

Comments
 (0)