This is an automated email from the ASF dual-hosted git repository. xiaoxiang pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/nuttx-website.git
The following commit(s) were added to refs/heads/master by this push: new 69485786 Add NuttX Online Demo 69485786 is described below commit 69485786e52ff306670ab74f7df988cf8658ee69 Author: Lee Lup Yuen <lu...@appkaki.com> AuthorDate: Tue Jan 23 08:40:36 2024 +0000 Add NuttX Online Demo This PR adds the NuttX Online Demo based on [TinyEMU 64-bit RISC-V Emulator](https://github.com/fernandotcl/TinyEMU) and WebAssembly. The [TinyEMU Port of NuttX](https://github.com/lupyuen2/wip-pinephone-nuttx/pull/54) shall be upstreamed later to NuttX Mainline. This will allow the Online Demo to be refreshed for new releases of NuttX. [More about NuttX for TinyEMU](https://lupyuen.github.io/articles/tinyemu) Modified Files: `index.md`: Add link to Demo Page: "Try the online demo here" `_includes/themes/apache/default.html`: Add Demo CSS New Files: `demo.md`: New Demo Page with NuttX in TinyEMU Emulator `demo/nuttx.bin`: NuttX Image compiled from the upcoming [TinyEMU Port of NuttX](https://github.com/lupyuen2/wip-pinephone-nuttx/pull/54) (64-bit RISC-V). Compiled with [these steps](https://github.com/lupyuen2/wip-pinephone-nuttx/releases/tag/tinyemu4-1) to produce these [Build Outputs](https://github.com/lupyuen2/wip-pinephone-nuttx/releases/tag/tinyemu4-1). `demo/*.js, *.wasm, *.cfg, *.png`: Demo Files from [TinyEMU](https://github.com/fernandotcl/TinyEMU) project, based on [MIT Licence](https://github.com/fernandotcl/TinyEMU/blob/master/MIT-LICENSE.txt). `assets/themes/apache/css/demo.css`: Demo CSS from [TinyEMU](https://github.com/fernandotcl/TinyEMU) project, based on [MIT Licence](https://github.com/fernandotcl/TinyEMU/blob/master/MIT-LICENSE.txt). `_includes/themes/apache/demo.html`: New Theme for Demo Page `_layouts/demo.html`: New Layout for Demo Page --- _includes/themes/apache/default.html | 1 + _includes/themes/apache/demo.html | 22 + _layouts/demo.html | 5 + assets/themes/apache/css/demo.css | 105 +++ index.md => demo.md | 19 +- demo/images/bg-scrollbar-thumb-y.png | Bin 0 -> 2888 bytes demo/images/bg-scrollbar-track-y.png | Bin 0 -> 960 bytes demo/images/bg-scrollbar-trackend-y.png | Bin 0 -> 211 bytes demo/images/upload-icon.png | Bin 0 -> 530 bytes demo/jslinux.js | 667 ++++++++++++++ demo/nuttx.bin | Bin 0 -> 246848 bytes demo/riscvemu64-wasm.js | 4 + demo/riscvemu64-wasm.wasm | Bin 0 -> 221309 bytes demo/root-riscv64.cfg | 7 + demo/term.js | 1460 +++++++++++++++++++++++++++++++ index.md | 5 + 16 files changed, 2280 insertions(+), 15 deletions(-) diff --git a/_includes/themes/apache/default.html b/_includes/themes/apache/default.html index bfd98222..8a5356dc 100644 --- a/_includes/themes/apache/default.html +++ b/_includes/themes/apache/default.html @@ -18,6 +18,7 @@ <link href="{{ site.baseurl }}/assets/themes/apache/bootstrap/css/bootstrap.css" rel="stylesheet"> <link href="{{ site.baseurl }}/assets/themes/apache/css/style.css?body=1" rel="stylesheet" type="text/css"> <link href="{{ site.baseurl }}/assets/themes/apache/css/syntax.css" rel="stylesheet" type="text/css" media="screen" /> + <link href="{{ site.baseurl }}/assets/themes/apache/css/demo.css" rel="stylesheet" type="text/css"/> <!-- Le fav and touch icons --> <!-- Update these with your own images <link rel="shortcut icon" href="images/favicon.ico"> diff --git a/_includes/themes/apache/demo.html b/_includes/themes/apache/demo.html new file mode 100644 index 00000000..53836b0b --- /dev/null +++ b/_includes/themes/apache/demo.html @@ -0,0 +1,22 @@ +<!--<div class="hero-unit {{ page.title | lowercase }}"> + <h1>{% if page.tagline %} <small>{{ page.tagline }}</small>{% endif %}</h1> +</div> +--> + +<div class="row"> + <div class="col-md-12"> + {{ content }} + + <div id="term_wrap"> + <div id="term_container"> + </div> + <div id="term_bar"> + <progress id="net_progress"> + </progress> + </div> + </div> + + </div> +</div> +<script type="text/javascript" src="/demo/term.js"></script> +<script type="text/javascript" src="/demo/jslinux.js"></script> diff --git a/_layouts/demo.html b/_layouts/demo.html new file mode 100644 index 00000000..16ea956d --- /dev/null +++ b/_layouts/demo.html @@ -0,0 +1,5 @@ +--- +layout: default +--- +{% include JB/setup %} +{% include themes/apache/demo.html %} diff --git a/assets/themes/apache/css/demo.css b/assets/themes/apache/css/demo.css new file mode 100644 index 00000000..5010d414 --- /dev/null +++ b/assets/themes/apache/css/demo.css @@ -0,0 +1,105 @@ +#os_table { + border: 1px solid; + border-collapse: collapse; + margin: 20px; +} + +#os_table td,#os_table th, #os_table tr { + border: 1px solid; + padding: 6px; +} + +.os_comment { + font-size: 12px; +} + +#copyright { + font-size: 10px; +} + +/* for the terminal */ +#term_wrap { + margin: 20px; + resize: both; + overflow: hidden; +} + +.term { + font-family: monospace,courier,fixed,swiss,sans-serif; + font-weight: normal; + font-variant-ligatures: none; + color: #f0f0f0; + background: #000000; + line-height: normal; + overflow: hidden; + white-space: nowrap; +} + +.term_content a { + color: inherit; + text-decoration: none; +} + +.term_content a:hover { + color: inherit; + text-decoration: underline; +} + +.term_cursor { + color: #000000; + background: #00ff00; +} + +.term_char_size { + display: inline-block; + visibility: hidden; + position: absolute; + top: 0px; + left: -1000px; + padding: 0px; +} + +.term_textarea { + position: absolute; + top: 0px; + left: 0px; + width: 0px; + height: 0px; + padding: 0px; + border: 0px; + margin: 0px; + opacity: 0; + resize: none; +} + +.term_scrollbar { background: transparent url(/demo/images/bg-scrollbar-track-y.png) no-repeat 0 0; position: relative; background-position: 0 0; float: right; height: 100%; } +.term_track { background: transparent url(/demo/images/bg-scrollbar-trackend-y.png) no-repeat 0 100%; height: 100%; width:13px; position: relative; padding: 0 1px; } +.term_thumb { background: transparent url(/demo/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(/demo/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; } + +#keyboard-icon { + margin-left: 5px + margin-right: 5px; +} + +/* file import */ +#files { + visibility: hidden; + width:1px; + height:1px; + padding: 0px; + margin: 0px; + border: 0px; +} + +label { + cursor: pointer; + margin-left: 5px; + margin-right: 5px; +} + +#net_progress { + visibility: hidden; + width: 80px; +} \ No newline at end of file diff --git a/index.md b/demo.md similarity index 58% copy from index.md copy to demo.md index 907a41e8..55fbea78 100644 --- a/index.md +++ b/demo.md @@ -1,6 +1,6 @@ --- -layout: page -title: Home +layout: demo +title: Demo tagline: Apache Project ! --- @@ -25,17 +25,6 @@ limitations under the License. {% include JB/setup %} -## Apache NuttX +## NuttX Online Demo -NuttX is a real-time operating system (RTOS) with an emphasis on standards -compliance and small footprint. Scalable from 8-bit to 64-bit microcontroller -environments, the primary governing standards in NuttX are Posix and ANSI -standards. Additional standard APIs from Unix and other common RTOS's (such as -VxWorks) are adopted for functionality not available under these standards, or -for functionality that is not appropriate for deeply-embedded environments (such -as fork()). - - -## Documentation - -Extensive documentation can be found [here]({{ site.baseurl }}/docs/latest). +Enter `help` to see the available commands. diff --git a/demo/images/bg-scrollbar-thumb-y.png b/demo/images/bg-scrollbar-thumb-y.png new file mode 100644 index 00000000..2ce41f2b Binary files /dev/null and b/demo/images/bg-scrollbar-thumb-y.png differ diff --git a/demo/images/bg-scrollbar-track-y.png b/demo/images/bg-scrollbar-track-y.png new file mode 100644 index 00000000..2636c54a Binary files /dev/null and b/demo/images/bg-scrollbar-track-y.png differ diff --git a/demo/images/bg-scrollbar-trackend-y.png b/demo/images/bg-scrollbar-trackend-y.png new file mode 100644 index 00000000..16d740c2 Binary files /dev/null and b/demo/images/bg-scrollbar-trackend-y.png differ diff --git a/demo/images/upload-icon.png b/demo/images/upload-icon.png new file mode 100644 index 00000000..0efc325f Binary files /dev/null and b/demo/images/upload-icon.png differ diff --git a/demo/jslinux.js b/demo/jslinux.js new file mode 100644 index 00000000..d800f8b4 --- /dev/null +++ b/demo/jslinux.js @@ -0,0 +1,667 @@ +/* + * 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, console_resize_event; +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']); + console_resize_event = Module.cwrap('console_resize_event', null, []); + 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; + } + + function term_wrap_onclick_handler() + { + var term_wrap_el, w, h, term_bar_el, bar_h; + term_wrap_el = document.getElementById("term_wrap"); + term_bar_el = document.getElementById("term_bar"); + w = term_wrap_el.clientWidth; + h = term_wrap_el.clientHeight; + bar_h = term_bar_el.clientHeight; + if (term.resizePixel(w, h - bar_h)) { + console_resize_event(); + } + } + + /* 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 { + var term_wrap_el; + width = 0; + height = 0; + + /* start the terminal */ + term = new Term({ cols: cols, rows: rows, scrollback: 10000, fontSize: font_size }); + term.setKeyHandler(term_handler); + term.open(document.getElementById("term_container"), + document.getElementById("term_paste")); + + term_wrap_el = document.getElementById("term_wrap") + term_wrap_el.style.width = term.term_el.style.width; + term_wrap_el.onclick = term_wrap_onclick_handler; + + 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); + } +})(); \ No newline at end of file diff --git a/demo/nuttx.bin b/demo/nuttx.bin new file mode 100644 index 00000000..9f232db6 Binary files /dev/null and b/demo/nuttx.bin differ diff --git a/demo/riscvemu64-wasm.js b/demo/riscvemu64-wasm.js new file mode 100644 index 00000000..93a39ae1 --- /dev/null +++ b/demo/riscvemu64-wasm.js @@ -0,0 +1,4 @@ +var Module=typeof Module!=="undefined"?Module:{};var moduleOverrides={};var key;for(key in Module){if(Module.hasOwnProperty(key)){moduleOverrides[key]=Module[key]}}Module["arguments"]=[];Module["thisProgram"]="./this.program";Module["quit"]=(function(status,toThrow){throw toThrow});Module["preRun"]=[];Module["postRun"]=[];var ENVIRONMENT_IS_WEB=false;var ENVIRONMENT_IS_WORKER=false;var ENVIRONMENT_IS_NODE=false;var ENVIRONMENT_IS_SHELL=false;ENVIRONMENT_IS_WEB=typeof window==="object";EN [...] + + + diff --git a/demo/riscvemu64-wasm.wasm b/demo/riscvemu64-wasm.wasm new file mode 100644 index 00000000..34f19613 Binary files /dev/null and b/demo/riscvemu64-wasm.wasm differ diff --git a/demo/root-riscv64.cfg b/demo/root-riscv64.cfg new file mode 100644 index 00000000..ef711447 --- /dev/null +++ b/demo/root-riscv64.cfg @@ -0,0 +1,7 @@ +/* VM configuration file */ +{ + version: 1, + machine: "riscv64", + memory_size: 256, + bios: "nuttx.bin", +} diff --git a/demo/term.js b/demo/term.js new file mode 100644 index 00000000..052db50d --- /dev/null +++ b/demo/term.js @@ -0,0 +1,1460 @@ +/* + * Javascript terminal + * + * Copyright (c) 2011-2020 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(options) +{ + var width, height, tot_height, scrollback; + + function dummy_key_handler() + { + } + + width = options.cols ? options.cols : 80; + height = options.rows ? options.rows : 25; + scrollback = options.scrollback ? options.scrollback : 0; + this.font_size = options.fontSize ? options.fontSize : 15; + + this.w = width; + this.h = height; + this.cur_h = height; /* current height of the scroll back buffer */ + tot_height = height + scrollback; + this.tot_h = tot_height; /* maximum height of the scroll back buffer */ + + /* y_base and y_disp are index in the circular buffer lines of + length cur_h. They are defined modulo tot_h, i.e. they wrap + when cur_h = tot_h. If cur_h < tot_h, y_base is always equal to + cur_h - h. */ + 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 = dummy_key_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; + + this.textarea_has_focus = false; +} + +Term.prototype.setKeyHandler = function(handler) +{ + this.handler = handler; +} + +/* return the size of a character in CSS pixels using the selected font */ +function term_get_char_size(parent_el, font_size) +{ + var el, g, ret; + el = document.createElement("div"); + el.classList.add("term", "term_char_size"); + el.style.fontSize = font_size + "px"; + el.textContent = "W"; + parent_el.appendChild(el); + g = el.getBoundingClientRect(); + /* the character width & height may not be an integer */ + ret = [g.width, g.height]; + return ret; +} + +Term.prototype.open = function(parent_el) +{ + var y, line, i, term, c, row_el, char_size_ret; + + /* 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<this.w;i++) + line[i] = c; + this.lines[y] = line; + } + + char_size_ret = term_get_char_size(parent_el, this.font_size); + /* size of the character in CSS pixels */ + this.char_width = char_size_ret[0]; + this.char_height = char_size_ret[1]; + + this.scrollbar_width = 15; + + /* size of term_el in CSS pixels */ + this.term_width = Math.ceil(this.w * this.char_width) + + this.scrollbar_width; + this.term_height = Math.ceil(this.h * this.char_height); + + /* create the terminal window */ + this.term_el = document.createElement("div"); + this.term_el.className = "term"; + /* XXX: could compute the font metrics */ + this.term_el.style.fontSize = this.font_size + "px"; + this.term_el.style.width = this.term_width + "px"; + this.term_el.style.height = this.term_height + "px"; + /* allow the terminal to take the focus */ + this.term_el.setAttribute("tabindex", "0"); + + /* scroll bar */ + this.scrollbar_el = document.createElement("div"); + this.scrollbar_el.className = "term_scrollbar"; + this.scrollbar_el.style.width = this.scrollbar_width + "px"; + this.term_el.appendChild(this.scrollbar_el); + + this.track_el = document.createElement("div"); + this.track_el.className = "term_track"; + this.track_el.onmousedown = this.mouseMoveHandler.bind(this); + this.scrollbar_el.appendChild(this.track_el); + + this.thumb_el = document.createElement("div"); + this.thumb_el.className = "term_thumb"; + this.thumb_el.onmousedown = this.mouseDownHandler.bind(this); + this.track_el.appendChild(this.thumb_el); + + this.end_el = document.createElement("div"); + this.end_el.className = "term_end"; + this.thumb_el.appendChild(this.end_el); + + /* current scrollbar position */ + this.thumb_size = -1; + this.thumb_pos = -1; + + /* terminal content */ + this.content_el = document.createElement("div"); + this.content_el.className = "term_content"; + this.content_el.style.width = (this.w) + "ch"; + this.term_el.appendChild(this.content_el); + + this.rows_el = []; + for(y=0;y<this.h;y++) { + row_el = document.createElement("div"); + this.rows_el.push(row_el); + this.content_el.appendChild(row_el); + } + + /* dummy textarea to get the input events and for the virtual + keyboard on mobile devices */ + this.textarea_el = document.createElement("textarea"); + this.textarea_el.classList.add("term_textarea"); + this.textarea_el.setAttribute("autocorrect", "off"); + this.textarea_el.setAttribute("autocapitalize", "off"); + this.textarea_el.setAttribute("spellcheck", "false"); + this.textarea_el.setAttribute("tabindex", "-1"); + this.term_el.appendChild(this.textarea_el); + + this.parent_el = parent_el; + parent_el.appendChild(this.term_el); + + this.refresh(0, this.h - 1); + + /* textarea_el events */ + // key handler + this.textarea_el.addEventListener("keydown", + this.keyDownHandler.bind(this), true); + this.textarea_el.addEventListener("keyup", + this.keyUpHandler.bind(this), true); + /* keypress is deprecated, so use input */ + this.textarea_el.addEventListener("input", + this.inputHandler.bind(this), true); + this.textarea_el.addEventListener("focus", + this.focusHandler.bind(this), true); + this.textarea_el.addEventListener("blur", + this.blurHandler.bind(this), true); + + /* term_el events */ + this.term_el.addEventListener("keydown", + this.termKeyDownHandler.bind(this), + true); + this.term_el.addEventListener("paste", + this.pasteHandler.bind(this), true); + this.term_el.addEventListener("mouseup", + this.termMouseUpHandler.bind(this), + true); + this.term_el.addEventListener("wheel", + this.wheelHandler.bind(this), false); + + // cursor blinking + term = this; + setInterval(function() { term.cursor_timer_cb(); }, 1000); + + this.term_el.focus(); +}; + +Term.prototype.refresh_scrollbar = function () +{ + var total_size, thumb_pos, thumb_size, y, y0; + total_size = this.term_el.clientHeight; + thumb_size = Math.ceil(this.h * total_size / this.cur_h); + /* position of the first line of the scroll back buffer */ + y0 = (this.y_base + this.h) % this.cur_h; + y = this.y_disp - y0; + if (y < 0) + y += this.cur_h; + thumb_pos = Math.floor(y * total_size / this.cur_h); + thumb_size = Math.max(thumb_size, 30); + thumb_size = Math.min(thumb_size, total_size); + thumb_pos = Math.min(thumb_pos, total_size - thumb_size); +// console.log("pos=" + thumb_pos + " size=" + thumb_size); + if (thumb_pos != this.thumb_pos || thumb_size != this.thumb_size) { + this.thumb_pos = thumb_pos; + this.thumb_size = thumb_size; + this.thumb_el.style.top = thumb_pos + "px"; + this.thumb_el.style.height = thumb_size + "px"; + } +} + +/* move the text area at the cursor position so that the browser shows + * the correct position when the virtual keyboard is used */ +Term.prototype.move_textarea = function() +{ + var x, y, base_x, base_y, pos; + + pos = this.term_el.getBoundingClientRect(); + base_x = pos.left + window.scrollX; + base_y = pos.top + window.scrollY; + /* position relative to the body */ + x = Math.ceil(this.x * this.char_width + base_x); + y = Math.ceil(this.y * this.char_height + base_y); + + this.textarea_el.style.width = Math.ceil(this.char_width) + "px"; + this.textarea_el.style.height = Math.ceil(this.char_height) + "px"; + this.textarea_el.style.left = x + "px"; + this.textarea_el.style.top = y + "px"; + this.textarea_el.style.zIndex = 1000; +} + +Term.prototype.refresh = function(ymin, ymax) +{ + var el, y, line, outline, c, w, i, j, cx, attr, last_attr, fg, bg, y1; + var http_link_len, http_link_str, bold, tmp, inverse; + + function is_http_link_char(c) + { + var str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~:/?#[]@!$&'()*+,;=`."; + return str.indexOf(String.fromCharCode(c)) >= 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 += '</span>'; + last_attr = this.def_attr; + } + outline += "<a href='" + http_link_str + "'>"; + } + } + if (i == cx) { + attr = -1; /* cursor */ + } + if (attr != last_attr) { + if (last_attr != this.def_attr) + outline += '</span>'; + if (attr != this.def_attr) { + if (attr == -1) { + /* cursor */ + outline += '<span class="term_cursor">'; + } else { + outline += '<span style="'; + fg = (attr >> 4) & 0xf; + bg = attr & 0xf; + bold = (attr >> 8) & 1; + inverse = (attr >> 9) & 1; + if (inverse) { + tmp = fg; + fg = bg; + bg = tmp; + } + if (bold) { + /* metrics are not OK for all fonts, so disabled */ + /* outline += 'font-weight:bold;'; */ + /* use the bright color */ + if (fg < 8) + fg += 8; + } + if (fg != 7) { + outline += 'color:' + this.colors[fg] + ';'; + } + if (bg != 0) { + outline += 'background-color:' + + this.colors[bg] + ';'; + } + 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 += '</span>'; + last_attr = this.def_attr; + } + outline += "</a>"; + } + } + } + if (last_attr != this.def_attr) { + outline += '</span>'; + } + + /* trim trailing spaces for copy/paste */ + outline = right_trim(outline, " "); + if (outline == "") + outline = " "; + + this.rows_el[y].innerHTML = outline; + } + + this.refresh_scrollbar(); + this.move_textarea(); +}; + +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.w;x++) + line[x] = c; + y1 = s.y_base + y; + if (y1 >= 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.inputHandler = function (ev) +{ + var str; + str = this.textarea_el.value; + if (str) { + this.textarea_el.value = ""; + this.show_cursor(); + if (this.utf8) + str = this.to_utf8(str); + this.handler(str); + return false; + } else { + return true; + } +}; + +Term.prototype.termKeyDownHandler = function(ev) +{ + this.interceptBrowserExit(ev); + /* give the focus back to the textarea when a key is pressed */ + this.textarea_el.focus(); +} + +Term.prototype.termMouseUpHandler = function(ev) +{ + var sel; + /* if no selection, can switch back up to the textarea focus */ + sel = window.getSelection(); + if (!sel || sel.isCollapsed) + this.textarea_el.focus(); +} + +Term.prototype.focusHandler = function (ev) +{ + this.textarea_has_focus = true; +}; + +Term.prototype.blurHandler = function (ev) +{ + /* allow unloading the page */ + window.onbeforeunload = null; + this.textarea_has_focus = false; +}; + +Term.prototype.pasteHandler = function (ev) +{ + var c, str; + if (!this.textarea_has_focus) { + c = ev.clipboardData; + if (c) { + str = c.getData("text/plain"); + if (this.utf8) + str = this.to_utf8(str); + this.queue_chars(str); + return false; + } + } +} + +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", ""); +} + +/* 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]; +}; + +/* resize the terminal (size in pixels). Return true if the display + size was modified. */ +/* XXX: may be simpler to separate the scrollback buffer from the + screen buffer */ +Term.prototype.resizePixel = function (new_width, new_height) +{ + var new_w, new_h, y, x, line, c, row_el, d, new_cur_h, e; + + if (new_width == this.term_width && new_height == this.term_height) + return false; + new_w = Math.floor((new_width - this.scrollbar_width) / + this.char_width); + new_h = Math.floor(new_height / this.char_height); + if (new_w <= 0 || new_h <= 0 || new_h > this.tot_h) + return false; + + this.term_width = new_width; + this.term_height = new_height; + this.term_el.style.width = this.term_width + "px"; + this.term_el.style.height = this.term_height + "px"; + + /* XXX: could keep the EOL positions */ + if (new_w < this.w) { + /* reduce the line width */ + for(y = 0; y < this.cur_h;y++) { + line = this.lines[y]; + line = line.slice(0, new_w); + } + } else if (new_w > this.w) { + /* increase the line width */ + c = 32 | (this.def_attr << 16); + for(y = 0; y < this.cur_h;y++) { + line = this.lines[y]; + for(x = this.w; x < new_w; x++) + line[x] = c; + } + } + + if (this.x >= new_w) + this.x = new_w - 1; + + d = new_h - this.h; + if (d < 0) { + d = -d; + /* remove displayed lines */ + + /* strip the DOM terminal content */ + for(y = new_h; y < this.h; y++) { + row_el = this.rows_el[y]; + this.content_el.removeChild(row_el); + } + this.rows_el = this.rows_el.slice(0, new_h); + + /* adjust cursor position if needed */ + if (this.y >= new_h) { + if (d > this.y) + d = this.y; + this.y -= d; + this.y_base += d; + if (this.y_base >= this.tot_h) + this.y_base -= this.tot_h; + } + + if (this.scroll_bottom > new_h) + this.scroll_bottom = new_h; + /* fail safe for scroll top */ + if (this.scroll_top >= this.scroll_bottom) + this.scroll_top = 0; + + } else if (d > 0) { + /* add displayed lines */ + + if (this.cur_h == this.tot_h) { + if (d > this.tot_h - this.h) + d = this.tot_h - this.h; + } else { + if (d > this.y_base) + d = this.y_base; + } + this.y_base -= d; + if (this.y_base < 0) + this.y_base += this.tot_h; + this.y += d; + + if (this.scroll_bottom == this.h) + this.scroll_bottom = new_h; + + /* extend the DOM terminal content */ + for(y = this.h; y < new_h; y++) { + row_el = document.createElement("div"); + this.rows_el.push(row_el); + this.content_el.appendChild(row_el); + } + } + + if (this.cur_h < this.tot_h) { + new_cur_h = this.y_base + new_h; + if (new_cur_h < this.cur_h) { + /* remove lines in the scroll back buffer */ + this.lines = this.lines.slice(0, new_cur_h); + } else if (new_cur_h > this.cur_h) { + /* add lines in the scroll back buffer */ + c = 32 | (this.def_attr << 16); + for(y = this.cur_h; y < new_cur_h; y++) { + line = new Array(); + for(x = 0; x < new_w; x++) + line[x] = c; + this.lines[y] = line; + } + } + this.cur_h = new_cur_h; + } + + this.w = new_w; + this.h = new_h; + + if (this.y >= this.h) + this.y = this.h - 1; + + /* reset display position */ + this.y_disp = this.y_base; +/* + console.log("lines.length", this.lines.length, "cur_h", this.cur_h, + "y_base", this.y_base, "h", this.h, + "scroll_bottom", this.scroll_bottom); +*/ + this.refresh(0, this.h - 1); + return true; +} \ No newline at end of file diff --git a/index.md b/index.md index 907a41e8..dd59b4b5 100644 --- a/index.md +++ b/index.md @@ -39,3 +39,8 @@ as fork()). ## Documentation Extensive documentation can be found [here]({{ site.baseurl }}/docs/latest). + + +## Online Demo + +Try the online demo [here]({{ site.baseurl }}/demo).