From 5e4018a0c0b7cee46d4b953151221e0929bf038c Mon Sep 17 00:00:00 2001 From: Jeff Carr Date: Fri, 30 May 2025 02:52:38 -0500 Subject: [PATCH] Bellard's files from 2017 or so --- Makefile | 7 + jslinux.js | 126 ++++++++++++++ term.js | 476 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 609 insertions(+) create mode 100644 Makefile create mode 100644 jslinux.js create mode 100644 term.js diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..53e4d91 --- /dev/null +++ b/Makefile @@ -0,0 +1,7 @@ +all: + @echo "this is Fabrice Bellard's unbelievable linux kernel in a browser" + +wget-javascript: + rm -f *.js + wget -c https://bellard.org/jslinux/term.js + wget -c https://bellard.org/jslinux/jslinux.js diff --git a/jslinux.js b/jslinux.js new file mode 100644 index 0000000..a706e00 --- /dev/null +++ b/jslinux.js @@ -0,0 +1,126 @@ +/* + Linux launcher + + Copyright (c) 2011-2012 Fabrice Bellard + + Redistribution or commercial use is prohibited without the author's + permission. +*/ +"use strict"; + +var term, pc, boot_start_time, init_state; + +function term_start() +{ + term = new Term(80, 30, term_handler); + + term.open(); +} + +/* send chars to the serial port */ +function term_handler(str) +{ + pc.serial.send_chars(str); +} + +function clipboard_set(val) +{ + var el; + el = document.getElementById("text_clipboard"); + el.value = val; +} + +function clipboard_get() +{ + var el; + el = document.getElementById("text_clipboard"); + return el.value; +} + +function clear_clipboard() +{ + var el; + el = document.getElementById("text_clipboard"); + el.value = ""; +} + +/* just used to display the boot time in the VM */ +function get_boot_time() +{ + return (+new Date()) - boot_start_time; +} + +function start() +{ + var params; + + init_state = new Object(); + + params = new Object(); + + /* serial output chars */ + params.serial_write = term.write.bind(term); + + /* memory size (in bytes) */ + params.mem_size = 31 * 1024 * 1024; + + /* clipboard I/O */ + params.clipboard_get = clipboard_get; + params.clipboard_set = clipboard_set; + + params.get_boot_time = get_boot_time; + + /* IDE drive. The raw disk image is split into files of + * 'block_size' KB. + */ + //params.hda = { url: "hda%d.bin", block_size: 64, nb_blocks: 912 }; + params.hda = { url: "bin/hda%d.bin", block_size: 64, nb_blocks: 912 }; + //modify by hao + pc = new PCEmulator(params); + + init_state.params = params; + + pc.load_binary("vmlinux-2.6.20.bin", 0x00100000, start2); +} + +function start2(ret) +{ + if (ret < 0) + return; + init_state.start_addr = 0x10000; + pc.load_binary("linuxstart.bin", init_state.start_addr, start3); +} + +function start3(ret) +{ + var block_list; + if (ret < 0) + return; + /* Preload blocks so that the boot time does not depend on the + * time to load the required disk data (optional) */ + block_list = [ 0, 7, 3, 643, 720, 256, 336, 644, 781, 387, 464, 475, 131, 589, 468, 472, 474, 776, 777, 778, 779, 465, 466, 473, 467, 469, 470, 512, 592, 471, 691, 697, 708, 792, 775, 769 ]; + pc.ide0.drives[0].bs.preload(block_list, start4); +} + +function start4(ret) +{ + var cmdline_addr; + + if (ret < 0) + return; + + /* set the Linux kernel command line */ + cmdline_addr = 0xf800; + pc.cpu.write_string(cmdline_addr, "console=ttyS0 root=/dev/hda ro init=/sbin/init notsc=1 hdb=none"); + + pc.cpu.set_reg(8, init_state.start_addr); /* eip */ + pc.cpu.set_reg(0, init_state.params.mem_size); /* eax */ + pc.cpu.set_reg(3, 0); /* ebx = initrd_size (no longer used) */ + pc.cpu.set_reg(1, cmdline_addr); /* ecx */ + + boot_start_time = (+new Date()); + + pc.start(); +} + +term_start(); diff --git a/term.js b/term.js new file mode 100644 index 0000000..2dfe106 --- /dev/null +++ b/term.js @@ -0,0 +1,476 @@ +/* + Javascript Terminal + + Copyright (c) 2011 Fabrice Bellard + + Redistribution or commercial use is prohibited without the author's + permission. +*/ +"use strict"; +function Term(aa, ba, term_handler) { + this.w = aa; + this.h = ba; + this.cur_h = ba; + this.tot_h = 1000; + this.y_base = 0; + this.y_disp = 0; + this.x = 0; + this.y = 0; + this.cursorstate = 0; + this.handler = term_handler; + this.convert_lf_to_crlf = false; + this.state = 0; + this.output_queue = ""; + this.bg_colors = ["#000000", "#ff0000", "#00ff00", "#ffff00", "#0000ff", "#ff00ff", "#00ffff", "#ffffff"]; + this.fg_colors = ["#000000", "#ff0000", "#00ff00", "#ffff00", "#0000ff", "#ff00ff", "#00ffff", "#ffffff"]; + this.def_attr = (7 << 3) | 0; + this.cur_attr = this.def_attr; + this.is_mac = (navigator.userAgent.indexOf("Mac") >= 0) ? true: false; + this.key_rep_state = 0; + this.key_rep_str = ""; +} +Term.prototype.open = function() { + var y, da, i, ea, c; + this.lines = new Array(); + c = 32 | (this.def_attr << 16); + for (y = 0; y < this.cur_h; y++) { + da = new Array(); + for (i = 0; i < this.w; i++) da[i] = c; + this.lines[y] = da; + } + document.writeln(''); + for (y = 0; y < this.h; y++) { + document.writeln(''); + } + document.writeln('
'); + this.refresh(0, this.h - 1); + document.addEventListener("keydown", this.keyDownHandler.bind(this), true); + document.addEventListener("keypress", this.keyPressHandler.bind(this), true); + ea = this; + setInterval(function() { + ea.cursor_timer_cb(); + }, + 1000); +}; +Term.prototype.refresh = function(fa, ga) { + var ha, y, da, ia, c, w, i, ja, ka, la, ma, na, oa; + for (y = fa; y <= ga; y++) { + oa = y + this.y_disp; + if (oa >= this.cur_h) oa -= this.cur_h; + da = this.lines[oa]; + ia = ""; + w = this.w; + if (y == this.y && this.cursor_state && this.y_disp == this.y_base) { + ja = this.x; + } else { + ja = -1; + } + la = this.def_attr; + for (i = 0; i < w; i++) { + c = da[i]; + ka = c >> 16; + c &= 65535; + if (i == ja) { + ka = -1; + } + if (ka != la) { + if (la != this.def_attr) ia += ''; + if (ka != this.def_attr) { + if (ka == -1) { + ia += ''; + } else { + ia += ''; + } + } + } + switch (c) { + case 32: + ia += " "; + break; + case 38: + ia += "&"; + break; + case 60: + ia += "<"; + break; + case 62: + ia += ">"; + break; + default: + if (c < 32) { + ia += " "; + } else { + ia += String.fromCharCode(c); + } + break; + } + la = ka; + } + if (la != this.def_attr) { + ia += ''; + } + ha = document.getElementById("tline" + y); + ha.innerHTML = ia; + } +}; +Term.prototype.cursor_timer_cb = function() { + this.cursor_state ^= 1; + this.refresh(this.y, this.y); +}; +Term.prototype.show_cursor = function() { + if (!this.cursor_state) { + this.cursor_state = 1; + this.refresh(this.y, this.y); + } +}; +Term.prototype.scroll = function() { + var y, da, x, c, oa; + if (this.cur_h < this.tot_h) { + this.cur_h++; + } + if (++this.y_base == this.cur_h) this.y_base = 0; + this.y_disp = this.y_base; + c = 32 | (this.def_attr << 16); + da = new Array(); + for (x = 0; x < this.w; x++) da[x] = c; + oa = this.y_base + this.h - 1; + if (oa >= this.cur_h) oa -= this.cur_h; + this.lines[oa] = da; +}; +Term.prototype.scroll_disp = function(n) { + var i, oa; + if (n >= 0) { + for (i = 0; i < n; i++) { + if (this.y_disp == this.y_base) break; + if (++this.y_disp == this.cur_h) this.y_disp = 0; + } + } else { + n = -n; + oa = this.y_base + this.h; + if (oa >= this.cur_h) oa -= this.cur_h; + for (i = 0; i < n; i++) { + if (this.y_disp == oa) break; + if (--this.y_disp < 0) this.y_disp = this.cur_h - 1; + } + } + this.refresh(0, this.h - 1); +}; +Term.prototype.write = function(pa) { + function qa(y) { + fa = Math.min(fa, y); + ga = Math.max(ga, y); + } + function ra(s, x, y) { + var l, i, c, oa; + oa = s.y_base + y; + if (oa >= s.cur_h) oa -= s.cur_h; + l = s.lines[oa]; + c = 32 | (s.def_attr << 16); + for (i = x; i < s.w; i++) l[i] = c; + qa(y); + } + function sa(s, ta) { + var j, n; + if (ta.length == 0) { + s.cur_attr = s.def_attr; + } else { + for (j = 0; j < ta.length; j++) { + n = ta[j]; + if (n >= 30 && n <= 37) { + s.cur_attr = (s.cur_attr & ~ (7 << 3)) | ((n - 30) << 3); + } else if (n >= 40 && n <= 47) { + s.cur_attr = (s.cur_attr & ~7) | (n - 40); + } else if (n == 0) { + s.cur_attr = s.def_attr; + } + } + } + } + var ua = 0; + var va = 1; + var wa = 2; + var i, c, fa, ga, l, n, j, oa; + fa = this.h; + ga = -1; + qa(this.y); + if (this.y_base != this.y_disp) { + this.y_disp = this.y_base; + fa = 0; + ga = this.h - 1; + } + for (i = 0; i < pa.length; i++) { + c = pa.charCodeAt(i); + switch (this.state) { + case ua: + switch (c) { + case 10: + if (this.convert_lf_to_crlf) { + this.x = 0; + } + this.y++; + if (this.y >= this.h) { + this.y--; + this.scroll(); + fa = 0; + ga = this.h - 1; + } + break; + case 13: + this.x = 0; + break; + case 8: + if (this.x > 0) { + this.x--; + } + break; + case 9: + n = (this.x + 8) & ~7; + if (n <= this.w) { + this.x = n; + } + break; + case 27: + this.state = va; + break; + default: + if (c >= 32) { + if (this.x >= this.w) { + this.x = 0; + this.y++; + if (this.y >= this.h) { + this.y--; + this.scroll(); + fa = 0; + ga = this.h - 1; + } + } + oa = this.y + this.y_base; + if (oa >= this.cur_h) oa -= this.cur_h; + this.lines[oa][this.x] = (c & 65535) | (this.cur_attr << 16); + this.x++; + qa(this.y); + } + break; + } + break; + case va: + if (c == 91) { + this.esc_params = new Array(); + this.cur_param = 0; + this.state = wa; + } else { + this.state = ua; + } + break; + case wa: + if (c >= 48 && c <= 57) { + this.cur_param = this.cur_param * 10 + c - 48; + } else { + this.esc_params[this.esc_params.length] = this.cur_param; + this.cur_param = 0; + if (c == 59) break; + this.state = ua; + switch (c) { + case 65: + n = this.esc_params[0]; + if (n < 1) n = 1; + this.y -= n; + if (this.y < 0) this.y = 0; + break; + case 66: + n = this.esc_params[0]; + if (n < 1) n = 1; + this.y += n; + if (this.y >= this.h) this.y = this.h - 1; + break; + case 67: + n = this.esc_params[0]; + if (n < 1) n = 1; + this.x += n; + if (this.x >= this.w - 1) this.x = this.w - 1; + break; + case 68: + n = this.esc_params[0]; + if (n < 1) n = 1; + this.x -= n; + if (this.x < 0) this.x = 0; + break; + case 72: + { + var xa, oa; + oa = this.esc_params[0] - 1; + if (this.esc_params.length >= 2) xa = this.esc_params[1] - 1; + else xa = 0; + if (oa < 0) oa = 0; + else if (oa >= this.h) oa = this.h - 1; + if (xa < 0) xa = 0; + else if (xa >= this.w) xa = this.w - 1; + this.x = xa; + this.y = oa; + } + break; + case 74: + ra(this, this.x, this.y); + for (j = this.y + 1; j < this.h; j++) ra(this, 0, j); + break; + case 75: + ra(this, this.x, this.y); + break; + case 109: + sa(this, this.esc_params); + break; + case 110: + this.queue_chars("\x1b[" + (this.y + 1) + ";" + (this.x + 1) + "R"); + break; + default: + break; + } + } + break; + } + } + qa(this.y); + if (ga >= fa) this.refresh(fa, ga); +}; +Term.prototype.writeln = function(pa) { + this.write(pa + '\r\n'); +}; +Term.prototype.keyDownHandler = function(ya) { + var pa; + pa = ""; + switch (ya.keyCode) { + case 8: + pa = ""; + break; + case 9: + pa = "\t"; + break; + case 13: + pa = "\r"; + break; + case 27: + pa = "\x1b"; + break; + case 37: + pa = "\x1b[D"; + break; + case 39: + pa = "\x1b[C"; + break; + case 38: + if (ya.ctrlKey) { + this.scroll_disp( - 1); + } else { + pa = "\x1b[A"; + } + break; + case 40: + if (ya.ctrlKey) { + this.scroll_disp(1); + } else { + pa = "\x1b[B"; + } + break; + case 46: + pa = "\x1b[3~"; + break; + case 45: + pa = "\x1b[2~"; + break; + case 36: + pa = "\x1bOH"; + break; + case 35: + pa = "\x1bOF"; + break; + case 33: + if (ya.ctrlKey) { + this.scroll_disp( - (this.h - 1)); + } else { + pa = "\x1b[5~"; + } + break; + case 34: + if (ya.ctrlKey) { + this.scroll_disp(this.h - 1); + } else { + pa = "\x1b[6~"; + } + break; + default: + if (ya.ctrlKey) { + if (ya.keyCode >= 65 && ya.keyCode <= 90) { + pa = String.fromCharCode(ya.keyCode - 64); + } else if (ya.keyCode == 32) { + pa = String.fromCharCode(0); + } + } else if ((!this.is_mac && ya.altKey) || (this.is_mac && ya.metaKey)) { + if (ya.keyCode >= 65 && ya.keyCode <= 90) { + pa = "\x1b" + String.fromCharCode(ya.keyCode + 32); + } + } + break; + } + if (pa) { + if (ya.stopPropagation) ya.stopPropagation(); + if (ya.preventDefault) ya.preventDefault(); + this.show_cursor(); + this.key_rep_state = 1; + this.key_rep_str = pa; + this.handler(pa); + return false; + } else { + this.key_rep_state = 0; + return true; + } +}; +Term.prototype.keyPressHandler = function(ya) { + var pa, za; + if (ya.stopPropagation) ya.stopPropagation(); + if (ya.preventDefault) ya.preventDefault(); + pa = ""; + if (! ("charCode" in ya)) { + za = ya.keyCode; + if (this.key_rep_state == 1) { + this.key_rep_state = 2; + return false; + } else if (this.key_rep_state == 2) { + this.show_cursor(); + this.handler(this.key_rep_str); + return false; + } + } else { + za = ya.charCode; + } + if (za != 0) { + if (!ya.ctrlKey && ((!this.is_mac && !ya.altKey) || (this.is_mac && !ya.metaKey))) { + pa = String.fromCharCode(za); + } + } + if (pa) { + this.show_cursor(); + this.handler(pa); + return false; + } else { + return true; + } +}; +Term.prototype.queue_chars = function(pa) { + this.output_queue += pa; + if (this.output_queue) setTimeout(this.outputHandler.bind(this), 0); +}; +Term.prototype.outputHandler = function() { + if (this.output_queue) { + this.handler(this.output_queue); + this.output_queue = ""; + } +}; \ No newline at end of file