|
| 1 | +import { Terminal } from "@xterm/xterm"; |
| 2 | +import "@xterm/xterm/css/xterm.css"; |
| 3 | +// @ts-expect-error — v86 ships no types |
| 4 | +import V86 from "v86"; |
| 5 | +import wasmUrl from "v86/build/v86.wasm?url"; |
| 6 | +import "./keyboard/virtual-keyboard"; |
| 7 | +import { terminalAdapter } from "./keyboard/output"; |
| 8 | + |
| 9 | +type V86Instance = { |
| 10 | + add_listener(event: string, cb: (...args: unknown[]) => void): void; |
| 11 | + serial0_send(data: string): void; |
| 12 | + stop(): Promise<void>; |
| 13 | + restart(): void; |
| 14 | +}; |
| 15 | + |
| 16 | +type V86Ctor = new (options: Record<string, unknown>) => V86Instance; |
| 17 | + |
| 18 | +const V86Constructor = V86 as V86Ctor; |
| 19 | + |
| 20 | +const HOST = import.meta.env.BASE_URL + "v86/"; |
| 21 | + |
| 22 | +const term = new Terminal({ |
| 23 | + cursorBlink: true, |
| 24 | + fontFamily: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace", |
| 25 | + fontSize: 14, |
| 26 | + theme: { |
| 27 | + background: "#0b1220", |
| 28 | + foreground: "#cbd5e1", |
| 29 | + cursor: "#10b981", |
| 30 | + }, |
| 31 | +}); |
| 32 | + |
| 33 | +const host = document.getElementById("term")!; |
| 34 | +term.open(host); |
| 35 | + |
| 36 | +const status = document.getElementById("status")!; |
| 37 | +const setStatus = (text: string): void => { |
| 38 | + status.textContent = text; |
| 39 | +}; |
| 40 | + |
| 41 | +term.writeln("\x1b[1;36mLinux on v86\x1b[0m — x86 emulator in WebAssembly"); |
| 42 | +term.writeln("Booting from linux4.iso (busybox)…\r\n"); |
| 43 | + |
| 44 | +const emulator = new V86Constructor({ |
| 45 | + wasm_path: wasmUrl, |
| 46 | + memory_size: 512 * 1024 * 1024, |
| 47 | + vga_memory_size: 8 * 1024 * 1024, |
| 48 | + bios: { url: HOST + "bios/seabios.bin" }, |
| 49 | + vga_bios: { url: HOST + "bios/vgabios.bin" }, |
| 50 | + cdrom: { url: HOST + "images/linux.iso" }, |
| 51 | + autostart: true, |
| 52 | + acpi: true, |
| 53 | + disable_keyboard: true, |
| 54 | + disable_mouse: true, |
| 55 | + disable_speaker: true, |
| 56 | +}); |
| 57 | + |
| 58 | +emulator.add_listener("emulator-ready", () => setStatus("ready")); |
| 59 | +emulator.add_listener("emulator-started", () => setStatus("running")); |
| 60 | +emulator.add_listener("download-progress", ((e: unknown) => { |
| 61 | + const p = e as { loaded: number; total?: number; file_name?: string }; |
| 62 | + if (p.total) { |
| 63 | + const pct = Math.round((p.loaded / p.total) * 100); |
| 64 | + setStatus(`downloading ${p.file_name ?? "image"} ${pct}%`); |
| 65 | + } |
| 66 | +}) as (...args: unknown[]) => void); |
| 67 | + |
| 68 | +const send = (data: string): void => { |
| 69 | + emulator.serial0_send(data); |
| 70 | +}; |
| 71 | + |
| 72 | +emulator.add_listener("serial0-output-byte", ((byte: unknown) => { |
| 73 | + term.write(Uint8Array.from([byte as number])); |
| 74 | +}) as (...args: unknown[]) => void); |
| 75 | + |
| 76 | +term.onData(send); |
| 77 | + |
| 78 | +const kb = document.querySelector("virtual-keyboard")!; |
| 79 | +kb.setAdapter(terminalAdapter(send)); |
| 80 | + |
| 81 | +term.focus(); |
0 commit comments