Compare commits
5 Commits
9aff2133a0
...
4f83e1af0b
Author | SHA1 | Date |
---|---|---|
Leon van Kammen | 4f83e1af0b | |
Leon van Kammen | ce44449fc4 | |
Leon van Kammen | 10a3ff6688 | |
Leon van Kammen | e5a24bbb66 | |
Leon van Kammen | 2105120819 |
|
@ -4,6 +4,8 @@ AFRAME.registerComponent('codemirror', {
|
|||
schema: {
|
||||
file: { type:"string"},
|
||||
term: { type:"selector", default: "[isoterminal]" },
|
||||
width: { type:"number", default:900},
|
||||
height: { type:"number", default:700},
|
||||
},
|
||||
|
||||
init: function () {
|
||||
|
@ -12,7 +14,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()
|
||||
},
|
||||
|
@ -32,8 +34,9 @@ AFRAME.registerComponent('codemirror', {
|
|||
html: (me) => `<div class="codemirror">
|
||||
</div>`,
|
||||
|
||||
css: (me) => `.codemirror{
|
||||
width:100%;
|
||||
css: (me) => `.CodeMirror{
|
||||
width: ${me.com.data.width}px !important;
|
||||
height: ${me.com.data.height-30}px !important;
|
||||
}
|
||||
.codemirror *{
|
||||
font-size: 14px;
|
||||
|
@ -42,7 +45,11 @@ AFRAME.registerComponent('codemirror', {
|
|||
letter-spacing: 0 !important;
|
||||
text-shadow: 0px 0px 10px #F075;
|
||||
}
|
||||
.wb-body + .codemirror{ overflow:hidden; }
|
||||
|
||||
.wb-body:has(> .codemirror){
|
||||
overflow:hidden;
|
||||
}
|
||||
|
||||
.CodeMirror {
|
||||
margin-top:18px;
|
||||
}
|
||||
|
@ -53,7 +60,7 @@ AFRAME.registerComponent('codemirror', {
|
|||
},
|
||||
|
||||
createEditor: function(value){
|
||||
this.el.setAttribute("window", `title: codemirror; uid: ${this.el.dom.id}; attach: #overlay; dom: #${this.el.dom.id};`)
|
||||
this.el.setAttribute("window", `title: codemirror; uid: ${this.el.dom.id}; attach: #overlay; dom: #${this.el.dom.id}; width: ${this.data.width}px; height: ${this.data.height}px`)
|
||||
this.editor = CodeMirror( this.el.dom, {
|
||||
value,
|
||||
mode: "htmlmixed",
|
||||
|
@ -67,33 +74,54 @@ AFRAME.registerComponent('codemirror', {
|
|||
}
|
||||
})
|
||||
this.editor.setOption("theme", "shadowfox")
|
||||
this.editor.updateFile = AFRAME.utils.throttleLeadingAndTrailing( (file,str) => {
|
||||
this.updateFile(file,str),
|
||||
2000
|
||||
})
|
||||
this.editor.updateFile = AFRAME.utils.throttle( (file,str) => {
|
||||
this.updateFile(file,str)
|
||||
}, 1500)
|
||||
this.editor.on('change', (instance,changeObj) => {
|
||||
this.editor.updateFile( this.data.file, instance.getValue() )
|
||||
})
|
||||
|
||||
this
|
||||
.handleFocus()
|
||||
|
||||
setTimeout( () => {
|
||||
this.el.setAttribute("html-as-texture-in-xr", `domid: #${this.el.dom.id}`) // only show aframe-html in xr
|
||||
},1500)
|
||||
},
|
||||
|
||||
handleFocus: function(){
|
||||
const focus = (showdom) => (e) => {
|
||||
if( this.editor ){
|
||||
this.editor.focus()
|
||||
}
|
||||
if( this.el.components.window && this.data.renderer == 'canvas'){
|
||||
this.el.components.window.show( showdom )
|
||||
}
|
||||
}
|
||||
this.el.addEventListener('obbcollisionstarted', focus(false) )
|
||||
this.el.sceneEl.addEventListener('enter-vr', focus(false) )
|
||||
this.el.sceneEl.addEventListener('enter-ar', focus(false) )
|
||||
this.el.sceneEl.addEventListener('exit-vr', focus(true) )
|
||||
this.el.sceneEl.addEventListener('exit-ar', focus(true) )
|
||||
},
|
||||
|
||||
updateFile: async function(file,str){
|
||||
// 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)
|
||||
console.log(str)
|
||||
await this.isoterminal.worker['emulator.update_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) => {
|
||||
|
|
|
@ -2,7 +2,8 @@ if( !AFRAME.components['html-as-texture-in-xr'] ){
|
|||
|
||||
AFRAME.registerComponent('html-as-texture-in-xr', {
|
||||
schema: {
|
||||
domid: { type:"string"}
|
||||
domid: { type:"string"},
|
||||
faceuser: { type: "boolean", default: false}
|
||||
},
|
||||
|
||||
dependencies:{
|
||||
|
@ -19,7 +20,9 @@ if( !AFRAME.components['html-as-texture-in-xr'] ){
|
|||
let s = await AFRAME.utils.require(this.dependencies)
|
||||
this.el.setAttribute("html",`html: ${this.data.domid}; cursor:#cursor; xrlayer: true`)
|
||||
this.el.setAttribute("visible", AFRAME.utils.XD() == '3D' ? 'true' : 'false' )
|
||||
this.el.setAttribute("position", AFRAME.utils.XD.getPositionInFrontOfCamera(0.5) )
|
||||
if( this.data.faceuser ){
|
||||
this.el.setAttribute("position", AFRAME.utils.XD.getPositionInFrontOfCamera(0.8) )
|
||||
}
|
||||
},
|
||||
|
||||
manifest: { // HTML5 manifest to identify app to xrsh
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
* │ │ ┌────────┐ ┌─────────▼──┐ ││ └───────────────┘ │ ▲
|
||||
* │ └───────►│ plane ├─────►text───┼►canvas │◄────────────────── enter-VR │ │
|
||||
* │ └────────┘ └────────────┘ ││ enter-AR ◄─┘ │
|
||||
* │ ││ │
|
||||
* │ renderer=canvas ││ │
|
||||
* │ ││ │
|
||||
* │ ISOTerminal.js ││ │
|
||||
* │ ┌───────────────────────────┐◄────┘│ │
|
||||
|
@ -35,23 +35,28 @@ 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 },
|
||||
depth: { type: 'number',"default": 0.03 },
|
||||
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":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
|
||||
memory: { type: 'number', "default":40 }, // VM memory (in MB) [NOTE: quest or smartphone might crash > 40mb ]
|
||||
bufferLatency: { type: 'number', "default":1 }, // 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
|
||||
|
||||
this.calculateDimension()
|
||||
this.initHud()
|
||||
this.setupBox()
|
||||
|
||||
fetch(this.data.iso,{method: 'HEAD'})
|
||||
.then( (res) => {
|
||||
if( res.status != 200 ) throw 'not found'
|
||||
|
@ -68,30 +73,40 @@ 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",
|
||||
// html to texture
|
||||
htmlinxr: "com/html-as-texture-in-xr.js",
|
||||
// isoterminal features
|
||||
// isoterminal global features
|
||||
PromiseWorker: "com/isoterminal/PromiseWorker.js",
|
||||
ISOTerminal: "com/isoterminal/ISOTerminal.js",
|
||||
localforage: "https://cdn.rawgit.com/mozilla/localForage/master/dist/localforage.js"
|
||||
localforage: "com/isoterminal/localforage.js",
|
||||
},
|
||||
|
||||
dom: {
|
||||
//scale: 0.5,
|
||||
scale: 0.66,
|
||||
events: ['click','keydown'],
|
||||
html: (me) => `<div class="isoterminal">
|
||||
<div id="term" tabindex="0">
|
||||
<pre></pre>
|
||||
</div>
|
||||
</div>`,
|
||||
|
||||
css: (me) => `.isoterminal{
|
||||
padding: ${me.com.data.padding}px;
|
||||
width:100%;
|
||||
height:100%;
|
||||
position:relative;
|
||||
}
|
||||
.isoterminal div{
|
||||
display:block;
|
||||
position:relative;
|
||||
line-height: ${me.com.data.lineHeight}px;
|
||||
}
|
||||
#term {
|
||||
outline: none !important;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Cousine';
|
||||
|
@ -108,6 +123,11 @@ if( typeof AFRAME != 'undefined '){
|
|||
|
||||
.isoterminal style{ display:none }
|
||||
|
||||
blink{
|
||||
border:none;
|
||||
padding:none;
|
||||
}
|
||||
|
||||
#overlay .winbox:has(> .isoterminal){
|
||||
background:transparent;
|
||||
box-shadow:none;
|
||||
|
@ -116,35 +136,21 @@ if( typeof AFRAME != 'undefined '){
|
|||
.wb-body:has(> .isoterminal){
|
||||
background: #000C;
|
||||
overflow:hidden;
|
||||
border-radius:7px;
|
||||
}
|
||||
|
||||
.XR .wb-body:has(> .isoterminal){
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.XR .isoterminal{
|
||||
background: #000;
|
||||
}
|
||||
.isoterminal *,
|
||||
.isoterminal .xterm-dom-renderer-owner-1 .xterm-rows {
|
||||
background:transparent !important;
|
||||
.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;
|
||||
}
|
||||
.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;
|
||||
}
|
||||
|
||||
@keyframes fade {
|
||||
from { opacity: 1.0; }
|
||||
|
@ -158,7 +164,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;
|
||||
|
@ -169,8 +174,7 @@ if( typeof AFRAME != 'undefined '){
|
|||
|
||||
initTerminal: async function(singleton){
|
||||
|
||||
if( this.data.xterm ){
|
||||
// why 3.12?
|
||||
// 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]
|
||||
|
@ -178,10 +182,7 @@ if( typeof AFRAME != 'undefined '){
|
|||
// * 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"
|
||||
}
|
||||
// * v3.12 had slightly better performance but still very heavy
|
||||
|
||||
await AFRAME.utils.require(this.requires)
|
||||
await AFRAME.utils.require({ // ISOTerminal plugins
|
||||
|
@ -210,42 +211,38 @@ 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}`)
|
||||
this.setupVT100(instance)
|
||||
setTimeout( () => {
|
||||
instance.setAttribute("html-as-texture-in-xr", `domid: #term; faceuser: true`)
|
||||
},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')
|
||||
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) )
|
||||
|
||||
// run iso
|
||||
let opts = {dom:instance.dom}
|
||||
for( let i in this.data ) opts[i] = this.data[i]
|
||||
this.isoterminal.start(opts)
|
||||
opts.cols = this.cols
|
||||
opts.rows = this.rows
|
||||
this.term.start(opts)
|
||||
})
|
||||
|
||||
instance.setAttribute("dom", "")
|
||||
|
||||
this.isoterminal.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.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
|
||||
|
@ -262,12 +259,10 @@ 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 ){
|
||||
if( this.el.components.window && this.data.renderer == 'canvas'){
|
||||
this.el.components.window.show( showdom )
|
||||
}
|
||||
this.el.emit('focus',e.detail)
|
||||
}
|
||||
|
||||
this.el.addEventListener('obbcollisionstarted', focus(false) )
|
||||
|
@ -287,6 +282,72 @@ if( typeof AFRAME != 'undefined '){
|
|||
}
|
||||
},
|
||||
|
||||
runTests: async function(){
|
||||
await AFRAME.utils.require({
|
||||
"test_util": "tests/util.js",
|
||||
"test_isoterminal":"tests/ISOTerminal.js"
|
||||
})
|
||||
console.test.run()
|
||||
},
|
||||
|
||||
setupVT100: function(instance){
|
||||
const el = this.el.dom.querySelector('#term')
|
||||
const opts = {
|
||||
cols: this.cols,
|
||||
rows: this.rows,
|
||||
el_or_id: el,
|
||||
max_scroll_lines: 100,
|
||||
nodim: true
|
||||
}
|
||||
this.vt100 = new VT100( opts )
|
||||
this.vt100.el = el
|
||||
this.vt100.curs_set( 1, true)
|
||||
el.focus()
|
||||
this.el.addEventListener('focus', () => el.focus())
|
||||
this.vt100.getch( (ch,t) => {
|
||||
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);
|
||||
this.vt100.addchr(chr)
|
||||
})
|
||||
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) )
|
||||
// })
|
||||
//})
|
||||
},
|
||||
|
||||
setupBox: function(){
|
||||
// setup slightly bigger black backdrop (this.el.getObject3D("mesh"))
|
||||
const w = this.data.width/950;
|
||||
const h = this.data.height/950;
|
||||
this.el.box = document.createElement('a-entity')
|
||||
this.el.box.setAttribute("geometry",`primitive: box; width:${w}; height:${h}; depth: -${this.data.depth}`)
|
||||
this.el.box.setAttribute("material","shader:flat; color:black; opacity:0.9; transparent:true; ")
|
||||
this.el.box.setAttribute("position",`0 0 ${(this.data.depth/2)-0.001}`)
|
||||
this.el.appendChild(this.el.box)
|
||||
},
|
||||
|
||||
calculateDimension: function(){
|
||||
if( this.data.width == -1 ) this.data.width = document.body.offsetWidth
|
||||
if( this.data.height == -1 ) this.data.height = document.body.offsetHeight
|
||||
if( this.data.height > this.data.width ) this.data.height = this.data.width // mobile smartphone fix
|
||||
this.data.width -= this.data.padding*2
|
||||
this.data.height -= this.data.padding*2
|
||||
this.cols = Math.floor(this.data.width/this.data.lineHeight*1.9)
|
||||
this.rows = Math.floor(this.data.height*0.5/this.data.lineHeight*1.7) // keep extra height for mobile browser bottom-bar (android)
|
||||
},
|
||||
|
||||
events:{
|
||||
|
||||
// combined AFRAME+DOM reactive events
|
||||
|
|
|
@ -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] )
|
||||
//})
|
||||
}
|
||||
|
@ -61,11 +61,11 @@ ISOTerminal.prototype.convert = {
|
|||
const bytes = new Uint8Array(buffer);
|
||||
const len = bytes.byteLength;
|
||||
for (let i = 0; i < len; i++) binary += String.fromCharCode(bytes[i]);
|
||||
return window.btoa(binary);
|
||||
return btoa(binary);
|
||||
},
|
||||
|
||||
base64ToArrayBuffer: function(base64) {
|
||||
const binaryString = window.atob(base64);
|
||||
const binaryString = atob(base64);
|
||||
const len = binaryString.length;
|
||||
const bytes = new Uint8Array(len);
|
||||
|
||||
|
@ -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)
|
||||
*/
|
||||
return this
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
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 = [
|
||||
|
@ -227,11 +211,14 @@ ISOTerminal.prototype.start = function(opts){
|
|||
\r[38;5;165m ▬▬▬▬▬▬▬▬ https://xrsh.isvery.ninja ▬▬▬▬▬▬▬▬▬▬▬▬
|
||||
\r[38;5;165m 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 credits
|
||||
\r -------
|
||||
\r @nlnet@nlnet.nl
|
||||
\r @lvk@mastodon.online
|
||||
\r @utopiah@mastodon.pirateparty.be
|
||||
\r https://www.w3.org/TR/webxr
|
||||
\r https://three.org
|
||||
\r https://aframe.org
|
||||
`
|
||||
|
||||
const text_color = "\r[38;5;129m"
|
||||
|
@ -303,16 +290,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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -12,7 +12,10 @@ emulator.fs9p.update_file = async function(file,data){
|
|||
}
|
||||
|
||||
const inode = this.GetInode(p.id);
|
||||
const buf = typeof data == 'string' ? convert.toUint8Array(data) : data
|
||||
const buf = typeof data == 'string' ? convert.toUint8Array(data) : data || ""
|
||||
if( buf.length == 0 ) return new Promise( (resolve,reject) => resolve(data) )
|
||||
|
||||
try{
|
||||
await this.Write(p.id,0, buf.length, buf )
|
||||
// update inode
|
||||
inode.size = buf.length
|
||||
|
@ -20,7 +23,10 @@ emulator.fs9p.update_file = async function(file,data){
|
|||
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){
|
||||
|
@ -44,5 +50,3 @@ emulator.fs9p.append_file = async function(file,data){
|
|||
|
||||
}
|
||||
|
||||
this['fs9p.append_file'] = function(){ emulator.fs9p.append_file.apply(emulator.fs9p, arguments[0]) }
|
||||
this['fs9p.update_file'] = function(){ emulator.fs9p.update_file.apply(emulator.fs9p, arguments[0]) }
|
||||
|
|
|
@ -1,15 +1,31 @@
|
|||
if( typeof emulator != 'undefined' ){
|
||||
// inside worker-thread
|
||||
importScripts("localforage.js") // we don't instance it again here (just use its functions)
|
||||
|
||||
this['emulator.restore_state'] = async function(data){
|
||||
await emulator.restore_state(data)
|
||||
return new Promise( (resolve,reject) => {
|
||||
localforage.getItem("state", async (err,stateBase64) => {
|
||||
if( stateBase64 && !err ){
|
||||
state = ISOTerminal.prototype.convert.base64ToArrayBuffer( stateBase64 )
|
||||
await emulator.restore_state(state)
|
||||
console.log("restored state")
|
||||
this.postMessage({event:"state_restored",data:false})
|
||||
}else return reject("worker.js: emulator.restore_state (could not get state from localforage)")
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
}
|
||||
this['emulator.save_state'] = async function(){
|
||||
console.log("saving session")
|
||||
let state = await emulator.save_state()
|
||||
this.postMessage({event:"state_saved",data:state})
|
||||
localforage.setDriver([
|
||||
localforage.INDEXEDDB,
|
||||
localforage.WEBSQL,
|
||||
localforage.LOCALSTORAGE
|
||||
]).then( () => {
|
||||
localforage.setItem("state", ISOTerminal.prototype.convert.arrayBufferToBase64(state) )
|
||||
console.log("state saved")
|
||||
})
|
||||
console.dir(state)
|
||||
}
|
||||
|
||||
|
||||
|
@ -30,27 +46,21 @@ if( typeof emulator != 'undefined' ){
|
|||
localforage.getItem("state", async (err,stateBase64) => {
|
||||
if( stateBase64 && !err && confirm('continue last session?') ){
|
||||
this.noboot = true // see feat/boot.js
|
||||
state = this.convert.base64ToArrayBuffer( stateBase64 )
|
||||
|
||||
this.addEventListener('state_restored', function(){
|
||||
try{
|
||||
await this.worker['emulator.restore_state']()
|
||||
// simulate / fastforward boot events
|
||||
this.postBoot( () => this.send("l\n") )
|
||||
this.postBoot( () => {
|
||||
this.send("l\n")
|
||||
this.send("hook wakeup\n")
|
||||
})
|
||||
|
||||
this.worker.postMessage({event:'emulator.restore_state',data:state})
|
||||
}catch(e){ console.error(e) }
|
||||
}
|
||||
})
|
||||
|
||||
this.save = async () => {
|
||||
const state = await this.worker.postMessage({event:"emulator.save_state",data:false})
|
||||
await this.worker['emulator.save_state']()
|
||||
}
|
||||
|
||||
this.addEventListener('state_saved', function(e){
|
||||
const state = e.detail
|
||||
localforage.setItem("state", this.convert.arrayBufferToBase64(state) )
|
||||
console.log("state saved")
|
||||
})
|
||||
|
||||
window.addEventListener("beforeunload", function (e) {
|
||||
var confirmationMessage = "Sure you want to leave?\nTIP: enter 'save' to continue this session later";
|
||||
(e || window.event).returnValue = confirmationMessage; //Gecko + IE
|
||||
|
|
|
@ -4,7 +4,11 @@ ISOTerminal.addEventListener('ready', function(e){
|
|||
|
||||
ISOTerminal.prototype.boot = async function(e){
|
||||
// set environment
|
||||
let env = ['export BROWSER=1']
|
||||
let env = [
|
||||
`export LINES=${this.opts.rows}`,
|
||||
`export COLUMNS=${this.opts.cols}`,
|
||||
'export BROWSER=1',
|
||||
]
|
||||
for ( let i in document.location ){
|
||||
if( typeof document.location[i] == 'string' ){
|
||||
env.push( 'export '+String(i).toUpperCase()+'="'+decodeURIComponent( document.location[i]+'"') )
|
||||
|
|
|
@ -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")
|
||||
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
|
||||
}
|
||||
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -20,10 +20,20 @@ if( typeof emulator != 'undefined' ){
|
|||
}else{
|
||||
// inside browser-thread
|
||||
|
||||
ISOTerminal.addEventListener('javascript-eval', function(e){
|
||||
ISOTerminal.addEventListener('javascript-eval', async function(e){
|
||||
const {script,PID} = e.detail
|
||||
let res = (new Function(`${script}`))()
|
||||
let res
|
||||
try{
|
||||
res = (new Function(`${script}`))()
|
||||
if( res && typeof res != 'string' ) res = JSON.stringify(res,null,2)
|
||||
}catch(e){
|
||||
console.error(e)
|
||||
console.info(script)
|
||||
res = "error: "+e.toString()
|
||||
if( e.filename ){
|
||||
res += "\n"+e.filename+":"+e.lineno+":"+e.colno
|
||||
}
|
||||
}
|
||||
// update output to 9p with PID as filename (in /mnt/run)
|
||||
this.emit('fs9p.update_file', [`run/${PID}`, this.convert.toUint8Array(res)] )
|
||||
})
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
@ -34,7 +34,7 @@ ISOTerminal.addEventListener('emulator-started', function(){
|
|||
str.trim().split("\n").map( (line) => {
|
||||
finalStr += '\x1b[38;5;165m/dev/browser: \x1b[0m'+prefix+line+'\n'
|
||||
})
|
||||
this.emit('fs9p.append_file', ["/dev/browser/console",finalStr])
|
||||
this.emit('append_file', ["/dev/browser/console",finalStr])
|
||||
})
|
||||
|
||||
window.addEventListener('error', function(event) {
|
||||
|
@ -46,7 +46,6 @@ ISOTerminal.addEventListener('emulator-started', function(){
|
|||
});
|
||||
|
||||
window.addEventListener('unhandledrejection', function(event) {
|
||||
console.error('Unhandled promise rejection:')
|
||||
console.error(event);
|
||||
});
|
||||
|
||||
|
|
|
@ -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,10 @@ 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]) }
|
||||
this['emulator.append_file'] = async function(){ emulator.fs9p.append_file.apply(emulator.fs9p, arguments[0]) }
|
||||
this['emulator.update_file'] = async function(){ emulator.fs9p.update_file.apply(emulator.fs9p, arguments[0]) }
|
||||
|
||||
// filename will be read from 9pfs: "/mnt/"+filename
|
||||
emulator.readFromPipe = function(filename,cb){
|
||||
|
@ -60,11 +64,23 @@ this.runISO = function(opts){
|
|||
* forward events/functions so non-worker world can reach them
|
||||
*/
|
||||
|
||||
this['serial0-input'] = function(c){ this.emulator.bus.send( 'serial0-input', c) } // to /dev/ttyS0
|
||||
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['serial0-input'] = function(c){ emulator.bus.send( 'serial0-input', c) } // to /dev/ttyS0
|
||||
this['serial1-input'] = function(c){ emulator.bus.send( 'serial1-input', c) } // to /dev/ttyS1
|
||||
this['serial2-input'] = function(c){ 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}})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -63,9 +63,27 @@ AFRAME.registerComponent('window', {
|
|||
|
||||
this.el.setAttribute("grabbable","")
|
||||
|
||||
if( this.el.object3D.position.x == 0 &&
|
||||
this.el.object3D.position.y == 0 &&
|
||||
this.el.object3D.position.z == 0 ){ // position next to previous window
|
||||
var els = [...document.querySelectorAll('[window]')]
|
||||
if( els.length < 2 ) return
|
||||
let current = els[ els.length-1 ]
|
||||
let last = els[ els.length-2 ]
|
||||
AFRAME.utils.positionObjectNextToNeighbor( current.object3D , last.object3D, els.length )
|
||||
}
|
||||
},
|
||||
|
||||
show: function(state){
|
||||
this.el.dom.closest('.winbox').style.display = state ? '' : 'none'
|
||||
}
|
||||
})
|
||||
|
||||
AFRAME.utils.positionObjectNextToNeighbor = function positionObjectNextToNeighbor(object, lastNeighbor = null, neighbours, margin = 0.45, degree = 20) {
|
||||
// *FIXME* this could be more sophisticated :)
|
||||
object.position.x = lastNeighbor.position.x + ((neighbours-1) * margin)
|
||||
object.position.y = lastNeighbor.position.y
|
||||
object.position.z = lastNeighbor.position.z
|
||||
//object.rotation.y += THREE.MathUtils.degToRad( (neighbours-1) * degree);
|
||||
|
||||
}
|
||||
|
|
33
com/xterm.js
33
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,22 +109,36 @@ AFRAME.registerComponent('xterm', {
|
|||
overflow: hidden;
|
||||
`)
|
||||
|
||||
this.el.terminalElement = terminalElement
|
||||
|
||||
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`)
|
||||
const w = 2;
|
||||
const h = (this.data.rows*5/this.data.cols)
|
||||
this.el.setAttribute("geometry",`primitive: box; width:${w}; height:${h}; 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.planeText.setAttribute("geometry",`primitive: plane; width:${w}; height:${h}`)
|
||||
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) )
|
||||
|
||||
}
|
||||
|
||||
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) => {
|
||||
if (!key.startsWith('theme_')) return theme
|
||||
|
@ -143,21 +158,13 @@ AFRAME.registerComponent('xterm', {
|
|||
disableStdin: false,
|
||||
rows: this.data.rows,
|
||||
cols: this.data.cols,
|
||||
fontFamily: 'Cousine, monospace',
|
||||
fontSize: this.fontSize,
|
||||
lineHeight: 1.15,
|
||||
useFlowControl: true,
|
||||
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')
|
||||
|
|
Loading…
Reference in New Issue