Compare commits
No commits in common. "9aff2133a076315899f756aec87067011be743e4" and "35ff5dc710d9e92a7803a3ab3f66c1fc35bf516d" have entirely different histories.
9aff2133a0
...
35ff5dc710
|
@ -91,7 +91,7 @@ AFRAME.registerComponent('codemirror', {
|
||||||
|
|
||||||
// component events
|
// component events
|
||||||
DOMready: function(e){
|
DOMready: function(e){
|
||||||
this.isoterminal.worker.postMessage.promise({event:'read_file',data: this.data.file })
|
this.isoterminal.emulator.read_file( this.data.file )
|
||||||
.then( this.isoterminal.convert.Uint8ArrayToString )
|
.then( this.isoterminal.convert.Uint8ArrayToString )
|
||||||
.then( (str) => {
|
.then( (str) => {
|
||||||
this.createEditor( str )
|
this.createEditor( str )
|
||||||
|
|
|
@ -1,33 +1,32 @@
|
||||||
/*
|
function ISOTerminal(instance,opts){
|
||||||
*
|
// create a neutral isoterminal object which can be decorated
|
||||||
* css/html template
|
// with prototype functions and has addListener() and dispatchEvent()
|
||||||
*
|
let obj = new EventTarget()
|
||||||
* ┌─────────┐ ┌────────────┐ ┌─────────────┐ exit-AR
|
obj.instance = instance
|
||||||
* ┌───────►│ com/dom ┼──►│ com/window ├─►│ domrenderer │◄────────── exit-VR ◄─┐
|
obj.opts = opts
|
||||||
* │ └─────────┘ └────────────┘ └─────▲───────┘ │
|
// register default event listeners (enable file based features like isoterminal/jsconsole.js e.g.)
|
||||||
* │ │ ┌───────────────┐ │
|
for( let event in ISOTerminal.listener )
|
||||||
* ┌──────────┴────────┐ ┌─────┴──────┐ │ xterm.js │ ┌─────────────────────────────┐
|
for( let cb in ISOTerminal.listener[event] )
|
||||||
* │ com/isoterminal ├────────────────────────────►│com/xterm.js│◄─┤ │ │com/html-as-texture-in-XR.js │
|
obj.addEventListener( event, ISOTerminal.listener[event][cb] )
|
||||||
* └────────┬─┬────────┘ └──┬──────┬▲─┘ │ xterm.css │ └─────────────────────────────┘
|
// compose object with functions
|
||||||
* │ │ ┌────────┐ ┌─────────▼──┐ ││ └───────────────┘ │ ▲
|
for( let i in ISOTerminal.prototype ) obj[i] = ISOTerminal.prototype[i]
|
||||||
* │ └───────►│ plane ├─────►text───┼►canvas │◄────────────────── enter-VR │ │
|
obj.emit('init')
|
||||||
* │ └────────┘ └────────────┘ ││ enter-AR ◄─┘ │
|
return obj
|
||||||
* │ ││ │
|
}
|
||||||
* │ ││ │
|
|
||||||
* │ ISOTerminal.js ││ │
|
ISOTerminal.prototype.emit = function(event,data){
|
||||||
* │ ┌───────────────────────────┐◄────┘│ │
|
data = data || false
|
||||||
* │ │ com/isoterminal/worker.js ├──────┘ │
|
this.dispatchEvent( new CustomEvent(event, {detail: data} ) )
|
||||||
* │ └──────────────┌────────────┤ │
|
}
|
||||||
* │ │ v86.js │ │
|
|
||||||
* │ │ feat/*.js │ │
|
ISOTerminal.addEventListener = (event,cb) => {
|
||||||
* │ │ libv86.js │ │
|
ISOTerminal.listener = ISOTerminal.listener || {}
|
||||||
* │ └────────────┘ │
|
ISOTerminal.listener[event] = ISOTerminal.listener[event] || []
|
||||||
* │ │
|
ISOTerminal.listener[event].push(cb)
|
||||||
* └────────────────────────────────────────────────────────────────────────────────────┘
|
}
|
||||||
*
|
|
||||||
* NOTE: For convenience reasons, events are forwarded between com/isoterminal.js, worker.js and ISOTerminal
|
// ISOTerminal has defacto support for AFRAME
|
||||||
* Instead of a melting pot of different functionnames, events are flowing through everything (ISOTerminal.emit())
|
// but can be decorated to work without it as well
|
||||||
*/
|
|
||||||
|
|
||||||
if( typeof AFRAME != 'undefined '){
|
if( typeof AFRAME != 'undefined '){
|
||||||
|
|
||||||
|
@ -35,23 +34,18 @@ if( typeof AFRAME != 'undefined '){
|
||||||
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"},
|
||||||
cols: { type: 'number',"default": 80 },
|
cols: { type: 'number',"default": 120 },
|
||||||
rows: { type: 'number',"default": 20 },
|
rows: { type: 'number',"default": 30 },
|
||||||
padding: { type: 'number',"default": 18 },
|
padding: { type: 'number',"default": 18 },
|
||||||
minimized: { type: 'boolean',"default":false},
|
minimized: { type: 'boolean',"default":false},
|
||||||
maximized: { type: 'boolean',"default":false},
|
maximized: { type: 'boolean',"default":false},
|
||||||
muteUntilPrompt:{ type: 'boolean',"default":true}, // mute stdout until a prompt is detected in ISO
|
|
||||||
HUD: { type: 'boolean',"default":false}, // link to camera movement
|
|
||||||
transparent: { type:'boolean', "default":false }, // need good gpu
|
transparent: { type:'boolean', "default":false }, // need good gpu
|
||||||
xterm: { type: 'boolean', "default":true }, // use xterm.js? (=slower)
|
xterm: { type: 'boolean', "default":true }, // use xterm.js? (=slower)
|
||||||
memory: { type: 'number', "default":64 }, // VM memory (in MB)
|
memory: { type: 'number', "default":48 } // 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
|
|
||||||
},
|
},
|
||||||
|
|
||||||
init: function(){
|
init: function(){
|
||||||
this.el.object3D.visible = false
|
this.el.object3D.visible = false
|
||||||
this.initHud()
|
|
||||||
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'
|
||||||
|
@ -74,14 +68,24 @@ if( typeof AFRAME != 'undefined '){
|
||||||
// html to texture
|
// html to texture
|
||||||
htmlinxr: "com/html-as-texture-in-xr.js",
|
htmlinxr: "com/html-as-texture-in-xr.js",
|
||||||
// isoterminal features
|
// isoterminal features
|
||||||
ISOTerminal: "com/isoterminal/ISOTerminal.js",
|
core: "com/isoterminal/core.js",
|
||||||
|
utils_9p: "com/isoterminal/feat/9pfs_utils.js",
|
||||||
|
boot: "com/isoterminal/feat/boot.js",
|
||||||
|
jsconsole: "com/isoterminal/feat/jsconsole.js",
|
||||||
|
javascript: "com/isoterminal/feat/javascript.js",
|
||||||
|
indexhtml: "com/isoterminal/feat/index.html.js",
|
||||||
|
indexjs: "com/isoterminal/feat/index.js.js",
|
||||||
|
autorestore: "com/isoterminal/feat/autorestore.js",
|
||||||
localforage: "https://cdn.rawgit.com/mozilla/localForage/master/dist/localforage.js"
|
localforage: "https://cdn.rawgit.com/mozilla/localForage/master/dist/localforage.js"
|
||||||
},
|
},
|
||||||
|
|
||||||
dom: {
|
dom: {
|
||||||
//scale: 0.5,
|
scale: 0.66,
|
||||||
events: ['click','keydown'],
|
events: ['click','keydown'],
|
||||||
html: (me) => `<div class="isoterminal">
|
html: (me) => `<div class="isoterminal">
|
||||||
|
<div id="screen" style="white-space: pre; font: 14px monospace; "></div>
|
||||||
|
<canvas style="display: none"></canvas>
|
||||||
|
<div id="serial"></div>
|
||||||
</div>`,
|
</div>`,
|
||||||
|
|
||||||
css: (me) => `.isoterminal{
|
css: (me) => `.isoterminal{
|
||||||
|
@ -89,10 +93,6 @@ if( typeof AFRAME != 'undefined '){
|
||||||
width:100%;
|
width:100%;
|
||||||
height:100%;
|
height:100%;
|
||||||
}
|
}
|
||||||
.isoterminal div{
|
|
||||||
display:block;
|
|
||||||
position:relative;
|
|
||||||
}
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Cousine';
|
font-family: 'Cousine';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
|
@ -105,14 +105,23 @@ if( typeof AFRAME != 'undefined '){
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
src: url(./com/isoterminal/assets/CousineBold.ttf) format('truetype');
|
src: url(./com/isoterminal/assets/CousineBold.ttf) format('truetype');
|
||||||
}
|
}
|
||||||
|
.isoterminal *{
|
||||||
|
white-space: pre;
|
||||||
|
line-height:16px;
|
||||||
|
display:inline;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.isoterminal *,
|
||||||
|
.xterm-dom-renderer-owner-1 .xterm-rows {
|
||||||
|
font-size: 14px;
|
||||||
|
font-family: "Cousine",Liberation Mono,DejaVu Sans Mono,Courier New,monospace;
|
||||||
|
font-weight:500 !important;
|
||||||
|
letter-spacing: 0 !important;
|
||||||
|
text-shadow: 0px 0px 10px #F075;
|
||||||
|
}
|
||||||
|
|
||||||
.isoterminal style{ display:none }
|
.isoterminal style{ display:none }
|
||||||
|
|
||||||
#overlay .winbox:has(> .isoterminal){
|
|
||||||
background:transparent;
|
|
||||||
box-shadow:none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wb-body:has(> .isoterminal){
|
.wb-body:has(> .isoterminal){
|
||||||
background: #000C;
|
background: #000C;
|
||||||
overflow:hidden;
|
overflow:hidden;
|
||||||
|
@ -122,29 +131,9 @@ if( typeof AFRAME != 'undefined '){
|
||||||
.XR .wb-body:has(> .isoterminal){
|
.XR .wb-body:has(> .isoterminal){
|
||||||
background: #000;
|
background: #000;
|
||||||
}
|
}
|
||||||
.isoterminal *,
|
|
||||||
.isoterminal .xterm-dom-renderer-owner-1 .xterm-rows {
|
.isoterminal div{ display:block; }
|
||||||
background:transparent !important;
|
.isoterminal span{ display: inline }
|
||||||
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 {
|
@keyframes fade {
|
||||||
from { opacity: 1.0; }
|
from { opacity: 1.0; }
|
||||||
|
@ -158,8 +147,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;
|
||||||
}
|
}
|
||||||
|
@ -170,28 +158,13 @@ if( typeof AFRAME != 'undefined '){
|
||||||
initTerminal: async function(singleton){
|
initTerminal: async function(singleton){
|
||||||
|
|
||||||
if( this.data.xterm ){
|
if( this.data.xterm ){
|
||||||
// why 3.12?
|
this.requires.xtermjs = "https://unpkg.com/@xterm/xterm@5.5.0/lib/xterm.js"
|
||||||
// first versions used 1.5.4, a typescript rewrite which:
|
this.requires.xtermcss = "https://unpkg.com/@xterm/xterm@5.5.0/css/xterm.css"
|
||||||
// * acts weird with oculus browser keyboard (does not repaint properly after typing)
|
this.requires.xterm = "com/isoterminal/feat/xterm.js"
|
||||||
// * does not use canvas anymore [which would be ideal for THREE.js texture]
|
// xterm relies on window.requestAnimationFrame which is not called in XR (xrSession.requestAnimationFrame is)
|
||||||
// * does not allow switching between dom/canvas
|
|
||||||
// * only allows a standalone WebGL addon (conflicts with THREE)
|
|
||||||
// * heavily dependent on requestAnimationFrame (conflicts with THREE)
|
|
||||||
// * typescript-rewrite results in ~300k lib (instead of 96k)
|
|
||||||
this.requires.xtermcss = "//unpkg.com/xterm@3.12.0/dist/xterm.css",
|
|
||||||
this.requires.xtermjs = "//unpkg.com/xterm@3.12.0/dist/xterm.js",
|
|
||||||
this.requires.xtermcss = "com/xterm.js"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await AFRAME.utils.require(this.requires)
|
let s = await AFRAME.utils.require(this.requires)
|
||||||
await AFRAME.utils.require({ // ISOTerminal plugins
|
|
||||||
boot: "com/isoterminal/feat/boot.js",
|
|
||||||
javascript: "com/isoterminal/feat/javascript.js",
|
|
||||||
jsconsole: "com/isoterminal/feat/jsconsole.js",
|
|
||||||
indexhtml: "com/isoterminal/feat/index.html.js",
|
|
||||||
indexjs: "com/isoterminal/feat/index.js.js",
|
|
||||||
autorestore: "com/isoterminal/feat/autorestore.js",
|
|
||||||
})
|
|
||||||
|
|
||||||
this.el.setAttribute("selfcontainer","")
|
this.el.setAttribute("selfcontainer","")
|
||||||
|
|
||||||
|
@ -213,20 +186,18 @@ if( typeof AFRAME != 'undefined '){
|
||||||
this.isoterminal = new ISOTerminal(instance,this.data)
|
this.isoterminal = new ISOTerminal(instance,this.data)
|
||||||
|
|
||||||
instance.addEventListener('DOMready', () => {
|
instance.addEventListener('DOMready', () => {
|
||||||
//instance.setAttribute("html-as-texture-in-xr", `domid: #${this.el.dom.id}`)
|
|
||||||
//instance.winbox.resize(720,380)
|
//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 = this.data.xterm ? 'width: 1024px; height:600px'
|
||||||
|
: 'width: 720px; height:455px'
|
||||||
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.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]
|
||||||
this.isoterminal.start(opts)
|
this.isoterminal.runISO(opts)
|
||||||
})
|
})
|
||||||
|
|
||||||
instance.setAttribute("dom", "")
|
instance.setAttribute("dom", "")
|
||||||
|
@ -243,6 +214,9 @@ if( typeof AFRAME != 'undefined '){
|
||||||
this.isoterminal.addEventListener('ready', (e)=>{
|
this.isoterminal.addEventListener('ready', (e)=>{
|
||||||
instance.dom.classList.remove('blink')
|
instance.dom.classList.remove('blink')
|
||||||
this.isoterminal.emit('status',"running")
|
this.isoterminal.emit('status',"running")
|
||||||
|
setTimeout( () => { // important: after window maximize animation to get true size
|
||||||
|
instance.setAttribute("html-as-texture-in-xr", `domid: #${instance.uid}`) // only show aframe-html in xr
|
||||||
|
},1500)
|
||||||
})
|
})
|
||||||
|
|
||||||
this.isoterminal.addEventListener('status', function(e){
|
this.isoterminal.addEventListener('status', function(e){
|
||||||
|
@ -261,32 +235,19 @@ if( typeof AFRAME != 'undefined '){
|
||||||
instance.addEventListener('window.onresize', resize )
|
instance.addEventListener('window.onresize', resize )
|
||||||
instance.addEventListener('window.onmaximize', resize )
|
instance.addEventListener('window.onmaximize', resize )
|
||||||
|
|
||||||
const focus = (showdom) => (e) => {
|
const focus = (e) => {
|
||||||
if( this.el.components.xterm ){
|
if( this.isoterminal?.emulator?.serial_adapter?.term ){
|
||||||
this.el.components.xterm.term.focus()
|
this.isoterminal.emulator.serial_adapter.term.focus()
|
||||||
}
|
|
||||||
if( this.el.components.window ){
|
|
||||||
this.el.components.window.show( showdom )
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
instance.addEventListener('obbcollisionstarted', focus )
|
||||||
|
|
||||||
this.el.addEventListener('obbcollisionstarted', focus(false) )
|
this.el.sceneEl.addEventListener('enter-vr', focus )
|
||||||
this.el.sceneEl.addEventListener('enter-vr', focus(false) )
|
this.el.sceneEl.addEventListener('enter-ar', focus )
|
||||||
this.el.sceneEl.addEventListener('enter-ar', focus(false) )
|
|
||||||
this.el.sceneEl.addEventListener('exit-vr', focus(true) )
|
|
||||||
this.el.sceneEl.addEventListener('exit-ar', focus(true) )
|
|
||||||
|
|
||||||
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")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
events:{
|
events:{
|
||||||
|
|
||||||
// combined AFRAME+DOM reactive events
|
// combined AFRAME+DOM reactive events
|
||||||
|
@ -296,6 +257,10 @@ if( typeof AFRAME != 'undefined '){
|
||||||
// reactive events for this.data updates
|
// reactive events for this.data updates
|
||||||
myvalue: function(e){ this.el.dom.querySelector('b').innerText = this.data.myvalue },
|
myvalue: function(e){ this.el.dom.querySelector('b').innerText = this.data.myvalue },
|
||||||
|
|
||||||
|
ready: function( ){
|
||||||
|
this.el.dom.style.display = 'none'
|
||||||
|
},
|
||||||
|
|
||||||
launcher: async function(){
|
launcher: async function(){
|
||||||
this.initTerminal()
|
this.initTerminal()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,318 +0,0 @@
|
||||||
function ISOTerminal(instance,opts){
|
|
||||||
// create a neutral isoterminal object which can be decorated
|
|
||||||
// with prototype functions and has addListener() and dispatchEvent()
|
|
||||||
let obj = new EventTarget()
|
|
||||||
obj.instance = instance
|
|
||||||
obj.opts = opts
|
|
||||||
// register default event listeners (enable file based features like isoterminal/jsconsole.js e.g.)
|
|
||||||
for( let event in ISOTerminal.listener )
|
|
||||||
for( let cb in ISOTerminal.listener[event] )
|
|
||||||
obj.addEventListener( event, ISOTerminal.listener[event][cb] )
|
|
||||||
// compose object with functions
|
|
||||||
for( let i in ISOTerminal.prototype ) obj[i] = ISOTerminal.prototype[i]
|
|
||||||
obj.emit('init')
|
|
||||||
return obj
|
|
||||||
}
|
|
||||||
|
|
||||||
ISOTerminal.prototype.emit = function(event,data,sender){
|
|
||||||
data = data || false
|
|
||||||
const evObj = new CustomEvent(event, {detail: data} )
|
|
||||||
//this.preventFrameDrop( () => {
|
|
||||||
// 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.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 !== undefined && typeof this[event] == 'function' ) this[event].apply(this, data && data.push ? data : [data] )
|
|
||||||
//})
|
|
||||||
}
|
|
||||||
|
|
||||||
ISOTerminal.addEventListener = (event,cb) => {
|
|
||||||
ISOTerminal.listener = ISOTerminal.listener || {}
|
|
||||||
ISOTerminal.listener[event] = ISOTerminal.listener[event] || []
|
|
||||||
ISOTerminal.listener[event].push(cb)
|
|
||||||
}
|
|
||||||
|
|
||||||
ISOTerminal.prototype.exec = function(shellscript){
|
|
||||||
this.send(shellscript+"\n",1)
|
|
||||||
}
|
|
||||||
|
|
||||||
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){
|
|
||||||
if( ttyNr == undefined) ttyNr = this.serial_input
|
|
||||||
if( ttyNr == undefined ){
|
|
||||||
if( this.emulator.serial_adapter ){
|
|
||||||
this.emulator.serial_adapter.term.paste(str)
|
|
||||||
}else this.emulator.keyboard_send_text(str) // vga screen
|
|
||||||
}else{
|
|
||||||
this.convert.toUint8Array( str ).map( (c) => {
|
|
||||||
this.preventFrameDrop(
|
|
||||||
() => this.worker.postMessage({event:`serial${ttyNr}-input`,data:c})
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ISOTerminal.prototype.convert = {
|
|
||||||
|
|
||||||
arrayBufferToBase64: function(buffer){
|
|
||||||
let binary = '';
|
|
||||||
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);
|
|
||||||
},
|
|
||||||
|
|
||||||
base64ToArrayBuffer: function(base64) {
|
|
||||||
const binaryString = window.atob(base64);
|
|
||||||
const len = binaryString.length;
|
|
||||||
const bytes = new Uint8Array(len);
|
|
||||||
|
|
||||||
for (let i = 0; i < len; i++) {
|
|
||||||
bytes[i] = binaryString.charCodeAt(i);
|
|
||||||
}
|
|
||||||
return bytes.buffer;
|
|
||||||
},
|
|
||||||
|
|
||||||
toUint8Array: function(str) {
|
|
||||||
str = String(str) || String("")
|
|
||||||
// Create a new Uint8Array with the same length as the input string
|
|
||||||
const uint8Array = new Uint8Array(str.length);
|
|
||||||
|
|
||||||
// Iterate over the string and populate the Uint8Array
|
|
||||||
for (let i = 0; i < str.length; i++) {
|
|
||||||
uint8Array[i] = str.charCodeAt(i);
|
|
||||||
}
|
|
||||||
return uint8Array;
|
|
||||||
},
|
|
||||||
|
|
||||||
Uint8ArrayToString: function(arr){
|
|
||||||
const decoder = new TextDecoder('utf-8'); // Specify encoding
|
|
||||||
return decoder.decode(arr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ISOTerminal.prototype.start = function(opts){
|
|
||||||
|
|
||||||
let me = this
|
|
||||||
this.opts = {...this.opts, ...opts}
|
|
||||||
let image = {}
|
|
||||||
if( opts.iso.match(/\.iso$/) ) image.cdrom = { url: opts.iso }
|
|
||||||
if( opts.iso.match(/\.bin$/) ) image.bzimage = { url: opts.iso }
|
|
||||||
|
|
||||||
opts = { ...image,
|
|
||||||
uart1:true, // /dev/ttyS1
|
|
||||||
uart2:true, // /dev/ttyS2
|
|
||||||
uart3:true, // /dev/ttyS3
|
|
||||||
wasm_path: "v86.wasm",
|
|
||||||
memory_size: opts.memory * 1024 * 1024,
|
|
||||||
vga_memory_size: 2 * 1024 * 1024,
|
|
||||||
//screen_container: opts.dom,
|
|
||||||
//serial_container: opts.dom,
|
|
||||||
bios: {
|
|
||||||
url: "bios/seabios.bin",
|
|
||||||
},
|
|
||||||
vga_bios: {
|
|
||||||
url: "bios/vgabios.bin",
|
|
||||||
//urg|: "com/isoterminal/bios/VGABIOS-lgpl-latest.bin",
|
|
||||||
},
|
|
||||||
network_relay_url: "wss://relay.widgetry.org/",
|
|
||||||
cmdline: "rw root=host9p rootfstype=9p rootflags=trans=virtio,cache=loose modules=virtio_pci tsc=reliable init_on_freg|=on vga=ask", //vga=0x122",
|
|
||||||
//bzimage_initrd_from_filesystem: true,
|
|
||||||
//filesystem: {
|
|
||||||
// baseurl: "com/isoterminal/v86/images/alpine-rootfs-flat",
|
|
||||||
// basefs: "com/isoterminal/v86/images/alpine-fs.json",
|
|
||||||
// },
|
|
||||||
//screen_dummy: true,
|
|
||||||
//disable_jit: false,
|
|
||||||
filesystem: {},
|
|
||||||
autostart: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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.preventFrameDrop( cb(event,data) )
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* postMessage.promise basically performs this.worker.postMessage
|
|
||||||
* in a promise way (to easily retrieve async output)
|
|
||||||
*/
|
|
||||||
|
|
||||||
this.worker.postMessage.promise = function(data){
|
|
||||||
if( typeof data != 'object' ) data = {data}
|
|
||||||
this.resolvers = this.resolvers || {}
|
|
||||||
this.id = this.id == undefined ? 0 : this.id
|
|
||||||
data.id = this.id++
|
|
||||||
// Send id and task to WebWorker
|
|
||||||
this.preventFrameDrop( () => this.worker.postMessage(data,getTransferable(data) ) )
|
|
||||||
return new Promise(resolve => this.resolvers[data.id] = resolve);
|
|
||||||
}.bind(this.worker.postMessage)
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
this.emit('runISO',{...opts, bufferLatency: this.opts.bufferLatency })
|
|
||||||
const loading = [
|
|
||||||
'loading quantum bits and bytes',
|
|
||||||
'preparing quantum flux capacitors',
|
|
||||||
'crunching peanuts and chakras',
|
|
||||||
'preparing parallel universe',
|
|
||||||
'loading quantum state fluctuations',
|
|
||||||
'preparing godmode',
|
|
||||||
'loading cat pawns and cuteness',
|
|
||||||
'beaming up scotty',
|
|
||||||
'still faster than Windows update',
|
|
||||||
'loading a microlinux',
|
|
||||||
'figuring out meaning of life',
|
|
||||||
'Aligning your chakras now',
|
|
||||||
'Breathing in good vibes',
|
|
||||||
'Finding inner peace soon',
|
|
||||||
'Centering your Zen energy',
|
|
||||||
'Awakening third eye powers',
|
|
||||||
'Tuning into the universe',
|
|
||||||
'Balancing your cosmic karma',
|
|
||||||
'Stretching time and space',
|
|
||||||
'Recharging your soul battery',
|
|
||||||
'Transcending earthly limits'
|
|
||||||
]
|
|
||||||
|
|
||||||
const empower = [
|
|
||||||
"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 tools are accessible and often better than closed alternatives",
|
|
||||||
"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: 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
|
|
||||||
`
|
|
||||||
|
|
||||||
const text_color = "\r[38;5;129m"
|
|
||||||
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('serial-output-string', motd + empowermsg + text_color + loadmsg + text_reset+"\n\r")
|
|
||||||
|
|
||||||
this.addEventListener('emulator-started', async (e) => {
|
|
||||||
|
|
||||||
// OVERLAY FS *FIXME*
|
|
||||||
//if( me.opts.overlayfs ){
|
|
||||||
// fetch(me.opts.overlayfs)
|
|
||||||
// .then( (f) => {
|
|
||||||
// f.arrayBuffer().then( (buf) => {
|
|
||||||
// emulator.create_file('overlayfs.zip', new Uint8Array(buf) )
|
|
||||||
// })
|
|
||||||
// })
|
|
||||||
//}
|
|
||||||
|
|
||||||
let line = ''
|
|
||||||
this.ready = false
|
|
||||||
|
|
||||||
this.addEventListener(`serial0-output-string`, async (e) => {
|
|
||||||
const str = e.detail
|
|
||||||
|
|
||||||
// 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 || !this.opts.muteUntilPrompt ) 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){
|
|
||||||
const resetBuffer = () => ({str:""})
|
|
||||||
this.buffer = this.buffer || resetBuffer()
|
|
||||||
this.buffer.str += String.fromCharCode(byte)
|
|
||||||
if( !this.buffer.id ){
|
|
||||||
cb(this.buffer.str) // send out leading call
|
|
||||||
this.buffer = resetBuffer()
|
|
||||||
this.buffer.id = setTimeout( () => { // accumulate succesive calls
|
|
||||||
if( this.buffer.str ) cb(this.buffer.str)
|
|
||||||
this.buffer = resetBuffer()
|
|
||||||
}, this.latency || 250)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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,193 @@
|
||||||
|
//ISOTerminal.prototype.exec(cmd_array,stdin){
|
||||||
|
// // exec(['lua'] "print \"hello\") ---> cat /dev/browser/js/stdin | lua > /dev/browser/js/stdout
|
||||||
|
//}
|
||||||
|
|
||||||
|
ISOTerminal.prototype.serial_input = undefined; // can be set to 0,1,2,3 to define stdinput tty (xterm plugin)
|
||||||
|
|
||||||
|
ISOTerminal.prototype.exec = function(shellscript){
|
||||||
|
//let ts = String(Date.now())+".job"
|
||||||
|
//this.emulator.create_file(ts, this.toUint8Array(shellscript) )
|
||||||
|
this.send(shellscript+"\n",1)
|
||||||
|
}
|
||||||
|
|
||||||
|
ISOTerminal.prototype.send = function(str, ttyNr){
|
||||||
|
if( !ttyNr ) ttyNr = this.serial_input
|
||||||
|
if( !ttyNr ){
|
||||||
|
if( this.emulator.serial_adapter ){
|
||||||
|
this.emulator.serial_adapter.term.paste(str)
|
||||||
|
}else this.emulator.keyboard_send_text(str) // vga screen
|
||||||
|
}else{
|
||||||
|
this.convert.toUint8Array( str ).map( (c) => this.emulator.bus.send(`serial${ttyNr}-input`, c ) )
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ISOTerminal.prototype.convert = {
|
||||||
|
|
||||||
|
arrayBufferToBase64: function(buffer){
|
||||||
|
let binary = '';
|
||||||
|
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);
|
||||||
|
},
|
||||||
|
|
||||||
|
base64ToArrayBuffer: function(base64) {
|
||||||
|
const binaryString = window.atob(base64);
|
||||||
|
const len = binaryString.length;
|
||||||
|
const bytes = new Uint8Array(len);
|
||||||
|
|
||||||
|
for (let i = 0; i < len; i++) {
|
||||||
|
bytes[i] = binaryString.charCodeAt(i);
|
||||||
|
}
|
||||||
|
return bytes.buffer;
|
||||||
|
},
|
||||||
|
|
||||||
|
toUint8Array: function(str) {
|
||||||
|
str = String(str) || String("")
|
||||||
|
// Create a new Uint8Array with the same length as the input string
|
||||||
|
const uint8Array = new Uint8Array(str.length);
|
||||||
|
|
||||||
|
// Iterate over the string and populate the Uint8Array
|
||||||
|
for (let i = 0; i < str.length; i++) {
|
||||||
|
uint8Array[i] = str.charCodeAt(i);
|
||||||
|
}
|
||||||
|
return uint8Array;
|
||||||
|
},
|
||||||
|
|
||||||
|
Uint8ArrayToString: function(arr){
|
||||||
|
const decoder = new TextDecoder('utf-8'); // Specify encoding
|
||||||
|
return decoder.decode(arr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ISOTerminal.prototype.runISO = function(opts){
|
||||||
|
|
||||||
|
let me = this
|
||||||
|
this.opts = {...this.opts, ...opts}
|
||||||
|
let image = {}
|
||||||
|
if( opts.iso.match(/\.iso$/) ) image.cdrom = { url: opts.iso }
|
||||||
|
if( opts.iso.match(/\.bin$/) ) image.bzimage = { url: opts.iso }
|
||||||
|
|
||||||
|
opts = { ...image,
|
||||||
|
uart1:true, // /dev/ttyS1
|
||||||
|
uart2:true, // /dev/ttyS2
|
||||||
|
uart3:true, // /dev/ttyS3
|
||||||
|
wasm_path: "com/isoterminal/v86.wasm",
|
||||||
|
memory_size: opts.memory * 1024 * 1024,
|
||||||
|
vga_memory_size: 2 * 1024 * 1024,
|
||||||
|
screen_container: opts.dom,
|
||||||
|
//serial_container: opts.dom,
|
||||||
|
bios: {
|
||||||
|
url: "com/isoterminal/bios/seabios.bin",
|
||||||
|
},
|
||||||
|
vga_bios: {
|
||||||
|
url: "com/isoterminal/bios/vgabios.bin",
|
||||||
|
//urg|: "com/isoterminal/bios/VGABIOS-lgpl-latest.bin",
|
||||||
|
},
|
||||||
|
network_relay_url: "wss://relay.widgetry.org/",
|
||||||
|
cmdline: "rw root=host9p rootfstype=9p rootflags=trans=virtio,cache=loose modules=virtio_pci tsc=reliable init_on_freg|=on vga=ask", //vga=0x122",
|
||||||
|
//bzimage_initrd_from_filesystem: true,
|
||||||
|
//filesystem: {
|
||||||
|
// baseurl: "com/isoterminal/v86/images/alpine-rootfs-flat",
|
||||||
|
// basefs: "com/isoterminal/v86/images/alpine-fs.json",
|
||||||
|
// },
|
||||||
|
//screen_dummy: true,
|
||||||
|
//disable_jit: false,
|
||||||
|
filesystem: {},
|
||||||
|
autostart: true,
|
||||||
|
};
|
||||||
|
this.emit('runISO',opts)
|
||||||
|
let emulator = this.emulator = new V86(opts)
|
||||||
|
|
||||||
|
const loading = [
|
||||||
|
'loading quantum bits and bytes',
|
||||||
|
'preparing quantum flux capacitors',
|
||||||
|
'crunching peanuts and chakras',
|
||||||
|
'preparing parallel universe',
|
||||||
|
'loading quantum state fluctuations',
|
||||||
|
'preparing godmode',
|
||||||
|
'loading cat pawns and cuteness',
|
||||||
|
'beaming up scotty',
|
||||||
|
'still faster than Windows update',
|
||||||
|
'loading a microlinux',
|
||||||
|
'figuring out meaning of life',
|
||||||
|
'Aligning your chakras now',
|
||||||
|
'Breathing in good vibes',
|
||||||
|
'Finding inner peace soon',
|
||||||
|
'Centering your Zen energy',
|
||||||
|
'Awakening third eye powers',
|
||||||
|
'Tuning into the universe',
|
||||||
|
'Balancing your cosmic karma',
|
||||||
|
'Stretching time and space',
|
||||||
|
'Recharging your soul battery',
|
||||||
|
'Transcending earthly limits'
|
||||||
|
]
|
||||||
|
|
||||||
|
let loadmsg = loading[ Math.floor(Math.random()*1000) % loading.length ]
|
||||||
|
this.emit('status',loadmsg)
|
||||||
|
|
||||||
|
// replace welcome message https://github.com/copy/v86/blob/3c77b98bc4bc7a5d51a2056ea73d7666ca50fc9d/src/browser/serial.js#L231
|
||||||
|
let welcome = "This is the serial console. Whatever you type or paste here will be sent to COM1"
|
||||||
|
let motd = "\r[38;5;129m"
|
||||||
|
let msg = `${loadmsg}, please wait..`
|
||||||
|
while( msg.length < welcome.length ) msg += " "
|
||||||
|
msg += "\n"
|
||||||
|
motd += msg+"\033[0m"
|
||||||
|
|
||||||
|
emulator.bus.register("emulator-started", async (e) => {
|
||||||
|
this.emit('emulator-started',e)
|
||||||
|
|
||||||
|
if( emulator.serial_adapter ){
|
||||||
|
emulator.serial_adapter.term.clear()
|
||||||
|
emulator.serial_adapter.term.write(motd)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if( me.opts.overlayfs ){
|
||||||
|
fetch(me.opts.overlayfs)
|
||||||
|
.then( (f) => {
|
||||||
|
f.arrayBuffer().then( (buf) => {
|
||||||
|
emulator.create_file('overlayfs.zip', new Uint8Array(buf) )
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let line = ''
|
||||||
|
let ready = false
|
||||||
|
emulator.add_listener(`serial0-output-byte`, async (byte) => {
|
||||||
|
this.emit('${this.serial}-output-byte',byte)
|
||||||
|
var chr = String.fromCharCode(byte);
|
||||||
|
if(chr < " " && chr !== "\n" && chr !== "\t" || chr > "~")
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(chr === "\n")
|
||||||
|
{
|
||||||
|
var new_line = line;
|
||||||
|
line = "";
|
||||||
|
}
|
||||||
|
else if(chr >= " " && chr <= "~")
|
||||||
|
{
|
||||||
|
line += chr;
|
||||||
|
}
|
||||||
|
if( !ready && line.match(/^(\/ #|~%|\[.*\]>)/) ){
|
||||||
|
this.emit('postReady',e)
|
||||||
|
setTimeout( () => this.emit('ready',e), 500 )
|
||||||
|
ready = true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ISOTerminal.prototype.readFromPipe = function(filename,cb){
|
||||||
|
|
||||||
|
this.emulator.add_listener("9p-write-end", async (opts) => {
|
||||||
|
if ( opts[0] == filename.replace(/.*\//,'') ){
|
||||||
|
const buf = await this.emulator.read_file("console.tty")
|
||||||
|
cb( this.convert.Uint8ArrayToString(buf) )
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
|
@ -1,8 +1,8 @@
|
||||||
let emulator = this.emulator
|
ISOTerminal.addEventListener('emulator-started', function(){
|
||||||
let me = this
|
let emulator = this.emulator
|
||||||
|
let isoterminal = this
|
||||||
|
|
||||||
emulator.fs9p.update_file = async function(file,data){
|
emulator.fs9p.update_file = async function(file,data){
|
||||||
const convert = ISOTerminal.prototype.convert
|
|
||||||
|
|
||||||
const p = this.SearchPath(file);
|
const p = this.SearchPath(file);
|
||||||
|
|
||||||
|
@ -12,19 +12,18 @@ 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' ? isoterminal.convert.toUint8Array(data) : data
|
||||||
await this.Write(p.id,0, buf.length, buf )
|
await this.Write(p.id,0, buf.length, buf )
|
||||||
// update inode
|
// update inode
|
||||||
inode.size = buf.length
|
inode.size = buf.length
|
||||||
const now = Math.round(Date.now() / 1000);
|
const now = Math.round(Date.now() / 1000);
|
||||||
inode.atime = inode.mtime = now;
|
inode.atime = inode.mtime = now;
|
||||||
me.postMessage({event:'exec',data:[`touch /mnt/${file}`]}) // update inode
|
isoterminal.exec(`touch ${file}`) // update inode
|
||||||
return new Promise( (resolve,reject) => resolve(buf) )
|
return new Promise( (resolve,reject) => resolve(buf) )
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
emulator.fs9p.append_file = async function(file,data){
|
emulator.fs9p.append_file = async function(file,data){
|
||||||
const convert = ISOTerminal.prototype.convert
|
|
||||||
|
|
||||||
const p = this.SearchPath(file);
|
const p = this.SearchPath(file);
|
||||||
|
|
||||||
|
@ -34,7 +33,7 @@ emulator.fs9p.append_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' ? isoterminal.convert.toUint8Array(data) : data
|
||||||
await this.Write(p.id, inode.size, buf.length, buf )
|
await this.Write(p.id, inode.size, buf.length, buf )
|
||||||
// update inode
|
// update inode
|
||||||
inode.size = inode.size + buf.length
|
inode.size = inode.size + buf.length
|
||||||
|
@ -42,7 +41,6 @@ emulator.fs9p.append_file = async function(file,data){
|
||||||
inode.atime = inode.mtime = now;
|
inode.atime = inode.mtime = now;
|
||||||
return new Promise( (resolve,reject) => resolve(buf) )
|
return new Promise( (resolve,reject) => resolve(buf) )
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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,25 +1,8 @@
|
||||||
if( typeof emulator != 'undefined' ){
|
ISOTerminal.addEventListener('emulator-started', function(e){
|
||||||
// inside worker-thread
|
|
||||||
|
|
||||||
this['emulator.restore_state'] = async function(data){
|
|
||||||
await emulator.restore_state(data)
|
|
||||||
console.log("restored state")
|
|
||||||
this.postMessage({event:"state_restored",data:false})
|
|
||||||
}
|
|
||||||
this['emulator.save_state'] = async function(){
|
|
||||||
console.log("saving session")
|
|
||||||
let state = await emulator.save_state()
|
|
||||||
this.postMessage({event:"state_saved",data:state})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}else{
|
|
||||||
// inside browser-thread
|
|
||||||
ISOTerminal.addEventListener('emulator-started', function(e){
|
|
||||||
this.autorestore(e)
|
this.autorestore(e)
|
||||||
})
|
})
|
||||||
|
|
||||||
ISOTerminal.prototype.autorestore = async function(e){
|
ISOTerminal.prototype.autorestore = async function(e){
|
||||||
|
|
||||||
localforage.setDriver([
|
localforage.setDriver([
|
||||||
localforage.INDEXEDDB,
|
localforage.INDEXEDDB,
|
||||||
|
@ -31,25 +14,32 @@ if( typeof emulator != 'undefined' ){
|
||||||
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
|
||||||
state = this.convert.base64ToArrayBuffer( stateBase64 )
|
state = this.convert.base64ToArrayBuffer( stateBase64 )
|
||||||
|
this.emulator.restore_state(state)
|
||||||
|
this.emit('postReady',e)
|
||||||
|
setTimeout( () => {
|
||||||
|
this.emit('ready',e)
|
||||||
|
// press CTRL+a l (=gnu screen redisplay)
|
||||||
|
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 )
|
||||||
|
|
||||||
this.addEventListener('state_restored', function(){
|
}, 500 )
|
||||||
// simulate / fastforward boot events
|
|
||||||
this.postBoot( () => this.send("l\n") )
|
|
||||||
})
|
|
||||||
|
|
||||||
this.worker.postMessage({event:'emulator.restore_state',data:state})
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
this.save = async () => {
|
this.save = async () => {
|
||||||
const state = await this.worker.postMessage({event:"emulator.save_state",data:false})
|
const state = await this.emulator.save_state()
|
||||||
}
|
console.log( String(this.convert.arrayBufferToBase64(state)).substr(0,5) )
|
||||||
|
|
||||||
this.addEventListener('state_saved', function(e){
|
|
||||||
const state = e.detail
|
|
||||||
localforage.setItem("state", this.convert.arrayBufferToBase64(state) )
|
localforage.setItem("state", this.convert.arrayBufferToBase64(state) )
|
||||||
console.log("state saved")
|
}
|
||||||
})
|
|
||||||
|
|
||||||
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";
|
||||||
|
@ -58,6 +48,5 @@ if( typeof emulator != 'undefined' ){
|
||||||
});
|
});
|
||||||
|
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,17 +6,22 @@ ISOTerminal.prototype.boot = async function(e){
|
||||||
// set environment
|
// set environment
|
||||||
let env = ['export BROWSER=1']
|
let env = ['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]+'"') )
|
||||||
}
|
}
|
||||||
}
|
await this.emulator.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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if( this.emulator.serial_adapter ) this.emulator.serial_adapter.term.focus()
|
||||||
|
else{
|
||||||
|
let els = [...document.querySelectorAll("div#screen")]
|
||||||
|
els.map( (el) => el.focus() )
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,24 +1,26 @@
|
||||||
|
ISOTerminal.addEventListener('init', function(){
|
||||||
|
|
||||||
if( typeof emulator != 'undefined' ){
|
this.addEventListener('emulator-started', function(e){
|
||||||
// inside worker-thread
|
|
||||||
|
const emulator = this.emulator
|
||||||
|
|
||||||
// unix to js device
|
// unix to js device
|
||||||
this.emulator.readFromPipe( 'root/index.html', async (data) => {
|
this.readFromPipe( '/mnt/index.html', async (data) => {
|
||||||
const buf = await emulator.read_file("root/index.html")
|
const buf = await emulator.read_file("index.html")
|
||||||
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)
|
||||||
try{
|
try{
|
||||||
this.postMessage({event:'runHTML',data:[html]})
|
this.runHTML(html)
|
||||||
}catch(e){
|
}catch(e){
|
||||||
console.error(e)
|
console.error(e)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
}else{
|
})
|
||||||
// inside browser-thread
|
|
||||||
|
|
||||||
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")
|
||||||
if( !$root ){
|
if( !$root ){
|
||||||
|
@ -27,6 +29,4 @@ if( typeof emulator != 'undefined' ){
|
||||||
$scene.appendChild($root)
|
$scene.appendChild($root)
|
||||||
}
|
}
|
||||||
$root.innerHTML = html
|
$root.innerHTML = html
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +1,26 @@
|
||||||
if( typeof emulator != 'undefined' ){
|
ISOTerminal.addEventListener('init', function(){
|
||||||
// inside worker-thread
|
|
||||||
|
this.addEventListener('emulator-started', function(e){
|
||||||
|
|
||||||
|
const emulator = this.emulator
|
||||||
|
|
||||||
// unix to js device
|
// unix to js device
|
||||||
this.emulator.readFromPipe( 'root/index.js', async (data) => {
|
this.readFromPipe( '/mnt/index.js', async (data) => {
|
||||||
const buf = await emulator.read_file("root/index.js")
|
const buf = await emulator.read_file("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)
|
||||||
try{
|
try{
|
||||||
this.postMessage({event:'runJavascript',data:[js]})
|
this.runJavascript(js)
|
||||||
}catch(e){
|
}catch(e){
|
||||||
console.error(e)
|
console.error(e)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
}else{
|
})
|
||||||
// inside browser-thread
|
|
||||||
|
|
||||||
ISOTerminal.prototype.runJavascript = function(js){
|
ISOTerminal.prototype.runJavascript = function(js){
|
||||||
let $root = document.querySelector("script#root")
|
let $root = document.querySelector("script#root")
|
||||||
if( !$root ){
|
if( !$root ){
|
||||||
$root = document.createElement("script")
|
$root = document.createElement("script")
|
||||||
|
@ -25,6 +28,5 @@ if( typeof emulator != 'undefined' ){
|
||||||
document.body.appendChild($root)
|
document.body.appendChild($root)
|
||||||
}
|
}
|
||||||
$root.innerHTML = js
|
$root.innerHTML = js
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,31 +1,29 @@
|
||||||
if( typeof emulator != 'undefined' ){
|
ISOTerminal.addEventListener('init', function(){
|
||||||
// inside worker-thread
|
|
||||||
|
this.addEventListener('emulator-started', function(e){
|
||||||
|
|
||||||
|
const emulator = this.emulator
|
||||||
|
|
||||||
// unix to js device
|
// unix to js device
|
||||||
emulator.readFromPipe( 'dev/browser/js', async (data) => {
|
this.readFromPipe( '/mnt/js', async (data) => {
|
||||||
const convert = ISOTerminal.prototype.convert
|
const buf = await emulator.read_file("js")
|
||||||
const buf = await this.emulator.read_file("dev/browser/js")
|
const script = this.convert.Uint8ArrayToString(buf)
|
||||||
const script = convert.Uint8ArrayToString(buf)
|
|
||||||
let PID="?"
|
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]
|
||||||
}
|
}
|
||||||
this.postMessage({event:'javascript-eval',data:{script,PID}})
|
let res = (new Function(`${script}`))()
|
||||||
|
if( res && typeof res != 'string' ) res = JSON.stringify(res,null,2)
|
||||||
|
// write output to 9p with PID as filename
|
||||||
|
// *FIXME* not flexible / robust
|
||||||
|
emulator.create_file(PID, this.convert.toUint8Array(res) )
|
||||||
}catch(e){
|
}catch(e){
|
||||||
console.error(e)
|
console.error(e)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
}else{
|
|
||||||
// inside browser-thread
|
|
||||||
|
|
||||||
ISOTerminal.addEventListener('javascript-eval', function(e){
|
|
||||||
const {script,PID} = e.detail
|
|
||||||
let res = (new Function(`${script}`))()
|
|
||||||
if( res && typeof res != 'string' ) res = JSON.stringify(res,null,2)
|
|
||||||
// update output to 9p with PID as filename (in /mnt/run)
|
|
||||||
this.emit('fs9p.update_file', [`run/${PID}`, this.convert.toUint8Array(res)] )
|
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
})
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,7 @@ ISOTerminal.prototype.redirectConsole = function(handler){
|
||||||
}
|
}
|
||||||
|
|
||||||
ISOTerminal.addEventListener('emulator-started', function(){
|
ISOTerminal.addEventListener('emulator-started', function(){
|
||||||
|
let emulator = this.emulator
|
||||||
|
|
||||||
this.redirectConsole( (str,prefix) => {
|
this.redirectConsole( (str,prefix) => {
|
||||||
let finalStr = ""
|
let finalStr = ""
|
||||||
|
@ -34,7 +35,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('fs9p.append_file', ["/dev/browser/console",finalStr])
|
emulator.fs9p.append_file( "console", finalStr )
|
||||||
})
|
})
|
||||||
|
|
||||||
window.addEventListener('error', function(event) {
|
window.addEventListener('error', function(event) {
|
||||||
|
@ -50,4 +51,9 @@ ISOTerminal.addEventListener('emulator-started', function(){
|
||||||
console.error(event);
|
console.error(event);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// enable/disable logging file (echo 1 > mnt/console.tty)
|
||||||
|
this.readFromPipe( '/mnt/console.tty', (data) => {
|
||||||
|
emulator.log_to_tty = ( String(data).trim() == '1')
|
||||||
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
|
@ -27,6 +27,7 @@ ISOTerminal.prototype.xtermInit = function(){
|
||||||
})
|
})
|
||||||
|
|
||||||
term.onRender( () => {
|
term.onRender( () => {
|
||||||
|
|
||||||
// xterm relies on requestAnimationFrame (which does not called in immersive mode)
|
// xterm relies on requestAnimationFrame (which does not called in immersive mode)
|
||||||
let _window = term._core._coreBrowserService._window
|
let _window = term._core._coreBrowserService._window
|
||||||
if( !_window._XRSH_proxied ){ // patch the planet!
|
if( !_window._XRSH_proxied ){ // patch the planet!
|
||||||
|
@ -39,10 +40,8 @@ ISOTerminal.prototype.xtermInit = function(){
|
||||||
// term.tid = null
|
// term.tid = null
|
||||||
// },100)
|
// },100)
|
||||||
//}
|
//}
|
||||||
this.i = 0
|
|
||||||
const requestAnimationFrameAFRAME = AFRAME.utils.throttleLeadingAndTrailing(
|
const requestAnimationFrameAFRAME = AFRAME.utils.throttleLeadingAndTrailing(
|
||||||
function(cb){ cb() }
|
function(cb){ cb() },150
|
||||||
,150
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// we proxy the _window object of xterm, and reroute
|
// we proxy the _window object of xterm, and reroute
|
||||||
|
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
@ -1,70 +0,0 @@
|
||||||
importScripts("libv86.js");
|
|
||||||
importScripts("ISOTerminal.js") // we don't instance it again here (just use its functions)
|
|
||||||
|
|
||||||
this.runISO = function(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")
|
|
||||||
|
|
||||||
// event forwarding
|
|
||||||
|
|
||||||
emulator.add_listener("serial0-output-byte", function(byte){
|
|
||||||
ISOTerminal.prototype.bufferOutput(byte, (str) => { // we buffer to prevent framerate dropping
|
|
||||||
if( !str ) return
|
|
||||||
this.postMessage({event:"serial0-output-string",data:str});
|
|
||||||
}, opts.bufferLatency )
|
|
||||||
}.bind(this));
|
|
||||||
|
|
||||||
emulator.add_listener("serial1-output-byte", function(byte){
|
|
||||||
ISOTerminal.prototype.bufferOutput(byte, (str) => { // we buffer to prevent framerate dropping
|
|
||||||
if( !str ) return
|
|
||||||
this.postMessage({event:"serial1-output-string",data:str});
|
|
||||||
}, opts.bufferLatency )
|
|
||||||
}.bind(this));
|
|
||||||
|
|
||||||
emulator.add_listener("serial2-output-byte", function(byte){
|
|
||||||
ISOTerminal.prototype.bufferOutput(byte, (str) => { // we buffer to prevent framerate dropping
|
|
||||||
if( !str ) return
|
|
||||||
this.postMessage({event:"serial2-output-string",data:str});
|
|
||||||
}, opts.bufferLatency )
|
|
||||||
}.bind(this));
|
|
||||||
|
|
||||||
emulator.add_listener("emulator-started", function(){
|
|
||||||
importScripts("feat/9pfs_utils.js")
|
|
||||||
this.postMessage({event:"emulator-started",data:false});
|
|
||||||
}.bind(this));
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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]) }
|
|
||||||
|
|
||||||
// filename will be read from 9pfs: "/mnt/"+filename
|
|
||||||
emulator.readFromPipe = function(filename,cb){
|
|
||||||
emulator.add_listener("9p-write-end", async (opts) => {
|
|
||||||
if ( opts[0] == filename.replace(/.*\//,'') ){
|
|
||||||
cb()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
importScripts("feat/javascript.js")
|
|
||||||
importScripts("feat/index.html.js")
|
|
||||||
importScripts("feat/autorestore.js")
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
* 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.onmessage = function(e){
|
|
||||||
let {event,data} = e.data
|
|
||||||
if( this[event] ) this[event](data)
|
|
||||||
}
|
|
|
@ -63,9 +63,5 @@ AFRAME.registerComponent('window', {
|
||||||
|
|
||||||
this.el.setAttribute("grabbable","")
|
this.el.setAttribute("grabbable","")
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
show: function(state){
|
|
||||||
this.el.dom.closest('.winbox').style.display = state ? '' : 'none'
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
261
com/xterm.js
261
com/xterm.js
|
@ -1,261 +0,0 @@
|
||||||
/*
|
|
||||||
* MIT License
|
|
||||||
*
|
|
||||||
* Copyright (c) 2019
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in all
|
|
||||||
* copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*
|
|
||||||
* 2019 Mauve Ranger
|
|
||||||
* 2024 Leon van Kammen
|
|
||||||
*/
|
|
||||||
|
|
||||||
let terminalInstance = 0
|
|
||||||
|
|
||||||
const TERMINAL_THEME = {
|
|
||||||
theme_foreground: {
|
|
||||||
// 'default': '#ffffff'
|
|
||||||
},
|
|
||||||
theme_background: {
|
|
||||||
// 'default': '#000'
|
|
||||||
},
|
|
||||||
theme_cursor: {
|
|
||||||
// 'default': '#ffffff'
|
|
||||||
},
|
|
||||||
theme_selection: {
|
|
||||||
// 'default': 'rgba(255, 255, 255, 0.3)'
|
|
||||||
},
|
|
||||||
theme_black: {
|
|
||||||
// 'default': '#000000'
|
|
||||||
},
|
|
||||||
theme_red: {
|
|
||||||
// 'default': '#e06c75'
|
|
||||||
},
|
|
||||||
theme_brightRed: {
|
|
||||||
// 'default': '#e06c75'
|
|
||||||
},
|
|
||||||
theme_green: {
|
|
||||||
// 'default': '#A4EFA1'
|
|
||||||
},
|
|
||||||
theme_brightGreen: {
|
|
||||||
// 'default': '#A4EFA1'
|
|
||||||
},
|
|
||||||
theme_brightYellow: {
|
|
||||||
// 'default': '#EDDC96'
|
|
||||||
},
|
|
||||||
theme_yellow: {
|
|
||||||
// 'default': '#EDDC96'
|
|
||||||
},
|
|
||||||
theme_magenta: {
|
|
||||||
// 'default': '#e39ef7'
|
|
||||||
},
|
|
||||||
theme_brightMagenta: {
|
|
||||||
// 'default': '#e39ef7'
|
|
||||||
},
|
|
||||||
theme_cyan: {
|
|
||||||
// 'default': '#5fcbd8'
|
|
||||||
},
|
|
||||||
theme_brightBlue: {
|
|
||||||
// 'default': '#5fcbd8'
|
|
||||||
},
|
|
||||||
theme_brightCyan: {
|
|
||||||
// 'default': '#5fcbd8'
|
|
||||||
},
|
|
||||||
theme_blue: {
|
|
||||||
// 'default': '#5fcbd8'
|
|
||||||
},
|
|
||||||
theme_white: {
|
|
||||||
// 'default': '#d0d0d0'
|
|
||||||
},
|
|
||||||
theme_brightBlack: {
|
|
||||||
// 'default': '#808080'
|
|
||||||
},
|
|
||||||
theme_brightWhite: {
|
|
||||||
// 'default': '#ffffff'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AFRAME.registerComponent('xterm', {
|
|
||||||
schema: Object.assign({
|
|
||||||
cols: { type: 'number', default: 110, },
|
|
||||||
rows: { type: 'number', default: Math.floor( (window.innerHeight * 0.7 ) * 0.054 ) },
|
|
||||||
canvasLatency:{ type:'number', default: 200 }
|
|
||||||
}, TERMINAL_THEME),
|
|
||||||
|
|
||||||
write: function(message) {
|
|
||||||
this.term.write(message)
|
|
||||||
},
|
|
||||||
init: function () {
|
|
||||||
const terminalElement = document.createElement('div')
|
|
||||||
terminalElement.setAttribute('style', `
|
|
||||||
width: 800px;
|
|
||||||
height: ${Math.floor( 800 * 0.527 )}px;
|
|
||||||
overflow: hidden;
|
|
||||||
`)
|
|
||||||
|
|
||||||
// setup slightly bigger black backdrop (this.el.getObject3D("mesh"))
|
|
||||||
// and terminal text (this.el.planeText.getObject("mesh"))
|
|
||||||
this.el.setAttribute("geometry",`primitive: box; width:2.07; height:${this.data.rows*5.3/this.data.cols}*2; depth: -0.12`)
|
|
||||||
this.el.setAttribute("material","shader:flat; color:black; opacity:0.5; transparent:true; ")
|
|
||||||
this.el.planeText = document.createElement('a-entity')
|
|
||||||
this.el.planeText.setAttribute("geometry",`primitive: plane; width:2; height:${this.data.rows*5/this.data.cols}*2`)
|
|
||||||
this.el.appendChild(this.el.planeText)
|
|
||||||
|
|
||||||
this.el.terminalElement = terminalElement
|
|
||||||
|
|
||||||
// we switch between dom/canvas rendering because canvas looks pixely in nonimmersive mode
|
|
||||||
this.el.sceneEl.addEventListener('enter-vr', this.enterImmersive.bind(this) )
|
|
||||||
this.el.sceneEl.addEventListener('enter-ar', this.enterImmersive.bind(this) )
|
|
||||||
this.el.sceneEl.addEventListener('exit-vr', this.exitImmersive.bind(this) )
|
|
||||||
this.el.sceneEl.addEventListener('exit-ar', this.exitImmersive.bind(this) )
|
|
||||||
|
|
||||||
// Build up a theme object
|
|
||||||
const theme = Object.keys(this.data).reduce((theme, key) => {
|
|
||||||
if (!key.startsWith('theme_')) return theme
|
|
||||||
const data = this.data[key]
|
|
||||||
if(!data) return theme
|
|
||||||
theme[key.slice('theme_'.length)] = data
|
|
||||||
return theme
|
|
||||||
}, {})
|
|
||||||
|
|
||||||
this.fontSize = 14
|
|
||||||
|
|
||||||
const term = this.term = new Terminal({
|
|
||||||
logLevel:"off",
|
|
||||||
theme: theme,
|
|
||||||
allowTransparency: true,
|
|
||||||
cursorBlink: true,
|
|
||||||
disableStdin: false,
|
|
||||||
rows: this.data.rows,
|
|
||||||
cols: this.data.cols,
|
|
||||||
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')
|
|
||||||
|
|
||||||
terminalElement.querySelector('.xterm-viewport').style.background = 'transparent'
|
|
||||||
|
|
||||||
// now we can scale canvases to the parent element
|
|
||||||
const $screen = terminalElement.querySelector('.xterm-screen')
|
|
||||||
$screen.style.width = '100%'
|
|
||||||
|
|
||||||
term.on('refresh', AFRAME.utils.throttleLeadingAndTrailing( () => this.update(), 150 ) )
|
|
||||||
term.on('data', (data) => {
|
|
||||||
this.el.emit('xterm-input', data)
|
|
||||||
})
|
|
||||||
|
|
||||||
this.el.addEventListener('serial-output-byte', (e) => {
|
|
||||||
const byte = e.detail
|
|
||||||
var chr = String.fromCharCode(byte);
|
|
||||||
this.term.write(chr)
|
|
||||||
})
|
|
||||||
|
|
||||||
this.el.addEventListener('serial-output-string', (e) => {
|
|
||||||
this.term.write(e.detail)
|
|
||||||
})
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
update: function(){
|
|
||||||
if( this.renderType == 'canvas' ){
|
|
||||||
const material = this.el.planeText.getObject3D('mesh').material
|
|
||||||
if (!material.map ) return
|
|
||||||
if( this.cursorCanvas ) this.canvasContext.drawImage(this.cursorCanvas, 0,0)
|
|
||||||
else console.log("no cursorCanvas")
|
|
||||||
material.map.needsUpdate = true
|
|
||||||
//material.needsUpdate = true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
setRenderType: function(type){
|
|
||||||
|
|
||||||
|
|
||||||
if( type.match(/(dom|canvas)/) ){
|
|
||||||
|
|
||||||
if( type == 'dom'){
|
|
||||||
this.el.dom.appendChild(this.el.terminalElement)
|
|
||||||
this.term.setOption('fontSize', this.fontSize )
|
|
||||||
this.term.setOption('rendererType',type )
|
|
||||||
this.renderType = type
|
|
||||||
}
|
|
||||||
|
|
||||||
if( type == 'canvas'){
|
|
||||||
this.el.appendChild(this.el.terminalElement)
|
|
||||||
this.term.setOption('fontSize', this.fontSize * 3 )
|
|
||||||
this.term.setOption('rendererType',type )
|
|
||||||
this.renderType = type
|
|
||||||
this.update()
|
|
||||||
setTimeout( () => {
|
|
||||||
this.canvas = this.el.terminalElement.querySelector('.xterm-text-layer')
|
|
||||||
this.canvas.id = "xterm-canvas"
|
|
||||||
this.canvasContext = this.canvas.getContext('2d')
|
|
||||||
this.cursorCanvas = this.el.terminalElement.querySelector('.xterm-cursor-layer')
|
|
||||||
// Create a texture from the canvas
|
|
||||||
const canvasTexture = new THREE.Texture(this.canvas)
|
|
||||||
//canvasTexture.minFilter = THREE.NearestFilter //LinearFilter
|
|
||||||
//canvasTexture.magFilter = THREE.LinearMipMapLinearFilter //THREE.NearestFilter //LinearFilter
|
|
||||||
canvasTexture.needsUpdate = true; // Ensure the texture updates
|
|
||||||
let plane = this.el.planeText.getObject3D("mesh") //this.el.getObject3D('mesh')
|
|
||||||
if( plane.material ) plane.material.dispose()
|
|
||||||
plane.material = new THREE.MeshBasicMaterial({
|
|
||||||
map: canvasTexture, // Set the texture from the canvas
|
|
||||||
transparent: false, // Set transparency
|
|
||||||
//side: THREE.DoubleSide // Set to double-sided rendering
|
|
||||||
//blending: THREE.AdditiveBlending
|
|
||||||
});
|
|
||||||
this.el.object3D.scale.x = 0.2
|
|
||||||
this.el.object3D.scale.y = 0.2
|
|
||||||
this.el.object3D.scale.z = 0.2
|
|
||||||
},100)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.el.terminalElement.style.opacity = type == 'canvas' ? 0 : 1
|
|
||||||
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
enterImmersive: function(){
|
|
||||||
if( this.mode == 'immersive' ) return
|
|
||||||
this.el.object3D.visible = true
|
|
||||||
this.mode = "immersive"
|
|
||||||
this.setRenderType('canvas')
|
|
||||||
this.term.focus()
|
|
||||||
},
|
|
||||||
|
|
||||||
exitImmersive: function(){
|
|
||||||
if( this.mode == 'nonimmersive' ) return
|
|
||||||
this.el.object3D.visible = false
|
|
||||||
this.mode = "nonimmersive"
|
|
||||||
this.setRenderType('dom')
|
|
||||||
},
|
|
||||||
|
|
||||||
})
|
|
Loading…
Reference in New Issue