/* * Copyright (C) 2024 Puter Technologies Inc. * * This file is part of Puter. * * Puter is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ import { V86 } from "./V86Wrapper.mjs"; /** * Class representing an Instance of an emulator machine. */ class Instance { /** * Create an Instance. * @param {Object} options - Options for configuring the instance. * @param {boolean} [options.term=true] - Terminal option. * @param {boolean} [options.screen=false] - Screen option. * @param {number} [options.memory=1024] - Memory size for the instance; must be power of two. * @param {HTMLElement} [options.spawnRoot=undefined] - Html element where instance should be spawned. * @param {boolean} [options.autoStart=true] - Whether to automatically start the instance. * @param {string} [options.remote="./"] - Remote URL, defaults to origin. * @param {string} [options.wsUrl=""] - Websocket URL option for communication with the outside world. * @throws Will throw an error if remote URL does not end with a slash. @throws Will throw an error if the amount of memory provided is not a power of two. */ constructor(options) { const defaultOptions = { term: false, screen: false, memory: 1024, spawnRoot: undefined, autoStart: true, remote: "./", instanceName: [...Array(10)].map(() => Math.random().toString(36)[2]).join(''), wsUrl: "", }; const instanceOptions = { ...defaultOptions, ...options }; if (!instanceOptions.remote.endsWith('/')) throw new Error("Instance ctor: Remote URL must end with a slash"); if (typeof self !== 'undefined' && self.crypto) { this.instanceID = self.crypto.randomUUID(); } else { this.instanceID = "Node"; } this.terminals = []; let v86Options = { wasm_path: instanceOptions.remote + "third-party/v86.wasm", preserve_mac_from_state_image: true, memory_size: instanceOptions.memory * 1024 * 1024, vga_memory_size: 8 * 1024 * 1024, initial_state: { url: instanceOptions.remote + "static/image.bin" }, filesystem: { baseurl: instanceOptions.remote + "static/9p-rootfs/" }, autostart: instanceOptions.autoStart, }; if (!(instanceOptions.wsUrl === "")) v86Options.network_relay_url = instanceOptions.wsUrl; if (!((Math.log(v86Options.memory_size) / Math.log(2)) % 1 === 0)) throw new Error("Instance ctor: Amount of memory provided isn't a power of two"); if (instanceOptions.screen === true) { if (instanceOptions.spawnRoot === undefined) throw new Error("Instance ctor: spawnRoot is undefined, cannot continue") instanceOptions.spawnRoot.appendChild((() => { const div = document.createElement("div"); div.setAttribute("id", instanceOptions.instanceName + '-screen'); const child_div = document.createElement("div"); child_div.setAttribute("style", "white-space: pre; font: 14px monospace; line-height: 14px"); const canvas = document.createElement("canvas"); canvas.setAttribute("style", "display: none"); div.appendChild(child_div); div.appendChild(canvas); return div; })()); v86Options.screen_container = document.getElementById(instanceOptions.instanceName + '-screen'); } this.vm = new V86(v86Options); if (instanceOptions.term === true) { if (instanceOptions.spawnRoot === undefined) throw new Error("Instance ctor: spawnRoot is undefined, cannot continue") var term = new Terminal({ allowTransparency: true, }); instanceOptions.spawnRoot.appendChild((() => { const div = document.createElement("div"); div.setAttribute("id", instanceOptions.instanceName + '-terminal'); return div; })()); term.open(document.getElementById(instanceOptions.instanceName + '-terminal')); term.write("Now booting emu, please stand by ..."); this.vm.add_listener("emulator-started", () => { // emulator.serial0_send("\nsh networking.sh > /dev/null 2>&1 &\n\n"); // emulator.serial0_send("clear\n"); term.write("Welcome to psl!"); this.vm.serial0_send("\n"); }); this.vm.add_listener("serial0-output-byte", (byte) => { var chr = String.fromCharCode(byte); if (chr <= "~") { term.write(chr); } }); term.onData(data => { this.vm.serial0_send(data); }); } } } export default Instance