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 + + + + +
+
+
+
+ + + + +
+
+ + + + + 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]; +};