Compare commits
5 Commits
0bec71ab55
...
17e07b0834
Author | SHA1 | Date |
---|---|---|
Leon van Kammen | 17e07b0834 | |
Leon van Kammen | 8369dde488 | |
Leon van Kammen | f75f34d6a6 | |
Leon van Kammen | 10c274fdd4 | |
Leon van Kammen | 7c34805952 |
|
@ -0,0 +1,159 @@
|
||||||
|
if( AFRAME.components.codemirror ) delete AFRAME.components.codemirror
|
||||||
|
|
||||||
|
AFRAME.registerComponent('codemirror', {
|
||||||
|
schema: {
|
||||||
|
file: { type:"string"},
|
||||||
|
term: { type:"selector", default: "[isoterminal]" },
|
||||||
|
},
|
||||||
|
|
||||||
|
init: function () {
|
||||||
|
this.el.object3D.visible = false
|
||||||
|
if( !this.data.term || !this.data.term.components ) throw 'codemirror cannot get isoterminal'
|
||||||
|
if( this.data.file && this.data.file[0] != '/'){
|
||||||
|
this.data.file = "root/"+this.data.file
|
||||||
|
}
|
||||||
|
this.isoterminal = this.data.term.components.isoterminal.isoterminal
|
||||||
|
//this.el.innerHTML = ` `
|
||||||
|
this.requireAll()
|
||||||
|
},
|
||||||
|
|
||||||
|
requireAll: async function(){
|
||||||
|
let s = await AFRAME.utils.require(this.requires)
|
||||||
|
setTimeout( () => this.el.setAttribute("dom",""), 300 )
|
||||||
|
},
|
||||||
|
|
||||||
|
requires:{
|
||||||
|
window: "com/window.js"
|
||||||
|
},
|
||||||
|
|
||||||
|
dom: {
|
||||||
|
scale: 0.5,
|
||||||
|
events: ['click','keydown'],
|
||||||
|
html: (me) => `<div class="codemirror">
|
||||||
|
</div>`,
|
||||||
|
|
||||||
|
css: (me) => `.codemirror{
|
||||||
|
width:100%;
|
||||||
|
}
|
||||||
|
.codemirror *{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
.wb-body + .codemirror{ overflow:hidden; }
|
||||||
|
.CodeMirror {
|
||||||
|
margin-top:18px;
|
||||||
|
}
|
||||||
|
.cm-s-shadowfox.CodeMirror {
|
||||||
|
background:transparent !important;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
},
|
||||||
|
|
||||||
|
createEditor: function(value){
|
||||||
|
this.el.setAttribute("window", `title: codemirror; uid: ${this.el.dom.id}; attach: #overlay; dom: #${this.el.dom.id};`)
|
||||||
|
this.editor = CodeMirror( this.el.dom, {
|
||||||
|
value,
|
||||||
|
mode: "htmlmixed",
|
||||||
|
lineNumbers: true,
|
||||||
|
styleActiveLine: true,
|
||||||
|
matchBrackets: true,
|
||||||
|
Tab: "indentMore",
|
||||||
|
defaultTab: function(cm) {
|
||||||
|
if (cm.somethingSelected()) cm.indentSelection("add");
|
||||||
|
else cm.replaceSelection(" ", "end");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.editor.setOption("theme", "shadowfox")
|
||||||
|
this.editor.updateFile = AFRAME.utils.throttleLeadingAndTrailing( (file,str) => {
|
||||||
|
this.updateFile(file,str),
|
||||||
|
2000
|
||||||
|
})
|
||||||
|
this.editor.on('change', (instance,changeObj) => {
|
||||||
|
this.editor.updateFile( this.data.file, instance.getValue() )
|
||||||
|
})
|
||||||
|
|
||||||
|
setTimeout( () => {
|
||||||
|
this.el.setAttribute("html-as-texture-in-xr", `domid: #${this.el.dom.id}`) // only show aframe-html in xr
|
||||||
|
},1500)
|
||||||
|
},
|
||||||
|
|
||||||
|
updateFile: async function(file,str){
|
||||||
|
// we don't do via shellcmd: isoterminal.exec(`echo '${str}' > ${file}`,1)
|
||||||
|
// as it would require all kindof ugly stringescaping
|
||||||
|
console.log("updating "+file)
|
||||||
|
await this.isoterminal.emulator.fs9p.update_file( file, str)
|
||||||
|
},
|
||||||
|
|
||||||
|
events:{
|
||||||
|
|
||||||
|
// component events
|
||||||
|
DOMready: function(e){
|
||||||
|
this.isoterminal.emulator.read_file( this.data.file )
|
||||||
|
.then( this.isoterminal.convert.Uint8ArrayToString )
|
||||||
|
.then( (str) => {
|
||||||
|
this.createEditor( str )
|
||||||
|
})
|
||||||
|
.catch( (e) => {
|
||||||
|
console.log("error opening "+this.data.file+", creating new one")
|
||||||
|
this.createEditor("")
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
manifest: { // HTML5 manifest to identify app to xrsh
|
||||||
|
"short_name": "Paste",
|
||||||
|
"name": "Paste",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "https://css.gg/clipboard.svg",
|
||||||
|
"type": "image/svg+xml",
|
||||||
|
"sizes": "512x512"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": "/?source=pwa",
|
||||||
|
"start_url": "/?source=pwa",
|
||||||
|
"background_color": "#3367D6",
|
||||||
|
"display": "standalone",
|
||||||
|
"scope": "/",
|
||||||
|
"theme_color": "#3367D6",
|
||||||
|
"shortcuts": [
|
||||||
|
{
|
||||||
|
"name": "What is the latest news?",
|
||||||
|
"cli":{
|
||||||
|
"usage": "helloworld <type> [options]",
|
||||||
|
"example": "helloworld news",
|
||||||
|
"args":{
|
||||||
|
"--latest": {type:"string"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"short_name": "Today",
|
||||||
|
"description": "View weather information for today",
|
||||||
|
"url": "/today?source=pwa",
|
||||||
|
"icons": [{ "src": "/images/today.png", "sizes": "192x192" }]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Paste the clipboard",
|
||||||
|
"screenshots": [
|
||||||
|
{
|
||||||
|
"src": "/images/screenshot1.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "540x720",
|
||||||
|
"form_factor": "narrow"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"help":`
|
||||||
|
Helloworld application
|
||||||
|
|
||||||
|
This is a help file which describes the application.
|
||||||
|
It will be rendered thru troika text, and will contain
|
||||||
|
headers based on non-punctualized lines separated by linebreaks,
|
||||||
|
in above's case "\nHelloworld application\n" will qualify as header.
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
13
com/dom.js
13
com/dom.js
|
@ -36,6 +36,10 @@ if( !AFRAME.components.dom ){
|
||||||
|
|
||||||
AFRAME.registerComponent('dom',{
|
AFRAME.registerComponent('dom',{
|
||||||
|
|
||||||
|
requires: {
|
||||||
|
"requestAnimationFrameXR": "com/requestAnimationFrameXR.js"
|
||||||
|
},
|
||||||
|
|
||||||
init: function(){
|
init: function(){
|
||||||
Object.values(this.el.components)
|
Object.values(this.el.components)
|
||||||
.map( (c) => {
|
.map( (c) => {
|
||||||
|
@ -127,12 +131,9 @@ if( !AFRAME.components.dom ){
|
||||||
return this
|
return this
|
||||||
},
|
},
|
||||||
|
|
||||||
stubRequestAnimationFrame: function(){
|
stubRequestAnimationFrame: async function(){
|
||||||
// stub, because WebXR with overrule this (it will not call the callback as expected in immersive mode)
|
let s = await AFRAME.utils.require(this.requires)
|
||||||
const requestAnimationFrame = window.requestAnimationFrame
|
this.el.setAttribute("requestAnimationFrameXR","")
|
||||||
window.requestAnimationFrame = (cb) => {
|
|
||||||
setTimeout( cb, 25 )
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
if( !AFRAME.components['html-as-textre-in-xr'] ){
|
if( !AFRAME.components['html-as-texture-in-xr'] ){
|
||||||
|
|
||||||
AFRAME.registerComponent('html-as-texture-in-xr', {
|
AFRAME.registerComponent('html-as-texture-in-xr', {
|
||||||
schema: {
|
schema: {
|
||||||
|
@ -12,6 +12,10 @@ if( !AFRAME.components['html-as-textre-in-xr'] ){
|
||||||
},
|
},
|
||||||
|
|
||||||
init: async function () {
|
init: async function () {
|
||||||
|
let el = document.querySelector(this.data.domid)
|
||||||
|
if( ! el ){
|
||||||
|
return console.error("html-as-texture-in-xr: cannot get dom element "+this.data.dom.id)
|
||||||
|
}
|
||||||
let s = await AFRAME.utils.require(this.dependencies)
|
let s = await AFRAME.utils.require(this.dependencies)
|
||||||
this.el.setAttribute("html",`html: ${this.data.domid}; cursor:#cursor; xrlayer: true`)
|
this.el.setAttribute("html",`html: ${this.data.domid}; cursor:#cursor; xrlayer: true`)
|
||||||
this.el.setAttribute("visible", AFRAME.utils.XD() == '3D' ? 'true' : 'false' )
|
this.el.setAttribute("visible", AFRAME.utils.XD() == '3D' ? 'true' : 'false' )
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
function ISOTerminal(){
|
function ISOTerminal(instance,opts){
|
||||||
// create a neutral isoterminal object which can be decorated
|
// create a neutral isoterminal object which can be decorated
|
||||||
// with prototype functions and has addListener() and dispatchEvent()
|
// with prototype functions and has addListener() and dispatchEvent()
|
||||||
let obj = new EventTarget()
|
let obj = new EventTarget()
|
||||||
|
obj.instance = instance
|
||||||
|
obj.opts = opts
|
||||||
// register default event listeners (enable file based features like isoterminal/jsconsole.js e.g.)
|
// register default event listeners (enable file based features like isoterminal/jsconsole.js e.g.)
|
||||||
for( let event in ISOTerminal.listener )
|
for( let event in ISOTerminal.listener )
|
||||||
for( let cb in ISOTerminal.listener[event] )
|
for( let cb in ISOTerminal.listener[event] )
|
||||||
|
@ -30,23 +32,35 @@ if( typeof AFRAME != 'undefined '){
|
||||||
|
|
||||||
AFRAME.registerComponent('isoterminal', {
|
AFRAME.registerComponent('isoterminal', {
|
||||||
schema: {
|
schema: {
|
||||||
iso: { type:"string", "default":"com/isoterminal/images/buildroot-bzimage.bin" },
|
iso: { type:"string", "default":"https://forgejo.isvery.ninja/assets/xrsh-buildroot/main/xrsh.iso" },
|
||||||
cols: { type: 'number',"default": 120 },
|
overlayfs: { type:"string"},
|
||||||
rows: { type: 'number',"default": 30 },
|
cols: { type: 'number',"default": 120 },
|
||||||
padding:{ type: 'number',"default": 18 },
|
rows: { type: 'number',"default": 30 },
|
||||||
transparent: { type:'boolean', "default":false } // need good gpu
|
padding: { type: 'number',"default": 18 },
|
||||||
|
minimized: { type: 'boolean',"default":false},
|
||||||
|
maximized: { type: 'boolean',"default":true},
|
||||||
|
transparent: { type:'boolean', "default":false }, // need good gpu
|
||||||
|
xterm: { type: 'boolean', "default":true }, // use xterm.js? (=slower)
|
||||||
|
memory: { type: 'number', "default":48 } // VM memory (in MB)
|
||||||
},
|
},
|
||||||
|
|
||||||
init: async function(){
|
init: async function(){
|
||||||
this.el.object3D.visible = false
|
this.el.object3D.visible = false
|
||||||
this.initTerminal(true)
|
fetch(this.data.iso,{method: 'HEAD'})
|
||||||
|
.then( (res) => {
|
||||||
|
if( res.status != 200 ) throw 'not found'
|
||||||
|
})
|
||||||
|
.catch( (e) => {
|
||||||
|
console.warn(this.data.iso+" could not be loaded, loading fallback ISO URL:")
|
||||||
|
console.warn(this.schema.iso.default)
|
||||||
|
this.data.iso = this.schema.iso.default
|
||||||
|
})
|
||||||
|
.finally( () => this.initTerminal(true) )
|
||||||
},
|
},
|
||||||
|
|
||||||
requires:{
|
requires:{
|
||||||
com: "com/dom.js",
|
com: "com/dom.js",
|
||||||
window: "com/window.js",
|
window: "com/window.js",
|
||||||
xtermjs: "https://unpkg.com/@xterm/xterm@5.5.0/lib/xterm.js",
|
|
||||||
xtermcss: "https://unpkg.com/@xterm/xterm@5.5.0/css/xterm.css",
|
|
||||||
v86: "com/isoterminal/libv86.js",
|
v86: "com/isoterminal/libv86.js",
|
||||||
// allow xrsh to selfcontain scene + itself
|
// allow xrsh to selfcontain scene + itself
|
||||||
xhook: "https://jpillora.com/xhook/dist/xhook.min.js",
|
xhook: "https://jpillora.com/xhook/dist/xhook.min.js",
|
||||||
|
@ -57,38 +71,60 @@ if( typeof AFRAME != 'undefined '){
|
||||||
core: "com/isoterminal/core.js",
|
core: "com/isoterminal/core.js",
|
||||||
utils_9p: "com/isoterminal/feat/9pfs_utils.js",
|
utils_9p: "com/isoterminal/feat/9pfs_utils.js",
|
||||||
boot: "com/isoterminal/feat/boot.js",
|
boot: "com/isoterminal/feat/boot.js",
|
||||||
xterm: "com/isoterminal/feat/xterm.js",
|
|
||||||
jsconsole: "com/isoterminal/feat/jsconsole.js",
|
jsconsole: "com/isoterminal/feat/jsconsole.js",
|
||||||
javascript: "com/isoterminal/feat/javascript.js",
|
javascript: "com/isoterminal/feat/javascript.js",
|
||||||
index: "com/isoterminal/feat/index.html.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"
|
||||||
},
|
},
|
||||||
|
|
||||||
dom: {
|
dom: {
|
||||||
scale: 0.5,
|
scale: 0.5,
|
||||||
events: ['click','keydown'],
|
events: ['click','keydown'],
|
||||||
html: (me) => `<div class="isoterminal"></div>`,
|
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>`,
|
||||||
|
|
||||||
css: (me) => `.isoterminal{
|
css: (me) => `.isoterminal{
|
||||||
padding: ${me.com.data.padding}px;
|
padding: ${me.com.data.padding}px;
|
||||||
width:100%;
|
width:100%;
|
||||||
height:100%;
|
height:100%;
|
||||||
}
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Cousine';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
src: url(./assets/Cousine.ttf) format('truetype');
|
||||||
|
}
|
||||||
.isoterminal *{
|
.isoterminal *{
|
||||||
white-space: pre;
|
white-space: pre;
|
||||||
font-size: 14px;
|
line-height:16px;
|
||||||
font-family: Liberation Mono,DejaVu Sans Mono,Courier New,monospace;
|
|
||||||
font-weight:700;
|
|
||||||
display:inline;
|
display:inline;
|
||||||
overflow: hidden;
|
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 }
|
||||||
|
|
||||||
.wb-body:has(> .isoterminal){
|
.wb-body:has(> .isoterminal){
|
||||||
background: #000F;
|
background: #000C;
|
||||||
overflow:hidden;
|
overflow:hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.XR .wb-body:has(> .isoterminal){
|
||||||
|
background: #000;
|
||||||
|
}
|
||||||
|
|
||||||
.isoterminal div{ display:block; }
|
.isoterminal div{ display:block; }
|
||||||
.isoterminal span{ display: inline }
|
.isoterminal span{ display: inline }
|
||||||
|
|
||||||
|
@ -113,6 +149,12 @@ if( typeof AFRAME != 'undefined '){
|
||||||
|
|
||||||
initTerminal: async function(singleton){
|
initTerminal: async function(singleton){
|
||||||
|
|
||||||
|
if( this.data.xterm ){
|
||||||
|
this.requires.xtermjs = "https://unpkg.com/@xterm/xterm@5.5.0/lib/xterm.js"
|
||||||
|
this.requires.xtermcss = "https://unpkg.com/@xterm/xterm@5.5.0/css/xterm.css"
|
||||||
|
this.requires.xterm = "com/isoterminal/feat/xterm.js"
|
||||||
|
}
|
||||||
|
|
||||||
let s = await AFRAME.utils.require(this.requires)
|
let s = await AFRAME.utils.require(this.requires)
|
||||||
|
|
||||||
this.el.setAttribute("selfcontainer","")
|
this.el.setAttribute("selfcontainer","")
|
||||||
|
@ -132,10 +174,13 @@ if( typeof AFRAME != 'undefined '){
|
||||||
}
|
}
|
||||||
|
|
||||||
// init isoterminal
|
// init isoterminal
|
||||||
this.isoterminal = new ISOTerminal()
|
this.isoterminal = new ISOTerminal(instance,this.data)
|
||||||
|
|
||||||
instance.addEventListener('DOMready', () => {
|
instance.addEventListener('DOMready', () => {
|
||||||
instance.setAttribute("window", `title: xrsh [booting linux iso..]; uid: ${instance.uid}; attach: #overlay; dom: #${instance.dom.id}`)
|
//instance.winbox.resize(720,380)
|
||||||
|
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.addEventListener('window.oncreate', (e) => {
|
instance.addEventListener('window.oncreate', (e) => {
|
||||||
|
@ -146,9 +191,21 @@ if( typeof AFRAME != 'undefined '){
|
||||||
this.isoterminal.runISO(opts)
|
this.isoterminal.runISO(opts)
|
||||||
})
|
})
|
||||||
|
|
||||||
this.isoterminal.addEventListener('ready', function(e){
|
instance.setAttribute("dom", "")
|
||||||
|
|
||||||
|
|
||||||
|
this.isoterminal.addEventListener('postReady', (e)=>{
|
||||||
|
// bugfix: send window dimensions to xterm (xterm.js does that from dom-sizechange to xterm via escape codes)
|
||||||
|
let wb = instance.winbox
|
||||||
|
if( this.data.maximized ){
|
||||||
|
wb.restore()
|
||||||
|
wb.maximize()
|
||||||
|
}else wb.resize()
|
||||||
|
})
|
||||||
|
|
||||||
|
this.isoterminal.addEventListener('ready', (e)=>{
|
||||||
instance.dom.classList.remove('blink')
|
instance.dom.classList.remove('blink')
|
||||||
instance.winbox.maximize()
|
this.isoterminal.emit('status',"running")
|
||||||
setTimeout( () => { // important: after window maximize animation to get true size
|
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
|
instance.setAttribute("html-as-texture-in-xr", `domid: #${instance.uid}`) // only show aframe-html in xr
|
||||||
},1500)
|
},1500)
|
||||||
|
@ -159,31 +216,24 @@ if( typeof AFRAME != 'undefined '){
|
||||||
const w = instance.winbox
|
const w = instance.winbox
|
||||||
if(!w) return
|
if(!w) return
|
||||||
w.titleBak = w.titleBak || w.title
|
w.titleBak = w.titleBak || w.title
|
||||||
instance.winbox.setTitle( `${w.titleBak} [${msg}]` )
|
w.setTitle( `${w.titleBak} [${msg}]` )
|
||||||
})
|
})
|
||||||
|
|
||||||
instance.addEventListener('window.onclose', (e) => {
|
instance.addEventListener('window.onclose', (e) => {
|
||||||
if( !confirm('do you want to kill this virtual machine and all its processes?') ) e.halt = true
|
if( !confirm('do you want to kill this virtual machine and all its processes?') ) e.halt = true
|
||||||
})
|
})
|
||||||
|
|
||||||
const resize = (w,h) => {
|
const resize = (w,h) => { }
|
||||||
if( this.isoterminal.emulator && this.isoterminal.emulator.serial_adapter ){
|
|
||||||
setTimeout( () => {
|
|
||||||
this.isoterminal.xtermAutoResize(this.isoterminal.emulator.serial_adapter.term,instance,-5)
|
|
||||||
},800) // wait for resize anim
|
|
||||||
}
|
|
||||||
}
|
|
||||||
instance.addEventListener('window.onresize', resize )
|
instance.addEventListener('window.onresize', resize )
|
||||||
instance.addEventListener('window.onmaximize', resize )
|
instance.addEventListener('window.onmaximize', resize )
|
||||||
|
|
||||||
instance.setAttribute("dom", "")
|
const focus = (e) => {
|
||||||
|
if( this.isoterminal?.emulator?.serial_adapter?.term ){
|
||||||
const focus = () => {
|
|
||||||
if( this.isoterminal?.emulator?.serial_adapter?.focus ){
|
|
||||||
this.isoterminal.emulator.serial_adapter.term.focus()
|
this.isoterminal.emulator.serial_adapter.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 )
|
||||||
|
|
||||||
|
|
|
@ -2,45 +2,90 @@
|
||||||
// // exec(['lua'] "print \"hello\") ---> cat /dev/browser/js/stdin | lua > /dev/browser/js/stdout
|
// // exec(['lua'] "print \"hello\") ---> cat /dev/browser/js/stdin | lua > /dev/browser/js/stdout
|
||||||
//}
|
//}
|
||||||
|
|
||||||
ISOTerminal.prototype.send = function(ttyNr, str){
|
ISOTerminal.prototype.serial_input = undefined; // can be set to 0,1,2,3 to define stdinput tty (xterm plugin)
|
||||||
this.toUint8Array( str ).map( (c) => this.emulator.bus.send(`serial${ttyNr}-input`, c ) )
|
|
||||||
|
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.toUint8Array = function(str) {
|
ISOTerminal.prototype.send = function(str, ttyNr){
|
||||||
str = String(str) || String("")
|
if( !ttyNr ) ttyNr = this.serial_input
|
||||||
// Create a new Uint8Array with the same length as the input string
|
if( !ttyNr ){
|
||||||
const uint8Array = new Uint8Array(str.length);
|
if( this.emulator.serial_adapter ){
|
||||||
|
this.emulator.serial_adapter.term.paste(str)
|
||||||
// Iterate over the string and populate the Uint8Array
|
}else this.emulator.keyboard_send_text(str) // vga screen
|
||||||
for (let i = 0; i < str.length; i++) {
|
}else{
|
||||||
uint8Array[i] = str.charCodeAt(i);
|
this.convert.toUint8Array( str ).map( (c) => this.emulator.bus.send(`serial${ttyNr}-input`, c ) )
|
||||||
}
|
}
|
||||||
return uint8Array;
|
}
|
||||||
},
|
|
||||||
|
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){
|
ISOTerminal.prototype.runISO = function(opts){
|
||||||
this.opts = opts
|
|
||||||
|
let me = this
|
||||||
|
this.opts = {...this.opts, ...opts}
|
||||||
let image = {}
|
let image = {}
|
||||||
if( opts.iso.match(/\.iso$/) ) image.cdrom = { url: opts.iso }
|
if( opts.iso.match(/\.iso$/) ) image.cdrom = { url: opts.iso }
|
||||||
if( opts.iso.match(/\.bin$/) ) image.bzimage = { url: opts.iso }
|
if( opts.iso.match(/\.bin$/) ) image.bzimage = { url: opts.iso }
|
||||||
|
|
||||||
let emulator = this.emulator = new V86({ ...image,
|
opts = { ...image,
|
||||||
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: "com/isoterminal/v86.wasm",
|
||||||
memory_size: 32 * 1024 * 1024,
|
memory_size: opts.memory * 1024 * 1024,
|
||||||
vga_memory_size: 2 * 1024 * 1024,
|
vga_memory_size: 2 * 1024 * 1024,
|
||||||
serial_container_xtermjs: opts.dom,
|
screen_container: opts.dom,
|
||||||
//screen_container: dom, //this.canvas.parentElement,
|
//serial_container: opts.dom,
|
||||||
bios: {
|
bios: {
|
||||||
url: "com/isoterminal/bios/seabios.bin",
|
url: "com/isoterminal/bios/seabios.bin",
|
||||||
},
|
},
|
||||||
vga_bios: {
|
vga_bios: {
|
||||||
url: "com/isoterminal/bios/vgabios.bin",
|
url: "com/isoterminal/bios/vgabios.bin",
|
||||||
|
//urg|: "com/isoterminal/bios/VGABIOS-lgpl-latest.bin",
|
||||||
},
|
},
|
||||||
network_relay_url: "wss://relay.widgetry.org/",
|
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_free=on",
|
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,
|
//bzimage_initrd_from_filesystem: true,
|
||||||
//filesystem: {
|
//filesystem: {
|
||||||
// baseurl: "com/isoterminal/v86/images/alpine-rootfs-flat",
|
// baseurl: "com/isoterminal/v86/images/alpine-rootfs-flat",
|
||||||
|
@ -50,8 +95,9 @@ ISOTerminal.prototype.runISO = function(opts){
|
||||||
//disable_jit: false,
|
//disable_jit: false,
|
||||||
filesystem: {},
|
filesystem: {},
|
||||||
autostart: true,
|
autostart: true,
|
||||||
});
|
};
|
||||||
|
this.emit('runISO',opts)
|
||||||
|
let emulator = this.emulator = new V86(opts)
|
||||||
|
|
||||||
const loading = [
|
const loading = [
|
||||||
'loading quantum bits and bytes',
|
'loading quantum bits and bytes',
|
||||||
|
@ -77,51 +123,39 @@ ISOTerminal.prototype.runISO = function(opts){
|
||||||
'Transcending earthly limits'
|
'Transcending earthly limits'
|
||||||
]
|
]
|
||||||
|
|
||||||
let loadmsg = loading[ Math.floor(Math.random()*1000) % loading.length-1 ]
|
let loadmsg = loading[ Math.floor(Math.random()*1000) % loading.length ]
|
||||||
this.emit('status',loadmsg)
|
this.emit('status',loadmsg)
|
||||||
|
|
||||||
// replace welcome message https://github.com/copy/v86/blob/3c77b98bc4bc7a5d51a2056ea73d7666ca50fc9d/src/browser/serial.js#L231
|
// 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 welcome = "This is the serial console. Whatever you type or paste here will be sent to COM1"
|
||||||
|
|
||||||
let motd = "\r[38;5;129m"
|
let motd = "\r[38;5;129m"
|
||||||
let msg = `${loadmsg}, please wait..`
|
let msg = `${loadmsg}, please wait..`
|
||||||
while( msg.length < welcome.length ) msg += " "
|
while( msg.length < welcome.length ) msg += " "
|
||||||
msg += "\n"
|
msg += "\n"
|
||||||
motd += msg+"\033[0m"
|
motd += msg+"\033[0m"
|
||||||
|
|
||||||
const files = [
|
|
||||||
"com/isoterminal/mnt/js",
|
|
||||||
"com/isoterminal/mnt/jsh",
|
|
||||||
"com/isoterminal/mnt/xrsh",
|
|
||||||
"com/isoterminal/mnt/profile",
|
|
||||||
"com/isoterminal/mnt/profile.sh",
|
|
||||||
"com/isoterminal/mnt/profile.xrsh",
|
|
||||||
"com/isoterminal/mnt/profile.js",
|
|
||||||
"com/isoterminal/mnt/motd",
|
|
||||||
"com/isoterminal/mnt/v86pipe"
|
|
||||||
]
|
|
||||||
|
|
||||||
emulator.bus.register("emulator-started", async (e) => {
|
emulator.bus.register("emulator-started", async (e) => {
|
||||||
this.emit('emulator-started',e)
|
this.emit('emulator-started',e)
|
||||||
emulator.serial_adapter.term.clear()
|
|
||||||
emulator.serial_adapter.term.write(motd)
|
|
||||||
|
|
||||||
let p = files.map( (f) => fetch(f) )
|
if( emulator.serial_adapter ){
|
||||||
Promise.all(p)
|
emulator.serial_adapter.term.clear()
|
||||||
.then( (files) => {
|
emulator.serial_adapter.term.write(motd)
|
||||||
files.map( (f) => {
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if( me.opts.overlayfs ){
|
||||||
|
fetch(me.opts.overlayfs)
|
||||||
|
.then( (f) => {
|
||||||
f.arrayBuffer().then( (buf) => {
|
f.arrayBuffer().then( (buf) => {
|
||||||
emulator.create_file( f.url.replace(/.*mnt\//,''), new Uint8Array(buf) )
|
emulator.create_file('overlayfs.zip', new Uint8Array(buf) )
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
}
|
||||||
|
|
||||||
//emulator.serial0_send('chmod +x /mnt/js')
|
|
||||||
//emulator.serial0_send()
|
|
||||||
let line = ''
|
let line = ''
|
||||||
let ready = false
|
let ready = false
|
||||||
emulator.add_listener("serial0-output-byte", async (byte) => {
|
emulator.add_listener(`serial0-output-byte`, async (byte) => {
|
||||||
this.emit('serial0-output-byte',byte)
|
this.emit('${this.serial}-output-byte',byte)
|
||||||
var chr = String.fromCharCode(byte);
|
var chr = String.fromCharCode(byte);
|
||||||
if(chr < " " && chr !== "\n" && chr !== "\t" || chr > "~")
|
if(chr < " " && chr !== "\n" && chr !== "\t" || chr > "~")
|
||||||
{
|
{
|
||||||
|
@ -137,12 +171,10 @@ ISOTerminal.prototype.runISO = function(opts){
|
||||||
{
|
{
|
||||||
line += chr;
|
line += chr;
|
||||||
}
|
}
|
||||||
|
if( !ready && line.match(/^(\/ #|~%|\[.*\]>)/) ){
|
||||||
if( !ready && line.match(/^(\/ #|~%)/) ){
|
this.emit('postReady',e)
|
||||||
this.emit('ready')
|
setTimeout( () => this.emit('ready',e), 500 )
|
||||||
ready = true
|
ready = true
|
||||||
//emulator.serial0_send("root\n")
|
|
||||||
//emulator.serial0_send("mv /mnt/js . && chmod +x js\n")
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -152,11 +184,9 @@ ISOTerminal.prototype.runISO = function(opts){
|
||||||
ISOTerminal.prototype.readFromPipe = function(filename,cb){
|
ISOTerminal.prototype.readFromPipe = function(filename,cb){
|
||||||
|
|
||||||
this.emulator.add_listener("9p-write-end", async (opts) => {
|
this.emulator.add_listener("9p-write-end", async (opts) => {
|
||||||
const decoder = new TextDecoder('utf-8');
|
|
||||||
if ( opts[0] == filename.replace(/.*\//,'') ){
|
if ( opts[0] == filename.replace(/.*\//,'') ){
|
||||||
const buf = await this.emulator.read_file("console.tty")
|
const buf = await this.emulator.read_file("console.tty")
|
||||||
const val = decoder.decode(buf)
|
cb( this.convert.Uint8ArrayToString(buf) )
|
||||||
cb(val)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -8,16 +8,17 @@ ISOTerminal.addEventListener('emulator-started', function(){
|
||||||
|
|
||||||
if(p.id === -1)
|
if(p.id === -1)
|
||||||
{
|
{
|
||||||
return Promise.resolve(null);
|
return emulator.create_file(file,data)
|
||||||
}
|
}
|
||||||
|
|
||||||
const inode = this.GetInode(p.id);
|
const inode = this.GetInode(p.id);
|
||||||
const buf = typeof data == 'string' ? isoterminal.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;
|
||||||
|
isoterminal.exec(`touch ${file}`) // update inode
|
||||||
return new Promise( (resolve,reject) => resolve(buf) )
|
return new Promise( (resolve,reject) => resolve(buf) )
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -32,7 +33,7 @@ ISOTerminal.addEventListener('emulator-started', function(){
|
||||||
}
|
}
|
||||||
|
|
||||||
const inode = this.GetInode(p.id);
|
const inode = this.GetInode(p.id);
|
||||||
const buf = typeof data == 'string' ? isoterminal.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
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
ISOTerminal.addEventListener('emulator-started', function(e){
|
||||||
|
this.autorestore(e)
|
||||||
|
})
|
||||||
|
|
||||||
|
ISOTerminal.prototype.autorestore = async function(e){
|
||||||
|
|
||||||
|
localforage.setDriver([
|
||||||
|
localforage.INDEXEDDB,
|
||||||
|
localforage.WEBSQL,
|
||||||
|
localforage.LOCALSTORAGE
|
||||||
|
]).then( () => {
|
||||||
|
|
||||||
|
localforage.getItem("state", async (err,stateBase64) => {
|
||||||
|
if( !err && confirm('continue last session?') ){
|
||||||
|
this.noboot = true // see feat/boot.js
|
||||||
|
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 )
|
||||||
|
|
||||||
|
}, 500 )
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
this.save = async () => {
|
||||||
|
const state = await this.emulator.save_state()
|
||||||
|
console.log( String(this.convert.arrayBufferToBase64(state)).substr(0,5) )
|
||||||
|
localforage.setItem("state", this.convert.arrayBufferToBase64(state) )
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener("beforeunload", function (e) {
|
||||||
|
var confirmationMessage = "Sure you want to leave?\nTIP: enter 'save' to continue this session later";
|
||||||
|
(e || window.event).returnValue = confirmationMessage; //Gecko + IE
|
||||||
|
return confirmationMessage; //Webkit, Safari, Chrome
|
||||||
|
});
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -1,21 +1,27 @@
|
||||||
ISOTerminal.addEventListener('ready', function(){
|
ISOTerminal.addEventListener('ready', function(e){
|
||||||
this.boot()
|
setTimeout( () => this.boot(), 50 ) // because of autorestore.js
|
||||||
})
|
})
|
||||||
|
|
||||||
ISOTerminal.prototype.boot = async function(){
|
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()+'="'+document.location[i]+'"')
|
env.push( 'export '+String(i).toUpperCase()+'="'+document.location[i]+'"')
|
||||||
}
|
}
|
||||||
await this.emulator.create_file("profile.browser", this.toUint8Array( env.join('\n') ) )
|
await this.emulator.create_file("profile.browser", this.convert.toUint8Array( env.join('\n') ) )
|
||||||
let boot = `clear ; echo 'preparing xrsh env..'; source /mnt/profile`
|
|
||||||
// exec hash as extra boot cmd
|
if( this.serial_input == 0 ){
|
||||||
if( document.location.hash.length > 1 ){
|
if( !this.noboot ){
|
||||||
boot += ` ; cmd='${decodeURI(document.location.hash.substr(1))}' && $cmd`
|
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() )
|
||||||
}
|
}
|
||||||
this.emulator.serial0_send(boot+"\n")
|
|
||||||
this.emulator.serial_adapter.term.focus()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,32 @@
|
||||||
ISOTerminal.addEventListener('ready', function(){
|
ISOTerminal.addEventListener('init', function(){
|
||||||
this.addEventListener('file-read', (e) => {
|
|
||||||
const data = e.detail
|
this.addEventListener('emulator-started', function(e){
|
||||||
if( data.file == 'index.html'){
|
|
||||||
data.promise = new Promise( (resolve,reject) => {
|
const emulator = this.emulator
|
||||||
resolve( this.toUint8Array(document.documentElement.outerHTML) )
|
|
||||||
})
|
// unix to js device
|
||||||
}
|
this.readFromPipe( '/mnt/index.html', async (data) => {
|
||||||
})
|
const buf = await emulator.read_file("index.html")
|
||||||
|
const decoder = new TextDecoder('utf-8');
|
||||||
|
const html = decoder.decode(buf)
|
||||||
|
try{
|
||||||
|
this.runHTML(html)
|
||||||
|
}catch(e){
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
ISOTerminal.prototype.runHTML = function(html){
|
||||||
|
let $scene = document.querySelector("a-scene")
|
||||||
|
let $root = document.querySelector("a-entity#root")
|
||||||
|
if( !$root ){
|
||||||
|
$root = document.createElement("a-entity")
|
||||||
|
$root.id = "root"
|
||||||
|
$scene.appendChild($root)
|
||||||
|
}
|
||||||
|
$root.innerHTML = html
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
ISOTerminal.addEventListener('init', function(){
|
||||||
|
|
||||||
|
this.addEventListener('emulator-started', function(e){
|
||||||
|
|
||||||
|
const emulator = this.emulator
|
||||||
|
|
||||||
|
// unix to js device
|
||||||
|
this.readFromPipe( '/mnt/index.js', async (data) => {
|
||||||
|
const buf = await emulator.read_file("index.js")
|
||||||
|
const decoder = new TextDecoder('utf-8');
|
||||||
|
const js = decoder.decode(buf)
|
||||||
|
try{
|
||||||
|
this.runJavascript(js)
|
||||||
|
}catch(e){
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
ISOTerminal.prototype.runJavascript = function(js){
|
||||||
|
let $root = document.querySelector("script#root")
|
||||||
|
if( !$root ){
|
||||||
|
$root = document.createElement("script")
|
||||||
|
$root.id = "root"
|
||||||
|
document.body.appendChild($root)
|
||||||
|
}
|
||||||
|
$root.innerHTML = js
|
||||||
|
}
|
||||||
|
|
|
@ -6,9 +6,8 @@ ISOTerminal.addEventListener('init', function(){
|
||||||
|
|
||||||
// unix to js device
|
// unix to js device
|
||||||
this.readFromPipe( '/mnt/js', async (data) => {
|
this.readFromPipe( '/mnt/js', async (data) => {
|
||||||
const buf = await emulator.read_file("dev/browser/js")
|
const buf = await emulator.read_file("js")
|
||||||
const decoder = new TextDecoder('utf-8');
|
const script = this.convert.Uint8ArrayToString(buf)
|
||||||
const script = decoder.decode(buf)
|
|
||||||
let PID="?"
|
let PID="?"
|
||||||
try{
|
try{
|
||||||
if( script.match(/^PID/) ){
|
if( script.match(/^PID/) ){
|
||||||
|
@ -18,7 +17,7 @@ ISOTerminal.addEventListener('init', function(){
|
||||||
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
|
// write output to 9p with PID as filename
|
||||||
// *FIXME* not flexible / robust
|
// *FIXME* not flexible / robust
|
||||||
emulator.create_file(PID, this.toUint8Array(res) )
|
emulator.create_file(PID, this.convert.toUint8Array(res) )
|
||||||
}catch(e){
|
}catch(e){
|
||||||
console.error(e)
|
console.error(e)
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,14 +30,12 @@ ISOTerminal.addEventListener('emulator-started', function(){
|
||||||
let emulator = this.emulator
|
let emulator = this.emulator
|
||||||
|
|
||||||
this.redirectConsole( (str,prefix) => {
|
this.redirectConsole( (str,prefix) => {
|
||||||
if( emulator.log_to_tty ){
|
let finalStr = ""
|
||||||
prefix = prefix ? prefix+' ' : ' '
|
prefix = prefix ? prefix+' ' : ' '
|
||||||
str.trim().split("\n").map( (line) => {
|
str.trim().split("\n").map( (line) => {
|
||||||
emulator.serial_adapter.term.write( '\r\x1b[38;5;165m/dev/browser: \x1b[0m'+prefix+line+'\n' )
|
finalStr += '\x1b[38;5;165m/dev/browser: \x1b[0m'+prefix+line+'\n'
|
||||||
})
|
})
|
||||||
emulator.serial_adapter.term.write( '\r' )
|
emulator.fs9p.append_file( "console", finalStr )
|
||||||
}
|
|
||||||
emulator.fs9p.append_file( "console", str )
|
|
||||||
})
|
})
|
||||||
|
|
||||||
window.addEventListener('error', function(event) {
|
window.addEventListener('error', function(event) {
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
ISOTerminal.addEventListener('ready', function(){
|
||||||
|
this.screenButtonsCreate()
|
||||||
|
})
|
||||||
|
|
||||||
|
ISOTerminal.prototype.screenButtonsCreate = function(){
|
||||||
|
let el = document.createElement("a-plane")
|
||||||
|
el.setAttribute("height","1")
|
||||||
|
el.setAttribute("width","1")
|
||||||
|
el.setAttribute("scale","0.1 0.07 1")
|
||||||
|
el.setAttribute("position", "-0.326 -0.270 0")
|
||||||
|
this.instance.appendChild(el)
|
||||||
|
}
|
||||||
|
|
|
@ -2,16 +2,22 @@ ISOTerminal.addEventListener('init', function(){
|
||||||
if( typeof Terminal != 'undefined' ) this.xtermInit()
|
if( typeof Terminal != 'undefined' ) this.xtermInit()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
ISOTerminal.addEventListener('runISO', function(e){
|
||||||
|
let opts = e.detail
|
||||||
|
opts.serial_container_xtermjs = opts.screen_container
|
||||||
|
delete opts.screen_container
|
||||||
|
})
|
||||||
|
|
||||||
ISOTerminal.prototype.xtermInit = function(){
|
ISOTerminal.prototype.xtermInit = function(){
|
||||||
|
this.serial_input = 0 // set input to serial line 0
|
||||||
let isoterm = this
|
let isoterm = this
|
||||||
// monkeypatch Xterm (which V86 initializes) so we can add our own constructor args
|
// monkeypatch Xterm (which V86 initializes) so we can add our own constructor args
|
||||||
window._Terminal = window.Terminal
|
window._Terminal = window.Terminal
|
||||||
window.Terminal = function(opts){
|
window.Terminal = function(opts){
|
||||||
const term = new window._Terminal({ ...opts,
|
const term = new window._Terminal({ ...opts,
|
||||||
cursorBlink:true,
|
cursorBlink:true,
|
||||||
onSelectionChange: function(e){
|
onSelectionChange: function(e){ console.log("selectchange") },
|
||||||
debugger
|
letterSpacing: 0
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
term.onSelectionChange( () => {
|
term.onSelectionChange( () => {
|
||||||
|
@ -27,6 +33,14 @@ ISOTerminal.prototype.xtermInit = function(){
|
||||||
// toggle immersive with ESCAPE
|
// toggle immersive with ESCAPE
|
||||||
//document.body.addEventListener('keydown', (e) => e.key == 'Escape' && this.emulator.serial_adapter.term.blur() )
|
//document.body.addEventListener('keydown', (e) => e.key == 'Escape' && this.emulator.serial_adapter.term.blur() )
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const resize = (w,h) => {
|
||||||
|
setTimeout( () => {
|
||||||
|
isoterm.xtermAutoResize(isoterm.emulator.serial_adapter.term, isoterm.instance,-3)
|
||||||
|
},800) // wait for resize anim
|
||||||
|
}
|
||||||
|
isoterm.instance.addEventListener('window.onresize', resize )
|
||||||
|
isoterm.instance.addEventListener('window.onmaximize', resize )
|
||||||
}
|
}
|
||||||
|
|
||||||
ISOTerminal.prototype.xtermAutoResize = function(term,instance,rowoffset){
|
ISOTerminal.prototype.xtermAutoResize = function(term,instance,rowoffset){
|
||||||
|
|
|
@ -1,62 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
#
|
|
||||||
# a minimalistic terminal muxer
|
|
||||||
|
|
||||||
# Save the original stdout and stderr file descriptors for later restoration
|
|
||||||
exec 3>&1 4>&2
|
|
||||||
|
|
||||||
# Function to check if a session is already running on the given VT
|
|
||||||
is_session_running() {
|
|
||||||
vt_number=$1
|
|
||||||
|
|
||||||
# Check if any process is running on /dev/tty<vt_number>
|
|
||||||
fuser /dev/tty"$vt_number" >/dev/null 2>&1
|
|
||||||
return $?
|
|
||||||
}
|
|
||||||
|
|
||||||
# Function to mute the output of a session
|
|
||||||
mute_session() {
|
|
||||||
vt_number=$1
|
|
||||||
if is_session_running "$vt_number"; then
|
|
||||||
# Redirect stdout and stderr of the session to /dev/null
|
|
||||||
exec > /dev/null 2>&1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Function to unmute the current session (restore stdout and stderr)
|
|
||||||
unmute_session() {
|
|
||||||
exec 1>&3 2>&4 # Restore stdout and stderr from file descriptors 3 and 4
|
|
||||||
}
|
|
||||||
|
|
||||||
# Function to start a new session if not already running
|
|
||||||
start_or_switch_session() {
|
|
||||||
vt_number=$1
|
|
||||||
|
|
||||||
# Mute all other sessions except the one we're switching to
|
|
||||||
for vt in $(seq 1 12); do # Assuming you have up to 12 VTs, adjust as needed
|
|
||||||
if [ "$vt" != "$vt_number" ]; then
|
|
||||||
mute_session "$vt"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
if is_session_running "$vt_number"; then
|
|
||||||
echo "Switching to existing session on VT$vt_number"
|
|
||||||
unmute_session # Unmute the session we're switching to
|
|
||||||
chvt "$vt_number"
|
|
||||||
else
|
|
||||||
echo "Starting a new session on VT$vt_number"
|
|
||||||
openvt -c "$vt_number" -- /bin/sh &
|
|
||||||
sleep 1 # Give the session a moment to start
|
|
||||||
unmute_session # Unmute the new session
|
|
||||||
chvt "$vt_number"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Ensure a session number is provided
|
|
||||||
if [ "$#" -ne 1 ]; then
|
|
||||||
echo "Usage: $0 <session_number>"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Start or switch to the session
|
|
||||||
start_or_switch_session $1
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
/*
|
||||||
|
* ## requestAnimationFrameXR
|
||||||
|
*
|
||||||
|
* reroutes requestAnimationFrame-calls to xrSession.requestAnimationFrame
|
||||||
|
* reason: in immersive mode this function behaves differently
|
||||||
|
* (causing HTML apps like xterm.js not getting updated due to relying
|
||||||
|
* on window.requestAnimationFrame)
|
||||||
|
*
|
||||||
|
* ```html
|
||||||
|
* <a-entity requestAnimationFrameXR dom/>
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
|
||||||
|
if( !AFRAME.systems.requestAnimationFrameXR ){
|
||||||
|
|
||||||
|
AFRAME.registerSystem('requestAnimationFrameXR',{
|
||||||
|
|
||||||
|
init: function init(){
|
||||||
|
if( document.location.hostname.match(/localhost/) ) return // allow webxr polyfill during development (they hang in XR)
|
||||||
|
AFRAME.systems.requestAnimationFrameXR.q = []
|
||||||
|
this.sceneEl.addEventListener('enter-vr', this.enable )
|
||||||
|
this.sceneEl.addEventListener('enter-ar', this.enable )
|
||||||
|
this.sceneEl.addEventListener('exit-vr', this.disable )
|
||||||
|
this.sceneEl.addEventListener('exit-ar', this.disable )
|
||||||
|
},
|
||||||
|
|
||||||
|
enable: function enable(){
|
||||||
|
this.requestAnimationFrame = window.requestAnimationFrame
|
||||||
|
// NOTE: we don't call xrSession.requestAnimationFrame directly like this:
|
||||||
|
//
|
||||||
|
// window.requestAnimationFrame = AFRAME.utils.throttleTick( (cb) => this.sceneEl.xrSession.requestAnimationFrame(cb), 50 )
|
||||||
|
//
|
||||||
|
// as that breaks webxr polyfill (for in-browser testing)
|
||||||
|
// instead we defer calls to tick() (which is called both in XR and non-XR)
|
||||||
|
//
|
||||||
|
window.requestAnimationFrame = (cb) => AFRAME.systems.requestAnimationFrameXR.q.push(cb)
|
||||||
|
const q = AFRAME.systems.requestAnimationFrameXR.q
|
||||||
|
this.tick = AFRAME.utils.throttleTick( () => {
|
||||||
|
while( q.length != 0 ) (q.pop())()
|
||||||
|
},50)
|
||||||
|
},
|
||||||
|
|
||||||
|
disable: function disable(){
|
||||||
|
delete this.tick
|
||||||
|
window.requestAnimationFrame = this.requestAnimationFrame
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
|
@ -1,13 +1,15 @@
|
||||||
AFRAME.registerComponent('window', {
|
AFRAME.registerComponent('window', {
|
||||||
schema:{
|
schema:{
|
||||||
title: {type:'string',"default":"title"},
|
title: {type:'string',"default":"title"},
|
||||||
width: {type:'string'}, // wrap
|
width: {type:'string'}, // wrap
|
||||||
height: {type:'string',"default":'260px'},
|
height: {type:'string',"default":'260px'},
|
||||||
uid: {type:'string'},
|
uid: {type:'string'},
|
||||||
attach: {type:'selector'},
|
attach: {type:'selector'},
|
||||||
dom: {type:'selector'},
|
dom: {type:'selector'},
|
||||||
x: {type:'string',"default":"center"},
|
max: {type:'boolean',"default":false},
|
||||||
y: {type:'string',"default":"center"}
|
min: {type:'boolean',"default":false},
|
||||||
|
x: {type:'string',"default":"center"},
|
||||||
|
y: {type:'string',"default":"center"}
|
||||||
},
|
},
|
||||||
|
|
||||||
dependencies:{
|
dependencies:{
|
||||||
|
@ -33,13 +35,15 @@ AFRAME.registerComponent('window', {
|
||||||
id: this.data.uid || String(Math.random()).substr(4), // important hint for html-mesh
|
id: this.data.uid || String(Math.random()).substr(4), // important hint for html-mesh
|
||||||
root: this.data.attach || document.body,
|
root: this.data.attach || document.body,
|
||||||
mount: this.data.dom,
|
mount: this.data.dom,
|
||||||
|
max: this.data.max,
|
||||||
|
min: this.data.min,
|
||||||
onresize: () => this.el.emit('window.onresize',{}),
|
onresize: () => this.el.emit('window.onresize',{}),
|
||||||
onmaximize: () => this.el.emit('window.onmaximize',{}),
|
onmaximize: () => this.el.emit('window.onmaximize',{}),
|
||||||
oncreate: () => {
|
oncreate: (e) => {
|
||||||
this.el.emit('window.oncreate',{})
|
this.el.emit('window.oncreate',{})
|
||||||
// resize after the dom content has been rendered & updated
|
// resize after the dom content has been rendered & updated
|
||||||
setTimeout( () => {
|
setTimeout( () => {
|
||||||
winbox.resize( this.el.dom.offsetWidth+'px', this.el.dom.offsetHeight+'px' )
|
if( !this.data.max ) winbox.resize( this.el.dom.offsetWidth+'px', this.el.dom.offsetHeight+'px' )
|
||||||
// hint grabbable's obb-collider to track the window-object
|
// hint grabbable's obb-collider to track the window-object
|
||||||
this.el.components['obb-collider'].data.trackedObject3D = 'components.html.el.object3D.children.0'
|
this.el.components['obb-collider'].data.trackedObject3D = 'components.html.el.object3D.children.0'
|
||||||
this.el.components['obb-collider'].update()
|
this.el.components['obb-collider'].update()
|
||||||
|
@ -50,8 +54,8 @@ AFRAME.registerComponent('window', {
|
||||||
this.el.emit('window.onclose',e)
|
this.el.emit('window.onclose',e)
|
||||||
if( e.halt ) return true
|
if( e.halt ) return true
|
||||||
this.data.dom.style.display = 'none';
|
this.data.dom.style.display = 'none';
|
||||||
|
if( this.el.parentNode ) this.el.remove() //parentElement.remove( this.el )
|
||||||
this.data.dom.parentElement.remove()
|
this.data.dom.parentElement.remove()
|
||||||
this.el.parentElement.remove( this.el )
|
|
||||||
return false
|
return false
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue