diff --git a/cfg/root-riscv64.cfg b/cfg/root-riscv64.cfg
new file mode 100644
index 0000000..e1349e2
--- /dev/null
+++ b/cfg/root-riscv64.cfg
@@ -0,0 +1,11 @@
+/* VM configuration file */
+{
+ version: 1,
+ machine: "riscv64",
+ memory_size: 128,
+ bios: "bbl64.bin",
+ kernel: "kernel-riscv64.bin",
+ cmdline: "console=hvc0 root=/dev/vda rw",
+ drive0: { file: "root-riscv64/blk.txt" },
+ eth0: { driver: "user" },
+}
diff --git a/cfg/root-x86.cfg b/cfg/root-x86.cfg
new file mode 100644
index 0000000..31f2afa
--- /dev/null
+++ b/cfg/root-x86.cfg
@@ -0,0 +1,10 @@
+/* VM configuration file */
+{
+ version: 1,
+ machine: "pc",
+ memory_size: 128,
+ kernel: "kernel-x86.bin",
+ cmdline: "loglevel=3 console=hvc0 root=/dev/vda rw",
+ drive0: { file: "root-x86/blk.txt" },
+ eth0: { driver: "user" },
+}
diff --git a/web/index.html b/web/index.html
new file mode 100644
index 0000000..8cde18f
--- /dev/null
+++ b/web/index.html
@@ -0,0 +1,27 @@
+
+
+
+
+ JSLinux
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+© 2017-2019 Fabrice Bellard
+
+
diff --git a/web/jslinux.js b/web/jslinux.js
new file mode 100644
index 0000000..d656c75
--- /dev/null
+++ b/web/jslinux.js
@@ -0,0 +1,646 @@
+/*
+ * JS Linux main
+ *
+ * Copyright (c) 2017 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+"use strict";
+
+var term, console_write1;
+var graphic_display, display_key_event, display_mouse_event;
+var net_state, net_write_packet, net_set_carrier;
+var display_wheel_event;
+var fs_import_file;
+var Module = {};
+var downloading_timer_pending = false;
+var downloading_timer;
+
+function on_update_file(f)
+{
+ var f, reader;
+ reader = new FileReader();
+ reader.onload = function (ev) {
+ var buf, buf_addr, buf_len;
+ buf = new Uint8Array(reader.result);
+ buf_len = buf.length;
+ buf_addr = _malloc(buf_len);
+ HEAPU8.set(buf, buf_addr);
+ /* the buffer is freed by the function */
+ fs_import_file(f.name, buf_addr, buf_len);
+ };
+ reader.readAsArrayBuffer(f);
+}
+
+function on_update_files(files)
+{
+ var i, n;
+ n = files.length;
+ for(i = 0; i < n; i++) {
+ on_update_file(files[i]);
+ }
+}
+
+function term_handler(str)
+{
+ var i;
+ for(i = 0; i < str.length; i++) {
+ console_write1(str.charCodeAt(i));
+ }
+}
+
+function downloading_timer_cb()
+{
+ var el = document.getElementById("net_progress");
+ el.style.visibility = "hidden";
+ downloading_timer_pending = false;
+}
+
+function update_downloading(flag)
+{
+ var el;
+ if (flag) {
+ if (downloading_timer_pending) {
+ clearTimeout(downloading_timer);
+ downloading_timer_pending = false;
+ } else {
+ el = document.getElementById("net_progress");
+ el.style.visibility = "visible";
+ }
+ } else {
+ downloading_timer_pending = true;
+ downloading_timer = setTimeout(downloading_timer_cb, 500);
+ }
+}
+
+function get_params()
+{
+ var url, query_str, p, tab, i, params, tab2;
+ query_str = window.location.href;
+ p = query_str.indexOf("?");
+ if (p < 0)
+ return {};
+ query_str = query_str.substr(p + 1);
+ tab = query_str.split("&");
+ params = {};
+ for(i = 0; i < tab.length; i++) {
+ tab2 = tab[i].split("=");
+ params[decodeURIComponent(tab2[0])] = decodeURIComponent(tab2[1]);
+ }
+ return params;
+}
+
+function get_absolute_url(fname)
+{
+ var path, p;
+
+ if (fname.indexOf(":") >= 0)
+ return fname;
+ path = window.location.pathname;
+ p = path.lastIndexOf("/");
+ if (p < 0)
+ return fname;
+ return window.location.origin + path.slice(0, p + 1) + fname;
+}
+
+function GraphicDisplay(parent_el, width, height)
+{
+ this.width = width;
+ this.height = height;
+
+ this.canvas_el = document.createElement("canvas");
+ this.canvas_el.width = width; /* logical width */
+ this.canvas_el.height = height; /* logical width */
+ /* displayed size */
+ this.canvas_el.style.width = width + "px";
+ this.canvas_el.style.height = height + "px";
+ this.canvas_el.style.cursor = "none";
+
+ parent_el.appendChild(this.canvas_el);
+
+ this.ctx = this.canvas_el.getContext("2d");
+ /* clear the display */
+ this.ctx.fillStyle = "#000000";
+ this.ctx.fillRect(0, 0, width, height);
+
+ this.image = this.ctx.createImageData(width, height);
+
+ this.key_pressed = new Uint8Array(128);
+
+ document.addEventListener("keydown",
+ this.keyDownHandler.bind(this), false);
+ document.addEventListener("keyup",
+ this.keyUpHandler.bind(this), false);
+ document.addEventListener("blur",
+ this.blurHandler.bind(this), false);
+
+ this.canvas_el.onmousedown = this.mouseMoveHandler.bind(this);
+ this.canvas_el.onmouseup = this.mouseMoveHandler.bind(this);
+ this.canvas_el.onmousemove = this.mouseMoveHandler.bind(this);
+ this.canvas_el.oncontextmenu = this.onContextMenuHandler.bind(this);
+ this.canvas_el.onwheel = this.wheelHandler.bind(this);
+}
+
+GraphicDisplay.code_to_input_map = {
+ "Escape": 0x01,
+ "Digit1": 0x02,
+ "Digit2": 0x03,
+ "Digit3": 0x04,
+ "Digit4": 0x05,
+ "Digit5": 0x06,
+ "Digit6": 0x07,
+ "Digit7": 0x08,
+ "Digit8": 0x09,
+ "Digit9": 0x0a,
+ "Digit0": 0x0b,
+ "Minus": 0x0c,
+ "Equal": 0x0d,
+ "Backspace": 0x0e,
+ "Tab": 0x0f,
+ "KeyQ": 0x10,
+ "KeyW": 0x11,
+ "KeyE": 0x12,
+ "KeyR": 0x13,
+ "KeyT": 0x14,
+ "KeyY": 0x15,
+ "KeyU": 0x16,
+ "KeyI": 0x17,
+ "KeyO": 0x18,
+ "KeyP": 0x19,
+ "BracketLeft": 0x1a,
+ "BracketRight": 0x1b,
+ "Enter": 0x1c,
+ "ControlLeft": 0x1d,
+ "KeyA": 0x1e,
+ "KeyS": 0x1f,
+ "KeyD": 0x20,
+ "KeyF": 0x21,
+ "KeyG": 0x22,
+ "KeyH": 0x23,
+ "KeyJ": 0x24,
+ "KeyK": 0x25,
+ "KeyL": 0x26,
+ "Semicolon": 0x27,
+ "Quote": 0x28,
+ "Backquote": 0x29,
+ "ShiftLeft": 0x2a,
+ "Backslash": 0x2b,
+ "KeyZ": 0x2c,
+ "KeyX": 0x2d,
+ "KeyC": 0x2e,
+ "KeyV": 0x2f,
+ "KeyB": 0x30,
+ "KeyN": 0x31,
+ "KeyM": 0x32,
+ "Comma": 0x33,
+ "Period": 0x34,
+ "Slash": 0x35,
+ "ShiftRight": 0x36,
+ "NumpadMultiply": 0x37,
+ "AltLeft": 0x38,
+ "Space": 0x39,
+ "CapsLock": 0x3a,
+ "F1": 0x3b,
+ "F2": 0x3c,
+ "F3": 0x3d,
+ "F4": 0x3e,
+ "F5": 0x3f,
+ "F6": 0x40,
+ "F7": 0x41,
+ "F8": 0x42,
+ "F9": 0x43,
+ "F10": 0x44,
+ "NumLock": 0x45,
+ "ScrollLock": 0x46,
+ "Numpad7": 0x47,
+ "Numpad8": 0x48,
+ "Numpad9": 0x49,
+ "NumpadSubtract": 0x4a,
+ "Numpad4": 0x4b,
+ "Numpad5": 0x4c,
+ "Numpad6": 0x4d,
+ "NumpadAdd": 0x4e,
+ "Numpad1": 0x4f,
+ "Numpad2": 0x50,
+ "Numpad3": 0x51,
+ "Numpad0": 0x52,
+ "NumpadDecimal": 0x53,
+ "IntlBackslash": 0x56,
+ "F11": 0x57,
+ "F12": 0x58,
+
+ "NumpadEnter": 96,
+ "ControlRight": 97,
+ "NumpadDivide": 98,
+ "AltRight": 100,
+ "Home": 102,
+ "ArrowUp": 103,
+ "PageUp": 104,
+ "ArrowLeft": 105,
+ "ArrowRight": 106,
+ "End": 107,
+ "ArrowDown": 108,
+ "PageDown": 109,
+ "Insert": 110,
+ "Delete": 111,
+ "OSLeft": 125,
+ "OSRight": 126,
+ "ContextMenu": 127,
+};
+
+GraphicDisplay.key_code_to_input_map = new Uint8Array([
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0x0E, 0x0F, 0, 0, 0, 0x1C, 0, 0,
+ 0x2A, 0x1D, 0x38, 0, 0x3A, 0, 0, 0, /* 0x10 */
+ 0, 0, 0, 0x01, 0, 0, 0, 0,
+ 0x39, 104, 109, 107, 102, 105, 103, 106, /* 0x20 */
+ 0x50, 0, 0, 0, 0, 0x52, 0x53, 0,
+ 0x0B, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, /* 0x30 */
+ 0x09, 0x0A, 0, 0x27, 0, 0x0D, 0, 0,
+ 0, 0x1E, 0x30, 0x2E, 0x20, 0x12, 0x21, 0x22, /* 0x40 */
+ 0x23, 0x17, 0x24, 0x25, 0x26, 0x32, 0x31, 0x18,
+ 0x19, 0x10, 0x13, 0x1F, 0x14, 0x16, 0x2F, 0x11, /* 0x50 */
+ 0x2D, 0x15, 0x2C, 125, 126, 127, 0, 0,
+ 0x52, 0x4F, 0x50, 0x51, 0x4B, 0x4C, 0x4D, 0x47, /* 0x60 */
+ 0x48, 0x49, 0x37, 0x4e, 0, 0x4a, 0x53, 98,
+ 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, /* 0x70 */
+ 0x43, 0x44, 0x57, 0x58, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, /* 0x80 */
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0x45, 0, 0, 0, 0, 0, 0, 0, /* 0x90 */
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, /* 0xa0 */
+ 0, 0, 0, 0, 0, 0x0C, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, /* 0xb0 */
+ 0, 0, 0x27, 0x0D, 0x33, 0x0C, 0x34, 0x35,
+ 0x29, 0, 0, 0, 0, 0, 0, 0, /* 0xc0 */
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, /* 0xd0 */
+ 0, 0, 0, 0x1A, 0x2B, 0x1B, 0x28, 0,
+ 125, 100, 0, 0, 0, 0, 0, 0, /* 0xe0 */
+ 0, 0, 0, 0, 0, 0, 0, 0,
+]);
+
+GraphicDisplay.prototype.keyHandler = function keyHandler(ev, isDown)
+{
+ var code, input_key_code;
+
+ /* At least avoid exiting the navigator if Ctrl-Q or Ctrl-W are
+ * pressed */
+ if (ev.ctrlKey) {
+ window.onbeforeunload = function() {
+ window.onbeforeunload = null;
+ return "CTRL-W or Ctrl-Q cannot be sent to the emulator.";
+ };
+ } else {
+ window.onbeforeunload = null;
+ }
+
+ if (typeof ev.code != "undefined") {
+ code = ev.code;
+ input_key_code = GraphicDisplay.code_to_input_map[code];
+ if (typeof input_key_code != "undefined") {
+// console.log("code=" + code + " isDown=" + isDown + " input_key_code=" + input_key_code);
+ this.key_pressed[input_key_code] = isDown;
+ display_key_event(isDown, input_key_code);
+
+ if (ev.stopPropagation)
+ ev.stopPropagation();
+ if (ev.preventDefault)
+ ev.preventDefault();
+ return false;
+ }
+ } else {
+ /* fallback using keyCodes. Works only with an US keyboard */
+ code = ev.keyCode;
+ if (code < 256) {
+ input_key_code = GraphicDisplay.key_code_to_input_map[code];
+// console.log("keyCode=" + code + " isDown=" + isDown + " input_key_code=" + input_key_code);
+ if (input_key_code) {
+ this.key_pressed[input_key_code] = isDown;
+ display_key_event(isDown, input_key_code);
+
+ if (ev.stopPropagation)
+ ev.stopPropagation();
+ if (ev.preventDefault)
+ ev.preventDefault();
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+GraphicDisplay.prototype.keyDownHandler = function keyDownHandler(ev)
+{
+ return this.keyHandler(ev, 1);
+}
+
+GraphicDisplay.prototype.keyUpHandler = function keyUpHandler(ev)
+{
+ return this.keyHandler(ev, 0);
+}
+
+GraphicDisplay.prototype.blurHandler = function blurHandler(ev, isDown)
+{
+ var i, n, key_pressed;
+ /* allow unloading the page */
+ window.onbeforeunload = null;
+ /* release all keys */
+ key_pressed = this.key_pressed;
+ for(i = 0; i < key_pressed.length; i++) {
+ if (key_pressed[i]) {
+ display_key_event(0, i);
+ key_pressed[i] = 0;
+ }
+ }
+}
+
+GraphicDisplay.prototype.mouseMoveHandler = function (ev)
+{
+ var x, y, rect, buttons;
+ rect = this.canvas_el.getBoundingClientRect();
+ x = ev.clientX - rect.left;
+ y = ev.clientY - rect.top;
+ buttons = ev.buttons & 7;
+// console.log("mouse: x=" + x + " y=" + y + " buttons=" + buttons);
+ display_mouse_event(x, y, buttons);
+ if (ev.stopPropagation)
+ ev.stopPropagation();
+ if (ev.preventDefault)
+ ev.preventDefault();
+ return false;
+}
+
+GraphicDisplay.prototype.wheelHandler = function (ev)
+{
+ if (ev.deltaY < 0) {
+ display_wheel_event(1);
+ } else if (ev.deltaY > 0) {
+ display_wheel_event(-1);
+ }
+ if (ev.stopPropagation)
+ ev.stopPropagation();
+ if (ev.preventDefault)
+ ev.preventDefault();
+}
+
+/* disable contextual menu */
+GraphicDisplay.prototype.onContextMenuHandler = function (ev)
+{
+ if (ev.stopPropagation)
+ ev.stopPropagation();
+ if (ev.preventDefault)
+ ev.preventDefault();
+ return false;
+}
+
+/* Network support */
+
+function Ethernet(url)
+{
+ try {
+ this.socket = new WebSocket(url);
+ } catch(err) {
+ this.socket = null;
+ console.log("Could not open websocket url=" + url);
+ return;
+ }
+ this.socket.binaryType = 'arraybuffer';
+ this.socket.onmessage = this.messageHandler.bind(this);
+ this.socket.onclose = this.closeHandler.bind(this);
+ this.socket.onopen = this.openHandler.bind(this);
+ this.socket.onerror = this.errorHandler.bind(this);
+}
+
+Ethernet.prototype.openHandler = function(e)
+{
+ net_set_carrier(1);
+}
+
+Ethernet.prototype.closeHandler = function(e)
+{
+ net_set_carrier(0);
+}
+
+Ethernet.prototype.errorHandler = function(e)
+{
+ console.log("Websocket error=" + e);
+}
+
+Ethernet.prototype.messageHandler = function(e)
+{
+ var str, buf_len, buf_addr, buf;
+ if (e.data instanceof ArrayBuffer) {
+ buf_len = e.data.byteLength;
+ buf = new Uint8Array(e.data);
+ buf_addr = _malloc(buf_len);
+ HEAPU8.set(buf, buf_addr);
+ net_write_packet(buf_addr, buf_len);
+ _free(buf_addr);
+ } else {
+ str = e.data.toString();
+ if (str.substring(0, 5) == "ping:") {
+ try {
+ this.socket.send('pong:' + str.substring(5));
+ } catch (err) {
+ }
+ }
+ }
+}
+
+Ethernet.prototype.recv_packet = function(buf)
+{
+ if (this.socket) {
+ try {
+ this.socket.send(buf);
+ } catch (err) {
+ }
+ }
+}
+
+function start_vm(user, pwd)
+{
+ var url, mem_size, cpu, params, vm_url, cmdline, cols, rows, guest_url;
+ var font_size, graphic_enable, width, height, net_url, alloc_size;
+ var drive_url, vm_file;
+
+ function loadScript(src, f) {
+ var head = document.getElementsByTagName("head")[0];
+ var script = document.createElement("script");
+ script.src = src;
+ var done = false;
+ script.onload = script.onreadystatechange = function() {
+ // attach to both events for cross browser finish detection:
+ if ( !done && (!this.readyState ||
+ this.readyState == "loaded" || this.readyState == "complete") ) {
+ done = true;
+ if (f) {
+ f();
+ }
+ script.onload = script.onreadystatechange = null;
+ head.removeChild(script);
+ }
+ };
+ head.appendChild(script);
+ }
+
+ function start()
+ {
+ /* C functions called from javascript */
+ console_write1 = Module.cwrap('console_queue_char', null, ['number']);
+ fs_import_file = Module.cwrap('fs_import_file', null, ['string', 'number', 'number']);
+ display_key_event = Module.cwrap('display_key_event', null, ['number', 'number']);
+ display_mouse_event = Module.cwrap('display_mouse_event', null, ['number', 'number', 'number']);
+ display_wheel_event = Module.cwrap('display_wheel_event', null, ['number']);
+ net_write_packet = Module.cwrap('net_write_packet', null, ['number', 'number']);
+ net_set_carrier = Module.cwrap('net_set_carrier', null, ['number']);
+
+ net_state = null;
+ if (net_url != "") {
+ net_state = new Ethernet(net_url);
+ }
+
+ Module.ccall("vm_start", null, ["string", "number", "string", "string", "number", "number", "number", "string"], [url, mem_size, cmdline, pwd, width, height, (net_state != null) | 0, drive_url]);
+ pwd = null;
+ }
+
+ /* read the parameters */
+
+ params = get_params();
+ cpu = params["cpu"] || "riscv64";
+ url = params["url"];
+ if (!url) {
+ if (cpu == "x86")
+ url = "root-x86.cfg";
+ else
+ url = "root-riscv64.cfg";
+ }
+ url = get_absolute_url(url);
+ mem_size = (params["mem"] | 0) || 128; /* in mb */
+ cmdline = params["cmdline"] || "";
+ cols = (params["cols"] | 0) || 80;
+ rows = (params["rows"] | 0) || 30;
+ font_size = (params["font_size"] | 0) || 15;
+ guest_url = params["guest_url"] || "";
+ width = (params["w"] | 0) || 1024;
+ height = (params["h"] | 0) || 640;
+ graphic_enable = params["graphic"] | 0;
+ net_url = params["net_url"] || ""; /* empty string means no network */
+ if (typeof net_url == "undefined")
+ net_url = "wss://relay.widgetry.org/";
+ drive_url = params["drive_url"] || "";
+
+ if (user) {
+ cmdline += " LOGIN_USER=" + user;
+ } else if (guest_url) {
+ cmdline += " GUEST_URL=" + guest_url;
+ }
+
+ if (graphic_enable) {
+ graphic_display = new GraphicDisplay(document.getElementById("term_container"), width, height);
+ } else {
+ width = 0;
+ height = 0;
+ /* start the terminal */
+ term = new Term(cols, rows, term_handler, 10000);
+ term.open(document.getElementById("term_container"),
+ document.getElementById("term_paste"));
+ term.term_el.style.fontSize = font_size + "px";
+ term.write("Loading...\r\n");
+ }
+
+// console.log("cpu=" + cpu + " url=" + url + " mem=" + mem_size);
+
+ switch(cpu) {
+ case "x86":
+ vm_file = "x86emu";
+ break;
+ case "riscv64":
+ case "riscv":
+ vm_file = "riscvemu64";
+ break;
+ case "riscv32":
+ vm_file = "riscvemu32";
+ break;
+ default:
+ term.writeln("Unknown cpu=" + cpu);
+ return;
+ }
+
+ if (typeof WebAssembly === "object") {
+ /* wasm support : the memory grows automatically */
+ vm_url = vm_file + "-wasm.js";
+ } else {
+ /* set the total memory */
+ alloc_size = mem_size;
+ if (cpu == "x86")
+ alloc_size += 16;
+ if (graphic_enable) {
+ /* frame buffer memory */
+ alloc_size += (width * height * 4 + 1048576 - 1) >> 20;
+ }
+ alloc_size += 32; /* extra space (XXX: reduce it ?) */
+ alloc_size = (alloc_size + 15) & -16; /* align to 16 MB */
+ Module.TOTAL_MEMORY = alloc_size << 20;
+ vm_url = vm_file + ".js";
+ }
+ Module.preRun = start;
+
+ loadScript(vm_url, null);
+}
+
+function on_login()
+{
+ var login_wrap_el = document.getElementById("wrap");
+ var term_wrap_el = document.getElementById("term_wrap");
+ var form = document.getElementById("form");
+ var status = document.getElementById("status");
+ var user = form.user.value;
+ var pwd = form.password.value;
+
+ if (user.length <= 1) {
+ status.innerHTML = "User name must be provided";
+ return false;
+ }
+
+ login_wrap_el.style.display = "none";
+ term_wrap_el.style.display = "block";
+ form.password.value = "";
+ form.user.value = "";
+
+ start_vm(user, pwd);
+
+ return false;
+}
+
+(function() {
+ var login, params;
+
+ params = get_params();
+ login = params["login"] || 0;
+ if (login) {
+ var login_wrap_el = document.getElementById("wrap");
+ login_wrap_el.style.display = "block";
+ } else {
+ var term_wrap_el = document.getElementById("term_wrap");
+ term_wrap_el.style.display = "block";
+ start_vm(null, null);
+ }
+})();
diff --git a/web/style.css b/web/style.css
new file mode 100644
index 0000000..9d39504
--- /dev/null
+++ b/web/style.css
@@ -0,0 +1,41 @@
+.term {
+ font-family: courier,fixed,swiss,monospace,sans-serif;
+ font-size: 15px;
+ color: #f0f0f0;
+ background: #000000;
+}
+
+.term_content a {
+ color: #ffff00;
+}
+
+.term_cursor {
+ color: #000000;
+ background: #00ff00;
+}
+
+.term_scrollbar { background: transparent url(images/bg-scrollbar-track-y.png) no-repeat 0 0; position: relative; background-position: 0 0; float: right; width: 15px; height: 100%; }
+.term_track { background: transparent url(images/bg-scrollbar-trackend-y.png) no-repeat 0 100%; height: 100%; width:13px; position: relative; padding: 0 1px; }
+.term_thumb { background: transparent url(images/bg-scrollbar-thumb-y.png) no-repeat 50% 100%; height: 20px; width: 25px; cursor: pointer; overflow: hidden; position: absolute; top: 0; left: -5px; }
+.term_thumb .term_end { background: transparent url(images/bg-scrollbar-thumb-y.png) no-repeat 50% 0; overflow: hidden; height: 5px; width: 25px; }
+.noSelect { user-select: none; -o-user-select: none; -moz-user-select: none; -khtml-user-select: none; -webkit-user-select: none; }
+#term_paste {
+ border: 1px solid;
+ height: 19px;
+}
+
+/* file import */
+#files {
+ visibility: hidden;
+ width:1px;
+ height:1px;
+ padding: 0px;
+ margin: 0px;
+ bordex: 0px;
+}
+
+label {
+ cursor: pointer;
+ margin-left: 5px;
+ margin-right: 5px;
+}
diff --git a/web/term.js b/web/term.js
new file mode 100644
index 0000000..1a9ca6c
--- /dev/null
+++ b/web/term.js
@@ -0,0 +1,1251 @@
+/*
+ * Javascript terminal
+ *
+ * Copyright (c) 2011-2017 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+"use strict";
+
+function Term(width, height, handler, tot_height)
+{
+ this.w = width;
+ this.h = height;
+
+ this.cur_h = height; /* current height of the scroll back buffer */
+ if (!tot_height || tot_height < height)
+ tot_height = height;
+ this.tot_h = tot_height; /* maximum height of the scroll back buffer */
+ this.y_base = 0; /* position of the current top screen line in the
+ * scroll back buffer */
+ this.y_disp = 0; /* position of the top displayed line in the
+ * scroll back buffer */
+ /* cursor position */
+ this.x = 0;
+ this.y = 0;
+ this.scroll_top = 0;
+ this.scroll_bottom = this.h;
+ this.cursorstate = 0;
+ this.handler = handler;
+ this.state = 0;
+ this.output_queue = "";
+ this.colors = [
+ /* normal */
+ "#000000",
+ "#aa0000",
+ "#00aa00",
+ "#aa5500",
+ "#0000aa",
+ "#aa00aa",
+ "#00aaaa",
+ "#aaaaaa",
+ /* bright */
+ "#555555",
+ "#ff5555",
+ "#55ff55",
+ "#ffff55",
+ "#5555ff",
+ "#ff55ff",
+ "#55ffff",
+ "#ffffff"
+ ];
+ /* attributes bits:
+ 0-3: bg
+ 4-7: fg
+ 8: bold
+ 9: inverse
+ */
+ this.def_attr = (7 << 4) | 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 = "";
+
+ this.utf8 = true;
+ this.utf8_state = 0;
+ this.utf8_val = 0;
+
+ this.application_cursor = false;
+ this.application_keypad = false;
+ /* if true, emulate some behaviors of the Linux console */
+ this.linux_console = true;
+}
+
+Term.prototype.open = function(parent_el, textarea_el)
+{
+ var y, line, i, term, c, row_el;
+
+ /* set initial content */
+ this.lines = new Array();
+ c = 32 | (this.def_attr << 16);
+ for(y = 0; y < this.cur_h;y++) {
+ line = new Array();
+ for(i=0;i= 0;
+ }
+
+ function right_trim(str, a)
+ {
+ var i, n;
+ n = a.length;
+ i = str.length;
+ while (i >= n && str.substr(i - n, n) == a)
+ i -= n;
+ return str.substr(0, i);
+ }
+
+ for(y = ymin; y <= ymax; y++) {
+ /* convert to HTML string */
+ y1 = y + this.y_disp;
+ if (y1 >= this.cur_h)
+ y1 -= this.cur_h;
+ line = this.lines[y1];
+ outline = "";
+ w = this.w;
+ if (y == this.y && this.cursor_state &&
+ this.y_disp == this.y_base) {
+ cx = this.x;
+ } else {
+ cx = -1;
+ }
+ last_attr = this.def_attr;
+ http_link_len = 0;
+ for(i = 0; i < w; i++) {
+ c = line[i];
+ attr = c >> 16;
+ c &= 0xffff;
+ /* test for http link */
+ if (c == 0x68 && (w - i) >= 8 && http_link_len == 0) {
+ /* test http:// or https:// */
+ if ((line[i + 1] & 0xffff) == 0x74 &&
+ (line[i + 2] & 0xffff) == 0x74 &&
+ (line[i + 3] & 0xffff) == 0x70 &&
+ (((line[i + 4] & 0xffff) == 0x3a &&
+ (line[i + 5] & 0xffff) == 0x2f &&
+ (line[i + 6] & 0xffff) == 0x2f) ||
+ ((line[i + 4] & 0xffff) == 0x73 &&
+ (line[i + 5] & 0xffff) == 0x3a &&
+ (line[i + 6] & 0xffff) == 0x2f &&
+ (line[i + 7] & 0xffff) == 0x2f))) {
+ http_link_str = "";
+ j = 0;
+ while ((i + j) < w &&
+ is_http_link_char(line[i + j] & 0xffff)) {
+ http_link_str += String.fromCharCode(line[i + j] & 0xffff);
+ j++;
+ }
+ http_link_len = j;
+ if (last_attr != this.def_attr) {
+ outline += '';
+ last_attr = this.def_attr;
+ }
+ outline += "";
+ }
+ }
+ if (i == cx) {
+ attr = -1; /* cursor */
+ }
+ if (attr != last_attr) {
+ if (last_attr != this.def_attr)
+ outline += '';
+ if (attr != this.def_attr) {
+ if (attr == -1) {
+ /* cursor */
+ outline += '';
+ } else {
+ outline += '';
+ }
+ }
+ }
+ switch(c) {
+ case 32:
+ outline += " ";
+ break;
+ case 38: // '&'
+ outline += "&";
+ break;
+ case 60: // '<'
+ outline += "<";
+ break;
+ case 62: // '>'
+ outline += ">";
+ break;
+ default:
+ if (c < 32) {
+ outline += " ";
+ } else {
+ outline += String.fromCharCode(c);
+ }
+ break;
+ }
+ last_attr = attr;
+ if (http_link_len != 0) {
+ http_link_len--;
+ if (http_link_len == 0) {
+ if (last_attr != this.def_attr) {
+ outline += '';
+ last_attr = this.def_attr;
+ }
+ outline += "";
+ }
+ }
+ }
+ if (last_attr != this.def_attr) {
+ outline += '';
+ }
+
+ /* trim trailing spaces for copy/paste */
+ outline = right_trim(outline, " ");
+ if (outline == "")
+ outline = " ";
+
+ this.rows_el[y].innerHTML = outline;
+ }
+
+ this.refresh_scrollbar();
+};
+
+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);
+ }
+};
+
+/* scroll down or up in the scroll back buffer by n lines */
+Term.prototype.scroll_disp = function(n)
+{
+ var i, y1;
+ /* slow but it does not really matters */
+ 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;
+ y1 = this.y_base + this.h;
+ if (y1 >= this.cur_h)
+ y1 -= this.cur_h;
+ for(i = 0; i < n; i++) {
+ if (this.y_disp == y1)
+ break;
+ if (--this.y_disp < 0)
+ this.y_disp = this.cur_h - 1;
+ }
+ }
+ this.refresh(0, this.h - 1);
+};
+
+Term.prototype.write = function(str)
+{
+ var s, ymin, ymax;
+
+ function update(y)
+ {
+ ymin = Math.min(ymin, y);
+ ymax = Math.max(ymax, y);
+ }
+
+ function get_erase_char()
+ {
+ var bg_mask, attr;
+ bg_mask = 0xf;
+ attr = (s.def_attr & ~bg_mask) | (s.cur_attr & bg_mask);
+ return 32 | (attr << 16);
+ }
+
+ function erase_chars(x1, x2, y) {
+ var l, i, c, y1;
+ y1 = s.y_base + y;
+ if (y1 >= s.cur_h)
+ y1 -= s.cur_h;
+ l = s.lines[y1];
+ c = get_erase_char();
+ for(i = x1; i < x2; i++)
+ l[i] = c;
+ update(y);
+ }
+
+ function erase_to_eol(x, y) {
+ erase_chars(x, s.w, y);
+ }
+
+ function erase_in_line(n) {
+ switch(n) {
+ case 0:
+ erase_to_eol(s.x, s.y);
+ break;
+ case 1:
+ erase_chars(0, s.x + 1, s.y);
+ break;
+ case 2:
+ erase_chars(0, s.w, s.y);
+ break;
+ }
+ }
+
+ function erase_in_display(n) {
+ var y;
+ switch(n) {
+ case 0:
+ erase_to_eol(s.x, s.y);
+ for(y = s.y + 1; y < s.h; y++)
+ erase_to_eol(0, y);
+ break;
+ case 1:
+ erase_chars(0, s.x + 1, s.y);
+ for(y = 0; y < s.y; y++) {
+ erase_to_eol(0, y);
+ }
+ break;
+ case 2:
+ for(y = 0; y < s.h; y++) {
+ erase_to_eol(0, y);
+ }
+ break;
+ }
+ }
+
+
+ function delete_chars(n)
+ {
+ var l, i, c, y1, j;
+ y1 = s.y + s.y_base;
+ if (y1 >= s.cur_h)
+ y1 -= s.cur_h;
+ l = s.lines[y1];
+ if (n < 1)
+ n = 1;
+ c = get_erase_char();
+ j = s.x + n;
+ for(i = s.x; i < s.w; i++) {
+ if (j < s.w)
+ l[i] = l[j];
+ else
+ l[i] = c;
+ j++;
+ }
+ update(s.y);
+ }
+
+ function insert_chars(n)
+ {
+ var l, i, c, y1, x1;
+ if (n < 1)
+ n = 1;
+ if (n > s.w - s.x)
+ n = s.w - s.x;
+ y1 = s.y + s.y_base;
+ if (y1 >= s.cur_h)
+ y1 -= s.cur_h;
+ l = s.lines[y1];
+ x1 = s.x + n;
+ for(i = s.w - 1; i >= x1; i--)
+ l[i] = l[i - n];
+ c = get_erase_char();
+ for(i = s.x; i < x1; i++)
+ l[i] = c;
+ update(s.y);
+ }
+
+ function csi_colors(esc_params)
+ {
+ var j, n, fg, bg, mask;
+
+ if (esc_params.length == 0) {
+ s.cur_attr= s.def_attr;
+ } else {
+ for(j = 0; j < esc_params.length; j++) {
+ n = esc_params[j];
+ if (n >= 30 && n <= 37) {
+ /* foreground */
+ fg = n - 30;
+ s.cur_attr = (s.cur_attr & ~(0xf << 4)) | (fg << 4);
+ } else if (n >= 40 && n <= 47) {
+ /* background */
+ bg = n - 40;
+ s.cur_attr = (s.cur_attr & ~0xf) | bg;
+ } else if (n >= 90 && n <= 97) {
+ /* bright foreground */
+ fg = n - 90 + 8;
+ s.cur_attr = (s.cur_attr & ~(0xf << 4)) | (fg << 4);
+ } else if (n >= 100 && n <= 107) {
+ /* bright background */
+ bg = n - 100 + 8;
+ s.cur_attr = (s.cur_attr & ~0xf) | bg;
+ } else if (n == 1) {
+ /* bold + bright */
+ s.cur_attr |= (1 << 8);
+ } else if (n == 0) {
+ /* default attr */
+ s.cur_attr = s.def_attr;
+ } else if (n == 7) {
+ /* inverse */
+ s.cur_attr |= (1 << 9);
+ } else if (n == 27) {
+ /* not inverse */
+ s.cur_attr &= ~(1 << 9);
+ } else if (n == 39) {
+ /* reset fg */
+ mask = 0x0f << 4;
+ s.cur_attr = (s.cur_attr & ~mask) | (s.def_attr & mask);
+ } else if (n == 49) {
+ /* reset bg */
+ mask = 0x0f;
+ s.cur_attr = (s.cur_attr & ~mask) | (s.def_attr & mask);
+ }
+ }
+ }
+ }
+
+ function empty_line(y, use_erase_char) {
+ var line, c, y1, x;
+ if (use_erase_char)
+ c = get_erase_char();
+ else
+ c = 32 | (s.def_attr << 16);
+ line = new Array();
+ for(x=0;x= s.cur_h)
+ y1 -= s.cur_h;
+ s.lines[y1] = line;
+ }
+
+ function scroll_down(top, bottom, use_erase_char)
+ {
+ var y, line, y1, y2;
+
+ if (top == 0 && bottom == s.h) {
+ /* increase height of buffer if possible */
+ if (s.cur_h < s.tot_h) {
+ s.cur_h++;
+ }
+ /* move down one line */
+ if (++s.y_base == s.cur_h)
+ s.y_base = 0;
+ s.y_disp = s.y_base;
+ } else {
+ /* partial scroll */
+ for(y = top; y < bottom - 1; y++) {
+ y1 = s.y_base + y;
+ if (y1 >= s.cur_h)
+ y1 -= s.cur_h;
+ y2 = y1 + 1;
+ if (y2 >= s.cur_h)
+ y2 -= s.cur_h;
+ s.lines[y1] = s.lines[y2];
+ }
+ }
+ empty_line(bottom - 1, use_erase_char);
+ update(top);
+ update(bottom - 1);
+ }
+
+ function scroll_up(top, bottom, use_erase_char) {
+ var y, y1, y2;
+ /* XXX: could scroll in the history */
+ for(y = bottom - 1; y > top; y--) {
+ y1 = s.y_base + y;
+ if (y1 >= s.cur_h)
+ y1 -= s.cur_h;
+ y2 = y1 - 1;
+ if (y2 >= s.cur_h)
+ y2 -= s.cur_h;
+ s.lines[y1] = s.lines[y2];
+ }
+ empty_line(top, use_erase_char);
+ update(top);
+ update(bottom - 1);
+ }
+
+ function down_with_scroll() {
+ s.y++;
+ if (s.y == s.scroll_bottom) {
+ s.y--;
+ scroll_down(s.scroll_top, s.scroll_bottom, false);
+ } else if (s.y >= s.h) {
+ s.y--;
+ scroll_down(0, s.h, false);
+ }
+ }
+
+ function up_with_scroll() {
+ if (s.y == s.scroll_top) {
+ scroll_up(s.scroll_top, s.scroll_bottom, true);
+ } else if (s.y == 0) {
+ scroll_up(0, s.h, true);
+ } else {
+ s.y--;
+ }
+ }
+
+ function insert_lines(n) {
+ var y2;
+ if (n < 1)
+ n = 1;
+ if (s.y < s.scroll_bottom)
+ y2 = s.scroll_bottom;
+ else
+ y2 = s.h;
+ while (n != 0) {
+ scroll_up(s.y, y2, true);
+ n--;
+ }
+ }
+
+ function delete_lines(n) {
+ var y2;
+ if (n < 1)
+ n = 1;
+ if (s.y < s.scroll_bottom)
+ y2 = s.scroll_bottom;
+ else
+ y2 = s.h;
+ while (n != 0) {
+ scroll_down(s.y, y2, true);
+ n--;
+ }
+ }
+
+ var TTY_STATE_NORM = 0;
+ var TTY_STATE_ESC = 1;
+ var TTY_STATE_CSI = 2;
+ var TTY_STATE_CHARSET = 3;
+
+ function handle_char(c) {
+ var i, l, n, j, y1, y2, x1;
+
+ switch(s.state) {
+ case TTY_STATE_NORM:
+ switch(c) {
+ case 10:
+ down_with_scroll();
+ break;
+ case 13:
+ s.x = 0;
+ break;
+ case 8:
+ if (s.x > 0) {
+ s.x--;
+ }
+ break;
+ case 9: /* tab */
+ n = (s.x + 8) & ~7;
+ if (n <= s.w) {
+ s.x = n;
+ }
+ break;
+ case 27:
+ s.state = TTY_STATE_ESC;
+ break;
+ default:
+ if (c >= 32) {
+ if (s.x >= s.w) {
+ s.x = 0;
+ down_with_scroll();
+ }
+ y1 = s.y + s.y_base;
+ if (y1 >= s.cur_h)
+ y1 -= s.cur_h;
+ s.lines[y1][s.x] = (c & 0xffff) |
+ (s.cur_attr << 16);
+ s.x++;
+ update(s.y);
+ }
+ break;
+ }
+ break;
+ case TTY_STATE_ESC:
+ switch(c) {
+ case 91: // '['
+ s.esc_params = new Array();
+ s.cur_param = 0;
+ s.esc_prefix = 0;
+ s.state = TTY_STATE_CSI;
+ break;
+ case 40: // '('
+ case 41: // ')'
+ s.state = TTY_STATE_CHARSET;
+ break;
+ case 61: // '='
+ s.application_keypad = true;
+ s.state = TTY_STATE_NORM;
+ break;
+ case 62: // '>'
+ s.application_keypad = false;
+ s.state = TTY_STATE_NORM;
+ break;
+ case 77: // 'M'
+ up_with_scroll();
+ s.state = TTY_STATE_NORM;
+ break;
+ default:
+ s.state = TTY_STATE_NORM;
+ break;
+ }
+ break;
+ case TTY_STATE_CSI:
+ if (c >= 48 && c <= 57) { // '0' '9'
+ /* numeric */
+ s.cur_param = s.cur_param * 10 + c - 48;
+ } else {
+ if (c == 63) { // '?'
+ s.esc_prefix = c;
+ break;
+ }
+ /* add parsed parameter */
+ s.esc_params[s.esc_params.length] = s.cur_param;
+ s.cur_param = 0;
+ if (c == 59) // ;
+ break;
+ s.state = TTY_STATE_NORM;
+
+ // console.log("term: csi=" + s.esc_params + " cmd="+c);
+ switch(c) {
+ case 64: // '@' insert chars
+ insert_chars(s.esc_params[0]);
+ break;
+ case 65: // 'A' up
+ n = s.esc_params[0];
+ if (n < 1)
+ n = 1;
+ s.y -= n;
+ if (s.y < 0)
+ s.y = 0;
+ break;
+ case 66: // 'B' down
+ n = s.esc_params[0];
+ if (n < 1)
+ n = 1;
+ s.y += n;
+ if (s.y >= s.h)
+ s.y = s.h - 1;
+ break;
+ case 67: // 'C' right
+ n = s.esc_params[0];
+ if (n < 1)
+ n = 1;
+ s.x += n;
+ if (s.x >= s.w - 1)
+ s.x = s.w - 1;
+ break;
+ case 68: // 'D' left
+ n = s.esc_params[0];
+ if (n < 1)
+ n = 1;
+ s.x -= n;
+ if (s.x < 0)
+ s.x = 0;
+ break;
+ case 71: /* 'G' cursor character absolute */
+ x1 = s.esc_params[0] - 1;
+ if (x1 < 0)
+ x1 = 0;
+ else if (x1 >= s.w)
+ x1 = s.w - 1;
+ s.x = x1;
+ break;
+ case 72: // 'H' goto xy
+ y1 = s.esc_params[0] - 1;
+ if (s.esc_params.length >= 2)
+ x1 = s.esc_params[1] - 1;
+ else
+ x1 = 0;
+ if (y1 < 0)
+ y1 = 0;
+ else if (y1 >= s.h)
+ y1 = s.h - 1;
+ if (x1 < 0)
+ x1 = 0;
+ else if (x1 >= s.w)
+ x1 = s.w - 1;
+ s.x = x1;
+ s.y = y1;
+ break;
+ case 74: // 'J' erase in display
+ erase_in_display(s.esc_params[0]);
+ break;
+ case 75: // 'K' erase in line
+ erase_in_line(s.esc_params[0]);
+ break;
+ case 76: // 'L' insert lines
+ insert_lines(s.esc_params[0]);
+ break;
+ case 77: // 'M' insert lines
+ delete_lines(s.esc_params[0]);
+ break;
+ case 80: // 'P'
+ delete_chars(s.esc_params[0]);
+ break;
+ case 100: // 'd' line position absolute
+ {
+ y1 = s.esc_params[0] - 1;
+ if (y1 < 0)
+ y1 = 0;
+ else if (y1 >= s.h)
+ y1 = s.h - 1;
+ s.y = y1;
+ }
+ break;
+ case 104: // 'h': set mode
+ if (s.esc_prefix == 63 && s.esc_params[0] == 1) {
+ s.application_cursor = true;
+ }
+ break;
+ case 108: // 'l': reset mode
+ if (s.esc_prefix == 63 && s.esc_params[0] == 1) {
+ s.application_cursor = false;
+ }
+ break;
+ case 109: // 'm': set color
+ csi_colors(s.esc_params);
+ break;
+ case 110: // 'n' return the cursor position
+ s.queue_chars("\x1b[" + (s.y + 1) + ";" + (s.x + 1) + "R");
+ break;
+ case 114: // 'r' set scroll region
+ y1 = s.esc_params[0] - 1;
+ if (y1 < 0)
+ y1 = 0;
+ else if (y1 >= s.h)
+ y1 = s.h - 1;
+ if (s.esc_params.length >= 2)
+ y2 = s.esc_params[1];
+ else
+ y2 = s.h;
+ if (y2 >= s.h || y2 <= y1)
+ y2 = s.h;
+ s.scroll_top = y1;
+ s.scroll_bottom = y2;
+ s.x = 0;
+ s.y = 0;
+ break;
+ default:
+ break;
+ }
+ }
+ break;
+ case TTY_STATE_CHARSET:
+ /* just ignore */
+ s.state = TTY_STATE_NORM;
+ break;
+ }
+ }
+
+ function handle_utf8(c) {
+ if (s.utf8_state !== 0 && (c >= 0x80 && c < 0xc0)) {
+ s.utf8_val = (s.utf8_val << 6) | (c & 0x3F);
+ s.utf8_state--;
+ if (s.utf8_state === 0) {
+ handle_char(s.utf8_val);
+ }
+ } else if (c >= 0xc0 && c < 0xf8) {
+ s.utf8_state = 1 + (c >= 0xe0) + (c >= 0xf0);
+ s.utf8_val = c & ((1 << (6 - s.utf8_state)) - 1);
+ } else {
+ s.utf8_state = 0;
+ handle_char(c);
+ }
+ }
+
+ var i, c, utf8;
+
+ /* update region is in ymin ymax */
+ s = this;
+ ymin = s.h;
+ ymax = -1;
+ update(s.y); // remove the cursor
+ /* reset top of displayed screen to top of real screen */
+ if (s.y_base != s.y_disp) {
+ s.y_disp = s.y_base;
+ /* force redraw */
+ ymin = 0;
+ ymax = s.h - 1;
+ }
+ utf8 = s.utf8;
+ for(i = 0; i < str.length; i++) {
+ c = str.charCodeAt(i);
+ if (utf8)
+ handle_utf8(c);
+ else
+ handle_char(c);
+ }
+ update(s.y); // show the cursor
+
+ if (ymax >= ymin)
+ s.refresh(ymin, ymax);
+};
+
+Term.prototype.writeln = function (str)
+{
+ this.write(str + '\r\n');
+};
+
+Term.prototype.interceptBrowserExit = function (ev)
+{
+ /* At least avoid exiting the navigator if Ctrl-Q or Ctrl-W are
+ * pressed */
+ if (ev.ctrlKey) {
+ window.onbeforeunload = function() {
+ window.onbeforeunload = null;
+ return "CTRL-W or Ctrl-Q cannot be sent to the emulator.";
+ };
+ } else {
+ window.onbeforeunload = null;
+ }
+}
+
+Term.prototype.keyDownHandler = function (ev)
+{
+ var str;
+
+ this.interceptBrowserExit(ev);
+
+ str="";
+ switch(ev.keyCode) {
+ case 8: /* backspace */
+ str = "\x7f";
+ break;
+ case 9: /* tab */
+ str = "\x09";
+ break;
+ case 13: /* enter */
+ str = "\x0d";
+ break;
+ case 27: /* escape */
+ str = "\x1b";
+ break;
+ case 37: /* left */
+ if (ev.ctrlKey) {
+ str = "\x1b[1;5D";
+ } else if (this.application_cursor) {
+ str = "\x1bOD";
+ } else {
+ str = "\x1b[D";
+ }
+ break;
+ case 39: /* right */
+ if (ev.ctrlKey) {
+ str = "\x1b[1;5C";
+ } else if (this.application_cursor) {
+ str = "\x1bOC";
+ } else {
+ str = "\x1b[C";
+ }
+ break;
+ case 38: /* up */
+ if (ev.ctrlKey) {
+ this.scroll_disp(-1);
+ } else if (this.application_cursor) {
+ str = "\x1bOA";
+ } else {
+ str = "\x1b[A";
+ }
+ break;
+ case 40: /* down */
+ if (ev.ctrlKey) {
+ this.scroll_disp(1);
+ } else if (this.application_cursor) {
+ str = "\x1bOB";
+ } else {
+ str = "\x1b[B";
+ }
+ break;
+ case 46: /* delete */
+ str = "\x1b[3~";
+ break;
+ case 45: /* insert */
+ str = "\x1b[2~";
+ break;
+ case 36: /* home */
+ if (this.linux_console)
+ str = "\x1b[1~";
+ else if (this.application_keypad)
+ str = "\x1bOH";
+ else
+ str = "\x1b[H";
+ break;
+ case 35: /* end */
+ if (this.linux_console)
+ str = "\x1b[4~";
+ else if (this.application_keypad)
+ str = "\x1bOF";
+ else
+ str = "\x1b[F";
+ break;
+ case 33: /* page up */
+ if (ev.ctrlKey) {
+ this.scroll_disp(-(this.h - 1));
+ } else {
+ str = "\x1b[5~";
+ }
+ break;
+ case 34: /* page down */
+ if (ev.ctrlKey) {
+ this.scroll_disp(this.h - 1);
+ } else {
+ str = "\x1b[6~";
+ }
+ break;
+ default:
+ if (ev.ctrlKey) {
+ /* ctrl + key */
+ if (ev.keyCode >= 65 && ev.keyCode <= 90) {
+ str = String.fromCharCode(ev.keyCode - 64);
+ } else if (ev.keyCode == 32) {
+ str = String.fromCharCode(0);
+ }
+ } else if ((!this.is_mac && ev.altKey) ||
+ (this.is_mac && ev.metaKey)) {
+ /* meta + key (Note: we only send lower case) */
+ if (ev.keyCode >= 65 && ev.keyCode <= 90) {
+ str = "\x1b" + String.fromCharCode(ev.keyCode + 32);
+ }
+ }
+ break;
+ }
+ // console.log("keydown: keycode=" + ev.keyCode + " charcode=" + ev.charCode + " str=" + str + " ctrl=" + ev.ctrlKey + " alt=" + ev.altKey + " meta=" + ev.metaKey);
+ if (str) {
+ if (ev.stopPropagation)
+ ev.stopPropagation();
+ if (ev.preventDefault)
+ ev.preventDefault();
+
+ this.show_cursor();
+ this.key_rep_state = 1;
+ this.key_rep_str = str;
+ this.handler(str);
+ return false;
+ } else {
+ this.key_rep_state = 0;
+ return true;
+ }
+};
+
+Term.prototype.keyUpHandler = function (ev)
+{
+ this.interceptBrowserExit(ev);
+};
+
+Term.prototype.to_utf8 = function(s)
+{
+ var i, n = s.length, r, c;
+ r = "";
+ for(i = 0; i < n; i++) {
+ c = s.charCodeAt(i);
+ if (c < 0x80) {
+ r += String.fromCharCode(c);
+ } else if (c < 0x800) {
+ r += String.fromCharCode((c >> 6) | 0xc0, (c & 0x3f) | 0x80);
+ } else if (c < 0x10000) {
+ r += String.fromCharCode((c >> 12) | 0xe0,
+ ((c >> 6) & 0x3f) | 0x80,
+ (c & 0x3f) | 0x80);
+ } else {
+ r += String.fromCharCode((c >> 18) | 0xf0,
+ ((c >> 12) & 0x3f) | 0x80,
+ ((c >> 6) & 0x3f) | 0x80,
+ (c & 0x3f) | 0x80);
+ }
+ }
+ return r;
+}
+
+Term.prototype.keyPressHandler = function (ev)
+{
+ var str, char_code;
+
+ if (ev.stopPropagation)
+ ev.stopPropagation();
+ if (ev.preventDefault)
+ ev.preventDefault();
+
+ str="";
+ if (!("charCode" in ev)) {
+ /* on Opera charCode is not defined and keypress is sent for
+ system keys. Moreover, only keupress is repeated which is a
+ problem for system keys. */
+ char_code = ev.keyCode;
+ if (this.key_rep_state == 1) {
+ this.key_rep_state = 2;
+ return false;
+ } else if (this.key_rep_state == 2) {
+ /* repetition */
+ this.show_cursor();
+ this.handler(this.key_rep_str);
+ return false;
+ }
+ } else {
+ char_code = ev.charCode;
+ }
+ if (char_code != 0) {
+ if (!ev.ctrlKey &&
+ ((!this.is_mac && !ev.altKey) ||
+ (this.is_mac && !ev.metaKey))) {
+ str = String.fromCharCode(char_code);
+ }
+ }
+ // console.log("keypress: keycode=" + ev.keyCode + " charcode=" + ev.charCode + " str=" + str + " ctrl=" + ev.ctrlKey + " alt=" + ev.altKey + " meta=" + ev.metaKey);
+ if (str) {
+ this.show_cursor();
+ if (this.utf8)
+ str = this.to_utf8(str);
+ this.handler(str);
+ return false;
+ } else {
+ return true;
+ }
+};
+
+Term.prototype.blurHandler = function (ev)
+{
+ /* allow unloading the page */
+ window.onbeforeunload = null;
+};
+
+Term.prototype.wheelHandler = function (ev)
+{
+ if (ev.deltaY < 0)
+ this.scroll_disp(-3);
+ else if (ev.deltaY > 0)
+ this.scroll_disp(3);
+ ev.stopPropagation();
+}
+
+Term.prototype.mouseDownHandler = function (ev)
+{
+ this.thumb_el.onmouseup = this.mouseUpHandler.bind(this);
+ document.onmousemove = this.mouseMoveHandler.bind(this);
+ document.onmouseup = this.mouseUpHandler.bind(this);
+
+ /* disable potential selection */
+ document.body.className += " noSelect";
+
+ this.mouseMoveHandler(ev);
+}
+
+Term.prototype.mouseMoveHandler = function (ev)
+{
+ var total_size, pos, new_y_disp, y, y0;
+ total_size = this.term_el.clientHeight;
+ y = ev.clientY - this.track_el.getBoundingClientRect().top;
+ pos = Math.floor((y - (this.thumb_size / 2)) * this.cur_h / total_size);
+ new_y_disp = Math.min(Math.max(pos, 0), this.cur_h - this.h);
+ /* position of the first line of the scroll back buffer */
+ y0 = (this.y_base + this.h) % this.cur_h;
+ new_y_disp += y0;
+ if (new_y_disp >= this.cur_h)
+ new_y_disp -= this.cur_h;
+ if (new_y_disp != this.y_disp) {
+ this.y_disp = new_y_disp;
+ this.refresh(0, this.h - 1);
+ }
+}
+
+Term.prototype.mouseUpHandler = function (ev)
+{
+ this.thumb_el.onmouseup = null;
+ document.onmouseup = null;
+ document.onmousemove = null;
+ document.body.className = document.body.className.replace(" noSelect", "");
+}
+
+Term.prototype.pasteHandler = function (ev)
+{
+ var c = ev.clipboardData, str;
+ if (c) {
+ str = c.getData("text/plain");
+ if (this.utf8)
+ str = this.to_utf8(str);
+ this.queue_chars(str);
+ setTimeout(this.textAreaReset.bind(this), 10);
+ return false;
+ }
+}
+
+Term.prototype.textAreaReset = function(ev)
+{
+ /* reset text */
+ this.textarea_el.value = "Paste Here";
+}
+
+/* output queue to send back asynchronous responses */
+Term.prototype.queue_chars = function (str)
+{
+ this.output_queue += str;
+ 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 = "";
+ }
+};
+
+Term.prototype.getSize = function ()
+{
+ return [this.w, this.h];
+};