diff --git a/com/codemirror.js b/com/codemirror.js index eadc0c8..28f77c4 100644 --- a/com/codemirror.js +++ b/com/codemirror.js @@ -12,7 +12,7 @@ AFRAME.registerComponent('codemirror', { if( this.data.file && this.data.file[0] != '/'){ this.data.file = "root/"+this.data.file } - this.isoterminal = this.data.term.components.isoterminal.isoterminal + this.isoterminal = this.data.term.components.isoterminal.term //this.el.innerHTML = ` ` this.requireAll() }, @@ -84,16 +84,18 @@ AFRAME.registerComponent('codemirror', { // we don't do via shellcmd: isoterminal.exec(`echo '${str}' > ${file}`,1) // as it would require all kindof ugly stringescaping console.log("updating "+file) - await this.isoterminal.emulator.fs9p.update_file( file, str) + await this.isoterminal.worker['emulator.create_file'](file, term.convert.toUint8Array(str) ) }, events:{ // component events DOMready: function(e){ - this.isoterminal.worker.postMessage.promise({event:'read_file',data: this.data.file }) + + this.isoterminal.worker['emulator.read_file'](this.data.file) .then( this.isoterminal.convert.Uint8ArrayToString ) .then( (str) => { + console.log("creating editor") this.createEditor( str ) }) .catch( (e) => { diff --git a/com/isoterminal.js b/com/isoterminal.js index 33d4ae3..530775e 100644 --- a/com/isoterminal.js +++ b/com/isoterminal.js @@ -5,13 +5,13 @@ * ┌─────────┐ ┌────────────┐ ┌─────────────┐ exit-AR * ┌───────►│ com/dom ┼──►│ com/window ├─►│ domrenderer │◄────────── exit-VR ◄─┐ * │ └─────────┘ └────────────┘ └─────▲───────┘ │ - * │ │ ┌───────────────┐ │ + * │ │ ┌───────────────┐ │ renderer=dom * ┌──────────┴────────┐ ┌─────┴──────┐ │ xterm.js │ ┌─────────────────────────────┐ * │ com/isoterminal ├────────────────────────────►│com/xterm.js│◄─┤ │ │com/html-as-texture-in-XR.js │ * └────────┬─┬────────┘ └──┬──────┬▲─┘ │ xterm.css │ └─────────────────────────────┘ * │ │ ┌────────┐ ┌─────────▼──┐ ││ └───────────────┘ │ ▲ * │ └───────►│ plane ├─────►text───┼►canvas │◄────────────────── enter-VR │ │ - * │ └────────┘ └────────────┘ ││ enter-AR ◄─┘ │ + * │ └────────┘ └────────────┘ ││ renderer=canvas enter-AR ◄─┘ │ * │ ││ │ * │ ││ │ * │ ISOTerminal.js ││ │ @@ -40,13 +40,15 @@ if( typeof AFRAME != 'undefined '){ padding: { type: 'number',"default": 18 }, minimized: { type: 'boolean',"default":false}, maximized: { type: 'boolean',"default":false}, - 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":true }, // 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":300 } // in ms: time between canvas re-draws + 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":true }, // 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 + debug: { type: 'boolean', "default":false } }, init: function(){ @@ -65,21 +67,22 @@ if( typeof AFRAME != 'undefined '){ }, requires:{ - com: "com/dom.js", - window: "com/window.js", - v86: "com/isoterminal/libv86.js", + com: "com/dom.js", + window: "com/window.js", + v86: "com/isoterminal/libv86.js", // allow xrsh to selfcontain scene + itself - xhook: "https://jpillora.com/xhook/dist/xhook.min.js", - selfcontain: "com/selfcontainer.js", + xhook: "https://jpillora.com/xhook/dist/xhook.min.js", + selfcontain: "com/selfcontainer.js", // html to texture - htmlinxr: "com/html-as-texture-in-xr.js", + htmlinxr: "com/html-as-texture-in-xr.js", // isoterminal features - ISOTerminal: "com/isoterminal/ISOTerminal.js", - localforage: "https://cdn.rawgit.com/mozilla/localForage/master/dist/localforage.js" + PromiseWorker: "com/isoterminal/PromiseWorker.js", + ISOTerminal: "com/isoterminal/ISOTerminal.js", + localforage: "https://cdn.rawgit.com/mozilla/localForage/master/dist/localforage.js" }, dom: { - //scale: 0.5, + scale: 1.0, events: ['click','keydown'], html: (me) => `
`, @@ -114,12 +117,16 @@ if( typeof AFRAME != 'undefined '){ } .wb-body:has(> .isoterminal){ - background: #000C; + background: #000C; overflow:hidden; border-radius:7px; } - .XR .wb-body:has(> .isoterminal){ + .XR .wb-body:has(> .isoterminal){ + background: transparent; + } + + .XR .isoterminal{ background: #000; } .isoterminal *, @@ -210,10 +217,12 @@ if( typeof AFRAME != 'undefined '){ } // init isoterminal - this.isoterminal = new ISOTerminal(instance,this.data) + this.term = new ISOTerminal(instance,this.data) instance.addEventListener('DOMready', () => { - //instance.setAttribute("html-as-texture-in-xr", `domid: #${this.el.dom.id}`) + if( this.data.renderer == 'dom' ){ + instance.setAttribute("html-as-texture-in-xr", `domid: #${this.el.dom.id}`) + } //instance.winbox.resize(720,380) let size = `width: ${Math.floor(this.data.cols*8.65)}; height: ${Math.floor(this.data.rows*21.1)}` instance.setAttribute("window", `title: xrsh.iso; uid: ${instance.uid}; attach: #overlay; dom: #${instance.dom.id}; ${size}; min: ${this.data.minimized}; max: ${this.data.maximized}`) @@ -221,17 +230,17 @@ if( typeof AFRAME != 'undefined '){ instance.addEventListener('window.oncreate', (e) => { instance.dom.classList.add('blink') - instance.setAttribute("xterm",`cols: ${this.data.cols}; rows: ${this.data.rows}; canvasLatency: ${this.data.canvasLatency}`) - instance.addEventListener("xterm-input", (e) => this.isoterminal.send(e.detail,0) ) + 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) ) // run iso let opts = {dom:instance.dom} for( let i in this.data ) opts[i] = this.data[i] - this.isoterminal.start(opts) + this.term.start(opts) }) instance.setAttribute("dom", "") - this.isoterminal.addEventListener('postReady', (e)=>{ + 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 ){ @@ -240,12 +249,13 @@ if( typeof AFRAME != 'undefined '){ }else wb.resize() }) - this.isoterminal.addEventListener('ready', (e)=>{ + this.term.addEventListener('ready', (e) => { instance.dom.classList.remove('blink') - this.isoterminal.emit('status',"running") + this.term.emit('status',"running") + if( this.data.debug ) this.runTests() }) - this.isoterminal.addEventListener('status', function(e){ + this.term.addEventListener('status', function(e){ let msg = e.detail const w = instance.winbox if(!w) return @@ -265,7 +275,7 @@ if( typeof AFRAME != 'undefined '){ if( this.el.components.xterm ){ this.el.components.xterm.term.focus() } - if( this.el.components.window ){ + if( this.el.components.window && this.data.renderer == 'canvas'){ this.el.components.window.show( showdom ) } } @@ -287,6 +297,14 @@ if( typeof AFRAME != 'undefined '){ } }, + runTests: async function(){ + await AFRAME.utils.require({ + "test_util": "tests/util.js", + "test_isoterminal":"tests/ISOTerminal.js" + }) + console.test.run() + }, + events:{ // combined AFRAME+DOM reactive events diff --git a/com/isoterminal/ISOTerminal.js b/com/isoterminal/ISOTerminal.js index b172be8..3d65a03 100644 --- a/com/isoterminal/ISOTerminal.js +++ b/com/isoterminal/ISOTerminal.js @@ -22,7 +22,7 @@ ISOTerminal.prototype.emit = function(event,data,sender){ // this feels complex, but actually keeps event- and function-names more concise in codebase this.dispatchEvent( evObj ) if( sender != "instance" && this.instance ) this.instance.dispatchEvent(evObj) - if( sender != "worker" && this.worker ) this.worker.postMessage({event,data}, this.getTransferable(data) ) + if( sender != "worker" && this.worker ) this.worker.postMessage({event,data}, PromiseWorker.prototype.getTransferable(data) ) if( sender !== undefined && typeof this[event] == 'function' ) this[event].apply(this, data && data.push ? data : [data] ) //}) } @@ -128,46 +128,30 @@ ISOTerminal.prototype.start = function(opts){ //disable_jit: false, filesystem: {}, autostart: true, + debug: this.opts.debug ? true : false }; + this + .setupWorker(opts) + .startVM(opts) +} + +ISOTerminal.prototype.setupWorker = function(opts){ + /* * the WebWorker (which runs v86) * */ - - this.worker = new Worker("com/isoterminal/worker.js"); - this.worker.onmessage = (e) => { - const {event,data} = e.data - const cb = (event,data) => () => { - if( data.promiseId ){ - this.workerPromise.resolver(data) // forward to promise resolver - }else this.emit(event,data,"worker") // forward event to world - - } + this.worker = new PromiseWorker( "com/isoterminal/worker.js", (cb,event,data) => { + if( !data.promiseId ) this.emit(event,data,"worker") // forward event to world this.preventFrameDrop( cb(event,data) ) - } - - /* - * postMessage.promise basically performs this.worker.postMessage - * in a promise way (to easily retrieve async output) - */ + }) - this.worker.postMessage.promise = function(data){ - if( typeof data != 'object' ) data = {data} - this.resolvers = this.resolvers || {} - this.id = this.id == undefined ? 0 : this.id - data.id = this.id++ - // Send id and task to WebWorker - this.preventFrameDrop( () => this.worker.postMessage(data,getTransferable(data) ) ) - return new Promise(resolve => this.resolvers[data.id] = resolve); - }.bind(this.worker.postMessage) + return this +} - this.worker.postMessage.promise.resolver = function(data){ - if( !data || !data.promiseId ) throw 'promiseId not given' - this.resolvers[ data.promiseId ](data); - delete this.resolvers[ data.promiseId ]; // Prevent memory leak - }.bind(this.worker.postMessage) +ISOTerminal.prototype.startVM = function(opts){ this.emit('runISO',{...opts, bufferLatency: this.opts.bufferLatency }) const loading = [ @@ -228,10 +212,10 @@ ISOTerminal.prototype.start = function(opts){ \r local-first, polyglot, unixy WebXR IDE & runtime \r \r credits: NLnet | @nlnet@nlnet.nl -\r MrDoob | THREE.js -\r Diego Marcos | AFRAME.js \r Leon van Kammen | @lvk@mastodon.online \r Fabien Benetou | @utopiah@mastodon.pirateparty.be +\r Mr Doob | THREE.js +\r Diego Marcos | AFRAME.js ` const text_color = "\r" @@ -303,16 +287,3 @@ ISOTerminal.prototype.preventFrameDrop = function(cb){ } } -ISOTerminal.prototype.getTransferable = function(data){ - function isTransferable(obj) { - return obj instanceof ArrayBuffer || - obj instanceof MessagePort || - obj instanceof ImageBitmap || - (typeof OffscreenCanvas !== 'undefined' && obj instanceof OffscreenCanvas) || - (typeof ReadableStream !== 'undefined' && obj instanceof ReadableStream) || - (typeof WritableStream !== 'undefined' && obj instanceof WritableStream) || - (typeof TransformStream !== 'undefined' && obj instanceof TransformStream); - } - if( isTransferable(data) ) console.log("Transferable!") - if( isTransferable(data) ) return isTransferable(data) ? [data] : undefined -} diff --git a/com/isoterminal/PromiseWorker.js b/com/isoterminal/PromiseWorker.js new file mode 100644 index 0000000..59447a1 --- /dev/null +++ b/com/isoterminal/PromiseWorker.js @@ -0,0 +1,77 @@ +/* + * This is basically a Javascript Proxy for 'new Worker()' + * which allows calling worker-functions via promises. + * + * It's basically comlink without the fat. + * + * const w = new PromiseWorker("worker.js", (cb,event_or_fn,data) => { + * cb(event_or_fn,data) // decorate/ratelimit/hooks here + * }) + * w.foo().then( console.dir ) + * + * in worker.js define a function: this.foo = () => return {foo:"Bar"} + */ + +function PromiseWorker(file, onmessage){ + + let proxy + const worker = new Worker(file) + + worker.onmessage = (e) => { // handle messages originating from worker + const {event,data} = e.data // this is worker.onmessage(...) + const cb = (event,data) => () => { // + if( data.promiseId ){ // + proxy.resolver(data) // forward to promise resolver + } + } + onmessage(cb,event,data) + } + + return proxy = new Proxy(this,{ + get(me,k){ + if( k.match(/(postMessage|onmessage)/) ) return worker[k].bind(worker) + if( k.match(/(resolver|promise)/) ) return this[k].bind(this) + // promisify postMessage(...) call + return function(){ + return this.promise(me,{event: k, data: [...arguments] }) + } + }, + + promise(me,msg){ + if( typeof msg != 'object' || !msg.event || !msg.data ){ + throw 'worker.promise({event:..,data:..}) did not receive correct msg : '+JSON.stringify(msg) + } + this.resolvers = this.resolvers || {last:1,pending:{}} + msg.data.promiseId = this.resolvers.last++ + // Send id and task to WebWorker + worker.postMessage(msg, PromiseWorker.prototype.getTransferable(msg.data) ) + return new Promise( resolve => this.resolvers.pending[ msg.data.promiseId ] = resolve ); + }, + + resolver(data){ + if( !data || !data.promiseId ) throw 'promiseId not given' + this.resolvers.pending[ data.promiseId ](data.result); + delete this.resolvers.pending[ data.promiseId ]; // Prevent memory leak + } + + }) + +} + +PromiseWorker.prototype.getTransferable = function(data){ + let objs = [] + function isTransferable(obj) { + return obj instanceof ArrayBuffer || + obj instanceof MessagePort || + obj instanceof ImageBitmap || + (typeof OffscreenCanvas !== 'undefined' && obj instanceof OffscreenCanvas) || + (typeof ReadableStream !== 'undefined' && obj instanceof ReadableStream) || + (typeof WritableStream !== 'undefined' && obj instanceof WritableStream) || + (typeof TransformStream !== 'undefined' && obj instanceof TransformStream); + } + for( var i in data ){ + if( isTransferable(data[i]) ) objs.push(data[i]) + } + if( objs.length ) debugger + return objs.length ? objs : undefined +} diff --git a/com/isoterminal/feat/9pfs_utils.js b/com/isoterminal/feat/9pfs_utils.js index 830f2c6..5ca310b 100644 --- a/com/isoterminal/feat/9pfs_utils.js +++ b/com/isoterminal/feat/9pfs_utils.js @@ -12,15 +12,19 @@ emulator.fs9p.update_file = async function(file,data){ } const inode = this.GetInode(p.id); - const buf = typeof data == 'string' ? convert.toUint8Array(data) : data - await this.Write(p.id,0, buf.length, buf ) - // update inode - inode.size = buf.length - const now = Math.round(Date.now() / 1000); - inode.atime = inode.mtime = now; - me.postMessage({event:'exec',data:[`touch /mnt/${file}`]}) // update inode - return new Promise( (resolve,reject) => resolve(buf) ) - + const buf = typeof data == 'string' ? convert.toUint8Array(data) : data || "" + try{ + await this.Write(p.id,0, buf.length, buf ) + // update inode + inode.size = buf.length + const now = Math.round(Date.now() / 1000); + inode.atime = inode.mtime = now; + me.postMessage({event:'exec',data:[`touch /mnt/${file}`]}) // update inode + return new Promise( (resolve,reject) => resolve(buf) ) + }catch(e){ + console.error({file,data}) + return new Promise( (resolve,reject) => reject(e) ) + } } emulator.fs9p.append_file = async function(file,data){ diff --git a/com/isoterminal/feat/autorestore.js b/com/isoterminal/feat/autorestore.js index 80d3e3d..099d278 100644 --- a/com/isoterminal/feat/autorestore.js +++ b/com/isoterminal/feat/autorestore.js @@ -9,7 +9,7 @@ if( typeof emulator != 'undefined' ){ this['emulator.save_state'] = async function(){ console.log("saving session") let state = await emulator.save_state() - this.postMessage({event:"state_saved",data:state}) + this.postMessage({event:"state_saved",data:state},[state]) } diff --git a/com/isoterminal/feat/index.html.js b/com/isoterminal/feat/index.html.js index c1ce3ee..74a016d 100644 --- a/com/isoterminal/feat/index.html.js +++ b/com/isoterminal/feat/index.html.js @@ -2,22 +2,31 @@ if( typeof emulator != 'undefined' ){ // inside worker-thread - // unix to js device - this.emulator.readFromPipe( 'root/index.html', async (data) => { - const buf = await emulator.read_file("root/index.html") - const decoder = new TextDecoder('utf-8'); - const html = decoder.decode(buf).replace(/^#!\/bin\/html/,'') // remove leftover shebangs if any - try{ - this.postMessage({event:'runHTML',data:[html]}) - }catch(e){ - console.error(e) - } - }) + this.listenIndexHTML = () => { + const file = "dev/browser/html" + emulator.readFromPipe( file, async (data) => { + const buf = await emulator.read_file( file ) + const decoder = new TextDecoder('utf-8'); + const html = decoder.decode(buf).replace(/^#!\/bin\/html/,'') // remove leftover shebangs if any + try{ + this.postMessage({event:'runHTML',data:[html]}) + }catch(e){ + console.error(file) + console.error(e) + } + }) + } }else{ // inside browser-thread + ISOTerminal.addEventListener('emulator-started', function(){ + this.addEventListener('ready', async () => { + this.worker.listenIndexHTML() + }) + }) + ISOTerminal.prototype.runHTML = function(html){ let $scene = document.querySelector("a-scene") let $root = document.querySelector("a-entity#root") @@ -26,6 +35,7 @@ if( typeof emulator != 'undefined' ){ $root.id = "root" $scene.appendChild($root) } + console.log(html) $root.innerHTML = html } diff --git a/com/isoterminal/feat/index.js.js b/com/isoterminal/feat/index.js.js index f5e81d9..8dfab17 100644 --- a/com/isoterminal/feat/index.js.js +++ b/com/isoterminal/feat/index.js.js @@ -2,8 +2,8 @@ if( typeof emulator != 'undefined' ){ // inside worker-thread // unix to js device - this.emulator.readFromPipe( 'root/index.js', async (data) => { - const buf = await emulator.read_file("root/index.js") + this.emulator.readFromPipe( 'dev/browser/js', async (data) => { + const buf = await emulator.read_file("dev/browser/js") const decoder = new TextDecoder('utf-8'); const js = decoder.decode(buf).replace(/^#!\/bin\/js/,'') // remove leftover shebangs if any try{ diff --git a/com/isoterminal/feat/jsconsole.js b/com/isoterminal/feat/jsconsole.js index 3075c20..288f983 100644 --- a/com/isoterminal/feat/jsconsole.js +++ b/com/isoterminal/feat/jsconsole.js @@ -9,17 +9,17 @@ ISOTerminal.prototype.redirectConsole = function(handler){ log.apply(log, args); }; console.error = (...args)=>{ - const textArg = args[0].message?args[0].message:args[0]; + const textArg = args[0] handler( textArg+'\n', '\x1b[31merror\x1b[0m'); err.apply(log, args); }; console.dir = (...args)=>{ - const textArg = args[0].message?args[0].message:args[0]; + const textArg = args[0] handler( JSON.stringify(textArg,null,2)+'\n'); dir.apply(log, args); }; console.warn = (...args)=>{ - const textArg = args[0].message?args[0].message:args[0]; + const textArg = args[0] handler(textArg+'\n','\x1b[38;5;208mwarn\x1b[0m'); err.apply(log, args); }; @@ -46,7 +46,6 @@ ISOTerminal.addEventListener('emulator-started', function(){ }); window.addEventListener('unhandledrejection', function(event) { - console.error('Unhandled promise rejection:') console.error(event); }); diff --git a/com/isoterminal/worker.js b/com/isoterminal/worker.js index 869069b..70ea1de 100644 --- a/com/isoterminal/worker.js +++ b/com/isoterminal/worker.js @@ -2,12 +2,14 @@ importScripts("libv86.js"); importScripts("ISOTerminal.js") // we don't instance it again here (just use its functions) this.runISO = function(opts){ + this.opts = opts + if( opts.debug ) console.dir(opts) + if( opts.cdrom && !opts.cdrom.url.match(/^http/) ) opts.cdrom.url = "../../"+opts.cdrom.url if( opts.bzimage && !opts.cdrom.url.match(/^http/) ) opts.bzimage.url = "../../"+opts.bzimage.url - console.dir(opts) let emulator = this.emulator = new V86(opts); - console.log("worker:started emulator") + console.log("[worker.js] started emulator") // event forwarding @@ -40,8 +42,8 @@ this.runISO = function(opts){ /* * forward events/functions so non-worker world can reach them */ - this['emulator.create_file'] = function(){ emulator.create_file.apply(emulator, arguments[0]) } - this['emulator.read_file'] = function(){ emulator.read_file.apply(emulator, arguments[0]) } + this['emulator.create_file'] = async function(){ return emulator.create_file.apply(emulator, arguments[0]) } + this['emulator.read_file'] = async function(){ return emulator.read_file.apply(emulator, arguments[0]) } // filename will be read from 9pfs: "/mnt/"+filename emulator.readFromPipe = function(filename,cb){ @@ -64,7 +66,19 @@ this['serial0-input'] = function(c){ this.emulator.bus.send( 'serial0-input', c) this['serial1-input'] = function(c){ this.emulator.bus.send( 'serial1-input', c) } // to /dev/ttyS1 this['serial2-input'] = function(c){ this.emulator.bus.send( 'serial2-input', c) } // to /dev/ttyS2 -this.onmessage = function(e){ +this.onmessage = async function(e){ let {event,data} = e.data - if( this[event] ) this[event](data) + if( this[event] ){ + if( this.opts?.debug ) console.log(`[worker.js] this.${event}(${JSON.stringify(data).substr(0,60)})`) + try{ + let result = await this[event](data) + if( data.promiseId ){ // auto-callback to ISOTerminal.worker.promise(...) + this.postMessage({event,data: {...data,result}}) + } + }catch(e){ + if( data.promiseId ){ // auto-callback to ISOTerminal.worker.promise(...) + this.postMessage({event,data: {...data,error:e}}) + } + } + } } diff --git a/com/xterm.js b/com/xterm.js index 8f3497e..6c53047 100644 --- a/com/xterm.js +++ b/com/xterm.js @@ -92,6 +92,7 @@ const TERMINAL_THEME = { AFRAME.registerComponent('xterm', { schema: Object.assign({ + XRrenderer: { type: 'string', default: 'canvas', }, cols: { type: 'number', default: 110, }, rows: { type: 'number', default: Math.floor( (window.innerHeight * 0.7 ) * 0.054 ) }, canvasLatency:{ type:'number', default: 200 } @@ -108,21 +109,33 @@ AFRAME.registerComponent('xterm', { overflow: hidden; `) - // setup slightly bigger black backdrop (this.el.getObject3D("mesh")) - // and terminal text (this.el.planeText.getObject("mesh")) - this.el.setAttribute("geometry",`primitive: box; width:2.07; height:${this.data.rows*5.3/this.data.cols}*2; depth: -0.12`) - this.el.setAttribute("material","shader:flat; color:black; opacity:0.5; transparent:true; ") - this.el.planeText = document.createElement('a-entity') - this.el.planeText.setAttribute("geometry",`primitive: plane; width:2; height:${this.data.rows*5/this.data.cols}*2`) - this.el.appendChild(this.el.planeText) - this.el.terminalElement = terminalElement - // we switch between dom/canvas rendering because canvas looks pixely in nonimmersive mode - this.el.sceneEl.addEventListener('enter-vr', this.enterImmersive.bind(this) ) - this.el.sceneEl.addEventListener('enter-ar', this.enterImmersive.bind(this) ) - this.el.sceneEl.addEventListener('exit-vr', this.exitImmersive.bind(this) ) - this.el.sceneEl.addEventListener('exit-ar', this.exitImmersive.bind(this) ) + if( this.data.XRrenderer == 'canvas' ){ + // setup slightly bigger black backdrop (this.el.getObject3D("mesh")) + // and terminal text (this.el.planeText.getObject("mesh")) + this.el.setAttribute("geometry",`primitive: box; width:2.07; height:${this.data.rows*5.3/this.data.cols}*2; depth: -0.12`) + this.el.setAttribute("material","shader:flat; color:black; opacity:0.5; transparent:true; ") + this.el.planeText = document.createElement('a-entity') + this.el.planeText.setAttribute("geometry",`primitive: plane; width:2; height:${this.data.rows*5/this.data.cols}*2`) + this.el.appendChild(this.el.planeText) + + // we switch between dom/canvas rendering because canvas looks pixely in nonimmersive mode + this.el.sceneEl.addEventListener('enter-vr', this.enterImmersive.bind(this) ) + this.el.sceneEl.addEventListener('enter-ar', this.enterImmersive.bind(this) ) + this.el.sceneEl.addEventListener('exit-vr', this.exitImmersive.bind(this) ) + this.el.sceneEl.addEventListener('exit-ar', this.exitImmersive.bind(this) ) + + } + + this.tick = AFRAME.utils.throttleLeadingAndTrailing( () => { + if( this.el.sceneEl.renderer.xr.isPresenting ){ + // workaround + // xterm relies on window.requestAnimationFrame (which is not called WebXR immersive mode) + //this.term._core.viewport._innerRefresh() + this.term._core.renderer._renderDebouncer._innerRefresh() + } + }, this.data.canvasLatency) // Build up a theme object const theme = Object.keys(this.data).reduce((theme, key) => { @@ -149,15 +162,6 @@ AFRAME.registerComponent('xterm', { rendererType: this.renderType // 'dom' // 'canvas' }) - this.tick = AFRAME.utils.throttleLeadingAndTrailing( () => { - if( this.el.sceneEl.renderer.xr.isPresenting ){ - // workaround - // xterm relies on window.requestAnimationFrame (which is not called WebXR immersive mode) - //this.term._core.viewport._innerRefresh() - this.term._core.renderer._renderDebouncer._innerRefresh() - } - }, this.data.canvasLatency) - this.term.open(terminalElement) this.term.focus() this.setRenderType('dom')