feat/webworker: work in progress [might break]
/ test (push) Successful in 5s
Details
/ test (push) Successful in 5s
Details
This commit is contained in:
parent
6b9cd42569
commit
0f3c7a0184
|
@ -35,8 +35,8 @@ 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": 120 },
|
cols: { type: 'number',"default": 80 },
|
||||||
rows: { type: 'number',"default": 30 },
|
rows: { type: 'number',"default": 20 },
|
||||||
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},
|
||||||
|
@ -74,7 +74,7 @@ if( typeof AFRAME != 'undefined '){
|
||||||
},
|
},
|
||||||
|
|
||||||
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>`,
|
</div>`,
|
||||||
|
@ -84,6 +84,10 @@ 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;
|
||||||
|
@ -96,21 +100,6 @@ 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 *,
|
|
||||||
.isotemrinal .xterm-dom-renderer-owner-1 .xterm-rows {
|
|
||||||
background:transparent !important;
|
|
||||||
font-size: 14px;
|
|
||||||
font-family: "Cousine",Liberation Mono,DejaVu Sans Mono,Courier New,monospace;
|
|
||||||
font-weight:500 !important;
|
|
||||||
letter-spacing: 0 !important;
|
|
||||||
text-shadow: 0px 0px 10px #F075;
|
|
||||||
}
|
|
||||||
|
|
||||||
.isoterminal style{ display:none }
|
.isoterminal style{ display:none }
|
||||||
|
|
||||||
|
@ -128,13 +117,28 @@ if( typeof AFRAME != 'undefined '){
|
||||||
.XR .wb-body:has(> .isoterminal){
|
.XR .wb-body:has(> .isoterminal){
|
||||||
background: #000;
|
background: #000;
|
||||||
}
|
}
|
||||||
|
.isoterminal *,
|
||||||
.isoterminal div{ display:block; }
|
.isoterminal .xterm-dom-renderer-owner-1 .xterm-rows {
|
||||||
.isoterminal span{ display: inline }
|
background:transparent !important;
|
||||||
|
font-size: 14px;
|
||||||
|
font-family: "Cousine",Liberation Mono,DejaVu Sans Mono,Courier New,monospace;
|
||||||
|
font-weight:500 !important;
|
||||||
|
text-shadow: 0px 0px 10px #F075;
|
||||||
|
}
|
||||||
|
.isoterminal .xterm-rows.xterm-focus .xterm-cursor.xterm-cursor-block {
|
||||||
|
background-color:#a5F !important;
|
||||||
|
}
|
||||||
|
.isoterminal .xterm-rows div{
|
||||||
|
height:8px;
|
||||||
|
height:18px;
|
||||||
|
}
|
||||||
|
.isoterminal .xterm-rows span{
|
||||||
|
width:8px;
|
||||||
|
}
|
||||||
.isoterminal .xterm-helpers {
|
.isoterminal .xterm-helpers {
|
||||||
position:absolute;
|
position:absolute;
|
||||||
opacity:0;
|
opacity:0;
|
||||||
|
top: -2000px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes fade {
|
@keyframes fade {
|
||||||
|
@ -149,6 +153,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;
|
||||||
|
@ -162,6 +167,7 @@ if( typeof AFRAME != 'undefined '){
|
||||||
if( this.data.xterm ){
|
if( this.data.xterm ){
|
||||||
// why 3.12?
|
// why 3.12?
|
||||||
// first versions used 1.5.4, a typescript rewrite which:
|
// first versions used 1.5.4, a typescript rewrite which:
|
||||||
|
// * acts weird with oculus browser keyboard (does not repaint properly after typing)
|
||||||
// * does not use canvas anymore [which would be ideal for THREE.js texture]
|
// * does not use canvas anymore [which would be ideal for THREE.js texture]
|
||||||
// * does not allow switching between dom/canvas
|
// * does not allow switching between dom/canvas
|
||||||
// * only allows a standalone WebGL addon (conflicts with THREE)
|
// * only allows a standalone WebGL addon (conflicts with THREE)
|
||||||
|
@ -177,8 +183,8 @@ if( typeof AFRAME != 'undefined '){
|
||||||
boot: "com/isoterminal/feat/boot.js",
|
boot: "com/isoterminal/feat/boot.js",
|
||||||
javascript: "com/isoterminal/feat/javascript.js",
|
javascript: "com/isoterminal/feat/javascript.js",
|
||||||
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",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -202,6 +208,7 @@ 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 = this.data.xterm ? 'width: 1024px; height:600px'
|
let size = this.data.xterm ? 'width: 1024px; height:600px'
|
||||||
: 'width: 720px; height:455px'
|
: 'width: 720px; height:455px'
|
||||||
|
@ -210,7 +217,7 @@ if( typeof AFRAME != 'undefined '){
|
||||||
|
|
||||||
instance.addEventListener('window.oncreate', (e) => {
|
instance.addEventListener('window.oncreate', (e) => {
|
||||||
instance.dom.classList.add('blink')
|
instance.dom.classList.add('blink')
|
||||||
instance.setAttribute("xterm","")
|
instance.setAttribute("xterm",`cols: ${this.data.cols}; rows: ${this.data.rows}`)
|
||||||
instance.addEventListener("xterm-input", (e) => this.isoterminal.send(e.detail,0) )
|
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}
|
||||||
|
@ -255,7 +262,7 @@ if( typeof AFRAME != 'undefined '){
|
||||||
this.el.components.xterm.term.focus()
|
this.el.components.xterm.term.focus()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
instance.addEventListener('obbcollisionstarted', focus )
|
//instance.addEventListener('obbcollisionstarted', focus )
|
||||||
|
|
||||||
this.el.sceneEl.addEventListener('enter-vr', focus )
|
this.el.sceneEl.addEventListener('enter-vr', focus )
|
||||||
this.el.sceneEl.addEventListener('enter-ar', focus )
|
this.el.sceneEl.addEventListener('enter-ar', focus )
|
||||||
|
|
|
@ -1,23 +1,53 @@
|
||||||
//ISOTerminal.prototype.exec(cmd_array,stdin){
|
function ISOTerminal(instance,opts){
|
||||||
// // exec(['lua'] "print \"hello\") ---> cat /dev/browser/js/stdin | lua > /dev/browser/js/stdout
|
// 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.serial_input = undefined; // can be set to 0,1,2,3 to define stdinput tty (xterm plugin)
|
ISOTerminal.prototype.emit = function(event,data,sender){
|
||||||
|
data = data || false
|
||||||
|
const evObj = new CustomEvent(event, {detail: data} )
|
||||||
|
// 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})
|
||||||
|
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){
|
ISOTerminal.prototype.exec = function(shellscript){
|
||||||
//let ts = String(Date.now())+".job"
|
console.log("exec:"+shellscript)
|
||||||
//this.emulator.create_file(ts, this.toUint8Array(shellscript) )
|
|
||||||
this.send(shellscript+"\n",1)
|
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){
|
ISOTerminal.prototype.send = function(str, ttyNr){
|
||||||
if( !ttyNr ) ttyNr = this.serial_input
|
if( ttyNr == undefined) ttyNr = this.serial_input
|
||||||
if( !ttyNr ){
|
if( ttyNr == undefined ){
|
||||||
if( this.emulator.serial_adapter ){
|
if( this.emulator.serial_adapter ){
|
||||||
this.emulator.serial_adapter.term.paste(str)
|
this.emulator.serial_adapter.term.paste(str)
|
||||||
}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.emulator.bus.send(`serial${ttyNr}-input`, c ) )
|
this.convert.toUint8Array( str ).map( (c) => {
|
||||||
|
this.worker.postMessage({event:`serial${ttyNr}-input`,data:c})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,7 +90,7 @@ ISOTerminal.prototype.convert = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ISOTerminal.prototype.runISO = function(opts){
|
ISOTerminal.prototype.start = function(opts){
|
||||||
|
|
||||||
let me = this
|
let me = this
|
||||||
this.opts = {...this.opts, ...opts}
|
this.opts = {...this.opts, ...opts}
|
||||||
|
@ -72,16 +102,16 @@ ISOTerminal.prototype.runISO = function(opts){
|
||||||
uart1:true, // /dev/ttyS1
|
uart1:true, // /dev/ttyS1
|
||||||
uart2:true, // /dev/ttyS2
|
uart2:true, // /dev/ttyS2
|
||||||
uart3:true, // /dev/ttyS3
|
uart3:true, // /dev/ttyS3
|
||||||
wasm_path: "com/isoterminal/v86.wasm",
|
wasm_path: "v86.wasm",
|
||||||
memory_size: opts.memory * 1024 * 1024,
|
memory_size: opts.memory * 1024 * 1024,
|
||||||
vga_memory_size: 2 * 1024 * 1024,
|
vga_memory_size: 2 * 1024 * 1024,
|
||||||
screen_container: opts.dom,
|
//screen_container: opts.dom,
|
||||||
//serial_container: opts.dom,
|
//serial_container: opts.dom,
|
||||||
bios: {
|
bios: {
|
||||||
url: "com/isoterminal/bios/seabios.bin",
|
url: "bios/seabios.bin",
|
||||||
},
|
},
|
||||||
vga_bios: {
|
vga_bios: {
|
||||||
url: "com/isoterminal/bios/vgabios.bin",
|
url: "bios/vgabios.bin",
|
||||||
//urg|: "com/isoterminal/bios/VGABIOS-lgpl-latest.bin",
|
//urg|: "com/isoterminal/bios/VGABIOS-lgpl-latest.bin",
|
||||||
},
|
},
|
||||||
network_relay_url: "wss://relay.widgetry.org/",
|
network_relay_url: "wss://relay.widgetry.org/",
|
||||||
|
@ -96,9 +126,31 @@ ISOTerminal.prototype.runISO = function(opts){
|
||||||
filesystem: {},
|
filesystem: {},
|
||||||
autostart: true,
|
autostart: true,
|
||||||
};
|
};
|
||||||
this.emit('runISO',opts)
|
|
||||||
let emulator = this.emulator = new V86(opts)
|
|
||||||
|
|
||||||
|
this.worker = new Worker("com/isoterminal/worker.js");
|
||||||
|
this.worker.onmessage = (e) => {
|
||||||
|
const {event,data} = e.data
|
||||||
|
this.emit(event,data,"worker")
|
||||||
|
}
|
||||||
|
|
||||||
|
//this.term = new window.Terminal({
|
||||||
|
// logLevel:"off",
|
||||||
|
// rows: 50,
|
||||||
|
// cols: 110
|
||||||
|
//})
|
||||||
|
//this.term.open( this.instance.dom )
|
||||||
|
//this.term.onData( (data) => {
|
||||||
|
// for(let i = 0; i < data.length; i++){
|
||||||
|
// this.worker.postMessage({event:"serial0-input", data: data.charCodeAt(i) })
|
||||||
|
// }
|
||||||
|
//})
|
||||||
|
|
||||||
|
//this.instance.addEventListener('serial-output-byte', (e) => {
|
||||||
|
// const byte = e.detail
|
||||||
|
// this.term.write(byte)
|
||||||
|
//})
|
||||||
|
|
||||||
|
this.emit('runISO',opts)
|
||||||
const loading = [
|
const loading = [
|
||||||
'loading quantum bits and bytes',
|
'loading quantum bits and bytes',
|
||||||
'preparing quantum flux capacitors',
|
'preparing quantum flux capacitors',
|
||||||
|
@ -123,71 +175,45 @@ ISOTerminal.prototype.runISO = function(opts){
|
||||||
'Transcending earthly limits'
|
'Transcending earthly limits'
|
||||||
]
|
]
|
||||||
|
|
||||||
let loadmsg = loading[ Math.floor(Math.random()*1000) % loading.length ]
|
const loadmsg = loading[ Math.floor(Math.random()*1000) % loading.length ] + "..(please wait..)"
|
||||||
|
const text_color = "\r[38;5;129m"
|
||||||
|
const text_reset = "\033[0m"
|
||||||
this.emit('status',loadmsg)
|
this.emit('status',loadmsg)
|
||||||
|
this.emit('serial-output-string', text_color + loadmsg + text_reset + "\n\r")
|
||||||
// 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 ){
|
this.addEventListener('emulator-started', async (e) => {
|
||||||
fetch(me.opts.overlayfs)
|
|
||||||
.then( (f) => {
|
// OVERLAY FS *FIXME*
|
||||||
f.arrayBuffer().then( (buf) => {
|
//if( me.opts.overlayfs ){
|
||||||
emulator.create_file('overlayfs.zip', new Uint8Array(buf) )
|
// fetch(me.opts.overlayfs)
|
||||||
})
|
// .then( (f) => {
|
||||||
})
|
// f.arrayBuffer().then( (buf) => {
|
||||||
}
|
// emulator.create_file('overlayfs.zip', new Uint8Array(buf) )
|
||||||
|
// })
|
||||||
|
// })
|
||||||
|
//}
|
||||||
|
|
||||||
let line = ''
|
let line = ''
|
||||||
let ready = false
|
let ready = false
|
||||||
emulator.add_listener(`serial0-output-byte`, async (byte) => {
|
this.addEventListener(`serial0-output-byte`, async (e) => {
|
||||||
this.emit('${this.serial}-output-byte',byte)
|
this.emit("serial-output-byte",e.detail) // send to xterm
|
||||||
|
const byte = e.detail
|
||||||
var chr = String.fromCharCode(byte);
|
var chr = String.fromCharCode(byte);
|
||||||
if(chr < " " && chr !== "\n" && chr !== "\t" || chr > "~")
|
if(chr < " " && chr !== "\n" && chr !== "\t" || chr > "~") return
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(chr === "\n")
|
if(chr === "\n")
|
||||||
{
|
{
|
||||||
var new_line = line;
|
var new_line = line;
|
||||||
line = "";
|
line = "";
|
||||||
}
|
}
|
||||||
else if(chr >= " " && chr <= "~")
|
else if(chr >= " " && chr <= "~"){ line += chr }
|
||||||
{
|
|
||||||
line += chr;
|
|
||||||
}
|
|
||||||
if( !ready && line.match(/^(\/ #|~%|\[.*\]>)/) ){
|
if( !ready && line.match(/^(\/ #|~%|\[.*\]>)/) ){
|
||||||
this.emit('postReady',e)
|
this.emit('postReady',{})
|
||||||
setTimeout( () => this.emit('ready',e), 500 )
|
setTimeout( () => this.emit('ready',{}), 500 )
|
||||||
ready = true
|
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,4 +1,5 @@
|
||||||
let emulator = this.emulator
|
let emulator = this.emulator
|
||||||
|
let me = this
|
||||||
|
|
||||||
emulator.fs9p.update_file = async function(file,data){
|
emulator.fs9p.update_file = async function(file,data){
|
||||||
const convert = ISOTerminal.prototype.convert
|
const convert = ISOTerminal.prototype.convert
|
||||||
|
@ -17,7 +18,7 @@ emulator.fs9p.update_file = async function(file,data){
|
||||||
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;
|
||||||
this.postMessage({event:'exec',data:[`touch ${file}`]}) // update inode
|
me.postMessage({event:'exec',data:[`touch /mnt/${file}`]}) // update inode
|
||||||
return new Promise( (resolve,reject) => resolve(buf) )
|
return new Promise( (resolve,reject) => resolve(buf) )
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,24 +1,22 @@
|
||||||
ISOTerminal.addEventListener('init', function(){
|
|
||||||
|
|
||||||
this.addEventListener('emulator-started', function(e){
|
if( typeof emulator != 'undefined' ){
|
||||||
|
// inside worker-thread
|
||||||
const emulator = this.emulator
|
|
||||||
|
|
||||||
// unix to js device
|
// unix to js device
|
||||||
this.readFromPipe( '/mnt/index.html', async (data) => {
|
this.emulator.readFromPipe( 'root/index.html', async (data) => {
|
||||||
const buf = await emulator.read_file("index.html")
|
const buf = await emulator.read_file("root/index.html")
|
||||||
const decoder = new TextDecoder('utf-8');
|
const decoder = new TextDecoder('utf-8');
|
||||||
const html = decoder.decode(buf)
|
const html = decoder.decode(buf).replace(/^#!\/bin\/html/,'') // remove leftover shebangs if any
|
||||||
try{
|
try{
|
||||||
this.runHTML(html)
|
this.postMessage({event:'runHTML',data:[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")
|
||||||
|
@ -30,3 +28,5 @@ ISOTerminal.prototype.runHTML = function(html){
|
||||||
}
|
}
|
||||||
$root.innerHTML = html
|
$root.innerHTML = html
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -1,24 +1,21 @@
|
||||||
ISOTerminal.addEventListener('init', function(){
|
if( typeof emulator != 'undefined' ){
|
||||||
|
// inside worker-thread
|
||||||
this.addEventListener('emulator-started', function(e){
|
|
||||||
|
|
||||||
const emulator = this.emulator
|
|
||||||
|
|
||||||
// unix to js device
|
// unix to js device
|
||||||
this.readFromPipe( '/mnt/index.js', async (data) => {
|
this.emulator.readFromPipe( 'root/index.js', async (data) => {
|
||||||
const buf = await emulator.read_file("index.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)
|
const js = decoder.decode(buf).replace(/^#!\/bin\/js/,'') // remove leftover shebangs if any
|
||||||
try{
|
try{
|
||||||
this.runJavascript(js)
|
this.postMessage({event:'runJavascript',data:[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")
|
||||||
|
@ -30,3 +27,4 @@ ISOTerminal.prototype.runJavascript = function(js){
|
||||||
$root.innerHTML = js
|
$root.innerHTML = js
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -24,9 +24,8 @@ if( typeof emulator != 'undefined' ){
|
||||||
const {script,PID} = e.detail
|
const {script,PID} = e.detail
|
||||||
let res = (new Function(`${script}`))()
|
let res = (new Function(`${script}`))()
|
||||||
if( res && typeof res != 'string' ) res = JSON.stringify(res,null,2)
|
if( res && typeof res != 'string' ) res = JSON.stringify(res,null,2)
|
||||||
// write output to 9p with PID as filename
|
// update output to 9p with PID as filename (in /mnt/run)
|
||||||
// *FIXME* not flexible / robust
|
this.emit('fs9p.update_file', [`run/${PID}`, this.convert.toUint8Array(res)] )
|
||||||
this.emit('emulator.create_file', [PID, this.convert.toUint8Array(res)] )
|
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
@ -30,17 +30,27 @@ importScripts("ISOTerminal.js") // we don't instance it again here (just use it
|
||||||
//};
|
//};
|
||||||
|
|
||||||
this.runISO = function(opts){
|
this.runISO = function(opts){
|
||||||
if( opts.cdrom ) opts.cdrom.url = "../../"+opts.cdrom.url
|
if( opts.cdrom && !opts.cdrom.url.match(/^http/) ) opts.cdrom.url = "../../"+opts.cdrom.url
|
||||||
if( opts.bzimage ) 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:started emulator")
|
console.log("worker:started emulator")
|
||||||
|
|
||||||
// event forwarding
|
// event forwarding
|
||||||
|
|
||||||
emulator.add_listener("serial0-output-byte", function(byte){
|
emulator.add_listener("serial0-output-byte", function(byte){
|
||||||
this.postMessage({event:"serial0-output-byte",data:byte});
|
this.postMessage({event:"serial0-output-byte",data:byte});
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
|
|
||||||
|
emulator.add_listener("serial1-output-byte", function(byte){
|
||||||
|
this.postMessage({event:"serial1-output-byte",data:byte});
|
||||||
|
}.bind(this));
|
||||||
|
|
||||||
|
emulator.add_listener("serial2-output-byte", function(byte){
|
||||||
|
this.postMessage({event:"serial2-output-byte",data:byte});
|
||||||
|
}.bind(this));
|
||||||
|
|
||||||
emulator.add_listener("emulator-started", function(){
|
emulator.add_listener("emulator-started", function(){
|
||||||
importScripts("feat/9pfs_utils.js")
|
importScripts("feat/9pfs_utils.js")
|
||||||
this.postMessage({event:"emulator-started",data:false});
|
this.postMessage({event:"emulator-started",data:false});
|
||||||
|
@ -52,7 +62,6 @@ this.runISO = function(opts){
|
||||||
this['emulator.create_file'] = function(){ emulator.create_file.apply(emulator, arguments[0]) }
|
this['emulator.create_file'] = function(){ emulator.create_file.apply(emulator, arguments[0]) }
|
||||||
this['emulator.read_file'] = function(){ emulator.read_file.apply(emulator, arguments[0]) }
|
this['emulator.read_file'] = function(){ emulator.read_file.apply(emulator, 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){
|
||||||
emulator.add_listener("9p-write-end", async (opts) => {
|
emulator.add_listener("9p-write-end", async (opts) => {
|
||||||
|
@ -62,15 +71,16 @@ this.runISO = function(opts){
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
importScripts("feat/javascript.js")
|
//importScripts("feat/javascript.js")
|
||||||
|
//importScripts("feat/index.html.js")
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
* 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){
|
this['serial0-input'] = function(c){ this.emulator.bus.send( 'serial0-input', c) } // to /dev/ttyS0
|
||||||
this.emulator.bus.send( 'serial0-input', c)
|
this['serial1-input'] = function(c){ this.emulator.bus.send( 'serial1-input', c) } // to /dev/ttyS1
|
||||||
}
|
this['serial2-input'] = function(c){ this.emulator.bus.send( 'serial2-input', c) } // to /dev/ttyS2
|
||||||
|
|
||||||
this.onmessage = function(e){
|
this.onmessage = function(e){
|
||||||
let {event,data} = e.data
|
let {event,data} = e.data
|
||||||
|
|
152
com/xterm.js
152
com/xterm.js
|
@ -1,3 +1,30 @@
|
||||||
|
/*
|
||||||
|
* 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
|
let terminalInstance = 0
|
||||||
|
|
||||||
const TERMINAL_THEME = {
|
const TERMINAL_THEME = {
|
||||||
|
@ -86,10 +113,16 @@ AFRAME.registerComponent('xterm', {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
`)
|
`)
|
||||||
|
|
||||||
this.el.dom.appendChild(terminalElement)
|
this.el.setAttribute("geometry",`primitive: plane; width:2; height:${this.data.rows*5/this.data.cols}*2`)
|
||||||
//document.body.appendChild(terminalElement)
|
|
||||||
this.el.terminalElement = terminalElement
|
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
|
// Build up a theme object
|
||||||
const theme = Object.keys(this.data).reduce((theme, key) => {
|
const theme = Object.keys(this.data).reduce((theme, key) => {
|
||||||
if (!key.startsWith('theme_')) return theme
|
if (!key.startsWith('theme_')) return theme
|
||||||
|
@ -99,9 +132,7 @@ AFRAME.registerComponent('xterm', {
|
||||||
return theme
|
return theme
|
||||||
}, {})
|
}, {})
|
||||||
|
|
||||||
this.renderType = 'dom'
|
const term = this.term = new Terminal({
|
||||||
|
|
||||||
const term = new Terminal({
|
|
||||||
theme: theme,
|
theme: theme,
|
||||||
allowTransparency: true,
|
allowTransparency: true,
|
||||||
cursorBlink: true,
|
cursorBlink: true,
|
||||||
|
@ -110,66 +141,123 @@ AFRAME.registerComponent('xterm', {
|
||||||
cols: this.data.cols,
|
cols: this.data.cols,
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
lineHeight: 1.15,
|
lineHeight: 1.15,
|
||||||
rendererType: this.renderType
|
rendererType: this.renderType // 'dom' // 'canvas'
|
||||||
})
|
})
|
||||||
|
|
||||||
this.term = term
|
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()
|
||||||
|
}
|
||||||
|
},150)
|
||||||
|
|
||||||
term.open(terminalElement)
|
this.term.open(terminalElement)
|
||||||
term.focus()
|
this.term.focus()
|
||||||
|
this.setRenderType('dom')
|
||||||
|
|
||||||
|
const refresh = term._core.renderer._renderDebouncer.refresh
|
||||||
|
let scene = this.el.sceneEl
|
||||||
|
term._core.renderer._renderDebouncer.refresh = function(){
|
||||||
|
refresh.apply(this,arguments)
|
||||||
|
if( scene.renderer.xr.isPresenting ){
|
||||||
|
this._innerRefresh()
|
||||||
|
}
|
||||||
|
}.bind(term._core.renderer._renderDebouncer)
|
||||||
|
|
||||||
terminalElement.querySelector('.xterm-viewport').style.background = 'transparent'
|
terminalElement.querySelector('.xterm-viewport').style.background = 'transparent'
|
||||||
|
|
||||||
////// now we can scale canvases to the parent element
|
// now we can scale canvases to the parent element
|
||||||
const $screen = terminalElement.querySelector('.xterm-screen')
|
const $screen = terminalElement.querySelector('.xterm-screen')
|
||||||
$screen.style.width = '100%'
|
$screen.style.width = '100%'
|
||||||
|
|
||||||
term.on('refresh', () => {
|
term.on('refresh', AFRAME.utils.throttle( () => this.update(), 150 ) )
|
||||||
if( this.renderType == 'canvas' ){
|
|
||||||
const material = this.el.getObject3D('mesh').material
|
|
||||||
if (!material.map) return
|
|
||||||
this.canvasContext.drawImage(this.cursorCanvas, 0,0)
|
|
||||||
material.map.needsUpdate = true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
term.on('data', (data) => {
|
term.on('data', (data) => {
|
||||||
this.el.emit('xterm-input', data)
|
this.el.emit('xterm-input', data)
|
||||||
})
|
})
|
||||||
|
|
||||||
this.el.addEventListener('click', () => {
|
|
||||||
term.focus()
|
|
||||||
})
|
|
||||||
|
|
||||||
this.el.addEventListener('serial-output-byte', (e) => {
|
this.el.addEventListener('serial-output-byte', (e) => {
|
||||||
const byte = e.detail
|
const byte = e.detail
|
||||||
var chr = String.fromCharCode(byte);
|
var chr = String.fromCharCode(byte);
|
||||||
this.term.write(chr)
|
this.term.write(chr)
|
||||||
})
|
})
|
||||||
|
|
||||||
this.el.addEventListener('serial-output-string', (e) => this.term.write(e.detail) )
|
this.el.addEventListener('serial-output-string', (e) => {
|
||||||
|
this.term.write(e.detail)
|
||||||
|
})
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
update: function(){
|
||||||
|
if( this.renderType == 'canvas' ){
|
||||||
|
const material = this.el.getObject3D('mesh').material
|
||||||
|
if (!material.map ) return
|
||||||
|
if( this.cursorCanvas ) this.canvasContext.drawImage(this.cursorCanvas, 0,0)
|
||||||
|
material.map.needsUpdate = true
|
||||||
|
//material.needsUpdate = true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
setRenderType: function(type){
|
setRenderType: function(type){
|
||||||
|
|
||||||
|
|
||||||
if( type.match(/(dom|canvas)/) ){
|
if( type.match(/(dom|canvas)/) ){
|
||||||
|
|
||||||
if( type == 'dom'){
|
if( type == 'dom'){
|
||||||
this.el.removeAttribute('material')
|
this.el.dom.appendChild(this.el.terminalElement)
|
||||||
}
|
this.term.setOption('fontSize', 14 )
|
||||||
|
this.term.setOption('rendererType',type )
|
||||||
term.setOption('rendererType',type )
|
|
||||||
this.renderType = type
|
this.renderType = type
|
||||||
|
}
|
||||||
|
|
||||||
if( type == 'canvas'){
|
if( type == 'canvas'){
|
||||||
this.canvas = terminalElement.querySelector('.xterm-text-layer')
|
this.el.appendChild(this.el.terminalElement)
|
||||||
|
this.term.setOption('fontSize', 48 )
|
||||||
|
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.canvasContext = this.canvas.getContext('2d')
|
||||||
this.cursorCanvas = terminalElement.querySelector('.xterm-cursor-layer')
|
this.cursorCanvas = this.el.terminalElement.querySelector('.xterm-cursor-layer')
|
||||||
this.el.setAttribute('material', 'transparent', true)
|
// Create a texture from the canvas
|
||||||
this.el.setAttribute('material', 'src', '#' + this.canvas.id)
|
const canvasTexture = new THREE.Texture(this.canvas)
|
||||||
|
//canvasTexture.minFilter = THREE.LinearFilter
|
||||||
|
//canvasTexture.magFilter = THREE.LinearFilter
|
||||||
|
canvasTexture.needsUpdate = true; // Ensure the texture updates
|
||||||
|
let plane = 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
|
||||||
|
});
|
||||||
|
this.el.getObject3D('mesh').scale.x = 0.3
|
||||||
|
this.el.getObject3D('mesh').scale.y = 0.3
|
||||||
|
this.el.getObject3D('mesh').scale.z = 0.3
|
||||||
|
},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