Compare commits
No commits in common. "main" and "feat/webworker" have entirely different histories.
main
...
feat/webwo
|
@ -4,8 +4,6 @@ AFRAME.registerComponent('codemirror', {
|
||||||
schema: {
|
schema: {
|
||||||
file: { type:"string"},
|
file: { type:"string"},
|
||||||
term: { type:"selector", default: "[isoterminal]" },
|
term: { type:"selector", default: "[isoterminal]" },
|
||||||
width: { type:"number", default:700},
|
|
||||||
height: { type:"number", default:500},
|
|
||||||
},
|
},
|
||||||
|
|
||||||
init: function () {
|
init: function () {
|
||||||
|
@ -14,7 +12,7 @@ AFRAME.registerComponent('codemirror', {
|
||||||
if( this.data.file && this.data.file[0] != '/'){
|
if( this.data.file && this.data.file[0] != '/'){
|
||||||
this.data.file = "root/"+this.data.file
|
this.data.file = "root/"+this.data.file
|
||||||
}
|
}
|
||||||
this.isoterminal = this.data.term.components.isoterminal.term
|
this.isoterminal = this.data.term.components.isoterminal.isoterminal
|
||||||
//this.el.innerHTML = ` `
|
//this.el.innerHTML = ` `
|
||||||
this.requireAll()
|
this.requireAll()
|
||||||
},
|
},
|
||||||
|
@ -34,9 +32,8 @@ AFRAME.registerComponent('codemirror', {
|
||||||
html: (me) => `<div class="codemirror">
|
html: (me) => `<div class="codemirror">
|
||||||
</div>`,
|
</div>`,
|
||||||
|
|
||||||
css: (me) => `.CodeMirror{
|
css: (me) => `.codemirror{
|
||||||
width: ${me.com.data.width}px !important;
|
width:100%;
|
||||||
height: ${me.com.data.height-30}px !important;
|
|
||||||
}
|
}
|
||||||
.codemirror *{
|
.codemirror *{
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
@ -45,11 +42,7 @@ AFRAME.registerComponent('codemirror', {
|
||||||
letter-spacing: 0 !important;
|
letter-spacing: 0 !important;
|
||||||
text-shadow: 0px 0px 10px #F075;
|
text-shadow: 0px 0px 10px #F075;
|
||||||
}
|
}
|
||||||
|
.wb-body + .codemirror{ overflow:hidden; }
|
||||||
.wb-body:has(> .codemirror){
|
|
||||||
overflow:hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.CodeMirror {
|
.CodeMirror {
|
||||||
margin-top:18px;
|
margin-top:18px;
|
||||||
}
|
}
|
||||||
|
@ -60,7 +53,7 @@ AFRAME.registerComponent('codemirror', {
|
||||||
},
|
},
|
||||||
|
|
||||||
createEditor: function(value){
|
createEditor: function(value){
|
||||||
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.el.setAttribute("window", `title: codemirror; uid: ${this.el.dom.id}; attach: #overlay; dom: #${this.el.dom.id};`)
|
||||||
this.editor = CodeMirror( this.el.dom, {
|
this.editor = CodeMirror( this.el.dom, {
|
||||||
value,
|
value,
|
||||||
mode: "htmlmixed",
|
mode: "htmlmixed",
|
||||||
|
@ -74,54 +67,33 @@ AFRAME.registerComponent('codemirror', {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
this.editor.setOption("theme", "shadowfox")
|
this.editor.setOption("theme", "shadowfox")
|
||||||
this.editor.updateFile = AFRAME.utils.throttle( (file,str) => {
|
this.editor.updateFile = AFRAME.utils.throttleLeadingAndTrailing( (file,str) => {
|
||||||
this.updateFile(file,str)
|
this.updateFile(file,str),
|
||||||
}, 1500)
|
2000
|
||||||
|
})
|
||||||
this.editor.on('change', (instance,changeObj) => {
|
this.editor.on('change', (instance,changeObj) => {
|
||||||
this.editor.updateFile( this.data.file, instance.getValue() )
|
this.editor.updateFile( this.data.file, instance.getValue() )
|
||||||
})
|
})
|
||||||
|
|
||||||
this
|
|
||||||
.handleFocus()
|
|
||||||
|
|
||||||
setTimeout( () => {
|
setTimeout( () => {
|
||||||
this.el.setAttribute("html-as-texture-in-xr", `domid: #${this.el.dom.id}`) // only show aframe-html in xr
|
this.el.setAttribute("html-as-texture-in-xr", `domid: #${this.el.dom.id}`) // only show aframe-html in xr
|
||||||
},1500)
|
},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){
|
updateFile: async function(file,str){
|
||||||
// we don't do via shellcmd: isoterminal.exec(`echo '${str}' > ${file}`,1)
|
// we don't do via shellcmd: isoterminal.exec(`echo '${str}' > ${file}`,1)
|
||||||
// as it would require all kindof ugly stringescaping
|
// as it would require all kindof ugly stringescaping
|
||||||
console.log("updating "+file)
|
console.log("updating "+file)
|
||||||
await this.isoterminal.worker.update_file(file, this.isoterminal.convert.toUint8Array(str) )
|
await this.isoterminal.emulator.fs9p.update_file( file, str)
|
||||||
this.isoterminal.exec("touch "+file) // *FIXME* notify filesystem (why does inotifyd need this? v86's 9pfees is cached?)
|
|
||||||
},
|
},
|
||||||
|
|
||||||
events:{
|
events:{
|
||||||
|
|
||||||
// component events
|
// component events
|
||||||
DOMready: function(e){
|
DOMready: function(e){
|
||||||
|
this.isoterminal.worker.postMessage.promise({event:'read_file',data: this.data.file })
|
||||||
this.isoterminal.worker.read_file(this.data.file)
|
|
||||||
.then( this.isoterminal.convert.Uint8ArrayToString )
|
.then( this.isoterminal.convert.Uint8ArrayToString )
|
||||||
.then( (str) => {
|
.then( (str) => {
|
||||||
console.log("creating editor")
|
|
||||||
this.createEditor( str )
|
this.createEditor( str )
|
||||||
})
|
})
|
||||||
.catch( (e) => {
|
.catch( (e) => {
|
||||||
|
|
|
@ -2,8 +2,7 @@ if( !AFRAME.components['html-as-texture-in-xr'] ){
|
||||||
|
|
||||||
AFRAME.registerComponent('html-as-texture-in-xr', {
|
AFRAME.registerComponent('html-as-texture-in-xr', {
|
||||||
schema: {
|
schema: {
|
||||||
domid: { type:"string"},
|
domid: { type:"string"}
|
||||||
faceuser: { type: "boolean", default: false}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
dependencies:{
|
dependencies:{
|
||||||
|
@ -20,9 +19,7 @@ if( !AFRAME.components['html-as-texture-in-xr'] ){
|
||||||
let s = await AFRAME.utils.require(this.dependencies)
|
let s = await AFRAME.utils.require(this.dependencies)
|
||||||
this.el.setAttribute("html",`html: ${this.data.domid}; cursor:#cursor; xrlayer: true`)
|
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("visible", AFRAME.utils.XD() == '3D' ? 'true' : 'false' )
|
||||||
if( this.data.faceuser ){
|
this.el.setAttribute("position", AFRAME.utils.XD.getPositionInFrontOfCamera(0.5) )
|
||||||
this.el.setAttribute("position", AFRAME.utils.XD.getPositionInFrontOfCamera(0.4) )
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
manifest: { // HTML5 manifest to identify app to xrsh
|
manifest: { // HTML5 manifest to identify app to xrsh
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
* │ │ ┌────────┐ ┌─────────▼──┐ ││ └───────────────┘ │ ▲
|
* │ │ ┌────────┐ ┌─────────▼──┐ ││ └───────────────┘ │ ▲
|
||||||
* │ └───────►│ plane ├─────►text───┼►canvas │◄────────────────── enter-VR │ │
|
* │ └───────►│ plane ├─────►text───┼►canvas │◄────────────────── enter-VR │ │
|
||||||
* │ └────────┘ └────────────┘ ││ enter-AR ◄─┘ │
|
* │ └────────┘ └────────────┘ ││ enter-AR ◄─┘ │
|
||||||
* │ renderer=canvas ││ │
|
* │ ││ │
|
||||||
* │ ││ │
|
* │ ││ │
|
||||||
* │ ISOTerminal.js ││ │
|
* │ ISOTerminal.js ││ │
|
||||||
* │ ┌───────────────────────────┐◄────┘│ │
|
* │ ┌───────────────────────────┐◄────┘│ │
|
||||||
|
@ -33,30 +33,22 @@ if( typeof AFRAME != 'undefined '){
|
||||||
|
|
||||||
AFRAME.registerComponent('isoterminal', {
|
AFRAME.registerComponent('isoterminal', {
|
||||||
schema: {
|
schema: {
|
||||||
iso: { type:"string", "default":"https://forgejo.isvery.ninja/assets/xrsh-buildroot/main/xrsh.iso" },
|
iso: { type:"string", "default":"https://forgejo.isvery.ninja/assets/xrsh-buildroot/main/xrsh.iso" },
|
||||||
overlayfs: { type:"string"},
|
overlayfs: { type:"string"},
|
||||||
width: { type: 'number',"default": -1 },
|
cols: { type: 'number',"default": 80 },
|
||||||
height: { type: 'number',"default": -1 },
|
rows: { type: 'number',"default": 20 },
|
||||||
depth: { type: 'number',"default": 0.03 },
|
padding: { type: 'number',"default": 18 },
|
||||||
lineHeight: { type: 'number',"default": 18 },
|
minimized: { type: 'boolean',"default":false},
|
||||||
padding: { type: 'number',"default": 18 },
|
maximized: { type: 'boolean',"default":false},
|
||||||
maximized: { type: 'boolean',"default":true},
|
transparent: { type:'boolean', "default":false }, // need good gpu
|
||||||
muteUntilPrompt:{ type: 'boolean',"default":true}, // mute stdout until a prompt is detected in ISO
|
xterm: { type: 'boolean', "default":true }, // use xterm.js? (=slower)
|
||||||
HUD: { type: 'boolean',"default":false}, // link to camera movement
|
memory: { type: 'number', "default":48 }, // VM memory (in MB)
|
||||||
transparent: { type:'boolean', "default":false }, // need good gpu
|
bufferLatency:{ type: 'number', "default":300 }, // in ms: bufferlatency from webworker to xterm (batch-update every char to texture)
|
||||||
memory: { type: 'number', "default":40 }, // VM memory (in MB) [NOTE: quest or smartphone might crash > 40mb ]
|
canvasLatency:{ type: 'number', "default":300 } // in ms: time between canvas re-draws
|
||||||
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(){
|
init: function(){
|
||||||
this.el.object3D.visible = false
|
this.el.object3D.visible = false
|
||||||
|
|
||||||
this.calculateDimension()
|
|
||||||
this.initHud()
|
|
||||||
this.setupBox()
|
|
||||||
this.setupPasteDrop()
|
|
||||||
|
|
||||||
fetch(this.data.iso,{method: 'HEAD'})
|
fetch(this.data.iso,{method: 'HEAD'})
|
||||||
.then( (res) => {
|
.then( (res) => {
|
||||||
if( res.status != 200 ) throw 'not found'
|
if( res.status != 200 ) throw 'not found'
|
||||||
|
@ -70,44 +62,33 @@ if( typeof AFRAME != 'undefined '){
|
||||||
},
|
},
|
||||||
|
|
||||||
requires:{
|
requires:{
|
||||||
com: "com/dom.js",
|
com: "com/dom.js",
|
||||||
window: "com/window.js",
|
window: "com/window.js",
|
||||||
pastedrop: "com/pastedrop.js",
|
v86: "com/isoterminal/libv86.js",
|
||||||
v86: "com/isoterminal/libv86.js",
|
|
||||||
vt100: "com/isoterminal/VT100.js",
|
|
||||||
// allow xrsh to selfcontain scene + itself
|
// allow xrsh to selfcontain scene + itself
|
||||||
xhook: "com/lib/xhook.min.js",
|
xhook: "https://jpillora.com/xhook/dist/xhook.min.js",
|
||||||
selfcontain: "com/selfcontainer.js",
|
selfcontain: "com/selfcontainer.js",
|
||||||
// html to texture
|
// html to texture
|
||||||
htmlinxr: "com/html-as-texture-in-xr.js",
|
htmlinxr: "com/html-as-texture-in-xr.js",
|
||||||
// isoterminal global features
|
// isoterminal features
|
||||||
PromiseWorker: "com/isoterminal/PromiseWorker.js",
|
ISOTerminal: "com/isoterminal/ISOTerminal.js",
|
||||||
ISOTerminal: "com/isoterminal/ISOTerminal.js",
|
localforage: "https://cdn.rawgit.com/mozilla/localForage/master/dist/localforage.js"
|
||||||
localforage: "com/isoterminal/localforage.js",
|
|
||||||
},
|
},
|
||||||
|
|
||||||
dom: {
|
dom: {
|
||||||
scale: 0.66,
|
//scale: 0.5,
|
||||||
events: ['click','keydown'],
|
events: ['click','keydown'],
|
||||||
html: (me) => `<div class="isoterminal">
|
html: (me) => `<div class="isoterminal">
|
||||||
<div id="term" tabindex="0">
|
|
||||||
<pre></pre>
|
|
||||||
</div>
|
|
||||||
</div>`,
|
</div>`,
|
||||||
|
|
||||||
css: (me) => `.isoterminal{
|
css: (me) => `.isoterminal{
|
||||||
padding: ${me.com.data.padding}px;
|
padding: ${me.com.data.padding}px;
|
||||||
width:100%;
|
width:100%;
|
||||||
height:90%;
|
height:100%;
|
||||||
position:relative;
|
|
||||||
}
|
}
|
||||||
.isoterminal div{
|
.isoterminal div{
|
||||||
display:block;
|
display:block;
|
||||||
position:relative;
|
position:relative;
|
||||||
line-height: ${me.com.data.lineHeight}px;
|
|
||||||
}
|
|
||||||
#term {
|
|
||||||
outline: none !important;
|
|
||||||
}
|
}
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Cousine';
|
font-family: 'Cousine';
|
||||||
|
@ -124,44 +105,42 @@ if( typeof AFRAME != 'undefined '){
|
||||||
|
|
||||||
.isoterminal style{ display:none }
|
.isoterminal style{ display:none }
|
||||||
|
|
||||||
blink{
|
|
||||||
border:none;
|
|
||||||
padding:none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#overlay .winbox:has(> .isoterminal){
|
#overlay .winbox:has(> .isoterminal){
|
||||||
background:transparent;
|
background:transparent;
|
||||||
box-shadow:none;
|
box-shadow:none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cursor {
|
|
||||||
background: #70F !important;
|
|
||||||
animation:fade 1000ms infinite;
|
|
||||||
-webkit-animation:fade 1000ms infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
.XR .cursor {
|
|
||||||
animation:none;
|
|
||||||
-webkit-animation:none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wb-body:has(> .isoterminal){
|
.wb-body:has(> .isoterminal){
|
||||||
background: #000C;
|
background: #000C;
|
||||||
overflow:hidden;
|
overflow:hidden;
|
||||||
|
border-radius:7px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.XR .wb-body:has(> .isoterminal){
|
.XR .wb-body:has(> .isoterminal){
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.XR .isoterminal{
|
|
||||||
background: #000;
|
background: #000;
|
||||||
}
|
}
|
||||||
.isoterminal *{
|
.isoterminal *,
|
||||||
font-size: 14px;
|
.isoterminal .xterm-dom-renderer-owner-1 .xterm-rows {
|
||||||
font-family: "Cousine",Liberation Mono,DejaVu Sans Mono,Courier New,monospace;
|
background:transparent !important;
|
||||||
font-weight:500 !important;
|
font-size: 14px;
|
||||||
text-shadow: 0px 0px 10px #F075;
|
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 {
|
@keyframes fade {
|
||||||
|
@ -176,6 +155,7 @@ if( typeof AFRAME != 'undefined '){
|
||||||
to { opacity: 1.0; }
|
to { opacity: 1.0; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.isoterminal .xterm-rows.xterm-focus .xterm-cursor.xterm-cursor-block,
|
||||||
.blink{
|
.blink{
|
||||||
animation:fade 1000ms infinite;
|
animation:fade 1000ms infinite;
|
||||||
-webkit-animation:fade 1000ms infinite;
|
-webkit-animation:fade 1000ms infinite;
|
||||||
|
@ -186,15 +166,19 @@ if( typeof AFRAME != 'undefined '){
|
||||||
|
|
||||||
initTerminal: async function(singleton){
|
initTerminal: async function(singleton){
|
||||||
|
|
||||||
// why not latest xterm or v3.12 with builtin-canvas support?
|
if( this.data.xterm ){
|
||||||
// first versions used 1.5.4, a typescript rewrite which:
|
// why 3.12?
|
||||||
// * acts weird with oculus browser keyboard (does not repaint properly after typing)
|
// first versions used 1.5.4, a typescript rewrite which:
|
||||||
// * does not use canvas anymore [which would be ideal for THREE.js texture]
|
// * acts weird with oculus browser keyboard (does not repaint properly after typing)
|
||||||
// * does not allow switching between dom/canvas
|
// * does not use canvas anymore [which would be ideal for THREE.js texture]
|
||||||
// * only allows a standalone WebGL addon (conflicts with THREE)
|
// * does not allow switching between dom/canvas
|
||||||
// * heavily dependent on requestAnimationFrame (conflicts with THREE)
|
// * only allows a standalone WebGL addon (conflicts with THREE)
|
||||||
// * typescript-rewrite results in ~300k lib (instead of 96k)
|
// * heavily dependent on requestAnimationFrame (conflicts with THREE)
|
||||||
// * v3.12 had slightly better performance but still very heavy
|
// * 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"
|
||||||
|
}
|
||||||
|
|
||||||
await AFRAME.utils.require(this.requires)
|
await AFRAME.utils.require(this.requires)
|
||||||
await AFRAME.utils.require({ // ISOTerminal plugins
|
await AFRAME.utils.require({ // ISOTerminal plugins
|
||||||
|
@ -203,9 +187,7 @@ if( typeof AFRAME != 'undefined '){
|
||||||
jsconsole: "com/isoterminal/feat/jsconsole.js",
|
jsconsole: "com/isoterminal/feat/jsconsole.js",
|
||||||
indexhtml: "com/isoterminal/feat/index.html.js",
|
indexhtml: "com/isoterminal/feat/index.html.js",
|
||||||
indexjs: "com/isoterminal/feat/index.js.js",
|
indexjs: "com/isoterminal/feat/index.js.js",
|
||||||
autorestore: "com/isoterminal/feat/autorestore.js",
|
autorestore: "com/isoterminal/feat/autorestore.js",
|
||||||
pastedropFeat: "com/isoterminal/feat/pastedrop.js",
|
|
||||||
httpfs: "com/isoterminal/feat/httpfs.js",
|
|
||||||
})
|
})
|
||||||
|
|
||||||
this.el.setAttribute("selfcontainer","")
|
this.el.setAttribute("selfcontainer","")
|
||||||
|
@ -225,39 +207,42 @@ if( typeof AFRAME != 'undefined '){
|
||||||
}
|
}
|
||||||
|
|
||||||
// init isoterminal
|
// init isoterminal
|
||||||
this.term = new ISOTerminal(instance,this.data)
|
this.isoterminal = new ISOTerminal(instance,this.data)
|
||||||
|
|
||||||
instance.addEventListener('DOMready', () => {
|
instance.addEventListener('DOMready', () => {
|
||||||
this.setupVT100(instance)
|
//instance.setAttribute("html-as-texture-in-xr", `domid: #${this.el.dom.id}`)
|
||||||
setTimeout( () => {
|
|
||||||
instance.setAttribute("html-as-texture-in-xr", `domid: #term; faceuser: true`)
|
|
||||||
},100)
|
|
||||||
//instance.winbox.resize(720,380)
|
//instance.winbox.resize(720,380)
|
||||||
let size = `width: ${this.data.width}; height: ${this.data.height}`
|
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}; class: no-full, no-resize, no-move`)
|
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.addEventListener('window.oncreate', (e) => {
|
||||||
instance.dom.classList.add('blink')
|
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
|
// run iso
|
||||||
let opts = {dom:instance.dom}
|
let opts = {dom:instance.dom}
|
||||||
for( let i in this.data ) opts[i] = this.data[i]
|
for( let i in this.data ) opts[i] = this.data[i]
|
||||||
opts.cols = this.cols
|
this.isoterminal.start(opts)
|
||||||
opts.rows = this.rows
|
|
||||||
this.term.start(opts)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
instance.setAttribute("dom", "")
|
instance.setAttribute("dom", "")
|
||||||
instance.setAttribute("pastedrop", "")
|
|
||||||
|
|
||||||
this.term.addEventListener('ready', (e) => {
|
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)=>{
|
||||||
instance.dom.classList.remove('blink')
|
instance.dom.classList.remove('blink')
|
||||||
this.term.emit('status',"running")
|
this.isoterminal.emit('status',"running")
|
||||||
if( this.data.debug ) this.runTests()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
this.term.addEventListener('status', function(e){
|
this.isoterminal.addEventListener('status', function(e){
|
||||||
let msg = e.detail
|
let msg = e.detail
|
||||||
const w = instance.winbox
|
const w = instance.winbox
|
||||||
if(!w) return
|
if(!w) return
|
||||||
|
@ -274,13 +259,14 @@ if( typeof AFRAME != 'undefined '){
|
||||||
instance.addEventListener('window.onmaximize', resize )
|
instance.addEventListener('window.onmaximize', resize )
|
||||||
|
|
||||||
const focus = (showdom) => (e) => {
|
const focus = (showdom) => (e) => {
|
||||||
this.el.emit('focus',e.detail)
|
if( this.el.components.xterm ){
|
||||||
if( this.el.components.window && this.data.renderer == 'canvas'){
|
this.el.components.xterm.term.focus()
|
||||||
|
}
|
||||||
|
if( this.el.components.window ){
|
||||||
this.el.components.window.show( showdom )
|
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-vr', focus(false) )
|
||||||
this.el.sceneEl.addEventListener('enter-ar', focus(false) )
|
this.el.sceneEl.addEventListener('enter-ar', focus(false) )
|
||||||
this.el.sceneEl.addEventListener('exit-vr', focus(true) )
|
this.el.sceneEl.addEventListener('exit-vr', focus(true) )
|
||||||
|
@ -289,96 +275,6 @@ if( typeof AFRAME != 'undefined '){
|
||||||
instance.object3D.quaternion.copy( AFRAME.scenes[0].camera.quaternion ) // face towards camera
|
instance.object3D.quaternion.copy( AFRAME.scenes[0].camera.quaternion ) // face towards camera
|
||||||
},
|
},
|
||||||
|
|
||||||
initHud: function(){
|
|
||||||
if( AFRAME.utils.device.isMobile() ) this.data.HUD = true
|
|
||||||
if( this.data.HUD ){
|
|
||||||
document.querySelector('[camera]').appendChild( this.el )
|
|
||||||
this.el.setAttribute("position","0 -0.03 -0.4")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
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')
|
|
||||||
this.term.opts.vt100 = {
|
|
||||||
cols: this.cols,
|
|
||||||
rows: this.rows,
|
|
||||||
el_or_id: el,
|
|
||||||
max_scroll_lines: this.rows,
|
|
||||||
nodim: true,
|
|
||||||
rainbow: [VT100.COLOR_MAGENTA, VT100.COLOR_CYAN ],
|
|
||||||
xr: AFRAME.scenes[0].renderer.xr,
|
|
||||||
map: {
|
|
||||||
'ArrowRight': { ch: false, ctrl: '\x1b\x66' }, // this triggers ash-shell forward-word
|
|
||||||
'ArrowLeft': { ch: false, ctrl: '\x1b\x62' } // backward-word
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.term.emit('initVT100',this)
|
|
||||||
this.vt100 = new VT100( this.term.opts.vt100 )
|
|
||||||
this.vt100.el = el
|
|
||||||
this.vt100.curs_set( 1, true)
|
|
||||||
this.vt100.focus()
|
|
||||||
this.el.addEventListener('focus', () => this.vt100.focus() )
|
|
||||||
this.vt100.getch( (ch,t) => {
|
|
||||||
this.term.send( ch )
|
|
||||||
})
|
|
||||||
|
|
||||||
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)
|
|
||||||
})
|
|
||||||
|
|
||||||
// translate file upload into pasteFile
|
|
||||||
this.vt100.upload.addEventListener('change', (e) => {
|
|
||||||
const file = this.vt100.upload.files[0];
|
|
||||||
const item = {...file, getAsFile: () => file }
|
|
||||||
this.el.emit('pasteFile', { item, type: file.type });
|
|
||||||
})
|
|
||||||
|
|
||||||
return this
|
|
||||||
},
|
|
||||||
|
|
||||||
setupPasteDrop: function(){
|
|
||||||
this.el.addEventListener('pasteFile', (e) => {
|
|
||||||
e.preventDefault() // prevent bubbling up to window (which is triggering this initially)
|
|
||||||
if( !this.term.pasteFile ) return // skip if feat/pastedrop.js is not loaded
|
|
||||||
this.term.pasteFile(e.detail)
|
|
||||||
})
|
|
||||||
return this
|
|
||||||
},
|
|
||||||
|
|
||||||
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 = Math.floor( document.body.offsetHeight - 30 )
|
|
||||||
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*2)
|
|
||||||
this.rows = Math.floor(this.data.height*0.53/this.data.lineHeight*1.7)
|
|
||||||
},
|
|
||||||
|
|
||||||
events:{
|
events:{
|
||||||
|
|
||||||
// combined AFRAME+DOM reactive events
|
// combined AFRAME+DOM reactive events
|
||||||
|
|
|
@ -17,14 +17,12 @@ function ISOTerminal(instance,opts){
|
||||||
ISOTerminal.prototype.emit = function(event,data,sender){
|
ISOTerminal.prototype.emit = function(event,data,sender){
|
||||||
data = data || false
|
data = data || false
|
||||||
const evObj = new CustomEvent(event, {detail: data} )
|
const evObj = new CustomEvent(event, {detail: data} )
|
||||||
this.preventFrameDrop( () => {
|
// forward event to worker/instance/AFRAME element or component-function
|
||||||
// forward event to worker/instance/AFRAME element or component-function
|
// this feels complex, but actually keeps event- and function-names more concise in codebase
|
||||||
// this feels complex, but actually keeps event- and function-names more concise in codebase
|
this.dispatchEvent( evObj )
|
||||||
this.dispatchEvent( evObj )
|
if( sender != "instance" && this.instance ) this.instance.dispatchEvent(evObj)
|
||||||
if( sender != "instance" && this.instance ) this.instance.dispatchEvent(evObj)
|
if( sender != "worker" && this.worker ) this.worker.postMessage({event,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] )
|
||||||
if( sender !== undefined && typeof this[event] == 'function' ) this[event].apply(this, data && data.push ? data : [data] )
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ISOTerminal.addEventListener = (event,cb) => {
|
ISOTerminal.addEventListener = (event,cb) => {
|
||||||
|
@ -37,10 +35,6 @@ ISOTerminal.prototype.exec = function(shellscript){
|
||||||
this.send(shellscript+"\n",1)
|
this.send(shellscript+"\n",1)
|
||||||
}
|
}
|
||||||
|
|
||||||
ISOTerminal.prototype.hook = function(hookname,args){
|
|
||||||
this.exec(`{ type hook || source /etc/profile.sh; }; hook ${hookname} "${args.join('" "')}"`)
|
|
||||||
}
|
|
||||||
|
|
||||||
ISOTerminal.prototype.serial_input = 0; // can be set to 0,1,2,3 to define stdinput tty (xterm plugin)
|
ISOTerminal.prototype.serial_input = 0; // can be set to 0,1,2,3 to define stdinput tty (xterm plugin)
|
||||||
|
|
||||||
ISOTerminal.prototype.send = function(str, ttyNr){
|
ISOTerminal.prototype.send = function(str, ttyNr){
|
||||||
|
@ -51,11 +45,7 @@ ISOTerminal.prototype.send = function(str, ttyNr){
|
||||||
}else this.emulator.keyboard_send_text(str) // vga screen
|
}else this.emulator.keyboard_send_text(str) // vga screen
|
||||||
}else{
|
}else{
|
||||||
this.convert.toUint8Array( str ).map( (c) => {
|
this.convert.toUint8Array( str ).map( (c) => {
|
||||||
this.preventFrameDrop(
|
this.worker.postMessage({event:`serial${ttyNr}-input`,data:c})
|
||||||
() => {
|
|
||||||
this.worker.postMessage({event:`serial${ttyNr}-input`,data:c})
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -67,11 +57,11 @@ ISOTerminal.prototype.convert = {
|
||||||
const bytes = new Uint8Array(buffer);
|
const bytes = new Uint8Array(buffer);
|
||||||
const len = bytes.byteLength;
|
const len = bytes.byteLength;
|
||||||
for (let i = 0; i < len; i++) binary += String.fromCharCode(bytes[i]);
|
for (let i = 0; i < len; i++) binary += String.fromCharCode(bytes[i]);
|
||||||
return btoa(binary);
|
return window.btoa(binary);
|
||||||
},
|
},
|
||||||
|
|
||||||
base64ToArrayBuffer: function(base64) {
|
base64ToArrayBuffer: function(base64) {
|
||||||
const binaryString = atob(base64);
|
const binaryString = window.atob(base64);
|
||||||
const len = binaryString.length;
|
const len = binaryString.length;
|
||||||
const bytes = new Uint8Array(len);
|
const bytes = new Uint8Array(len);
|
||||||
|
|
||||||
|
@ -134,30 +124,52 @@ ISOTerminal.prototype.start = function(opts){
|
||||||
//disable_jit: false,
|
//disable_jit: false,
|
||||||
filesystem: {},
|
filesystem: {},
|
||||||
autostart: true,
|
autostart: true,
|
||||||
debug: this.opts.debug ? true : false
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this
|
|
||||||
.setupWorker(opts)
|
|
||||||
.startVM(opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
ISOTerminal.prototype.setupWorker = function(opts){
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* the WebWorker (which runs v86)
|
* the WebWorker (which runs v86)
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
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) )
|
|
||||||
})
|
|
||||||
|
|
||||||
return this
|
this.worker = new Worker("com/isoterminal/worker.js");
|
||||||
}
|
this.worker.onmessage = (e) => {
|
||||||
|
const xr = this.instance.sceneEl.renderer.xr
|
||||||
|
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
|
||||||
|
|
||||||
|
}
|
||||||
|
// don't let workers cause framerate dropping
|
||||||
|
if( xr.isPresenting ){
|
||||||
|
xr.getSession().requestAnimationFrame(cb(event,data))
|
||||||
|
}else{
|
||||||
|
window.requestAnimationFrame(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.worker.postMessage(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 })
|
this.emit('runISO',{...opts, bufferLatency: this.opts.bufferLatency })
|
||||||
const loading = [
|
const loading = [
|
||||||
|
@ -184,54 +196,12 @@ ISOTerminal.prototype.startVM = function(opts){
|
||||||
'Transcending earthly limits'
|
'Transcending earthly limits'
|
||||||
]
|
]
|
||||||
|
|
||||||
const empower = [
|
const loadmsg = loading[ Math.floor(Math.random()*1000) % loading.length ] + "..(please wait..)"
|
||||||
"FOSS gives users control over their software, offering freedom to modify and share",
|
|
||||||
"Feeling powerless with tech? FOSS escapes a mindset known as learned helplessness",
|
|
||||||
"FOSS breaks this cycle by showing that anyone can learn and contribute",
|
|
||||||
"Proprietary software can make users dependent, but FOSS offers real choices",
|
|
||||||
"FOSS communities provide support and encourage users to develop new skills",
|
|
||||||
"Learned helplessness fades when we realize tech isn’t too complex to understand",
|
|
||||||
"FOSS empowers users to customize and improve their tools",
|
|
||||||
"Engaging with FOSS helps build confidence and self-reliance in tech",
|
|
||||||
"FOSS shows that anyone can shape the digital world with curiosity and effort",
|
|
||||||
"Linux can revive old computers, extending their life and reducing e-waste",
|
|
||||||
"Many lightweight Linux distributions run smoothly on older hardware",
|
|
||||||
"Installing Linux on aging devices keeps them functional instead of sending them to the landfill",
|
|
||||||
"Linux uses fewer resources, making it ideal for reusing older machines",
|
|
||||||
"By using Linux, you can avoid buying new hardware, cutting down on tech waste",
|
|
||||||
"Instead of discarding slow devices, Linux can bring them back to life",
|
|
||||||
"Linux supports a wide range of devices, helping to prevent e-waste",
|
|
||||||
"Open-source drivers in Linux enable compatibility with old peripherals, reducing the need for replacements",
|
|
||||||
"Free Linux software helps users avoid planned obsolescence in commercial products",
|
|
||||||
"Switching to Linux promotes sustainability by reducing demand for new gadgets and lowering e-waste"
|
|
||||||
]
|
|
||||||
|
|
||||||
const motd = `
|
|
||||||
\r[38;5;57m . . ____ _____________ ________. ._. ._. . .
|
|
||||||
\r[38;5;93m . . .\\ \\/ /\\______ \\/ _____// | \\. .
|
|
||||||
\r[38;5;93m . . . \\ / | _/\\_____ \\/ ~ \\ .
|
|
||||||
\r[38;5;129m . . . / \\ | | \\/ \\ Y / .
|
|
||||||
\r[38;5;165m . . ./___/\\ \\ |____|_ /_______ /\\___|_ /. .
|
|
||||||
\r[38;5;201m . . . . . .\\_/. . . . \\/ . . . .\\/ . . _ \\/ . .
|
|
||||||
\r[38;5;165m ▬▬▬▬▬▬▬▬ https://xrsh.isvery.ninja ▬▬▬▬▬▬▬▬▬▬▬▬
|
|
||||||
\r[38;5;165m local-first, polyglot, unixy WebXR IDE & runtime
|
|
||||||
\r
|
|
||||||
\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"
|
const text_color = "\r[38;5;129m"
|
||||||
const text_reset = "\033[0m"
|
const text_reset = "\033[0m"
|
||||||
const loadmsg = "\n\r "+loading[ Math.floor(Math.random()*1000) % loading.length ] + "..[please wait]"
|
|
||||||
const empowermsg = "\n\r "+text_reset+'"'+empower[ Math.floor(Math.random()*1000) % empower.length ] + '"\n\r'
|
|
||||||
this.emit('status',loadmsg)
|
this.emit('status',loadmsg)
|
||||||
this.emit('serial-output-string', motd + empowermsg + text_color + loadmsg + text_reset+"\n\r")
|
this.emit('serial-output-string', text_color + loadmsg + text_reset + "\n\r")
|
||||||
|
|
||||||
|
|
||||||
this.addEventListener('emulator-started', async (e) => {
|
this.addEventListener('emulator-started', async (e) => {
|
||||||
|
|
||||||
|
@ -252,25 +222,17 @@ ISOTerminal.prototype.startVM = function(opts){
|
||||||
const str = e.detail
|
const str = e.detail
|
||||||
|
|
||||||
// lets scan for a prompt so we can send a 'ready' event to the world
|
// lets scan for a prompt so we can send a 'ready' event to the world
|
||||||
if( !this.ready && str.match(/\n(\/ #|~%|\[.*\]>)/) ) this.postBoot()
|
if( !this.ready && str.match(/\n(\/ #|~%|\[.*\]>)/) ){
|
||||||
|
this.emit('postReady',{})
|
||||||
if( this.ready || !this.opts.muteUntilPrompt ) this.emit('serial-output-string', e.detail )
|
this.ready = true
|
||||||
|
setTimeout( () => this.emit('ready',{}), 500 )
|
||||||
|
}
|
||||||
|
if( this.ready ) this.emit('serial-output-string', e.detail )
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
ISOTerminal.prototype.postBoot = function(cb){
|
|
||||||
this.emit('postReady',{})
|
|
||||||
this.ready = true
|
|
||||||
setTimeout( () => {
|
|
||||||
this.emit('ready',{})
|
|
||||||
if( cb ) cb()
|
|
||||||
}, 500 )
|
|
||||||
}
|
|
||||||
|
|
||||||
// this is allows (unsophisticated) outputbuffering
|
|
||||||
ISOTerminal.prototype.bufferOutput = function(byte,cb,latency){
|
ISOTerminal.prototype.bufferOutput = function(byte,cb,latency){
|
||||||
const resetBuffer = () => ({str:""})
|
const resetBuffer = () => ({str:""})
|
||||||
this.buffer = this.buffer || resetBuffer()
|
this.buffer = this.buffer || resetBuffer()
|
||||||
|
@ -285,13 +247,3 @@ ISOTerminal.prototype.bufferOutput = function(byte,cb,latency){
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ISOTerminal.prototype.preventFrameDrop = function(cb){
|
|
||||||
// don't let workers cause framerate dropping
|
|
||||||
const xr = this.instance.sceneEl.renderer.xr
|
|
||||||
if( xr.isPresenting ){
|
|
||||||
xr.getSession().requestAnimationFrame(cb)
|
|
||||||
}else{
|
|
||||||
window.requestAnimationFrame(cb)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -1,77 +0,0 @@
|
||||||
/*
|
|
||||||
* 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,20 +12,15 @@ emulator.fs9p.update_file = async function(file,data){
|
||||||
}
|
}
|
||||||
|
|
||||||
const inode = this.GetInode(p.id);
|
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) )
|
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) )
|
||||||
|
|
||||||
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;
|
|
||||||
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){
|
emulator.fs9p.append_file = async function(file,data){
|
||||||
|
@ -49,47 +44,5 @@ emulator.fs9p.append_file = async function(file,data){
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
emulator.fs9p.read_file_world = async function(file){
|
this['fs9p.append_file'] = function(){ emulator.fs9p.append_file.apply(emulator.fs9p, arguments[0]) }
|
||||||
const p = this.SearchPath(file);
|
this['fs9p.update_file'] = function(){ emulator.fs9p.update_file.apply(emulator.fs9p, arguments[0]) }
|
||||||
|
|
||||||
if(p.id === -1)
|
|
||||||
{
|
|
||||||
return Promise.resolve(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
const inode = this.GetInode(p.id);
|
|
||||||
const perms = this.parseFilePermissions(inode.mode)
|
|
||||||
|
|
||||||
if( !perms.world.read ){
|
|
||||||
return Promise.resolve(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.Read(p.id, 0, inode.size);
|
|
||||||
}
|
|
||||||
|
|
||||||
emulator.fs9p.parseFilePermissions = function(permissionInt) {
|
|
||||||
// Convert the permission integer to octal
|
|
||||||
const octalPermissions = permissionInt.toString(8);
|
|
||||||
|
|
||||||
// Extract the permission bits (last 3 digits in octal)
|
|
||||||
const permissionBits = octalPermissions.slice(-3);
|
|
||||||
|
|
||||||
|
|
||||||
function parsePermission(digit) {
|
|
||||||
const num = parseInt(digit, 10);
|
|
||||||
|
|
||||||
return {
|
|
||||||
read: Boolean(num & 4), // 4 = read
|
|
||||||
write: Boolean(num & 2), // 2 = write
|
|
||||||
execute: Boolean(num & 1) // 1 = execute
|
|
||||||
};
|
|
||||||
}
|
|
||||||
// Decode the permissions
|
|
||||||
const permissions = {
|
|
||||||
owner: parsePermission(permissionBits[0]),
|
|
||||||
group: parsePermission(permissionBits[1]),
|
|
||||||
world: parsePermission(permissionBits[2]),
|
|
||||||
};
|
|
||||||
|
|
||||||
return permissions;
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,30 +1,13 @@
|
||||||
if( typeof emulator != 'undefined' ){
|
if( typeof emulator != 'undefined' ){
|
||||||
// inside worker-thread
|
// inside worker-thread
|
||||||
importScripts("localforage.js") // we don't instance it again here (just use its functions)
|
|
||||||
|
|
||||||
this.restore_state = async function(data){
|
this['emulator.restore_state'] = async function(){
|
||||||
return new Promise( (resolve,reject) => {
|
await emulator.restore_state.apply(emulator, arguments[0])
|
||||||
localforage.getItem("state", async (err,stateBase64) => {
|
this.postMessage({event:"state_restored",data:false})
|
||||||
if( stateBase64 && !err ){
|
|
||||||
state = ISOTerminal.prototype.convert.base64ToArrayBuffer( stateBase64 )
|
|
||||||
await emulator.restore_state(state)
|
|
||||||
console.log("restored state")
|
|
||||||
}else return reject("worker.js: emulator.restore_state (could not get state from localforage)")
|
|
||||||
resolve()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
this.save_state = async function(){
|
this['emulator.save_state'] = async function(){
|
||||||
console.log("saving session")
|
let state = await emulator.save_state.apply(emulator, arguments[0])
|
||||||
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")
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -45,21 +28,42 @@ if( typeof emulator != 'undefined' ){
|
||||||
localforage.getItem("state", async (err,stateBase64) => {
|
localforage.getItem("state", async (err,stateBase64) => {
|
||||||
if( stateBase64 && !err && confirm('continue last session?') ){
|
if( stateBase64 && !err && confirm('continue last session?') ){
|
||||||
this.noboot = true // see feat/boot.js
|
this.noboot = true // see feat/boot.js
|
||||||
try{
|
state = this.convert.base64ToArrayBuffer( stateBase64 )
|
||||||
await this.worker.restore_state()
|
|
||||||
// simulate / fastforward boot events
|
this.addEventListener('state_restored', function(){
|
||||||
this.postBoot( () => {
|
this.emit('postReady',e)
|
||||||
this.send("l\n")
|
setTimeout( () => {
|
||||||
this.send("hook wakeup\n")
|
this.emit('ready',e)
|
||||||
})
|
// press CTRL+a l (=gnu screen redisplay)
|
||||||
}catch(e){ console.error(e) }
|
setTimeout( () => this.send("l\n"),400 )
|
||||||
|
// reload index.js
|
||||||
|
this.emulator.read_file("root/index.js")
|
||||||
|
.then( this.convert.Uint8ArrayToString )
|
||||||
|
.then( this.runJavascript )
|
||||||
|
.catch( console.error )
|
||||||
|
// reload index.html
|
||||||
|
this.emulator.read_file("root/index.html")
|
||||||
|
.then( this.convert.Uint8ArrayToString )
|
||||||
|
.then( this.runHTML )
|
||||||
|
.catch( console.error )
|
||||||
|
|
||||||
|
}, 500 )
|
||||||
|
})
|
||||||
|
|
||||||
|
this.worker.postMessage({event:'emulator.restore_state',data:state})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
this.save = async () => {
|
this.save = async () => {
|
||||||
await this.worker.save_state()
|
const state = await this.worker.postMessage({event:"save_state",data:false})
|
||||||
|
console.log( String(this.convert.arrayBufferToBase64(state)).substr(0,5) )
|
||||||
|
localforage.setItem("state", this.convert.arrayBufferToBase64(state) )
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.addEventListener('state_saved', function(data){
|
||||||
|
debugger
|
||||||
|
})
|
||||||
|
|
||||||
window.addEventListener("beforeunload", function (e) {
|
window.addEventListener("beforeunload", function (e) {
|
||||||
var confirmationMessage = "Sure you want to leave?\nTIP: enter 'save' to continue this session later";
|
var confirmationMessage = "Sure you want to leave?\nTIP: enter 'save' to continue this session later";
|
||||||
(e || window.event).returnValue = confirmationMessage; //Gecko + IE
|
(e || window.event).returnValue = confirmationMessage; //Gecko + IE
|
||||||
|
|
|
@ -4,21 +4,18 @@ ISOTerminal.addEventListener('ready', function(e){
|
||||||
|
|
||||||
ISOTerminal.prototype.boot = async function(e){
|
ISOTerminal.prototype.boot = async function(e){
|
||||||
// set environment
|
// set environment
|
||||||
let env = [
|
let env = ['export BROWSER=1']
|
||||||
`export LINES=${this.opts.rows}`,
|
|
||||||
`export COLUMNS=${this.opts.cols}`,
|
|
||||||
'export BROWSER=1',
|
|
||||||
]
|
|
||||||
for ( let i in document.location ){
|
for ( let i in document.location ){
|
||||||
if( typeof document.location[i] == 'string' ){
|
if( typeof document.location[i] == 'string' ){
|
||||||
env.push( 'export '+String(i).toUpperCase()+'="'+decodeURIComponent( document.location[i]+'"') )
|
env.push( 'export '+String(i).toUpperCase()+'="'+decodeURIComponent( document.location[i]+'"') )
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.worker.create_file("profile.browser", this.convert.toUint8Array( env.join('\n') ) )
|
await this.emit("emulator.create_file", ["profile.browser", this.convert.toUint8Array( env.join('\n') ) ] )
|
||||||
|
|
||||||
if( this.serial_input == 0 ){
|
if( this.serial_input == 0 ){
|
||||||
if( !this.noboot ){
|
if( !this.noboot ){
|
||||||
this.send("source /etc/profile # \\o/ FOSS powa!\n")
|
let boot = "source /etc/profile\n"
|
||||||
|
this.send(boot+"\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,30 +0,0 @@
|
||||||
if( typeof emulator != 'undefined' ){
|
|
||||||
|
|
||||||
}else{
|
|
||||||
|
|
||||||
ISOTerminal.addEventListener('ready', function(e){
|
|
||||||
|
|
||||||
// listen for http request to the filesystem ( file://host/path )
|
|
||||||
xhook.before( (request,callback) => {
|
|
||||||
|
|
||||||
if (request.url.match(/^file:\/\/xrsh\/mnt\/.*/) ){
|
|
||||||
let response
|
|
||||||
let file = request.url.replace(/^file:\/\/xrsh\/mnt\//,'')
|
|
||||||
this.worker.read_file_world(file)
|
|
||||||
.then( (data) => {
|
|
||||||
response = new Response( new Blob( [data] ) ) // wrap Uint8Array into array
|
|
||||||
response.status = 200
|
|
||||||
callback(response)
|
|
||||||
})
|
|
||||||
.catch( (e) => {
|
|
||||||
response = new Response()
|
|
||||||
response.status = 404
|
|
||||||
callback(response)
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
callback()
|
|
||||||
})
|
|
||||||
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -2,31 +2,22 @@
|
||||||
if( typeof emulator != 'undefined' ){
|
if( typeof emulator != 'undefined' ){
|
||||||
// inside worker-thread
|
// inside worker-thread
|
||||||
|
|
||||||
this.listenIndexHTML = () => {
|
// unix to js device
|
||||||
const file = "dev/browser/html"
|
this.emulator.readFromPipe( 'root/index.html', async (data) => {
|
||||||
emulator.readFromPipe( file, async (data) => {
|
const buf = await emulator.read_file("root/index.html")
|
||||||
const buf = await emulator.read_file( file )
|
const decoder = new TextDecoder('utf-8');
|
||||||
const decoder = new TextDecoder('utf-8');
|
const html = decoder.decode(buf).replace(/^#!\/bin\/html/,'') // remove leftover shebangs if any
|
||||||
const html = decoder.decode(buf).replace(/^#!\/bin\/html/,'') // remove leftover shebangs if any
|
try{
|
||||||
try{
|
this.postMessage({event:'runHTML',data:[html]})
|
||||||
this.postMessage({event:'runHTML',data:[html]})
|
}catch(e){
|
||||||
}catch(e){
|
console.error(e)
|
||||||
console.error(file)
|
}
|
||||||
console.error(e)
|
})
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}else{
|
}else{
|
||||||
// inside browser-thread
|
// inside browser-thread
|
||||||
|
|
||||||
ISOTerminal.addEventListener('emulator-started', function(){
|
|
||||||
this.addEventListener('ready', async () => {
|
|
||||||
this.worker.listenIndexHTML()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
ISOTerminal.prototype.runHTML = function(html){
|
ISOTerminal.prototype.runHTML = function(html){
|
||||||
let $scene = document.querySelector("a-scene")
|
let $scene = document.querySelector("a-scene")
|
||||||
let $root = document.querySelector("a-entity#root")
|
let $root = document.querySelector("a-entity#root")
|
||||||
|
@ -35,7 +26,6 @@ if( typeof emulator != 'undefined' ){
|
||||||
$root.id = "root"
|
$root.id = "root"
|
||||||
$scene.appendChild($root)
|
$scene.appendChild($root)
|
||||||
}
|
}
|
||||||
console.log(html)
|
|
||||||
$root.innerHTML = html
|
$root.innerHTML = html
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,8 +2,8 @@ if( typeof emulator != 'undefined' ){
|
||||||
// inside worker-thread
|
// inside worker-thread
|
||||||
|
|
||||||
// unix to js device
|
// unix to js device
|
||||||
this.emulator.readFromPipe( 'dev/browser/js', async (data) => {
|
this.emulator.readFromPipe( 'root/index.js', async (data) => {
|
||||||
const buf = await emulator.read_file("dev/browser/js")
|
const buf = await emulator.read_file("root/index.js")
|
||||||
const decoder = new TextDecoder('utf-8');
|
const decoder = new TextDecoder('utf-8');
|
||||||
const js = decoder.decode(buf).replace(/^#!\/bin\/js/,'') // remove leftover shebangs if any
|
const js = decoder.decode(buf).replace(/^#!\/bin\/js/,'') // remove leftover shebangs if any
|
||||||
try{
|
try{
|
||||||
|
|
|
@ -6,7 +6,7 @@ if( typeof emulator != 'undefined' ){
|
||||||
const convert = ISOTerminal.prototype.convert
|
const convert = ISOTerminal.prototype.convert
|
||||||
const buf = await this.emulator.read_file("dev/browser/js")
|
const buf = await this.emulator.read_file("dev/browser/js")
|
||||||
const script = convert.Uint8ArrayToString(buf)
|
const script = convert.Uint8ArrayToString(buf)
|
||||||
let PID=null
|
let PID="?"
|
||||||
try{
|
try{
|
||||||
if( script.match(/^PID/) ){
|
if( script.match(/^PID/) ){
|
||||||
PID = script.match(/^PID=([0-9]+);/)[1]
|
PID = script.match(/^PID=([0-9]+);/)[1]
|
||||||
|
@ -20,24 +20,12 @@ if( typeof emulator != 'undefined' ){
|
||||||
}else{
|
}else{
|
||||||
// inside browser-thread
|
// inside browser-thread
|
||||||
|
|
||||||
ISOTerminal.addEventListener('javascript-eval', async function(e){
|
ISOTerminal.addEventListener('javascript-eval', function(e){
|
||||||
const {script,PID} = e.detail
|
const {script,PID} = e.detail
|
||||||
let res
|
let res = (new Function(`${script}`))()
|
||||||
try{
|
if( res && typeof res != 'string' ) res = JSON.stringify(res,null,2)
|
||||||
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)
|
// update output to 9p with PID as filename (in /mnt/run)
|
||||||
if( PID ){
|
this.emit('fs9p.update_file', [`run/${PID}`, this.convert.toUint8Array(res)] )
|
||||||
this.worker.update_file(`run/${PID}`, this.convert.toUint8Array(res) )
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,17 +9,17 @@ ISOTerminal.prototype.redirectConsole = function(handler){
|
||||||
log.apply(log, args);
|
log.apply(log, args);
|
||||||
};
|
};
|
||||||
console.error = (...args)=>{
|
console.error = (...args)=>{
|
||||||
const textArg = args[0]
|
const textArg = args[0].message?args[0].message:args[0];
|
||||||
handler( textArg+'\n', '\x1b[31merror\x1b[0m');
|
handler( textArg+'\n', '\x1b[31merror\x1b[0m');
|
||||||
err.apply(log, args);
|
err.apply(log, args);
|
||||||
};
|
};
|
||||||
console.dir = (...args)=>{
|
console.dir = (...args)=>{
|
||||||
const textArg = args[0]
|
const textArg = args[0].message?args[0].message:args[0];
|
||||||
handler( JSON.stringify(textArg,null,2)+'\n');
|
handler( JSON.stringify(textArg,null,2)+'\n');
|
||||||
dir.apply(log, args);
|
dir.apply(log, args);
|
||||||
};
|
};
|
||||||
console.warn = (...args)=>{
|
console.warn = (...args)=>{
|
||||||
const textArg = args[0]
|
const textArg = args[0].message?args[0].message:args[0];
|
||||||
handler(textArg+'\n','\x1b[38;5;208mwarn\x1b[0m');
|
handler(textArg+'\n','\x1b[38;5;208mwarn\x1b[0m');
|
||||||
err.apply(log, args);
|
err.apply(log, args);
|
||||||
};
|
};
|
||||||
|
@ -34,7 +34,7 @@ ISOTerminal.addEventListener('emulator-started', function(){
|
||||||
str.trim().split("\n").map( (line) => {
|
str.trim().split("\n").map( (line) => {
|
||||||
finalStr += '\x1b[38;5;165m/dev/browser: \x1b[0m'+prefix+line+'\n'
|
finalStr += '\x1b[38;5;165m/dev/browser: \x1b[0m'+prefix+line+'\n'
|
||||||
})
|
})
|
||||||
this.emit('append_file', ["/dev/browser/console",finalStr])
|
this.emit('fs9p.append_file', ["/dev/browser/console",finalStr])
|
||||||
})
|
})
|
||||||
|
|
||||||
window.addEventListener('error', function(event) {
|
window.addEventListener('error', function(event) {
|
||||||
|
@ -46,6 +46,7 @@ ISOTerminal.addEventListener('emulator-started', function(){
|
||||||
});
|
});
|
||||||
|
|
||||||
window.addEventListener('unhandledrejection', function(event) {
|
window.addEventListener('unhandledrejection', function(event) {
|
||||||
|
console.error('Unhandled promise rejection:')
|
||||||
console.error(event);
|
console.error(event);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,32 +0,0 @@
|
||||||
if( typeof emulator != 'undefined' ){
|
|
||||||
// inside worker-thread
|
|
||||||
|
|
||||||
}else{
|
|
||||||
// inside browser-thread
|
|
||||||
//
|
|
||||||
ISOTerminal.prototype.pasteWriteFile = async function(data,type,filename){
|
|
||||||
this.pasteWriteFile.fileCount = this.pasteWriteFile.fileCount || 0
|
|
||||||
const file = `clipboard/`+ ( filename || `user-paste-${this.pasteWriteFile.fileCount}`)
|
|
||||||
await this.worker.create_file(file, data )
|
|
||||||
// run the xrsh hook
|
|
||||||
this.hook("clipboard", [ `/mnt/${file}`, type ] )
|
|
||||||
console.log("clipboard paste: /mnt/"+file)
|
|
||||||
this.pasteWriteFile.fileCount += 1
|
|
||||||
}
|
|
||||||
|
|
||||||
ISOTerminal.prototype.pasteFile = async function(data){
|
|
||||||
const {type,item,pastedText} = data
|
|
||||||
if( pastedText){
|
|
||||||
this.pasteWriteFile( this.convert.toUint8Array(pastedText) ,type)
|
|
||||||
}else{
|
|
||||||
const file = item.getAsFile();
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.onload = (e) => {
|
|
||||||
const arr = new Uint8Array(e.target.result)
|
|
||||||
this.pasteWriteFile( arr, type, file.name ); // or use readAsDataURL for images
|
|
||||||
};
|
|
||||||
reader.readAsArrayBuffer(file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,15 +1,41 @@
|
||||||
importScripts("libv86.js");
|
importScripts("libv86.js");
|
||||||
importScripts("ISOTerminal.js") // we don't instance it again here (just use its functions)
|
importScripts("ISOTerminal.js") // we don't instance it again here (just use its functions)
|
||||||
|
|
||||||
this.runISO = function(opts){
|
//var emulator = new V86({
|
||||||
this.opts = opts
|
// wasm_path: "../build/v86.wasm",
|
||||||
if( opts.debug ) console.dir(opts)
|
// memory_size: 32 * 1024 * 1024,
|
||||||
|
// vga_memory_size: 2 * 1024 * 1024,
|
||||||
|
// bios: {
|
||||||
|
// url: "../bios/seabios.bin",
|
||||||
|
// },
|
||||||
|
// vga_bios: {
|
||||||
|
// url: "../bios/vgabios.bin",
|
||||||
|
// },
|
||||||
|
// cdrom: {
|
||||||
|
// url: "../images/linux4.iso",
|
||||||
|
// },
|
||||||
|
// autostart: true,
|
||||||
|
//});
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//emulator.add_listener("serial0-output-byte", function(byte)
|
||||||
|
//{
|
||||||
|
// var chr = String.fromCharCode(byte);
|
||||||
|
// this.postMessage(chr);
|
||||||
|
//}.bind(this));
|
||||||
|
//
|
||||||
|
//this.onmessage = function(e)
|
||||||
|
//{
|
||||||
|
// emulator.serial0_send(e.data);
|
||||||
|
//};
|
||||||
|
|
||||||
|
this.runISO = function(opts){
|
||||||
if( opts.cdrom && !opts.cdrom.url.match(/^http/) ) opts.cdrom.url = "../../"+opts.cdrom.url
|
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
|
if( opts.bzimage && !opts.cdrom.url.match(/^http/) ) opts.bzimage.url = "../../"+opts.bzimage.url
|
||||||
|
|
||||||
|
console.dir(opts)
|
||||||
let emulator = this.emulator = new V86(opts);
|
let emulator = this.emulator = new V86(opts);
|
||||||
console.log("[worker.js] started emulator")
|
console.log("worker:started emulator")
|
||||||
|
|
||||||
// event forwarding
|
// event forwarding
|
||||||
|
|
||||||
|
@ -42,11 +68,8 @@ this.runISO = function(opts){
|
||||||
/*
|
/*
|
||||||
* forward events/functions so non-worker world can reach them
|
* forward events/functions so non-worker world can reach them
|
||||||
*/
|
*/
|
||||||
this.create_file = async function(){ return emulator.create_file.apply(emulator, arguments[0]) }
|
this['emulator.create_file'] = function(){ emulator.create_file.apply(emulator, arguments[0]) }
|
||||||
this.read_file = async function(){ return emulator.read_file.apply(emulator, arguments[0]) }
|
this['emulator.read_file'] = function(){ emulator.read_file.apply(emulator, arguments[0]) }
|
||||||
this.read_file_world = async function(){ return emulator.fs9p.read_file_world.apply(emulator.fs9p, arguments[0]) }
|
|
||||||
this.append_file = async function(){ emulator.fs9p.append_file.apply(emulator.fs9p, arguments[0]) }
|
|
||||||
this.update_file = async function(){ emulator.fs9p.update_file.apply(emulator.fs9p, arguments[0]) }
|
|
||||||
|
|
||||||
// filename will be read from 9pfs: "/mnt/"+filename
|
// filename will be read from 9pfs: "/mnt/"+filename
|
||||||
emulator.readFromPipe = function(filename,cb){
|
emulator.readFromPipe = function(filename,cb){
|
||||||
|
@ -65,23 +88,11 @@ this.runISO = function(opts){
|
||||||
* forward events/functions so non-worker world can reach them
|
* forward events/functions so non-worker world can reach them
|
||||||
*/
|
*/
|
||||||
|
|
||||||
this['serial0-input'] = function(c){ emulator.bus.send( 'serial0-input', c) } // to /dev/ttyS0
|
this['serial0-input'] = function(c){ this.emulator.bus.send( 'serial0-input', c) } // to /dev/ttyS0
|
||||||
this['serial1-input'] = function(c){ emulator.bus.send( 'serial1-input', c) } // to /dev/ttyS1
|
this['serial1-input'] = function(c){ this.emulator.bus.send( 'serial1-input', c) } // to /dev/ttyS1
|
||||||
this['serial2-input'] = function(c){ emulator.bus.send( 'serial2-input', c) } // to /dev/ttyS2
|
this['serial2-input'] = function(c){ this.emulator.bus.send( 'serial2-input', c) } // to /dev/ttyS2
|
||||||
|
|
||||||
this.onmessage = async function(e){
|
this.onmessage = function(e){
|
||||||
let {event,data} = e.data
|
let {event,data} = e.data
|
||||||
if( this[event] ){
|
if( this[event] ) this[event](data)
|
||||||
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}})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,179 @@
|
||||||
|
AFRAME.registerComponent('paste', {
|
||||||
|
schema: {
|
||||||
|
foo: { type:"string"}
|
||||||
|
},
|
||||||
|
|
||||||
|
init: function () {
|
||||||
|
this.el.object3D.visible = false
|
||||||
|
//this.el.innerHTML = ` `
|
||||||
|
},
|
||||||
|
|
||||||
|
requires:{
|
||||||
|
osbutton: "com/osbutton.js"
|
||||||
|
},
|
||||||
|
|
||||||
|
events:{
|
||||||
|
|
||||||
|
// component events
|
||||||
|
somecomponent: function( ){ console.log("component requirement mounted") },
|
||||||
|
ready: function(e){ console.log("requires are loaded") },
|
||||||
|
|
||||||
|
launcher: function(e){
|
||||||
|
const paste = () => {
|
||||||
|
navigator.clipboard.readText()
|
||||||
|
.then( (base64) => {
|
||||||
|
let mimetype = base64.replace(/;base64,.*/,'')
|
||||||
|
let data = base64.replace(/.*;base64,/,'')
|
||||||
|
let type = this.textHeuristic(data)
|
||||||
|
console.log("type="+type)
|
||||||
|
switch( this.textHeuristic(data) ){
|
||||||
|
case "aframe": this.insertAFRAME(data); break;
|
||||||
|
default: this.insertText(data); break;
|
||||||
|
}
|
||||||
|
this.count += 1
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
navigator.permissions.query({ name: 'clipboard-read' })
|
||||||
|
.then( (permission) => {
|
||||||
|
if( permission.state != 'granted' ){
|
||||||
|
this.el.sceneEl.exitVR()
|
||||||
|
setTimeout( () => paste(), 500 )
|
||||||
|
return
|
||||||
|
}else paste()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
textHeuristic: function(text){
|
||||||
|
// Script type identification clues
|
||||||
|
const bashClues = ["|", "if ", "fi", "cat"];
|
||||||
|
const htmlClues = ["/>", "href=", "src="];
|
||||||
|
const aframeClues = ["<a-entity", "/>", "position="];
|
||||||
|
const jsClues = ["var ", "let ", "function ", "setTimeout","console."];
|
||||||
|
// Count occurrences of clues for each script type
|
||||||
|
const bashCount = bashClues.reduce((acc, clue) => acc + (text.includes(clue) ? 1 : 0), 0);
|
||||||
|
const htmlCount = htmlClues.reduce((acc, clue) => acc + (text.includes(clue) ? 1 : 0), 0);
|
||||||
|
const aframeCount = aframeClues.reduce((acc, clue) => acc + (text.includes(clue) ? 1 : 0), 0);
|
||||||
|
const jsCount = jsClues.reduce((acc, clue) => acc + (text.includes(clue) ? 1 : 0), 0);
|
||||||
|
|
||||||
|
// Identify the script with the most clues or return unknown if inconclusive
|
||||||
|
const maxCount = Math.max(bashCount, htmlCount, jsCount, aframeCount);
|
||||||
|
if (maxCount === 0) {
|
||||||
|
return "unknown";
|
||||||
|
} else if (bashCount === maxCount) {
|
||||||
|
return "bash";
|
||||||
|
} else if (htmlCount === maxCount) {
|
||||||
|
return "html";
|
||||||
|
} else if (jsCount === maxCount) {
|
||||||
|
return "javascript";
|
||||||
|
} else {
|
||||||
|
return "aframe";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
insertAFRAME: function(data){
|
||||||
|
let scene = document.createElement('a-entity')
|
||||||
|
scene.id = "embedAframe"
|
||||||
|
scene.innerHTML = data
|
||||||
|
let el = document.createElement('a-text')
|
||||||
|
el.setAttribute("value",data)
|
||||||
|
el.setAttribute("color","white")
|
||||||
|
el.setAttribute("align","center")
|
||||||
|
el.setAttribute("anchor","align")
|
||||||
|
let osbutton = this.wrapOSButton(el,"aframe",data)
|
||||||
|
AFRAME.scenes[0].appendChild(osbutton)
|
||||||
|
console.log(data)
|
||||||
|
},
|
||||||
|
|
||||||
|
insertText: function(data){
|
||||||
|
let el = document.createElement('a-text')
|
||||||
|
el.setAttribute("value",data)
|
||||||
|
el.setAttribute("color","white")
|
||||||
|
el.setAttribute("align","center")
|
||||||
|
el.setAttribute("anchor","align")
|
||||||
|
let osbutton = this.wrapOSButton(el,"text",data)
|
||||||
|
AFRAME.scenes[0].appendChild(osbutton)
|
||||||
|
console.log(data)
|
||||||
|
},
|
||||||
|
|
||||||
|
wrapOSButton: function(el,type,data){
|
||||||
|
let osbutton = document.createElement('a-entity')
|
||||||
|
let height = type == 'aframe' ? 0.3 : 0.1
|
||||||
|
let depth = type == 'aframe' ? 0.3 : 0.05
|
||||||
|
osbutton.setAttribute("osbutton",`width:0.3; height: ${height}; depth: ${depth}; color:blue `)
|
||||||
|
osbutton.appendChild(el)
|
||||||
|
osbutton.object3D.position.copy( this.getPositionInFrontOfCamera() )
|
||||||
|
return osbutton
|
||||||
|
},
|
||||||
|
|
||||||
|
getPositionInFrontOfCamera: function(distance){
|
||||||
|
const camera = this.el.sceneEl.camera;
|
||||||
|
let pos = new THREE.Vector3()
|
||||||
|
let direction = new THREE.Vector3();
|
||||||
|
// Get camera's forward direction (without rotation)
|
||||||
|
camera.getWorldDirection(direction);
|
||||||
|
camera.getWorldPosition(pos)
|
||||||
|
direction.normalize();
|
||||||
|
// Scale the direction by 1 meter
|
||||||
|
if( !distance ) distance = 1.5
|
||||||
|
direction.multiplyScalar(distance);
|
||||||
|
// Add the camera's position to the scaled direction to get the target point
|
||||||
|
pos.add(direction);
|
||||||
|
return pos
|
||||||
|
},
|
||||||
|
|
||||||
|
manifest: { // HTML5 manifest to identify app to xrsh
|
||||||
|
"short_name": "Paste",
|
||||||
|
"name": "Paste",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "https://css.gg/clipboard.svg",
|
||||||
|
"type": "image/svg+xml",
|
||||||
|
"sizes": "512x512"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": "/?source=pwa",
|
||||||
|
"start_url": "/?source=pwa",
|
||||||
|
"background_color": "#3367D6",
|
||||||
|
"display": "standalone",
|
||||||
|
"scope": "/",
|
||||||
|
"theme_color": "#3367D6",
|
||||||
|
"shortcuts": [
|
||||||
|
{
|
||||||
|
"name": "What is the latest news?",
|
||||||
|
"cli":{
|
||||||
|
"usage": "helloworld <type> [options]",
|
||||||
|
"example": "helloworld news",
|
||||||
|
"args":{
|
||||||
|
"--latest": {type:"string"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"short_name": "Today",
|
||||||
|
"description": "View weather information for today",
|
||||||
|
"url": "/today?source=pwa",
|
||||||
|
"icons": [{ "src": "/images/today.png", "sizes": "192x192" }]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Paste the clipboard",
|
||||||
|
"screenshots": [
|
||||||
|
{
|
||||||
|
"src": "/images/screenshot1.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "540x720",
|
||||||
|
"form_factor": "narrow"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"help":`
|
||||||
|
Helloworld application
|
||||||
|
|
||||||
|
This is a help file which describes the application.
|
||||||
|
It will be rendered thru troika text, and will contain
|
||||||
|
headers based on non-punctualized lines separated by linebreaks,
|
||||||
|
in above's case "\nHelloworld application\n" will qualify as header.
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
115
com/pastedrop.js
115
com/pastedrop.js
|
@ -1,115 +0,0 @@
|
||||||
AFRAME.registerComponent('pastedrop', {
|
|
||||||
schema: {
|
|
||||||
foo: { type:"string"}
|
|
||||||
},
|
|
||||||
|
|
||||||
init: function () {
|
|
||||||
|
|
||||||
window.addEventListener('paste', this.onPaste.bind(this) )
|
|
||||||
|
|
||||||
document.body.addEventListener('dragover',(e) => e.preventDefault() )
|
|
||||||
document.body.addEventListener('drop', this.onDrop.bind(this) )
|
|
||||||
},
|
|
||||||
|
|
||||||
initClipboard: function(){
|
|
||||||
navigator.permissions.query({ name: 'clipboard-read' })
|
|
||||||
.then( (permission) => {
|
|
||||||
if( permission.state != 'granted' ){
|
|
||||||
this.el.sceneEl.exitVR()
|
|
||||||
setTimeout( () => this.paste(), 500 )
|
|
||||||
return
|
|
||||||
}else this.paste()
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
//getClipboard: function(){
|
|
||||||
// navigator.clipboard.readText()
|
|
||||||
// .then( async (base64) => {
|
|
||||||
// let mimetype = base64.replace(/;base64,.*/,'')
|
|
||||||
// let data = base64.replace(/.*;base64,/,'')
|
|
||||||
// let type = this.textHeuristic(data)
|
|
||||||
// const term = document.querySelector('[isoterminal]').components.isoterminal.term
|
|
||||||
// this.el.emit('pasteFile',{}) /*TODO* data incompatible */
|
|
||||||
// })
|
|
||||||
//},
|
|
||||||
|
|
||||||
onDrop: function(e){
|
|
||||||
e.preventDefault()
|
|
||||||
this.onPaste({...e, type: "paste", clipboardData: e.dataTransfer})
|
|
||||||
},
|
|
||||||
|
|
||||||
onPaste: function(e){
|
|
||||||
if( e.type != "paste" ) return
|
|
||||||
|
|
||||||
const clipboardData = e.clipboardData || navigator.clipboard;
|
|
||||||
const items = clipboardData.items;
|
|
||||||
|
|
||||||
for (let i = 0; i < items.length; i++) {
|
|
||||||
const item = items[i];
|
|
||||||
const type = item.type;
|
|
||||||
|
|
||||||
// Check if the item is a file
|
|
||||||
if (item.kind === "file") {
|
|
||||||
this.el.emit('pasteFile',{item,type})
|
|
||||||
} else if (type === "text/plain") {
|
|
||||||
const pastedText = clipboardData.getData("text/plain");
|
|
||||||
const newType = "text" // let /root/hook.d/mimetype/text further decide whether this is text/plain (or something else)
|
|
||||||
this.el.emit('pasteFile',{item,type:newType,pastedText})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
manifest: { // HTML5 manifest to identify app to xrsh
|
|
||||||
"short_name": "Paste",
|
|
||||||
"name": "Paste",
|
|
||||||
"icons": [
|
|
||||||
{
|
|
||||||
"src": "https://css.gg/clipboard.svg",
|
|
||||||
"type": "image/svg+xml",
|
|
||||||
"sizes": "512x512"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"id": "/?source=pwa",
|
|
||||||
"start_url": "/?source=pwa",
|
|
||||||
"background_color": "#3367D6",
|
|
||||||
"display": "standalone",
|
|
||||||
"scope": "/",
|
|
||||||
"theme_color": "#3367D6",
|
|
||||||
"shortcuts": [
|
|
||||||
{
|
|
||||||
"name": "What is the latest news?",
|
|
||||||
"cli":{
|
|
||||||
"usage": "helloworld <type> [options]",
|
|
||||||
"example": "helloworld news",
|
|
||||||
"args":{
|
|
||||||
"--latest": {type:"string"}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"short_name": "Today",
|
|
||||||
"description": "View weather information for today",
|
|
||||||
"url": "/today?source=pwa",
|
|
||||||
"icons": [{ "src": "/images/today.png", "sizes": "192x192" }]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "Paste the clipboard",
|
|
||||||
"screenshots": [
|
|
||||||
{
|
|
||||||
"src": "/images/screenshot1.png",
|
|
||||||
"type": "image/png",
|
|
||||||
"sizes": "540x720",
|
|
||||||
"form_factor": "narrow"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"help":`
|
|
||||||
Helloworld application
|
|
||||||
|
|
||||||
This is a help file which describes the application.
|
|
||||||
It will be rendered thru troika text, and will contain
|
|
||||||
headers based on non-punctualized lines separated by linebreaks,
|
|
||||||
in above's case "\nHelloworld application\n" will qualify as header.
|
|
||||||
`
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
|
@ -34,22 +34,16 @@ AFRAME.registerComponent('selfcontainer', {
|
||||||
|
|
||||||
installProxyServer: function(){
|
installProxyServer: function(){
|
||||||
if( !window.store ) window.store = {}
|
if( !window.store ) window.store = {}
|
||||||
|
|
||||||
// selfcontain every webrequest to store (and serve if stored)
|
// selfcontain every webrequest to store (and serve if stored)
|
||||||
let curry = function(me){
|
let curry = function(me){
|
||||||
return function(request, response, cb){
|
return function(request, response, cb){
|
||||||
|
|
||||||
let data = request ? window.store[ request.url ] || false : false
|
let data = request ? window.store[ request.url ] || false : false
|
||||||
if( data ){ // return inline version
|
if( data ){ // return inline version
|
||||||
console.log('selfcontainer.js: serving '+request.url+' from cache')
|
console.log('selfcontained cache: '+request.url)
|
||||||
let res = new Response()
|
let res = new Response()
|
||||||
res[ data.binary ? 'data' : 'text' ] = data.binary ? () => me.convert.base64ToArrayBuffer(data.text) : data.text
|
res[ data.binary ? 'data' : 'text' ] = data.binary ? () => me.convert.base64ToArrayBuffer(data.text) : data.text
|
||||||
cb(res)
|
cb(res)
|
||||||
}else{
|
}else{
|
||||||
|
|
||||||
if( request.url.match(/(^file:\/\/xrsh)/) ) return cb(response)
|
|
||||||
|
|
||||||
console.log("selfcontainer.js: caching "+request.url)
|
|
||||||
if( response.text ){
|
if( response.text ){
|
||||||
data = {text: response.text}
|
data = {text: response.text}
|
||||||
}else{
|
}else{
|
||||||
|
|
|
@ -9,8 +9,7 @@ AFRAME.registerComponent('window', {
|
||||||
max: {type:'boolean',"default":false},
|
max: {type:'boolean',"default":false},
|
||||||
min: {type:'boolean',"default":false},
|
min: {type:'boolean',"default":false},
|
||||||
x: {type:'string',"default":"center"},
|
x: {type:'string',"default":"center"},
|
||||||
y: {type:'string',"default":"center"},
|
y: {type:'string',"default":"center"}
|
||||||
"class": {type:'array',"default":[]},
|
|
||||||
},
|
},
|
||||||
|
|
||||||
dependencies:{
|
dependencies:{
|
||||||
|
@ -29,7 +28,6 @@ AFRAME.registerComponent('window', {
|
||||||
|
|
||||||
this.el.dom.style.display = 'none'
|
this.el.dom.style.display = 'none'
|
||||||
let winbox = this.el.winbox = new WinBox( this.data.title, {
|
let winbox = this.el.winbox = new WinBox( this.data.title, {
|
||||||
class: this.data.class,
|
|
||||||
height:this.data.height,
|
height:this.data.height,
|
||||||
width:this.data.width,
|
width:this.data.width,
|
||||||
x: this.data.x,
|
x: this.data.x,
|
||||||
|
@ -65,26 +63,9 @@ AFRAME.registerComponent('window', {
|
||||||
|
|
||||||
this.el.setAttribute("grabbable","")
|
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, 0.02 )
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
show: function(state){
|
show: function(state){
|
||||||
this.el.dom.closest('.winbox').style.display = state ? '' : 'none'
|
this.el.dom.closest('.winbox').style.display = state ? '' : 'none'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
AFRAME.utils.positionObjectNextToNeighbor = function positionObjectNextToNeighbor(object, lastNeighbor = null, margin ){
|
|
||||||
// *FIXME* this could be more sophisticated :)
|
|
||||||
object.position.x = lastNeighbor.position.x + margin
|
|
||||||
object.position.y = lastNeighbor.position.y - margin
|
|
||||||
object.position.z = lastNeighbor.position.z + margin
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
70
com/xterm.js
70
com/xterm.js
|
@ -92,7 +92,6 @@ const TERMINAL_THEME = {
|
||||||
|
|
||||||
AFRAME.registerComponent('xterm', {
|
AFRAME.registerComponent('xterm', {
|
||||||
schema: Object.assign({
|
schema: Object.assign({
|
||||||
XRrenderer: { type: 'string', default: 'canvas', },
|
|
||||||
cols: { type: 'number', default: 110, },
|
cols: { type: 'number', default: 110, },
|
||||||
rows: { type: 'number', default: Math.floor( (window.innerHeight * 0.7 ) * 0.054 ) },
|
rows: { type: 'number', default: Math.floor( (window.innerHeight * 0.7 ) * 0.054 ) },
|
||||||
canvasLatency:{ type:'number', default: 200 }
|
canvasLatency:{ type:'number', default: 200 }
|
||||||
|
@ -104,40 +103,26 @@ AFRAME.registerComponent('xterm', {
|
||||||
init: function () {
|
init: function () {
|
||||||
const terminalElement = document.createElement('div')
|
const terminalElement = document.createElement('div')
|
||||||
terminalElement.setAttribute('style', `
|
terminalElement.setAttribute('style', `
|
||||||
width: 800px;
|
width: ${Math.floor( window.innerWidth * 0.7 )}px;
|
||||||
height: ${Math.floor( 800 * 0.527 )}px;
|
height: ${Math.floor( window.innerHeight * 0.7 )}px;
|
||||||
overflow: hidden;
|
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
|
this.el.terminalElement = terminalElement
|
||||||
|
|
||||||
if( this.data.XRrenderer == 'canvas' ){
|
// we switch between dom/canvas rendering because canvas looks pixely in nonimmersive mode
|
||||||
// setup slightly bigger black backdrop (this.el.getObject3D("mesh"))
|
this.el.sceneEl.addEventListener('enter-vr', this.enterImmersive.bind(this) )
|
||||||
// and terminal text (this.el.planeText.getObject("mesh"))
|
this.el.sceneEl.addEventListener('enter-ar', this.enterImmersive.bind(this) )
|
||||||
const w = 2;
|
this.el.sceneEl.addEventListener('exit-vr', this.exitImmersive.bind(this) )
|
||||||
const h = (this.data.rows*5/this.data.cols)
|
this.el.sceneEl.addEventListener('exit-ar', this.exitImmersive.bind(this) )
|
||||||
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:${w}; height:${h}`)
|
|
||||||
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
|
// Build up a theme object
|
||||||
const theme = Object.keys(this.data).reduce((theme, key) => {
|
const theme = Object.keys(this.data).reduce((theme, key) => {
|
||||||
|
@ -148,8 +133,6 @@ AFRAME.registerComponent('xterm', {
|
||||||
return theme
|
return theme
|
||||||
}, {})
|
}, {})
|
||||||
|
|
||||||
this.fontSize = 14
|
|
||||||
|
|
||||||
const term = this.term = new Terminal({
|
const term = this.term = new Terminal({
|
||||||
logLevel:"off",
|
logLevel:"off",
|
||||||
theme: theme,
|
theme: theme,
|
||||||
|
@ -158,13 +141,20 @@ AFRAME.registerComponent('xterm', {
|
||||||
disableStdin: false,
|
disableStdin: false,
|
||||||
rows: this.data.rows,
|
rows: this.data.rows,
|
||||||
cols: this.data.cols,
|
cols: this.data.cols,
|
||||||
fontFamily: 'Cousine, monospace',
|
fontSize: 14,
|
||||||
fontSize: this.fontSize,
|
|
||||||
lineHeight: 1.15,
|
lineHeight: 1.15,
|
||||||
useFlowControl: true,
|
|
||||||
rendererType: this.renderType // 'dom' // 'canvas'
|
rendererType: this.renderType // 'dom' // 'canvas'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
this.tick = AFRAME.utils.throttle( () => {
|
||||||
|
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.open(terminalElement)
|
||||||
this.term.focus()
|
this.term.focus()
|
||||||
this.setRenderType('dom')
|
this.setRenderType('dom')
|
||||||
|
@ -175,7 +165,7 @@ AFRAME.registerComponent('xterm', {
|
||||||
const $screen = terminalElement.querySelector('.xterm-screen')
|
const $screen = terminalElement.querySelector('.xterm-screen')
|
||||||
$screen.style.width = '100%'
|
$screen.style.width = '100%'
|
||||||
|
|
||||||
term.on('refresh', AFRAME.utils.throttleLeadingAndTrailing( () => this.update(), 150 ) )
|
term.on('refresh', AFRAME.utils.throttle( () => this.update(), 150 ) )
|
||||||
term.on('data', (data) => {
|
term.on('data', (data) => {
|
||||||
this.el.emit('xterm-input', data)
|
this.el.emit('xterm-input', data)
|
||||||
})
|
})
|
||||||
|
@ -210,14 +200,14 @@ AFRAME.registerComponent('xterm', {
|
||||||
|
|
||||||
if( type == 'dom'){
|
if( type == 'dom'){
|
||||||
this.el.dom.appendChild(this.el.terminalElement)
|
this.el.dom.appendChild(this.el.terminalElement)
|
||||||
this.term.setOption('fontSize', this.fontSize )
|
this.term.setOption('fontSize', 14 )
|
||||||
this.term.setOption('rendererType',type )
|
this.term.setOption('rendererType',type )
|
||||||
this.renderType = type
|
this.renderType = type
|
||||||
}
|
}
|
||||||
|
|
||||||
if( type == 'canvas'){
|
if( type == 'canvas'){
|
||||||
this.el.appendChild(this.el.terminalElement)
|
this.el.appendChild(this.el.terminalElement)
|
||||||
this.term.setOption('fontSize', this.fontSize * 3 )
|
this.term.setOption('fontSize', 48 )
|
||||||
this.term.setOption('rendererType',type )
|
this.term.setOption('rendererType',type )
|
||||||
this.renderType = type
|
this.renderType = type
|
||||||
this.update()
|
this.update()
|
||||||
|
@ -228,8 +218,8 @@ AFRAME.registerComponent('xterm', {
|
||||||
this.cursorCanvas = this.el.terminalElement.querySelector('.xterm-cursor-layer')
|
this.cursorCanvas = this.el.terminalElement.querySelector('.xterm-cursor-layer')
|
||||||
// Create a texture from the canvas
|
// Create a texture from the canvas
|
||||||
const canvasTexture = new THREE.Texture(this.canvas)
|
const canvasTexture = new THREE.Texture(this.canvas)
|
||||||
//canvasTexture.minFilter = THREE.NearestFilter //LinearFilter
|
//canvasTexture.minFilter = THREE.LinearFilter
|
||||||
//canvasTexture.magFilter = THREE.LinearMipMapLinearFilter //THREE.NearestFilter //LinearFilter
|
//canvasTexture.magFilter = THREE.LinearFilter
|
||||||
canvasTexture.needsUpdate = true; // Ensure the texture updates
|
canvasTexture.needsUpdate = true; // Ensure the texture updates
|
||||||
let plane = this.el.planeText.getObject3D("mesh") //this.el.getObject3D('mesh')
|
let plane = this.el.planeText.getObject3D("mesh") //this.el.getObject3D('mesh')
|
||||||
if( plane.material ) plane.material.dispose()
|
if( plane.material ) plane.material.dispose()
|
||||||
|
|
Loading…
Reference in New Issue