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 += "&nbsp;";
+                break;
+            case 38: // '&'
+                outline += "&amp;";
+                break;
+            case 60: // '<'
+                outline += "&lt;";
+                break;
+            case 62: // '>'
+                outline += "&gt;";
+                break;
+            default:
+                if (c < 32) {
+                    outline += "&nbsp;";
+                } 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, "&nbsp;");
+        if (outline == "")
+            outline = "&nbsp;";
+        
+        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).

Reply via email to