diff --git a/com/isoterminal.js b/com/isoterminal.js index cb582fe..552cde1 100644 --- a/com/isoterminal.js +++ b/com/isoterminal.js @@ -35,24 +35,27 @@ if( typeof AFRAME != 'undefined '){ schema: { iso: { type:"string", "default":"https://forgejo.isvery.ninja/assets/xrsh-buildroot/main/xrsh.iso" }, overlayfs: { type:"string"}, - cols: { type: 'number',"default": 80 }, - rows: { type: 'number',"default": 20 }, + width: { type: 'number',"default": -1 }, + height: { type: 'number',"default": -1 }, + lineHeight: { type: 'number',"default": 18 }, padding: { type: 'number',"default": 18 }, minimized: { type: 'boolean',"default":false}, - maximized: { type: 'boolean',"default":false}, + maximized: { type: 'boolean',"default":true}, muteUntilPrompt:{ type: 'boolean',"default":true}, // mute stdout until a prompt is detected in ISO HUD: { type: 'boolean',"default":false}, // link to camera movement transparent: { type:'boolean', "default":false }, // need good gpu - xterm: { type: 'boolean', "default":false }, // use xterm.js? (=slower) memory: { type: 'number', "default":64 }, // VM memory (in MB) - bufferLatency: { type: 'number', "default":300 }, // in ms: bufferlatency from webworker to xterm (batch-update every char to texture) - canvasLatency: { type: 'number', "default":500 }, // in ms: time between canvas re-draws - renderer: { type: 'string', "default":"canvas" },// 'dom' or 'canvas' (=faster) for immersive mode + bufferLatency: { type: 'number', "default":30 }, // in ms: bufferlatency from webworker to xterm (batch-update every char to texture) debug: { type: 'boolean', "default":false } }, init: function(){ this.el.object3D.visible = false + if( this.data.width == -1 ) this.data.width = document.body.offsetWidth + if( this.data.height == -1 ) this.data.height = document.body.offsetHeight + this.data.width -= this.data.padding*2 + this.data.height -= this.data.padding*2 + this.initHud() fetch(this.data.iso,{method: 'HEAD'}) .then( (res) => { @@ -70,6 +73,7 @@ if( typeof AFRAME != 'undefined '){ com: "com/dom.js", window: "com/window.js", v86: "com/isoterminal/libv86.js", + vt100: "com/isoterminal/VT100.js", // allow xrsh to selfcontain scene + itself xhook: "https://jpillora.com/xhook/dist/xhook.min.js", selfcontain: "com/selfcontainer.js", @@ -85,8 +89,9 @@ if( typeof AFRAME != 'undefined '){ scale: 1.0, events: ['click','keydown'], html: (me) => `
- -

+                          
+

+                          
`, css: (me) => `.isoterminal{ @@ -98,7 +103,10 @@ if( typeof AFRAME != 'undefined '){ .isoterminal div{ display:block; position:relative; - line-height:18px; + line-height: ${me.com.data.lineHeight}px; + } + #vt100 { + outline: none !important; } @font-face { font-family: 'Cousine'; @@ -115,6 +123,15 @@ if( typeof AFRAME != 'undefined '){ .isoterminal style{ display:none } + blink{ + border:none; + padding:none; + } + span blink:last-of-type{ + border-right: 8px solid #F07; + padding-right: 3px; + } + #overlay .winbox:has(> .isoterminal){ background:transparent; box-shadow:none; @@ -132,28 +149,11 @@ if( typeof AFRAME != 'undefined '){ .XR .isoterminal{ background: #000; } - .isoterminal *, - .isoterminal .xterm-dom-renderer-owner-1 .xterm-rows { - background:transparent !important; - font-size: 14px; - font-family: "Cousine",Liberation Mono,DejaVu Sans Mono,Courier New,monospace; - font-weight:500 !important; - text-shadow: 0px 0px 10px #F075; - } - .isoterminal .xterm-rows.xterm-focus .xterm-cursor.xterm-cursor-block { - background-color:#a5F !important; - } - .isoterminal .xterm-rows div{ - height:8px; - height:18px; - } - .isoterminal .xterm-rows span{ - width:8px; - } - .isoterminal .xterm-helpers { - position:absolute; - opacity:0; - top: -2000px; + .isoterminal *{ + font-size: 14px; + font-family: "Cousine",Liberation Mono,DejaVu Sans Mono,Courier New,monospace; + font-weight:500 !important; + text-shadow: 0px 0px 10px #F075; } @keyframes fade { @@ -168,7 +168,6 @@ if( typeof AFRAME != 'undefined '){ to { opacity: 1.0; } } - .isoterminal .xterm-rows.xterm-focus .xterm-cursor.xterm-cursor-block, .blink{ animation:fade 1000ms infinite; -webkit-animation:fade 1000ms infinite; @@ -179,21 +178,15 @@ if( typeof AFRAME != 'undefined '){ initTerminal: async function(singleton){ - if( this.data.xterm ){ - // why 3.12? - // first versions used 1.5.4, a typescript rewrite which: - // * acts weird with oculus browser keyboard (does not repaint properly after typing) - // * does not use canvas anymore [which would be ideal for THREE.js texture] - // * does not allow switching between dom/canvas - // * only allows a standalone WebGL addon (conflicts with THREE) - // * heavily dependent on requestAnimationFrame (conflicts with THREE) - // * typescript-rewrite results in ~300k lib (instead of 96k) - this.requires.xtermcss = "//unpkg.com/xterm@3.12.0/dist/xterm.css", - this.requires.xtermjs = "//unpkg.com/xterm@3.12.0/dist/xterm.js", - this.requires.xtermcss = "com/xterm.js" - }else{ - this.requires.vt100 = "com/isoterminal/VT100.js" - } + // why not latest xterm or v3.12 with builtin-canvas support? + // first versions used 1.5.4, a typescript rewrite which: + // * acts weird with oculus browser keyboard (does not repaint properly after typing) + // * does not use canvas anymore [which would be ideal for THREE.js texture] + // * does not allow switching between dom/canvas + // * only allows a standalone WebGL addon (conflicts with THREE) + // * heavily dependent on requestAnimationFrame (conflicts with THREE) + // * typescript-rewrite results in ~300k lib (instead of 96k) + // * v3.12 had slightly better performance but still very heavy await AFRAME.utils.require(this.requires) await AFRAME.utils.require({ // ISOTerminal plugins @@ -225,18 +218,15 @@ if( typeof AFRAME != 'undefined '){ this.term = new ISOTerminal(instance,this.data) instance.addEventListener('DOMready', () => { - if( this.data.renderer == 'dom' || !this.data.xterm ){ - instance.setAttribute("html-as-texture-in-xr", `domid: #${this.el.dom.id}; faceuser: true`) - } - if( !this.data.xterm ) this.setupVT100(instance) + instance.setAttribute("html-as-texture-in-xr", `domid: #${this.el.dom.id}; faceuser: true`) + setTimeout( () => this.setupVT100(instance),100) //instance.winbox.resize(720,380) - let size = `width: ${Math.floor(this.data.cols*8.65)}; height: ${Math.floor(this.data.rows*21.1)}` + let size = `width: ${this.data.width}; height: ${this.data.height}` instance.setAttribute("window", `title: xrsh.iso; uid: ${instance.uid}; attach: #overlay; dom: #${instance.dom.id}; ${size}; min: ${this.data.minimized}; max: ${this.data.maximized}`) }) instance.addEventListener('window.oncreate', (e) => { instance.dom.classList.add('blink') - if( this.data.xterm ) this.setupXterm(instance) // run iso let opts = {dom:instance.dom} @@ -246,15 +236,6 @@ if( typeof AFRAME != 'undefined '){ instance.setAttribute("dom", "") - this.term.addEventListener('postReady', (e)=>{ - // bugfix: send window dimensions to xterm (xterm.js does that from dom-sizechange to xterm via escape codes) - let wb = instance.winbox - if( this.data.maximized ){ - wb.restore() - wb.maximize() - }else wb.resize() - }) - this.term.addEventListener('ready', (e) => { instance.dom.classList.remove('blink') this.term.emit('status',"running") @@ -278,9 +259,6 @@ if( typeof AFRAME != 'undefined '){ instance.addEventListener('window.onmaximize', resize ) const focus = (showdom) => (e) => { - if( this.el.components.xterm ){ - this.el.components.xterm.term.focus() - } if( this.el.components.window && this.data.renderer == 'canvas'){ this.el.components.window.show( showdom ) } @@ -311,14 +289,22 @@ if( typeof AFRAME != 'undefined '){ console.test.run() }, - setupXterm: function(){ - instance.setAttribute("xterm",`cols: ${this.data.cols}; rows: ${this.data.rows}; canvasLatency: ${this.data.canvasLatency}; XRrenderer: ${this.data.renderer}`) - instance.addEventListener("xterm-input", (e) => this.term.send(e.detail,0) ) - }, - setupVT100: function(instance){ - this.vt100 = new VT100(100,50, this.el.dom, 100 ) - this.vt100.curs_set(1,true) + const el = this.el.dom.querySelector('#vt100') + this.vt100 = new VT100( + Math.floor(this.data.width/this.data.lineHeight), + Math.floor(this.data.height*0.8/this.data.lineHeight), + el, + 100 + ) + this.vt100.curs_set( 1, true) + el.focus() + this.vt100.getch( (ch,t) => { + console.log(ch) + this.term.send( ch ) + this.vt100.curs_set( 0, true) + }) + this.el.addEventListener('serial-output-byte', (e) => { const byte = e.detail var chr = String.fromCharCode(byte); @@ -327,6 +313,14 @@ if( typeof AFRAME != 'undefined '){ this.el.addEventListener('serial-output-string', (e) => { this.vt100.write(e.detail) }) + + + //this.el.dom.querySelector('input').addEventListener('keyup', (e) => { + // VT100.handle_onkeypress_( {charCode : e.charCode || e.keyCode, keyCode: e.keyCode}, (chars) => { + // debugger + // chars.map( (c) => this.term.send(str) ) + // }) + //}) }, events:{ diff --git a/com/isoterminal/VT100.js b/com/isoterminal/VT100.js index 3281f8f..4d51ba1 100644 --- a/com/isoterminal/VT100.js +++ b/com/isoterminal/VT100.js @@ -86,7 +86,7 @@ function VT100(wd, ht, el_or_id, max_scroll_lines, fg, bg) fg = VT100.COLOR_WHITE; } if (typeof(bg) == 'undefined') { - bg = VT100.COLOR_BLACK; + bg = VT100.COLOR_TRANSPARENT //COLOR_BLACK; } var r; @@ -116,13 +116,14 @@ function VT100(wd, ht, el_or_id, max_scroll_lines, fg, bg) this.key_buf_ = []; this.echo_ = false; this.esc_state_ = 0; - this.log_level_ = VT100.WARN; + this.log_level_ = VT100.DEBUG //WARN; this.clear_all(); this.refresh(); } // public constants -- colours and colour pairs +VT100.COLOR_TRANSPARENT = -1; VT100.COLOR_BLACK = 0; VT100.COLOR_BLUE = 1; VT100.COLOR_GREEN = 2; @@ -164,96 +165,98 @@ VT100.the_vt_ = undefined; // class methods // this is actually an event handler -VT100.handle_onkeypress_ = function VT100_handle_onkeypress(event) +VT100.handle_onkeypress_ = function VT100_handle_onkeypress(event,cb) { //dump("event target: " + event.target.id + "\n"); //dump("event originalTarget: " + event.originalTarget.id + "\n"); var vt = VT100.the_vt_, ch; if (vt === undefined) return true; - if (VT100.browser_ie_ || VT100.browser_opera_) { - ch = event.keyCode; - if (ch == 13) - ch = 10; - else if (ch > 255 || (ch < 32 && ch != 8)) - return true; - ch = String.fromCharCode(ch); - } else { - ch = event.charCode; - //dump("ch: " + ch + "\n"); - //dump("ctrl?: " + event.ctrlKey + "\n"); - vt.debug("onkeypress:: keyCode: " + event.keyCode + ", ch: " + event.charCode); - if (ch) { - if (ch > 255) - return true; - if (event.ctrlKey && event.shiftKey) { - // Don't send the copy/paste commands. - var charStr = String.fromCharCode(ch); - if (charStr == 'C' || charStr == 'V') { - return false; - } - } - if (event.ctrlKey) { - ch = String.fromCharCode(ch - 96); - } else { - ch = String.fromCharCode(ch); - if (ch == '\r') - ch = '\n'; - } - } else { - switch (event.keyCode) { - case event.DOM_VK_BACK_SPACE: - ch = '\b'; - break; - case event.DOM_VK_TAB: - ch = '\t'; - break; - case event.DOM_VK_RETURN: - case event.DOM_VK_ENTER: - ch = '\r'; - break; - case event.DOM_VK_UP: - if (this.cursor_key_mode_ == VT100.CK_CURSOR) - ch = '\x1b[A'; - else - ch = '\x1bOA'; - break; - case event.DOM_VK_DOWN: - if (this.cursor_key_mode_ == VT100.CK_CURSOR) - ch = '\x1b[B'; - else - ch = '\x1bOB'; - break; - case event.DOM_VK_RIGHT: - if (this.cursor_key_mode_ == VT100.CK_CURSOR) - ch = '\x1b[C'; - else - ch = '\x1bOC'; - break; - case event.DOM_VK_LEFT: - if (this.cursor_key_mode_ == VT100.CK_CURSOR) - ch = '\x1b[D'; - else - ch = '\x1bOD'; - break; - case event.DOM_VK_DELETE: - ch = '\x1b[3~'; - break; - case event.DOM_VK_HOME: - ch = '\x1b[H'; - break; - case event.DOM_VK_ESCAPE: - ch = '\x1b'; - break; - default: - return true; - } - } - // Stop the event from doing anything else. - event.preventDefault(); - } + //if ( event.keyCode != undefined || !event.charCode){ + // ch = event.keyCode; + // if (ch == 13) + // ch = 10; + // else if (ch > 255 || (ch < 32 && ch != 8)) + // return true; + // ch = String.fromCharCode(ch); + //} else { + ch = event.charCode; + //dump("ch: " + ch + "\n"); + //dump("ctrl?: " + event.ctrlKey + "\n"); + vt.debug("onkeypress:: keyCode: " + event.keyCode + ", ch: " + event.charCode); + if (ch) { + if (ch > 255) + return true; + if (event.ctrlKey && event.shiftKey) { + // Don't send the copy/paste commands. + var charStr = String.fromCharCode(ch); + if (charStr == 'C' || charStr == 'V') { + return false; + } + } + if (event.ctrlKey) { + ch = String.fromCharCode(ch - 96); + } else { + ch = String.fromCharCode(ch); + if (ch == '\r') + ch = '\n'; + } + } else { + switch (event.key) { + case "Backspace": + ch = '\b'; + break; + case "Tab": + ch = '\t'; + break; + case event.DOM_VK_RETURN: + case event.DOM_VK_ENTER: + ch = '\r'; + break; + case event.DOM_VK_UP: + if (this.cursor_key_mode_ == VT100.CK_CURSOR) + ch = '\x1b[A'; + else + ch = '\x1bOA'; + break; + case event.DOM_VK_DOWN: + if (this.cursor_key_mode_ == VT100.CK_CURSOR) + ch = '\x1b[B'; + else + ch = '\x1bOB'; + break; + case event.DOM_VK_RIGHT: + if (this.cursor_key_mode_ == VT100.CK_CURSOR) + ch = '\x1b[C'; + else + ch = '\x1bOC'; + break; + case event.DOM_VK_LEFT: + if (this.cursor_key_mode_ == VT100.CK_CURSOR) + ch = '\x1b[D'; + else + ch = '\x1bOD'; + break; + case event.DOM_VK_DELETE: + ch = '\x1b[3~'; + break; + case event.DOM_VK_HOME: + ch = '\x1b[H'; + break; + case event.DOM_VK_ESCAPE: + ch = '\x1b'; + break; + default: + return true; + } + } + // Stop the event from doing anything else. + event.preventDefault(); vt.key_buf_.push(ch); - setTimeout(VT100.go_getch_, 0); + if( cb ){ + cb(vt.key_buf_) + vt.key_buf_ = [] + }else setTimeout(VT100.go_getch_, 0); return false; } @@ -345,7 +348,7 @@ VT100.prototype.html_colours_ = function VT100_html_colours_(attr) f: '#' + (fg & 4 ? co1 : co0) + (fg & 2 ? co1 : co0) + (fg & 1 ? co1 : co0), - b: '#' + (bg & 4 ? co1 : co0) + + b: attr.bg == VT100.COLOR_TRANSPARENT ? 'transparent' : '#' + (bg & 4 ? co1 : co0) + (bg & 2 ? co1 : co0) + (bg & 1 ? co1 : co0) }; @@ -545,12 +548,10 @@ VT100.prototype.curs_set = function(vis, grab, eventist) this.grab_events_ = true; VT100.the_vt_ = this; eventist.addEventListener("keypress", VT100.handle_onkeypress_, false); - if (VT100.browser_ie_) - document.onkeydown = VT100.handle_onkeydown_; + eventist.addEventListener("keydown", VT100.handle_onkeypress_, false); } else { eventist.removeEventListener("keypress", VT100.handle_onkeypress_, false); - if (VT100.browser_ie_) - document.onkeydown = VT100.handle_onkeydown_; + eventist.removeEventListener("keydown", VT100.handle_onkeypress_, false); this.grab_events_ = false; VT100.the_vt_ = undefined; } @@ -831,7 +832,7 @@ VT100.prototype.write = function VT100_write(stuff) } else { code = this.escape(ch); } - this.debug(" write:: ch: " + ch.charCodeAt(0) + ", '" + code + "'"); + this.debug(" write:: ch: " + ch.charCodeAt(0) + ", '" + code + "'"); codes += code; }