Compare commits
34 Commits
feat/javas
...
main
Author | SHA1 | Date |
---|---|---|
Leon van Kammen | 9aff2133a0 | |
Leon van Kammen | 2e56d235ed | |
Leon van Kammen | 0e8d173289 | |
Leon van Kammen | 0f3c7a0184 | |
Leon van Kammen | 6b9cd42569 | |
Leon van Kammen | 35ff5dc710 | |
Leon van Kammen | c6ce294e69 | |
Leon van Kammen | eb086dfd74 | |
Leon van Kammen | bc82d87541 | |
Leon van Kammen | 9d69ef9c57 | |
Leon van Kammen | 44f79ac02a | |
Leon van Kammen | 36b9eaadcb | |
Leon van Kammen | 17e07b0834 | |
Leon van Kammen | 8369dde488 | |
Leon van Kammen | f75f34d6a6 | |
Leon van Kammen | 10c274fdd4 | |
Leon van Kammen | 7c34805952 | |
Leon van Kammen | 0bec71ab55 | |
Leon van Kammen | b1bbaac311 | |
Leon van Kammen | 31b6f99050 | |
Leon van Kammen | 658e113853 | |
Leon van Kammen | 98c3adb791 | |
Leon van Kammen | af7e837554 | |
Leon van Kammen | 590647ece8 | |
Leon van Kammen | f9cdea25e5 | |
Leon van Kammen | 54c547fe4d | |
Leon van Kammen | 3c3340144f | |
Leon van Kammen | 2ba3cc185a | |
Leon van Kammen | 3d52b69340 | |
Leon van Kammen | 04c9bb825b | |
Leon van Kammen | 72b55b7c5d | |
Leon van Kammen | f7a178b5e7 | |
Leon van Kammen | fdc1d55637 | |
Leon van Kammen | 4cdba16072 |
|
@ -0,0 +1,3 @@
|
||||||
|
com/isoterminal/mnt/hook.d
|
||||||
|
com/isoterminal/mnt/console*
|
||||||
|
com/isoterminal/mnt/dev/*
|
|
@ -11,3 +11,11 @@ This is a library of useful AFRAME components which can be used in any AFRAME ap
|
||||||
|
|
||||||
<a-entity helloworld="foo:1" class="cubes" name="box">
|
<a-entity helloworld="foo:1" class="cubes" name="box">
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Funding
|
||||||
|
|
||||||
|
This project is funded through [NGI0 Entrust](https://nlnet.nl/entrust), a fund established by [NLnet](https://nlnet.nl) with financial support from the European Commission's [Next Generation Internet](https://ngi.eu) program. Learn more at the [NLnet project page](https://nlnet.nl/project/xrsh).
|
||||||
|
|
||||||
|
[<img src="https://nlnet.nl/logo/banner.png" alt="NLnet foundation logo" width="20%" />](https://nlnet.nl)
|
||||||
|
[<img src="https://nlnet.nl/image/logos/NGI0_tag.svg" alt="NGI Zero Logo" width="20%" />](https://nlnet.nl/entrust)
|
||||||
|
|
||||||
|
|
|
@ -1,75 +0,0 @@
|
||||||
AFRAME.registerComponent('aframe-inspector', {
|
|
||||||
schema: {
|
|
||||||
foo: { type:"string"}
|
|
||||||
},
|
|
||||||
|
|
||||||
init: function () {
|
|
||||||
document.querySelector('a-scene').setAttribute("inspector","url: https://cdn.jsdelivr.net/gh/aframevr/aframe-inspector@master/dist/aframe-inspector.min.js")
|
|
||||||
},
|
|
||||||
|
|
||||||
requires:{ },
|
|
||||||
|
|
||||||
events:{
|
|
||||||
|
|
||||||
// component events
|
|
||||||
ready: function(e){ },
|
|
||||||
|
|
||||||
launcher: function(e){
|
|
||||||
$('[inspector]').components.inspector.openInspector()
|
|
||||||
},
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
manifest: { // HTML5 manifest to identify app to xrsh
|
|
||||||
"short_name": "inspector",
|
|
||||||
"name": "AFRAME inspector",
|
|
||||||
"icons": [
|
|
||||||
{
|
|
||||||
"src": "https://css.gg/list-tree.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": "use CTRL+ALT+i to launch inspector",
|
|
||||||
"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.
|
|
||||||
`
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
|
@ -1,182 +0,0 @@
|
||||||
AFRAME.registerComponent('isoterminal', {
|
|
||||||
schema: {
|
|
||||||
cols: { type: 'number', default: 120 },
|
|
||||||
rows: { type: 'number', default: 50 },
|
|
||||||
transparent: { type:'boolean', default:false } // need good gpu
|
|
||||||
},
|
|
||||||
|
|
||||||
init: function(){
|
|
||||||
this.el.object3D.visible = false
|
|
||||||
},
|
|
||||||
|
|
||||||
requires:{
|
|
||||||
html: "https://unpkg.com/aframe-htmlmesh@2.1.0/build/aframe-html.js", // html to AFRAME
|
|
||||||
winboxjs: "https://unpkg.com/winbox@0.2.82/dist/winbox.bundle.min.js", // deadsimple windows: https://nextapps-de.github.io/winbox
|
|
||||||
winboxcss: "https://unpkg.com/winbox@0.2.82/dist/css/winbox.min.css", //
|
|
||||||
xtermcss: "https://unpkg.com/xterm@3.12.0/dist/xterm.css",
|
|
||||||
xtermjs: "https://unpkg.com/xterm@3.12.0/dist/xterm.js",
|
|
||||||
axterm: "https://unpkg.com/aframe-xterm-component/aframe-xterm-component.js"
|
|
||||||
},
|
|
||||||
|
|
||||||
dom: {
|
|
||||||
scale: 3,
|
|
||||||
events: ['click','keydown'],
|
|
||||||
html: (me) => `<div></div>`,
|
|
||||||
|
|
||||||
css: (me) => `.isoterminal{
|
|
||||||
overflow:hidden;
|
|
||||||
}`
|
|
||||||
},
|
|
||||||
|
|
||||||
createTerminal: function(){
|
|
||||||
this.el.object3D.visible = true
|
|
||||||
|
|
||||||
const term = this.term = new Terminal({
|
|
||||||
allowTransparency: this.data.transparent,
|
|
||||||
cursorBlink: true,
|
|
||||||
disableStdin: false,
|
|
||||||
rows: this.data.rows,
|
|
||||||
cols: this.data.cols,
|
|
||||||
fontSize: 16
|
|
||||||
})
|
|
||||||
|
|
||||||
term.open(this.el.dom)
|
|
||||||
this.canvas = this.el.dom.querySelector('.xterm-text-layer')
|
|
||||||
this.canvas.id = 'terminal-' + (terminalInstance++)
|
|
||||||
this.canvasContext = this.canvas.getContext('2d')
|
|
||||||
|
|
||||||
this.cursorCanvas = this.el.dom.querySelector('.xterm-cursor-layer')
|
|
||||||
|
|
||||||
//this.el.setAttribute('material', `transparent: ${this.data.transparent?'true':'false'}; src: #${this.canvas.id}` )
|
|
||||||
|
|
||||||
term.on('refresh', () => {
|
|
||||||
console.log("refresh")
|
|
||||||
})
|
|
||||||
|
|
||||||
term.on('data', (data) => {
|
|
||||||
this.el.emit('xterm-data', data)
|
|
||||||
})
|
|
||||||
|
|
||||||
this.el.addEventListener('click', () => {
|
|
||||||
term.focus()
|
|
||||||
})
|
|
||||||
|
|
||||||
const message = 'Hello from \x1B[1;3;31mWebVR\x1B[0m !\r\n$ '
|
|
||||||
term.write(message)
|
|
||||||
},
|
|
||||||
|
|
||||||
events:{
|
|
||||||
|
|
||||||
// combined AFRAME+DOM reactive events
|
|
||||||
click: function(e){ }, //
|
|
||||||
keydown: function(e){ }, //
|
|
||||||
|
|
||||||
// reactive events for this.data updates
|
|
||||||
myvalue: function(e){ this.el.dom.querySelector('b').innerText = this.data.myvalue },
|
|
||||||
|
|
||||||
ready: function( ){
|
|
||||||
this.el.dom.style.display = 'none'
|
|
||||||
},
|
|
||||||
|
|
||||||
launcher: function(){
|
|
||||||
this.el.dom.style.display = ''
|
|
||||||
this.createTerminal()
|
|
||||||
|
|
||||||
new WinBox( this.manifest.iso + ' ' + this.manifest.name, {
|
|
||||||
width: window.innerWidth*0.8,
|
|
||||||
height: window.innerHeight*0.8,
|
|
||||||
x:"center",
|
|
||||||
y:"center",
|
|
||||||
id: this.el.uid, // important hint for html-mesh
|
|
||||||
root: document.querySelector("#overlay"),
|
|
||||||
mount: this.el.dom,
|
|
||||||
onclose: () => {
|
|
||||||
if( !confirm('do you want to kill this virtual machine and all its processes?') ) return true
|
|
||||||
this.el.dom.style.display = 'none'
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
manifest: { // HTML5 manifest to identify app to xrsh
|
|
||||||
"iso": "linux-x64-4.15.iso",
|
|
||||||
"short_name": "ISOTerm",
|
|
||||||
"name": "terminal",
|
|
||||||
"icons": [
|
|
||||||
{
|
|
||||||
"src": "https://css.gg/terminal.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": "Hello world information",
|
|
||||||
"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.
|
|
||||||
`
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
AFRAME.registerComponent('xterm-shell', {
|
|
||||||
dependencies: ['xterm'],
|
|
||||||
init: function() {
|
|
||||||
const message = 'Run \x1B[1;3;31m\'node server.js\'\x1B[0m to open a shell\r\n'
|
|
||||||
const xterm = this.el.components['xterm']
|
|
||||||
|
|
||||||
xterm.write(message)
|
|
||||||
|
|
||||||
const socket = new WebSocket('ws://localhost:8080/')
|
|
||||||
|
|
||||||
// Listen on data, write it to the terminal
|
|
||||||
socket.onmessage = ({data}) => {
|
|
||||||
xterm.write(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
socket.onclose = () => {
|
|
||||||
xterm.write('\r\nConnection closed.\r\n')
|
|
||||||
}
|
|
||||||
|
|
||||||
// Listen on user input, send it to the connection
|
|
||||||
this.el.addEventListener('xterm-data', ({detail}) => {
|
|
||||||
socket.send(detail)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
|
@ -1,111 +0,0 @@
|
||||||
AFRAME.registerComponent('load', {
|
|
||||||
schema: {
|
|
||||||
foo: { type:"string"}
|
|
||||||
},
|
|
||||||
|
|
||||||
init: function () {
|
|
||||||
this.el.object3D.visible = false
|
|
||||||
|
|
||||||
//this.el.innerHTML = ` `
|
|
||||||
},
|
|
||||||
|
|
||||||
requires:{
|
|
||||||
// somecomponent: "https://unpkg.com/some-aframe-component/mycom.min.js"
|
|
||||||
},
|
|
||||||
|
|
||||||
events:{
|
|
||||||
|
|
||||||
// component events
|
|
||||||
somecomponent: function( ){ console.log("component requirement mounted") },
|
|
||||||
ready: function(e){ console.log("requires are loaded") },
|
|
||||||
|
|
||||||
launcher: function(e){
|
|
||||||
this.filedialog( (data) => {
|
|
||||||
if( data.match(/<html/) && confirm('reload new XR shell environment?') ){
|
|
||||||
return document.write(data)
|
|
||||||
}
|
|
||||||
if( data.match(/<a-/) ){
|
|
||||||
let el = document.createElement('a-entity')
|
|
||||||
el.innerHTML = data
|
|
||||||
AFRAME.scenes[0].appendChild(el)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
filedialog: function(onDone){
|
|
||||||
if( !this.fileEl ){
|
|
||||||
let el = this.fileEl = document.createElement('input')
|
|
||||||
el.type = "file"
|
|
||||||
el.addEventListener('change', (e) =>{
|
|
||||||
var file = e.target.files[0];
|
|
||||||
if (!file) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var reader = new FileReader();
|
|
||||||
reader.onload = function(e) {
|
|
||||||
var contents = e.target.result;
|
|
||||||
onDone(contents)
|
|
||||||
};
|
|
||||||
reader.readAsText(file);
|
|
||||||
})
|
|
||||||
document.body.appendChild(el)
|
|
||||||
}
|
|
||||||
this.fileEl.click()
|
|
||||||
},
|
|
||||||
|
|
||||||
manifest: { // HTML5 manifest to identify app to xrsh
|
|
||||||
"short_name": "Hello world",
|
|
||||||
"name": "Hello world",
|
|
||||||
"icons": [
|
|
||||||
{
|
|
||||||
"src": "https://css.gg/arrow-up-r.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": "Hello world information",
|
|
||||||
"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.
|
|
||||||
`
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
|
@ -1,130 +0,0 @@
|
||||||
AFRAME.registerComponent('manual', {
|
|
||||||
schema: {
|
|
||||||
foo: { type:"string"}
|
|
||||||
},
|
|
||||||
|
|
||||||
init: function () {
|
|
||||||
// requires are loaded
|
|
||||||
this.el.addEventListener('ready', () => this.el.dom.style.display = 'none' )
|
|
||||||
},
|
|
||||||
|
|
||||||
requires:{
|
|
||||||
html: "https://unpkg.com/aframe-htmlmesh@2.1.0/build/aframe-html.js", // html to AFRAME
|
|
||||||
winboxjs: "https://unpkg.com/winbox@0.2.82/dist/winbox.bundle.min.js", // deadsimple windows: https://nextapps-de.github.io/winbox
|
|
||||||
winboxcss: "https://unpkg.com/winbox@0.2.82/dist/css/winbox.min.css", // deadsimple windows: https://nextapps-de.github.io/winbox
|
|
||||||
},
|
|
||||||
|
|
||||||
dom: {
|
|
||||||
scale: 3,
|
|
||||||
events: ['click','keydown'],
|
|
||||||
html: (me) => `<div>
|
|
||||||
|
|
||||||
<h1>Welcome to XRSHell</h1>
|
|
||||||
<br>
|
|
||||||
<!-- <img src="https://i.imgur.com/BW22wrb.png"/> -->
|
|
||||||
<br><br>
|
|
||||||
The <b>xrsh</b> (xrshell) brings the <a href="https://en.wikipedia.org/wiki/Free_and_open-source_software" target="_blank">FOSS</a>- and <a href="https://en.wikipedia.org/wiki/Linux" target="_blank">Linux</a>-soul to <a href="https://en.wikipedia.org/wiki/WebXR" target="_blank">WebXR</a>, promoting the use of (interactive text) terminal and user-provided operating systems inside WebXR.
|
|
||||||
<br><br>Technically, <b>xrsh</b> is a bundle of freshly created re-usable FOSS WebXR components.<br>These provide a common filesystem interface for interacting with WebXR, offering the well-known linux/unix toolchain including a commandline to invoke, store, edit and run WebXR utilities - regardless of their implementation.
|
|
||||||
<br><br>Think of it as termux for the VR/AR headset browser, which can be used to e.g. livecode (using terminal auto-completion!) for XR component (registries).
|
|
||||||
|
|
||||||
<br>
|
|
||||||
<ul>
|
|
||||||
<li><a href="https://forgejo.isvery.ninja/xrsh" target="_blank">source xrsh</a></li>
|
|
||||||
<li><a href="https://forgejo.isvery.ninja/xrsh-apps" target="_blank">source xrsh apps</a></li>
|
|
||||||
<li><a href="https://forgejo.isvery.ninja/xrsh-media" target="_blank">roadmap meeting recordings</a></li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
</div>`,
|
|
||||||
css: (me) => `.manual > div { padding:11px }`
|
|
||||||
},
|
|
||||||
|
|
||||||
events:{
|
|
||||||
|
|
||||||
// component events
|
|
||||||
html: function( ){ console.log("html-mesh requirement mounted") },
|
|
||||||
|
|
||||||
// combined AFRAME+DOM reactive events
|
|
||||||
click: function(e){ }, //
|
|
||||||
keydown: function(e){ }, //
|
|
||||||
|
|
||||||
// reactive events for this.data updates
|
|
||||||
myvalue: function(e){ this.el.dom.querySelector('b').innerText = this.data.myvalue },
|
|
||||||
|
|
||||||
launcher: function(){
|
|
||||||
new WinBox( this.manifest.name, {
|
|
||||||
width: '70%',
|
|
||||||
height: '70%',
|
|
||||||
x:"center",
|
|
||||||
y:"center",
|
|
||||||
id: this.el.uid, // important hint for html-mesh
|
|
||||||
root: document.querySelector("#overlay"),
|
|
||||||
mount: this.el.dom,
|
|
||||||
onclose: () => { this.el.dom.style.display = 'none'; return false; }
|
|
||||||
});
|
|
||||||
this.el.dom.style.display = ''
|
|
||||||
this.el.setAttribute("xrf", document.location.search || "https://coderofsalvation.github.io/xrsh-media/assets/background.glb")
|
|
||||||
// navigate with: AFRAME.XRF.navigator.to("https://xrfragment.org/index.glb")
|
|
||||||
},
|
|
||||||
|
|
||||||
DOMready: function( ){
|
|
||||||
this.el.dom.style.display = 'none'
|
|
||||||
console.log("this.el.dom has been added to DOM")
|
|
||||||
this.data.myvalue = 1
|
|
||||||
setInterval( () => this.data.myvalue++, 100 )
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
manifest: { // HTML5 manifest to identify app to xrsh
|
|
||||||
"short_name": "XRSH Manual",
|
|
||||||
"name": "XRSH Manual",
|
|
||||||
"icons": [
|
|
||||||
{
|
|
||||||
"src": "https://css.gg/coffee.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": "Hello world information",
|
|
||||||
"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.
|
|
||||||
`
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
|
@ -1,95 +0,0 @@
|
||||||
AFRAME.registerComponent('vconsole', {
|
|
||||||
schema: {
|
|
||||||
foo: { type:"string"}
|
|
||||||
},
|
|
||||||
|
|
||||||
init: function () {
|
|
||||||
//AFRAME.XRF.navigator.to("https://coderofsalvation.github.io/xrsh-media/assets/background.glb")
|
|
||||||
document.head.innerHTML += `
|
|
||||||
<style type="text/css">
|
|
||||||
.vc-mask { display: none !important }
|
|
||||||
.vc-switch { display: none !important }
|
|
||||||
.vc-panel {
|
|
||||||
right:unset !important;
|
|
||||||
width:100%;
|
|
||||||
max-width:600px;
|
|
||||||
z-index:100 !important;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
`
|
|
||||||
},
|
|
||||||
|
|
||||||
requires:{
|
|
||||||
vconsole: "https://unpkg.com/vconsole@latest/dist/vconsole.min.js"
|
|
||||||
},
|
|
||||||
|
|
||||||
events:{
|
|
||||||
|
|
||||||
// requires are loaded
|
|
||||||
ready: function(e){
|
|
||||||
this.vConsole = new window.VConsole()
|
|
||||||
document.querySelector('.vc-mask').remove()
|
|
||||||
document.querySelector('.vc-switch').remove()
|
|
||||||
},
|
|
||||||
|
|
||||||
launcher: function(){
|
|
||||||
if( !this.vConsole ) return
|
|
||||||
let panel = document.querySelector('.vc-panel')
|
|
||||||
if( panel.style.display == 'none' ) this.vConsole.show()
|
|
||||||
else this.vConsole.hide()
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
manifest: { // HTML5 manifest to identify app to xrsh
|
|
||||||
"short_name": "Hello world",
|
|
||||||
"name": "Hello world",
|
|
||||||
"icons": [
|
|
||||||
{
|
|
||||||
"src": "https://css.gg/border-bottom.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": "Hello world information",
|
|
||||||
"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.
|
|
||||||
`
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
|
@ -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.worker.postMessage.promise({event:'read_file',data: 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.
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
|
@ -18,7 +18,9 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
AFRAME.registerComponent('data2event',{
|
if( !AFRAME.components.data2event ){
|
||||||
|
|
||||||
|
AFRAME.registerComponent('data2event',{
|
||||||
|
|
||||||
init: function(){
|
init: function(){
|
||||||
setTimeout( () => {
|
setTimeout( () => {
|
||||||
|
@ -43,4 +45,6 @@ AFRAME.registerComponent('data2event',{
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
20
com/dom.js
20
com/dom.js
|
@ -32,7 +32,13 @@
|
||||||
* | `DOMready` | self | fired when dom component (`this.dom`) is created |
|
* | `DOMready` | self | fired when dom component (`this.dom`) is created |
|
||||||
*/
|
*/
|
||||||
|
|
||||||
AFRAME.registerComponent('dom',{
|
if( !AFRAME.components.dom ){
|
||||||
|
|
||||||
|
AFRAME.registerComponent('dom',{
|
||||||
|
|
||||||
|
requires: {
|
||||||
|
"requestAnimationFrameXR": "com/requestAnimationFrameXR.js"
|
||||||
|
},
|
||||||
|
|
||||||
init: function(){
|
init: function(){
|
||||||
Object.values(this.el.components)
|
Object.values(this.el.components)
|
||||||
|
@ -53,7 +59,6 @@ AFRAME.registerComponent('dom',{
|
||||||
.assignUniqueID()
|
.assignUniqueID()
|
||||||
.scaleDOMvsXR()
|
.scaleDOMvsXR()
|
||||||
.triggerKeyboardForInputs()
|
.triggerKeyboardForInputs()
|
||||||
.stubRequestAnimationFrame()
|
|
||||||
|
|
||||||
document.querySelector('#overlay').appendChild(this.el.dom)
|
document.querySelector('#overlay').appendChild(this.el.dom)
|
||||||
this.el.emit('DOMready',{el: this.el.dom})
|
this.el.emit('DOMready',{el: this.el.dom})
|
||||||
|
@ -125,12 +130,5 @@ AFRAME.registerComponent('dom',{
|
||||||
return this
|
return this
|
||||||
},
|
},
|
||||||
|
|
||||||
stubRequestAnimationFrame: function(){
|
})
|
||||||
// stub, because WebXR with overrule this (it will not call the callback as expected in immersive mode)
|
}
|
||||||
const requestAnimationFrame = window.requestAnimationFrame
|
|
||||||
window.requestAnimationFrame = (cb) => {
|
|
||||||
setTimeout( cb, 25 )
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
|
@ -57,9 +57,7 @@ AFRAME.registerComponent('helloworld-htmlform', {
|
||||||
const instance = this.el.cloneNode(false)
|
const instance = this.el.cloneNode(false)
|
||||||
this.el.sceneEl.appendChild( instance )
|
this.el.sceneEl.appendChild( instance )
|
||||||
instance.setAttribute("dom", "")
|
instance.setAttribute("dom", "")
|
||||||
instance.setAttribute("xd", "") // allows flipping between DOM/WebGL when toggling XD-button
|
instance.setAttribute("show-texture-in-xr", "") // only show aframe-html in xr
|
||||||
instance.setAttribute("visible", AFRAME.utils.XD() == '3D' ? 'true' : 'false' )
|
|
||||||
instance.setAttribute("position", AFRAME.utils.XD.getPositionInFrontOfCamera(0.5) )
|
|
||||||
instance.setAttribute("grabbable","")
|
instance.setAttribute("grabbable","")
|
||||||
instance.object3D.quaternion.copy( AFRAME.scenes[0].camera.quaternion ) // face towards camera
|
instance.object3D.quaternion.copy( AFRAME.scenes[0].camera.quaternion ) // face towards camera
|
||||||
|
|
||||||
|
@ -75,7 +73,11 @@ AFRAME.registerComponent('helloworld-htmlform', {
|
||||||
root: document.querySelector("#overlay"),
|
root: document.querySelector("#overlay"),
|
||||||
mount: instance.dom,
|
mount: instance.dom,
|
||||||
onclose: () => { instance.dom.style.display = 'none'; return false; },
|
onclose: () => { instance.dom.style.display = 'none'; return false; },
|
||||||
oncreate: () => instance.setAttribute("html",`html:#${instance.uid}; cursor:#cursor`)
|
oncreate: () => {
|
||||||
|
instance.setAttribute("position", AFRAME.utils.XD.getPositionInFrontOfCamera(0.5) )
|
||||||
|
instance.setAttribute("html",`html:#${instance.uid}; cursor:#cursor`)
|
||||||
|
instance.setAttribute("show-texture-in-xr","") // only show aframe-html texture in xr mode
|
||||||
|
}
|
||||||
});
|
});
|
||||||
instance.dom.style.display = AFRAME.utils.XD() == '3D' ? 'none' : '' // show/hide
|
instance.dom.style.display = AFRAME.utils.XD() == '3D' ? 'none' : '' // show/hide
|
||||||
|
|
||||||
|
|
|
@ -87,7 +87,9 @@ AFRAME.registerComponent('helloworld-iframe', {
|
||||||
onclose: () => { instance.dom.style.display = 'none'; return false; },
|
onclose: () => { instance.dom.style.display = 'none'; return false; },
|
||||||
oncreate: () => {
|
oncreate: () => {
|
||||||
com.data.url = URL
|
com.data.url = URL
|
||||||
|
instance.setAttribute("position", AFRAME.utils.XD.getPositionInFrontOfCamera(0.5) )
|
||||||
instance.setAttribute("html",`html:#${instance.uid}; cursor:#cursor`)
|
instance.setAttribute("html",`html:#${instance.uid}; cursor:#cursor`)
|
||||||
|
instance.setAttribute("show-texture-in-xr","") // only show aframe-html texture in xr mode
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
instance.dom.style.display = ''
|
instance.dom.style.display = ''
|
||||||
|
|
|
@ -5,7 +5,7 @@ AFRAME.registerComponent('helloworld-window', {
|
||||||
|
|
||||||
requires: {
|
requires: {
|
||||||
dom: "./com/dom.js", // interpret .dom object
|
dom: "./com/dom.js", // interpret .dom object
|
||||||
xd: "./com/dom.js", // allow switching between 2D/3D
|
xrtexture: "./com/html-as-texture-in-xr.js", // allow switching between 3D/3D
|
||||||
html: "https://unpkg.com/aframe-htmlmesh@2.1.0/build/aframe-html.js", // html to AFRAME
|
html: "https://unpkg.com/aframe-htmlmesh@2.1.0/build/aframe-html.js", // html to AFRAME
|
||||||
winboxjs: "https://unpkg.com/winbox@0.2.82/dist/winbox.bundle.min.js", // deadsimple windows: https://nextapps-de.github.io/winbox
|
winboxjs: "https://unpkg.com/winbox@0.2.82/dist/winbox.bundle.min.js", // deadsimple windows: https://nextapps-de.github.io/winbox
|
||||||
winboxcss: "https://unpkg.com/winbox@0.2.82/dist/css/winbox.min.css", //
|
winboxcss: "https://unpkg.com/winbox@0.2.82/dist/css/winbox.min.css", //
|
||||||
|
@ -40,9 +40,6 @@ AFRAME.registerComponent('helloworld-window', {
|
||||||
const instance = this.el.cloneNode(false)
|
const instance = this.el.cloneNode(false)
|
||||||
this.el.sceneEl.appendChild( instance )
|
this.el.sceneEl.appendChild( instance )
|
||||||
instance.setAttribute("dom", "")
|
instance.setAttribute("dom", "")
|
||||||
instance.setAttribute("xd", "") // allows flipping between DOM/WebGL when toggling XD-button
|
|
||||||
instance.setAttribute("visible", AFRAME.utils.XD() == '3D' ? 'true' : 'false' )
|
|
||||||
instance.setAttribute("position", AFRAME.utils.XD.getPositionInFrontOfCamera(0.5) )
|
|
||||||
instance.setAttribute("grabbable","")
|
instance.setAttribute("grabbable","")
|
||||||
instance.object3D.quaternion.copy( AFRAME.scenes[0].camera.quaternion ) // face towards camera
|
instance.object3D.quaternion.copy( AFRAME.scenes[0].camera.quaternion ) // face towards camera
|
||||||
|
|
||||||
|
@ -59,7 +56,9 @@ AFRAME.registerComponent('helloworld-window', {
|
||||||
root: document.querySelector("#overlay"),
|
root: document.querySelector("#overlay"),
|
||||||
mount: instance.dom,
|
mount: instance.dom,
|
||||||
onclose: () => { instance.dom.style.display = 'none'; return false; },
|
onclose: () => { instance.dom.style.display = 'none'; return false; },
|
||||||
oncreate: () => instance.setAttribute("html",`html:#${instance.uid}; cursor:#cursor`)
|
oncreate: () => {
|
||||||
|
instance.setAttribute("show-texture-in-xr",`domid: #${instance.uid}`) // only show aframe-html texture in xr mode
|
||||||
|
}
|
||||||
});
|
});
|
||||||
instance.dom.style.display = '' // show
|
instance.dom.style.display = '' // show
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,126 @@
|
||||||
|
if( !AFRAME.components['html-as-texture-in-xr'] ){
|
||||||
|
|
||||||
|
AFRAME.registerComponent('html-as-texture-in-xr', {
|
||||||
|
schema: {
|
||||||
|
domid: { type:"string"}
|
||||||
|
},
|
||||||
|
|
||||||
|
dependencies:{
|
||||||
|
html: "https://unpkg.com/aframe-htmlmesh@2.1.0/build/aframe-html.js", // html to AFRAME
|
||||||
|
//html: "https://coderofsalvation.github.io/aframe-htmlmesh/build/aframe-html.js"
|
||||||
|
//html: "com/aframe-html.js"
|
||||||
|
},
|
||||||
|
|
||||||
|
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)
|
||||||
|
this.el.setAttribute("html",`html: ${this.data.domid}; cursor:#cursor; xrlayer: true`)
|
||||||
|
this.el.setAttribute("visible", AFRAME.utils.XD() == '3D' ? 'true' : 'false' )
|
||||||
|
this.el.setAttribute("position", AFRAME.utils.XD.getPositionInFrontOfCamera(0.5) )
|
||||||
|
},
|
||||||
|
|
||||||
|
manifest: { // HTML5 manifest to identify app to xrsh
|
||||||
|
"short_name": "show-texture-in-xr",
|
||||||
|
"name": "2D/3D switcher",
|
||||||
|
"icons": [],
|
||||||
|
"id": "/?source=pwa",
|
||||||
|
"start_url": "/?source=pwa",
|
||||||
|
"background_color": "#3367D6",
|
||||||
|
"display": "standalone",
|
||||||
|
"scope": "/",
|
||||||
|
"theme_color": "#3367D6",
|
||||||
|
"category":"system",
|
||||||
|
"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": "use ESC-key to toggle between 2D / 3D",
|
||||||
|
"screenshots": [
|
||||||
|
{
|
||||||
|
"src": "/images/screenshot1.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "540x720",
|
||||||
|
"form_factor": "narrow"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"help":`
|
||||||
|
Helloworld application
|
||||||
|
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
AFRAME.utils.XD = function(){
|
||||||
|
return document.body.classList.contains('XR') ? '3D' : '2D'
|
||||||
|
}
|
||||||
|
|
||||||
|
AFRAME.utils.XD.toggle = function(state){
|
||||||
|
state = state != undefined ? state : state || !document.body.className.match(/XR/)
|
||||||
|
document.body.classList[ state ? 'add' : 'remove'](['XR'])
|
||||||
|
AFRAME.scenes[0].emit( state ? '3D' : '2D')
|
||||||
|
}
|
||||||
|
|
||||||
|
AFRAME.utils.XD.getPositionInFrontOfCamera = function(distance){
|
||||||
|
const camera = AFRAME.scenes[0].camera;
|
||||||
|
let pos = new THREE.Vector3()
|
||||||
|
let direction = new THREE.Vector3();
|
||||||
|
// Get camera's forward direction (without rotation)
|
||||||
|
camera.getWorldDirection(direction);
|
||||||
|
camera.getWorldPosition(pos)
|
||||||
|
direction.normalize();
|
||||||
|
// Scale the direction by 1 meter
|
||||||
|
if( !distance ) distance = 1.5
|
||||||
|
direction.multiplyScalar(distance);
|
||||||
|
// Add the camera's position to the scaled direction to get the target point
|
||||||
|
pos.add(direction);
|
||||||
|
return pos
|
||||||
|
}
|
||||||
|
|
||||||
|
AFRAME.registerSystem('html-as-texture-in-xr',{
|
||||||
|
|
||||||
|
init: function(){
|
||||||
|
this.sceneEl.addEventListener('enter-vr',() => AFRAME.utils.XD.toggle(true) )
|
||||||
|
this.sceneEl.addEventListener('exit-vr', () => AFRAME.utils.XD.toggle(false) )
|
||||||
|
this.sceneEl.addEventListener('2D', () => this.showElements(false) )
|
||||||
|
this.sceneEl.addEventListener('3D', () => this.showElements(true) )
|
||||||
|
|
||||||
|
document.head.innerHTML += `<style type="text/css">
|
||||||
|
.XR #toggle_overlay{
|
||||||
|
background: transparent;
|
||||||
|
color: #3aacff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
.XR #overlay{
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
</style>`
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
showElements: function(state){
|
||||||
|
let els = [...document.querySelectorAll('[html-as-texture-in-xr]')]
|
||||||
|
els = els.filter( (el) => el != this.el ? el : null ) // filter out self
|
||||||
|
els.map( (el) => el.setAttribute("visible", state ? true : false ) )
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
|
@ -1,52 +1,150 @@
|
||||||
AFRAME.registerComponent('isoterminal', {
|
/*
|
||||||
|
*
|
||||||
|
* css/html template
|
||||||
|
*
|
||||||
|
* ┌─────────┐ ┌────────────┐ ┌─────────────┐ exit-AR
|
||||||
|
* ┌───────►│ com/dom ┼──►│ com/window ├─►│ domrenderer │◄────────── exit-VR ◄─┐
|
||||||
|
* │ └─────────┘ └────────────┘ └─────▲───────┘ │
|
||||||
|
* │ │ ┌───────────────┐ │
|
||||||
|
* ┌──────────┴────────┐ ┌─────┴──────┐ │ xterm.js │ ┌─────────────────────────────┐
|
||||||
|
* │ com/isoterminal ├────────────────────────────►│com/xterm.js│◄─┤ │ │com/html-as-texture-in-XR.js │
|
||||||
|
* └────────┬─┬────────┘ └──┬──────┬▲─┘ │ xterm.css │ └─────────────────────────────┘
|
||||||
|
* │ │ ┌────────┐ ┌─────────▼──┐ ││ └───────────────┘ │ ▲
|
||||||
|
* │ └───────►│ plane ├─────►text───┼►canvas │◄────────────────── enter-VR │ │
|
||||||
|
* │ └────────┘ └────────────┘ ││ enter-AR ◄─┘ │
|
||||||
|
* │ ││ │
|
||||||
|
* │ ││ │
|
||||||
|
* │ ISOTerminal.js ││ │
|
||||||
|
* │ ┌───────────────────────────┐◄────┘│ │
|
||||||
|
* │ │ com/isoterminal/worker.js ├──────┘ │
|
||||||
|
* │ └──────────────┌────────────┤ │
|
||||||
|
* │ │ v86.js │ │
|
||||||
|
* │ │ feat/*.js │ │
|
||||||
|
* │ │ libv86.js │ │
|
||||||
|
* │ └────────────┘ │
|
||||||
|
* │ │
|
||||||
|
* └────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
*
|
||||||
|
* NOTE: For convenience reasons, events are forwarded between com/isoterminal.js, worker.js and ISOTerminal
|
||||||
|
* Instead of a melting pot of different functionnames, events are flowing through everything (ISOTerminal.emit())
|
||||||
|
*/
|
||||||
|
|
||||||
|
if( typeof AFRAME != 'undefined '){
|
||||||
|
|
||||||
|
AFRAME.registerComponent('isoterminal', {
|
||||||
schema: {
|
schema: {
|
||||||
iso: { type:"string", "default":"com/isoterminal/xrsh.iso" },
|
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": 80 },
|
||||||
padding:{ type: 'number',"default": 18 },
|
rows: { type: 'number',"default": 20 },
|
||||||
transparent: { type:'boolean', "default":false } // need good gpu
|
padding: { type: 'number',"default": 18 },
|
||||||
|
minimized: { 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
|
||||||
|
xterm: { type: 'boolean', "default":true }, // use xterm.js? (=slower)
|
||||||
|
memory: { type: 'number', "default":64 }, // VM memory (in MB)
|
||||||
|
bufferLatency: { type: 'number', "default":300 }, // in ms: bufferlatency from webworker to xterm (batch-update every char to texture)
|
||||||
|
canvasLatency: { type: 'number', "default":300 } // in ms: time between canvas re-draws
|
||||||
},
|
},
|
||||||
|
|
||||||
init: function(){
|
init: function(){
|
||||||
this.el.object3D.visible = false
|
this.el.object3D.visible = false
|
||||||
|
this.initHud()
|
||||||
|
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:{
|
||||||
'window': "com/window.js",
|
com: "com/dom.js",
|
||||||
xtermjs: "https://unpkg.com/@xterm/xterm@5.5.0/lib/xterm.js",
|
window: "com/window.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
|
||||||
//axterm: "https://unpkg.com/aframe-xterm-component/aframe-xterm-component.js"
|
xhook: "https://jpillora.com/xhook/dist/xhook.min.js",
|
||||||
|
selfcontain: "com/selfcontainer.js",
|
||||||
|
// html to texture
|
||||||
|
htmlinxr: "com/html-as-texture-in-xr.js",
|
||||||
|
// isoterminal features
|
||||||
|
ISOTerminal: "com/isoterminal/ISOTerminal.js",
|
||||||
|
localforage: "https://cdn.rawgit.com/mozilla/localForage/master/dist/localforage.js"
|
||||||
},
|
},
|
||||||
|
|
||||||
dom: {
|
dom: {
|
||||||
scale: 0.7,
|
//scale: 0.5,
|
||||||
events: ['click','keydown'],
|
events: ['click','keydown'],
|
||||||
html: (me) => `<div class="isoterminal"></div>`,
|
html: (me) => `<div class="isoterminal">
|
||||||
|
</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%;
|
||||||
}
|
}
|
||||||
.isoterminal *{
|
.isoterminal div{
|
||||||
white-space: pre;
|
display:block;
|
||||||
font-size: 14px;
|
position:relative;
|
||||||
font-family: Liberation Mono,DejaVu Sans Mono,Courier New,monospace;
|
}
|
||||||
font-weight:700;
|
@font-face {
|
||||||
display:inline;
|
font-family: 'Cousine';
|
||||||
overflow: hidden;
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
src: url(./com/isoterminal/assets/Cousine.ttf) format('truetype');
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Cousine';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 700;
|
||||||
|
src: url(./com/isoterminal/assets/CousineBold.ttf) format('truetype');
|
||||||
}
|
}
|
||||||
|
|
||||||
.isoterminal style{ display:none }
|
.isoterminal style{ display:none }
|
||||||
|
|
||||||
.wb-body:has(> .isoterminal){
|
#overlay .winbox:has(> .isoterminal){
|
||||||
background: #000c;
|
background:transparent;
|
||||||
overflow:hidden;
|
box-shadow:none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.isoterminal div{ display:block; }
|
.wb-body:has(> .isoterminal){
|
||||||
.isoterminal span{ display: inline }
|
background: #000C;
|
||||||
|
overflow:hidden;
|
||||||
|
border-radius:7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.XR .wb-body:has(> .isoterminal){
|
||||||
|
background: #000;
|
||||||
|
}
|
||||||
|
.isoterminal *,
|
||||||
|
.isoterminal .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;
|
||||||
|
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; }
|
||||||
|
@ -60,273 +158,133 @@ AFRAME.registerComponent('isoterminal', {
|
||||||
to { opacity: 1.0; }
|
to { opacity: 1.0; }
|
||||||
}
|
}
|
||||||
|
|
||||||
.blink {
|
.isoterminal .xterm-rows.xterm-focus .xterm-cursor.xterm-cursor-block,
|
||||||
|
.blink{
|
||||||
animation:fade 1000ms infinite;
|
animation:fade 1000ms infinite;
|
||||||
-webkit-animation:fade 1000ms infinite;
|
-webkit-animation:fade 1000ms infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
`
|
`
|
||||||
},
|
},
|
||||||
|
|
||||||
toUint8Array: function(str) {
|
initTerminal: async function(singleton){
|
||||||
// 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
|
if( this.data.xterm ){
|
||||||
for (let i = 0; i < str.length; i++) {
|
// why 3.12?
|
||||||
uint8Array[i] = str.charCodeAt(i);
|
// 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 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"
|
||||||
}
|
}
|
||||||
return uint8Array;
|
|
||||||
},
|
|
||||||
|
|
||||||
runISO: function(dom,instance){
|
await AFRAME.utils.require(this.requires)
|
||||||
//var term = new Terminal()
|
await AFRAME.utils.require({ // ISOTerminal plugins
|
||||||
//term.open(dom)
|
boot: "com/isoterminal/feat/boot.js",
|
||||||
//term.write('Hello from \x1B[1;3;31mxterm.js\x1B[0m $ ')``
|
javascript: "com/isoterminal/feat/javascript.js",
|
||||||
if( typeof Terminal == undefined ) throw 'xterm terminal not loaded'
|
jsconsole: "com/isoterminal/feat/jsconsole.js",
|
||||||
// monkeypatch Xterm (which V86 initializes) so we can add our own constructor args
|
indexhtml: "com/isoterminal/feat/index.html.js",
|
||||||
window._Terminal = window.Terminal
|
indexjs: "com/isoterminal/feat/index.js.js",
|
||||||
window.Terminal = function(opts){
|
autorestore: "com/isoterminal/feat/autorestore.js",
|
||||||
const term = new window._Terminal({ ...opts,
|
|
||||||
cursorBlink:true,
|
|
||||||
onSelectionChange: function(e){
|
|
||||||
debugger
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
term.onSelectionChange( () => {
|
this.el.setAttribute("selfcontainer","")
|
||||||
document.execCommand('copy')
|
|
||||||
term.select(0, 0, 0)
|
// *DISABLED* instance this component
|
||||||
instance.setStatus('copied to clipboard')
|
// rason: we only need one term for now (more = too cpu heavy)
|
||||||
})
|
let instance
|
||||||
return term
|
if( singleton ){
|
||||||
|
instance = this.el
|
||||||
|
}else{
|
||||||
|
if( this.instance ){
|
||||||
|
const el = document.querySelector('.isoterminal')
|
||||||
|
return console.warn('TODO: allow multiple terminals for future beefy devices(see v86 examples)')
|
||||||
|
}
|
||||||
|
instance = this.instance = this.el.cloneNode(false)
|
||||||
|
this.el.sceneEl.appendChild( instance )
|
||||||
}
|
}
|
||||||
|
|
||||||
instance.setStatus = (msg) => {
|
// init isoterminal
|
||||||
const w = instance.winbox
|
this.isoterminal = new ISOTerminal(instance,this.data)
|
||||||
w.titleBak = w.titleBak || w.title
|
|
||||||
instance.winbox.setTitle( `${w.titleBak} [${msg}]` )
|
|
||||||
}
|
|
||||||
|
|
||||||
var emulator = window.emulator = dom.emulator = new V86({
|
instance.addEventListener('DOMready', () => {
|
||||||
wasm_path: "com/isoterminal/v86.wasm",
|
//instance.setAttribute("html-as-texture-in-xr", `domid: #${this.el.dom.id}`)
|
||||||
memory_size: 32 * 1024 * 1024,
|
//instance.winbox.resize(720,380)
|
||||||
vga_memory_size: 2 * 1024 * 1024,
|
let size = `width: ${Math.floor(this.data.cols*8.65)}; height: ${Math.floor(this.data.rows*21.1)}`
|
||||||
serial_container_xtermjs: dom,
|
instance.setAttribute("window", `title: xrsh.iso; uid: ${instance.uid}; attach: #overlay; dom: #${instance.dom.id}; ${size}; min: ${this.data.minimized}; max: ${this.data.maximized}`)
|
||||||
//screen_container: dom, //this.canvas.parentElement,
|
|
||||||
bios: {
|
|
||||||
url: "com/isoterminal/bios/seabios.bin",
|
|
||||||
},
|
|
||||||
vga_bios: {
|
|
||||||
url: "com/isoterminal/bios/vgabios.bin",
|
|
||||||
},
|
|
||||||
network_relay_url: "wss://relay.widgetry.org/",
|
|
||||||
cdrom: {
|
|
||||||
url: this.data.iso,
|
|
||||||
},
|
|
||||||
cmdline: "rw root=host9p rootfstype=9p rootflags=trans=virtio,cache=loose modules=virtio_pci tsc=reliable init_on_free=on",
|
|
||||||
bzimage:{
|
|
||||||
url: "com/isoterminal/images/buildroot-bzimage.bin"
|
|
||||||
},
|
|
||||||
//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,
|
|
||||||
});
|
|
||||||
|
|
||||||
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 motd = "\n\r"
|
|
||||||
motd += "[38;5;57m " + ' ____ _____________ _________ ___ ___ ' + "\n\r"
|
|
||||||
motd += "[38;5;93m " + ' \\ \\/ /\\______ \\/ _____// | \\ ' + "\n\r"
|
|
||||||
motd += "[38;5;93m " + ' \\ / | _/\\_____ \\/ ~ \\ ' + "\n\r"
|
|
||||||
motd += "[38;5;129m " + ' / \\ | | \\/ \\ Y / ' + "\n\r"
|
|
||||||
motd += "[38;5;165m " + ' /___/\\ \\ |____|_ /_______ /\\___|_ / ' + "\n\r"
|
|
||||||
motd += "[38;5;201m " + ' \\_/ \\/ \\/ \\/ ' + "\n\r"
|
|
||||||
motd += " \n\r"
|
|
||||||
motd += `${loading[ Math.floor(Math.random()*1000) % loading.length-1 ]}, please wait..\n\r\n\r`
|
|
||||||
motd += "\033[0m"
|
|
||||||
|
|
||||||
const files = [
|
|
||||||
"com/isoterminal/mnt/js",
|
|
||||||
"com/isoterminal/mnt/jsh",
|
|
||||||
"com/isoterminal/mnt/confirm",
|
|
||||||
"com/isoterminal/mnt/prompt",
|
|
||||||
"com/isoterminal/mnt/alert",
|
|
||||||
"com/isoterminal/mnt/hook",
|
|
||||||
"com/isoterminal/mnt/xrsh",
|
|
||||||
"com/isoterminal/mnt/profile",
|
|
||||||
"com/isoterminal/mnt/profile.xrsh",
|
|
||||||
"com/isoterminal/mnt/profile.js",
|
|
||||||
"com/isoterminal/mnt/motd",
|
|
||||||
"com/isoterminal/mnt/v86pipe"
|
|
||||||
]
|
|
||||||
|
|
||||||
const redirectConsole = (handler) => {
|
|
||||||
const log = console.log;
|
|
||||||
const dir = console.dir;
|
|
||||||
const err = console.error;
|
|
||||||
const warn = console.warn;
|
|
||||||
console.log = (...args)=>{
|
|
||||||
const textArg = args[0];
|
|
||||||
handler(textArg+'\n');
|
|
||||||
log.apply(log, args);
|
|
||||||
};
|
|
||||||
console.error = (...args)=>{
|
|
||||||
const textArg = args[0].message?args[0].message:args[0];
|
|
||||||
handler( textArg+'\n', '\x1b[31merror\x1b[0m');
|
|
||||||
err.apply(log, args);
|
|
||||||
};
|
|
||||||
console.dir = (...args)=>{
|
|
||||||
const textArg = args[0].message?args[0].message:args[0];
|
|
||||||
handler( JSON.stringify(textArg,null,2)+'\n');
|
|
||||||
dir.apply(log, args);
|
|
||||||
};
|
|
||||||
console.warn = (...args)=>{
|
|
||||||
const textArg = args[0].message?args[0].message:args[0];
|
|
||||||
handler(textArg+'\n','\x1b[38;5;208mwarn\x1b[0m');
|
|
||||||
err.apply(log, args);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
emulator.bus.register("emulator-started", async () => {
|
|
||||||
emulator.serial_adapter.term.element.querySelector('.xterm-viewport').style.background = 'transparent'
|
|
||||||
emulator.serial_adapter.term.clear()
|
|
||||||
emulator.serial_adapter.term.write(motd)
|
|
||||||
|
|
||||||
emulator.create_file("motd", this.toUint8Array(motd) )
|
|
||||||
emulator.create_file("js", this.toUint8Array(`#!/bin/sh
|
|
||||||
cat /mnt/motd
|
|
||||||
cat > /dev/null
|
|
||||||
`))
|
|
||||||
|
|
||||||
redirectConsole( (str,prefix) => {
|
|
||||||
if( emulator.log_to_tty ){
|
|
||||||
prefix = prefix ? prefix+' ' : ' '
|
|
||||||
str.trim().split("\n").map( (line) => {
|
|
||||||
emulator.serial_adapter.term.write( '\r\x1b[38;5;165m/dev/browser: \x1b[0m'+prefix+line+'\n' )
|
|
||||||
})
|
|
||||||
emulator.serial_adapter.term.write( '\r' )
|
|
||||||
}
|
|
||||||
emulator.create_file( "console", this.toUint8Array( str ) )
|
|
||||||
})
|
})
|
||||||
|
|
||||||
let p = files.map( (f) => fetch(f) )
|
instance.addEventListener('window.oncreate', (e) => {
|
||||||
Promise.all(p)
|
instance.dom.classList.add('blink')
|
||||||
.then( (files) => {
|
instance.setAttribute("xterm",`cols: ${this.data.cols}; rows: ${this.data.rows}; canvasLatency: ${this.data.canvasLatency}`)
|
||||||
files.map( (f) => {
|
instance.addEventListener("xterm-input", (e) => this.isoterminal.send(e.detail,0) )
|
||||||
f.arrayBuffer().then( (buf) => {
|
// run iso
|
||||||
emulator.create_file( f.url.replace(/.*mnt\//,''), new Uint8Array(buf) )
|
let opts = {dom:instance.dom}
|
||||||
})
|
for( let i in this.data ) opts[i] = this.data[i]
|
||||||
})
|
this.isoterminal.start(opts)
|
||||||
})
|
})
|
||||||
|
|
||||||
//emulator.serial0_send('chmod +x /mnt/js')
|
instance.setAttribute("dom", "")
|
||||||
//emulator.serial0_send()
|
|
||||||
let line = ''
|
|
||||||
let ready = false
|
|
||||||
emulator.add_listener("serial0-output-byte", async (byte) => {
|
|
||||||
var chr = String.fromCharCode(byte);
|
|
||||||
if(chr < " " && chr !== "\n" && chr !== "\t" || chr > "~")
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(chr === "\n")
|
this.isoterminal.addEventListener('postReady', (e)=>{
|
||||||
{
|
// bugfix: send window dimensions to xterm (xterm.js does that from dom-sizechange to xterm via escape codes)
|
||||||
var new_line = line;
|
let wb = instance.winbox
|
||||||
line = "";
|
if( this.data.maximized ){
|
||||||
}
|
wb.restore()
|
||||||
else if(chr >= " " && chr <= "~")
|
wb.maximize()
|
||||||
{
|
}else wb.resize()
|
||||||
line += chr;
|
})
|
||||||
}
|
|
||||||
|
|
||||||
//if(!ran_command && line.endsWith("~% "))
|
this.isoterminal.addEventListener('ready', (e)=>{
|
||||||
//{
|
|
||||||
// ran_command = true;
|
|
||||||
// emulator.serial0_send("chmod +x /mnt/test-i386\n");
|
|
||||||
// emulator.serial0_send("/mnt/test-i386 > /mnt/result\n");
|
|
||||||
// emulator.serial0_send("echo test fini''shed\n");
|
|
||||||
//}
|
|
||||||
//console.dir({line,new_line})
|
|
||||||
if( !ready && line.match(/^(\/ #|~%)/) ){
|
|
||||||
instance.dom.classList.remove('blink')
|
instance.dom.classList.remove('blink')
|
||||||
// set environment
|
this.isoterminal.emit('status',"running")
|
||||||
let env = ['export BROWSER=1']
|
|
||||||
for ( let i in document.location ){
|
|
||||||
if( typeof document.location[i] == 'string' )
|
|
||||||
env.push( 'export '+String(i).toUpperCase()+'="'+document.location[i]+'"')
|
|
||||||
}
|
|
||||||
env.map( (e) => emulator.serial0_send(`echo '${e}' >> /mnt/profile\n`) )
|
|
||||||
let boot = `source /mnt/profile`
|
|
||||||
// exec hash as extra boot cmd
|
|
||||||
if( document.location.hash.length > 1 ){
|
|
||||||
boot += `&& cmd='${decodeURI(document.location.hash.substr(1))}' && $cmd`
|
|
||||||
}
|
|
||||||
emulator.serial0_send(boot+"\n")
|
|
||||||
instance.winbox.maximize()
|
|
||||||
emulator.serial_adapter.term.focus()
|
|
||||||
ready = true
|
|
||||||
//emulator.serial0_send("root\n")
|
|
||||||
//emulator.serial0_send("mv /mnt/js . && chmod +x js\n")
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// unix to js device
|
|
||||||
emulator.add_listener("9p-write-end", async (opts) => {
|
|
||||||
const decoder = new TextDecoder('utf-8');
|
|
||||||
|
|
||||||
if ( opts[0] == 'js' ){
|
|
||||||
const buf = await emulator.read_file("dev/browser/js")
|
|
||||||
const script = decoder.decode(buf)
|
|
||||||
try{
|
|
||||||
let res = (new Function(`${script}`))()
|
|
||||||
if( res && typeof res != 'string' ) res = JSON.stringify(res,null,2)
|
|
||||||
}catch(e){
|
|
||||||
console.error(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// enable/disable logging file (echo 1 > mnt/console.tty)
|
this.isoterminal.addEventListener('status', function(e){
|
||||||
emulator.add_listener("9p-write-end", async (opts) => {
|
let msg = e.detail
|
||||||
const decoder = new TextDecoder('utf-8');
|
const w = instance.winbox
|
||||||
if ( opts[0] == 'console.tty' ){
|
if(!w) return
|
||||||
const buf = await emulator.read_file("console.tty")
|
w.titleBak = w.titleBak || w.title
|
||||||
const val = decoder.decode(buf)
|
w.setTitle( `${w.titleBak} [${msg}]` )
|
||||||
emulator.log_to_tty = ( String(val).trim() == '1')
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
});
|
instance.addEventListener('window.onclose', (e) => {
|
||||||
|
if( !confirm('do you want to kill this virtual machine and all its processes?') ) e.halt = true
|
||||||
|
})
|
||||||
|
|
||||||
|
const resize = (w,h) => { }
|
||||||
|
instance.addEventListener('window.onresize', resize )
|
||||||
|
instance.addEventListener('window.onmaximize', resize )
|
||||||
|
|
||||||
|
const focus = (showdom) => (e) => {
|
||||||
|
if( this.el.components.xterm ){
|
||||||
|
this.el.components.xterm.term.focus()
|
||||||
|
}
|
||||||
|
if( this.el.components.window ){
|
||||||
|
this.el.components.window.show( showdom )
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.el.addEventListener('obbcollisionstarted', focus(false) )
|
||||||
|
this.el.sceneEl.addEventListener('enter-vr', focus(false) )
|
||||||
|
this.el.sceneEl.addEventListener('enter-ar', focus(false) )
|
||||||
|
this.el.sceneEl.addEventListener('exit-vr', focus(true) )
|
||||||
|
this.el.sceneEl.addEventListener('exit-ar', focus(true) )
|
||||||
|
|
||||||
|
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:{
|
||||||
|
@ -338,89 +296,10 @@ AFRAME.registerComponent('isoterminal', {
|
||||||
// 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(){
|
||||||
if( this.instance ){
|
this.initTerminal()
|
||||||
const el = document.querySelector('.isoterminal')
|
|
||||||
return console.warn('TODO: allow multiple terminals (see v86 examples)')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let s = await AFRAME.utils.require(this.requires)
|
|
||||||
// instance this component
|
|
||||||
const instance = this.instance = this.el.cloneNode(false)
|
|
||||||
this.el.sceneEl.appendChild( instance )
|
|
||||||
|
|
||||||
instance.addEventListener('DOMready', () => {
|
|
||||||
this.runISO(instance.dom, instance)
|
|
||||||
instance.setAttribute("window", `title: ${this.data.iso}; uid: ${instance.uid}; attach: #overlay; dom: #${instance.dom.id}`)
|
|
||||||
})
|
|
||||||
|
|
||||||
instance.addEventListener('window.oncreate', (e) => {
|
|
||||||
instance.dom.classList.add('blink')
|
|
||||||
})
|
|
||||||
|
|
||||||
instance.addEventListener('window.onclose', (e) => {
|
|
||||||
if( !confirm('do you want to kill this virtual machine and all its processes?') ) e.halt = true
|
|
||||||
})
|
|
||||||
|
|
||||||
const resize = (w,h) => {
|
|
||||||
if( instance.dom.emulator && instance.dom.emulator.serial_adapter ){
|
|
||||||
setTimeout( () => {
|
|
||||||
this.autoResize(instance.dom.emulator.serial_adapter.term,instance,-5)
|
|
||||||
},500) // wait for resize anim
|
|
||||||
}
|
|
||||||
}
|
|
||||||
instance.addEventListener('window.onresize', resize )
|
|
||||||
instance.addEventListener('window.onmaximize', resize )
|
|
||||||
|
|
||||||
instance.setAttribute("dom", "")
|
|
||||||
instance.setAttribute("xd", "") // allows flipping between DOM/WebGL when toggling XD-button
|
|
||||||
instance.setAttribute("visible", AFRAME.utils.XD() == '3D' ? 'true' : 'false' )
|
|
||||||
instance.setAttribute("position", AFRAME.utils.XD.getPositionInFrontOfCamera(0.5) )
|
|
||||||
|
|
||||||
const focus = () => document.querySelector('canvas.a-canvas').focus()
|
|
||||||
instance.addEventListener('obbcollisionstarted', focus )
|
|
||||||
this.el.sceneEl.addEventListener('enter-vr', focus )
|
|
||||||
|
|
||||||
instance.object3D.quaternion.copy( AFRAME.scenes[0].camera.quaternion ) // face towards camera
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
autoResize: function(term,instance,rowoffset){
|
|
||||||
if( !term.element ) return
|
|
||||||
|
|
||||||
const defaultScrollWidth = 24;
|
|
||||||
const MINIMUM_COLS = 2;
|
|
||||||
const MINIMUM_ROWS = 2;
|
|
||||||
|
|
||||||
const dims = term._core._renderService.dimensions;
|
|
||||||
const scrollbarWidth = (term.options.scrollback === 0
|
|
||||||
? 0
|
|
||||||
: (term.options.overviewRuler?.width || defaultScrollWidth ));
|
|
||||||
|
|
||||||
const parentElementStyle = window.getComputedStyle(instance.dom);
|
|
||||||
const parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height'));
|
|
||||||
const parentElementWidth = Math.max(0, parseInt(parentElementStyle.getPropertyValue('width')));
|
|
||||||
const elementStyle = window.getComputedStyle(term.element);
|
|
||||||
const elementPadding = {
|
|
||||||
top: parseInt(elementStyle.getPropertyValue('padding-top')),
|
|
||||||
bottom: parseInt(elementStyle.getPropertyValue('padding-bottom')),
|
|
||||||
right: parseInt(elementStyle.getPropertyValue('padding-right')),
|
|
||||||
left: parseInt(elementStyle.getPropertyValue('padding-left'))
|
|
||||||
};
|
|
||||||
const elementPaddingVer = elementPadding.top + elementPadding.bottom;
|
|
||||||
const elementPaddingHor = elementPadding.right + elementPadding.left;
|
|
||||||
const availableHeight = parentElementHeight - elementPaddingVer;
|
|
||||||
const availableWidth = parentElementWidth - elementPaddingHor - scrollbarWidth;
|
|
||||||
const geometry = {
|
|
||||||
cols: Math.max(MINIMUM_COLS, Math.floor(availableWidth / dims.css.cell.width)),
|
|
||||||
rows: Math.max(MINIMUM_ROWS, Math.floor(availableHeight / dims.css.cell.height))
|
|
||||||
};
|
|
||||||
term.resize(geometry.cols, geometry.rows + (rowoffset||0) );
|
|
||||||
},
|
},
|
||||||
|
|
||||||
manifest: { // HTML5 manifest to identify app to xrsh
|
manifest: { // HTML5 manifest to identify app to xrsh
|
||||||
|
@ -467,13 +346,14 @@ AFRAME.registerComponent('isoterminal', {
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"help":`
|
"help":`
|
||||||
Helloworld application
|
Helloworld application
|
||||||
|
|
||||||
This is a help file which describes the application.
|
This is a help file which describes the application.
|
||||||
It will be rendered thru troika text, and will contain
|
It will be rendered thru troika text, and will contain
|
||||||
headers based on non-punctualized lines separated by linebreaks,
|
headers based on non-punctualized lines separated by linebreaks,
|
||||||
in above's case "\nHelloworld application\n" will qualify as header.
|
in above's case "\nHelloworld application\n" will qualify as header.
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,318 @@
|
||||||
|
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
|
||||||
|
}
|
Binary file not shown.
|
@ -0,0 +1,16 @@
|
||||||
|
--2024-09-23 17:49:04-- https://fonts.gstatic.com/s/cousine/v27/d6lNkaiiRdih4SpP9Z8K6T4.ttf
|
||||||
|
Resolving fonts.gstatic.com (fonts.gstatic.com)... 142.250.180.195, 2a00:1450:400d:806::2003
|
||||||
|
Connecting to fonts.gstatic.com (fonts.gstatic.com)|142.250.180.195|:443... connected.
|
||||||
|
HTTP request sent, awaiting response... 200 OK
|
||||||
|
Length: 287924 (281K) [font/ttf]
|
||||||
|
Saving to: ‘d6lNkaiiRdih4SpP9Z8K6T4.ttf’
|
||||||
|
|
||||||
|
0K .......... .......... .......... .......... .......... 17% 2.31M 0s
|
||||||
|
50K .......... .......... .......... .......... .......... 35% 1.91M 0s
|
||||||
|
100K .......... .......... .......... .......... .......... 53% 9.09M 0s
|
||||||
|
150K .......... .......... .......... .......... .......... 71% 3.03M 0s
|
||||||
|
200K .......... .......... .......... .......... .......... 88% 3.40M 0s
|
||||||
|
250K .......... .......... .......... . 100% 12.4M=0.09s
|
||||||
|
|
||||||
|
2024-09-23 17:49:05 (3.23 MB/s) - ‘d6lNkaiiRdih4SpP9Z8K6T4.ttf’ saved [287924/287924]
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
let emulator = this.emulator
|
||||||
|
let me = this
|
||||||
|
|
||||||
|
emulator.fs9p.update_file = async function(file,data){
|
||||||
|
const convert = ISOTerminal.prototype.convert
|
||||||
|
|
||||||
|
const p = this.SearchPath(file);
|
||||||
|
|
||||||
|
if(p.id === -1)
|
||||||
|
{
|
||||||
|
return emulator.create_file(file,data)
|
||||||
|
}
|
||||||
|
|
||||||
|
const inode = this.GetInode(p.id);
|
||||||
|
const buf = typeof data == 'string' ? convert.toUint8Array(data) : data
|
||||||
|
await this.Write(p.id,0, buf.length, buf )
|
||||||
|
// update inode
|
||||||
|
inode.size = buf.length
|
||||||
|
const now = Math.round(Date.now() / 1000);
|
||||||
|
inode.atime = inode.mtime = now;
|
||||||
|
me.postMessage({event:'exec',data:[`touch /mnt/${file}`]}) // update inode
|
||||||
|
return new Promise( (resolve,reject) => resolve(buf) )
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
emulator.fs9p.append_file = async function(file,data){
|
||||||
|
const convert = ISOTerminal.prototype.convert
|
||||||
|
|
||||||
|
const p = this.SearchPath(file);
|
||||||
|
|
||||||
|
if(p.id === -1)
|
||||||
|
{
|
||||||
|
return Promise.resolve(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
const inode = this.GetInode(p.id);
|
||||||
|
const buf = typeof data == 'string' ? convert.toUint8Array(data) : data
|
||||||
|
await this.Write(p.id, inode.size, buf.length, buf )
|
||||||
|
// update inode
|
||||||
|
inode.size = inode.size + buf.length
|
||||||
|
const now = Math.round(Date.now() / 1000);
|
||||||
|
inode.atime = inode.mtime = now;
|
||||||
|
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]) }
|
|
@ -0,0 +1,63 @@
|
||||||
|
if( typeof emulator != 'undefined' ){
|
||||||
|
// 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)
|
||||||
|
})
|
||||||
|
|
||||||
|
ISOTerminal.prototype.autorestore = async function(e){
|
||||||
|
|
||||||
|
localforage.setDriver([
|
||||||
|
localforage.INDEXEDDB,
|
||||||
|
localforage.WEBSQL,
|
||||||
|
localforage.LOCALSTORAGE
|
||||||
|
]).then( () => {
|
||||||
|
|
||||||
|
localforage.getItem("state", async (err,stateBase64) => {
|
||||||
|
if( stateBase64 && !err && confirm('continue last session?') ){
|
||||||
|
this.noboot = true // see feat/boot.js
|
||||||
|
state = this.convert.base64ToArrayBuffer( stateBase64 )
|
||||||
|
|
||||||
|
this.addEventListener('state_restored', function(){
|
||||||
|
// simulate / fastforward boot events
|
||||||
|
this.postBoot( () => this.send("l\n") )
|
||||||
|
})
|
||||||
|
|
||||||
|
this.worker.postMessage({event:'emulator.restore_state',data:state})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
this.save = async () => {
|
||||||
|
const state = await this.worker.postMessage({event:"emulator.save_state",data:false})
|
||||||
|
}
|
||||||
|
|
||||||
|
this.addEventListener('state_saved', function(e){
|
||||||
|
const state = e.detail
|
||||||
|
localforage.setItem("state", this.convert.arrayBufferToBase64(state) )
|
||||||
|
console.log("state saved")
|
||||||
|
})
|
||||||
|
|
||||||
|
window.addEventListener("beforeunload", function (e) {
|
||||||
|
var confirmationMessage = "Sure you want to leave?\nTIP: enter 'save' to continue this session later";
|
||||||
|
(e || window.event).returnValue = confirmationMessage; //Gecko + IE
|
||||||
|
return confirmationMessage; //Webkit, Safari, Chrome
|
||||||
|
});
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
ISOTerminal.addEventListener('ready', function(e){
|
||||||
|
setTimeout( () => this.boot(), 50 ) // because of autorestore.js
|
||||||
|
})
|
||||||
|
|
||||||
|
ISOTerminal.prototype.boot = async function(e){
|
||||||
|
// set environment
|
||||||
|
let env = ['export BROWSER=1']
|
||||||
|
for ( let i in document.location ){
|
||||||
|
if( typeof document.location[i] == 'string' ){
|
||||||
|
env.push( 'export '+String(i).toUpperCase()+'="'+decodeURIComponent( document.location[i]+'"') )
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await this.emit("emulator.create_file", ["profile.browser", this.convert.toUint8Array( env.join('\n') ) ] )
|
||||||
|
|
||||||
|
if( this.serial_input == 0 ){
|
||||||
|
if( !this.noboot ){
|
||||||
|
this.send("source /etc/profile # \\o/ FOSS powa!\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
|
||||||
|
if( typeof emulator != 'undefined' ){
|
||||||
|
// inside worker-thread
|
||||||
|
|
||||||
|
// unix to js device
|
||||||
|
this.emulator.readFromPipe( 'root/index.html', async (data) => {
|
||||||
|
const buf = await emulator.read_file("root/index.html")
|
||||||
|
const decoder = new TextDecoder('utf-8');
|
||||||
|
const html = decoder.decode(buf).replace(/^#!\/bin\/html/,'') // remove leftover shebangs if any
|
||||||
|
try{
|
||||||
|
this.postMessage({event:'runHTML',data:[html]})
|
||||||
|
}catch(e){
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
}else{
|
||||||
|
// inside browser-thread
|
||||||
|
|
||||||
|
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,30 @@
|
||||||
|
if( typeof emulator != 'undefined' ){
|
||||||
|
// inside worker-thread
|
||||||
|
|
||||||
|
// unix to js device
|
||||||
|
this.emulator.readFromPipe( 'root/index.js', async (data) => {
|
||||||
|
const buf = await emulator.read_file("root/index.js")
|
||||||
|
const decoder = new TextDecoder('utf-8');
|
||||||
|
const js = decoder.decode(buf).replace(/^#!\/bin\/js/,'') // remove leftover shebangs if any
|
||||||
|
try{
|
||||||
|
this.postMessage({event:'runJavascript',data:[js]})
|
||||||
|
}catch(e){
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
}else{
|
||||||
|
// inside browser-thread
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
if( typeof emulator != 'undefined' ){
|
||||||
|
// inside worker-thread
|
||||||
|
|
||||||
|
// unix to js device
|
||||||
|
emulator.readFromPipe( 'dev/browser/js', async (data) => {
|
||||||
|
const convert = ISOTerminal.prototype.convert
|
||||||
|
const buf = await this.emulator.read_file("dev/browser/js")
|
||||||
|
const script = convert.Uint8ArrayToString(buf)
|
||||||
|
let PID="?"
|
||||||
|
try{
|
||||||
|
if( script.match(/^PID/) ){
|
||||||
|
PID = script.match(/^PID=([0-9]+);/)[1]
|
||||||
|
}
|
||||||
|
this.postMessage({event:'javascript-eval',data:{script,PID}})
|
||||||
|
}catch(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)] )
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
ISOTerminal.prototype.redirectConsole = function(handler){
|
||||||
|
const log = console.log;
|
||||||
|
const dir = console.dir;
|
||||||
|
const err = console.error;
|
||||||
|
const warn = console.warn;
|
||||||
|
console.log = (...args)=>{
|
||||||
|
const textArg = args[0];
|
||||||
|
handler(textArg+'\n');
|
||||||
|
log.apply(log, args);
|
||||||
|
};
|
||||||
|
console.error = (...args)=>{
|
||||||
|
const textArg = args[0].message?args[0].message:args[0];
|
||||||
|
handler( textArg+'\n', '\x1b[31merror\x1b[0m');
|
||||||
|
err.apply(log, args);
|
||||||
|
};
|
||||||
|
console.dir = (...args)=>{
|
||||||
|
const textArg = args[0].message?args[0].message:args[0];
|
||||||
|
handler( JSON.stringify(textArg,null,2)+'\n');
|
||||||
|
dir.apply(log, args);
|
||||||
|
};
|
||||||
|
console.warn = (...args)=>{
|
||||||
|
const textArg = args[0].message?args[0].message:args[0];
|
||||||
|
handler(textArg+'\n','\x1b[38;5;208mwarn\x1b[0m');
|
||||||
|
err.apply(log, args);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ISOTerminal.addEventListener('emulator-started', function(){
|
||||||
|
|
||||||
|
this.redirectConsole( (str,prefix) => {
|
||||||
|
let finalStr = ""
|
||||||
|
prefix = prefix ? prefix+' ' : ' '
|
||||||
|
str.trim().split("\n").map( (line) => {
|
||||||
|
finalStr += '\x1b[38;5;165m/dev/browser: \x1b[0m'+prefix+line+'\n'
|
||||||
|
})
|
||||||
|
this.emit('fs9p.append_file', ["/dev/browser/console",finalStr])
|
||||||
|
})
|
||||||
|
|
||||||
|
window.addEventListener('error', function(event) {
|
||||||
|
if( event.filename ){
|
||||||
|
console.error(event.filename+":"+event.lineno+":"+event.colno)
|
||||||
|
console.error(event.message);
|
||||||
|
console.error(event.error);
|
||||||
|
}else console.error(event)
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('unhandledrejection', function(event) {
|
||||||
|
console.error('Unhandled promise rejection:')
|
||||||
|
console.error(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)
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,120 @@
|
||||||
|
ISOTerminal.addEventListener('init', function(){
|
||||||
|
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(){
|
||||||
|
this.serial_input = 0 // set input to serial line 0
|
||||||
|
let isoterm = this
|
||||||
|
// monkeypatch Xterm (which V86 initializes) so we can add our own constructor args
|
||||||
|
window._Terminal = window.Terminal
|
||||||
|
window.Terminal = function(opts){
|
||||||
|
const term = new window._Terminal({ ...opts,
|
||||||
|
cursorBlink:true,
|
||||||
|
onSelectionChange: function(e){ console.log("selectchange") },
|
||||||
|
letterSpacing: 0
|
||||||
|
})
|
||||||
|
|
||||||
|
term.onSelectionChange( () => {
|
||||||
|
document.execCommand('copy')
|
||||||
|
term.select(0, 0, 0)
|
||||||
|
isoterm.emit('status','copied to clipboard')
|
||||||
|
})
|
||||||
|
|
||||||
|
term.onRender( () => {
|
||||||
|
// xterm relies on requestAnimationFrame (which does not called in immersive mode)
|
||||||
|
let _window = term._core._coreBrowserService._window
|
||||||
|
if( !_window._XRSH_proxied ){ // patch the planet!
|
||||||
|
|
||||||
|
//_window.requestAnimationFrameAFRAME = function(cb){
|
||||||
|
// if( term.tid != null ) clearTimeout(term.tid)
|
||||||
|
// term.tid = setTimeout( function(){
|
||||||
|
// console.log("render")
|
||||||
|
// cb()
|
||||||
|
// term.tid = null
|
||||||
|
// },100)
|
||||||
|
//}
|
||||||
|
this.i = 0
|
||||||
|
const requestAnimationFrameAFRAME = AFRAME.utils.throttleLeadingAndTrailing(
|
||||||
|
function(cb){ cb() }
|
||||||
|
,150
|
||||||
|
)
|
||||||
|
|
||||||
|
// we proxy the _window object of xterm, and reroute
|
||||||
|
// requestAnimationFrame to requestAnimationFrameAFRAME
|
||||||
|
_window_new = new Proxy(_window,{
|
||||||
|
get(me,k){
|
||||||
|
if( k == '_XRSH_proxied' ) return true
|
||||||
|
if( k == 'requestAnimationFrame' ){
|
||||||
|
return requestAnimationFrameAFRAME.bind(me)
|
||||||
|
}
|
||||||
|
return typeof me[k] == 'function' ? me[k].bind(me) : me[k]
|
||||||
|
},
|
||||||
|
set(me,k,v){
|
||||||
|
me[k] = v
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
term._core._coreBrowserService._window = _window_new
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
return term
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
this.addEventListener('emulator-started', function(){
|
||||||
|
this.emulator.serial_adapter.term.element.querySelector('.xterm-viewport').style.background = 'transparent'
|
||||||
|
// toggle immersive with ESCAPE
|
||||||
|
//document.body.addEventListener('keydown', (e) => e.key == 'Escape' && this.emulator.serial_adapter.term.blur() )
|
||||||
|
})
|
||||||
|
|
||||||
|
const resize = (w,h) => {
|
||||||
|
setTimeout( () => {
|
||||||
|
if( isoterm?.emulator?.serial_adapter?.term ){
|
||||||
|
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){
|
||||||
|
if( !term.element ) return
|
||||||
|
|
||||||
|
const defaultScrollWidth = 24;
|
||||||
|
const MINIMUM_COLS = 2;
|
||||||
|
const MINIMUM_ROWS = 2;
|
||||||
|
|
||||||
|
const dims = term._core._renderService.dimensions;
|
||||||
|
const scrollbarWidth = (term.options.scrollback === 0
|
||||||
|
? 0
|
||||||
|
: (term.options.overviewRuler?.width || defaultScrollWidth ));
|
||||||
|
|
||||||
|
const parentElementStyle = window.getComputedStyle(instance.dom);
|
||||||
|
const parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height'));
|
||||||
|
const parentElementWidth = Math.max(0, parseInt(parentElementStyle.getPropertyValue('width')));
|
||||||
|
const elementStyle = window.getComputedStyle(term.element);
|
||||||
|
const elementPadding = {
|
||||||
|
top: parseInt(elementStyle.getPropertyValue('padding-top')),
|
||||||
|
bottom: parseInt(elementStyle.getPropertyValue('padding-bottom')),
|
||||||
|
right: parseInt(elementStyle.getPropertyValue('padding-right')),
|
||||||
|
left: parseInt(elementStyle.getPropertyValue('padding-left'))
|
||||||
|
};
|
||||||
|
const elementPaddingVer = elementPadding.top + elementPadding.bottom;
|
||||||
|
const elementPaddingHor = elementPadding.right + elementPadding.left;
|
||||||
|
const availableHeight = parentElementHeight - elementPaddingVer;
|
||||||
|
const availableWidth = parentElementWidth - elementPaddingHor - scrollbarWidth;
|
||||||
|
const geometry = {
|
||||||
|
cols: Math.max(MINIMUM_COLS, Math.floor(availableWidth / dims.css.cell.width)),
|
||||||
|
rows: Math.max(MINIMUM_ROWS, Math.floor(availableHeight / dims.css.cell.height))
|
||||||
|
};
|
||||||
|
term.resize(geometry.cols, geometry.rows + (rowoffset||0) );
|
||||||
|
}
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
|
@ -1,6 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
title=$1
|
|
||||||
shift
|
|
||||||
msg="$*"
|
|
||||||
echo "[38;5;165m$title $(printf "\033[0m")$msg"
|
|
||||||
hook alert $title "$msg"
|
|
|
@ -1,4 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
read -p "$(printf "\033[0m")[?] [38;5;165m$1 [y/n] $(printf "\033[0m")" y
|
|
||||||
test $y = y && echo true && exit
|
|
||||||
echo false
|
|
|
@ -1,11 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
|
|
||||||
test -z $1 && { echo "usage: hook <cmd_or_jsfunction> [args]"; exit 0; }
|
|
||||||
cmd=$1
|
|
||||||
shift
|
|
||||||
|
|
||||||
test -d ~/hook.d/$cmd && {
|
|
||||||
find ~/hook.d/$cmd/ -type f -executable | while read hook; do
|
|
||||||
{ $hook "$@" || true; } | awk '{ gsub(/\/root\/\//,"",$1); $1 = sprintf("%-40s", $1)} 1'
|
|
||||||
done
|
|
||||||
}
|
|
|
@ -1,16 +1,28 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
PID=$$
|
||||||
test -z "$1" && { echo "Usage: js 'somefunction(1)'"; exit 0; }
|
test -z "$1" && { echo "Usage: js 'somefunction(1)'"; exit 0; }
|
||||||
|
|
||||||
test -n "$BROWSER" || { alert warning "/dev/browser not active (are you running outside of v86?)"; }
|
test -n "$BROWSER" || { alert warning "/dev/browser not active (are you running outside of v86?)"; }
|
||||||
javascript="$*"
|
javascript="$*"
|
||||||
|
|
||||||
# if we are run as shebang, use the file as input
|
# if we are run as shebang, use the file as input
|
||||||
case "$1" in
|
test -f "$1" && {
|
||||||
*/*) javascript="args = String('$*').split(' '); $(cat $1 | tail +2)"
|
javascript="args = String('$*').split(' '); $(cat $1 | tail +2)"
|
||||||
;;
|
}
|
||||||
esac
|
|
||||||
|
# below is not ideal
|
||||||
|
# first I tried /dev/ttyS* https://github.com/copy/v86/issues/530
|
||||||
|
# and differentiate processes by prefixing output by PID's
|
||||||
|
# to parse it later with AWK, but it was very hairy
|
||||||
|
|
||||||
|
OUTPUT=/mnt/$PID
|
||||||
|
echo -n "PID=$PID; $javascript" > /dev/browser/js
|
||||||
|
sleep 0.1
|
||||||
|
test -f $OUTPUT && {
|
||||||
|
cat $OUTPUT
|
||||||
|
rm $OUTPUT
|
||||||
|
}
|
||||||
|
|
||||||
echo -n "$javascript" > /dev/browser/js
|
|
||||||
|
|
||||||
# should we use flock, an awesome way to make processes read/write the same file
|
# should we use flock, an awesome way to make processes read/write the same file
|
||||||
# while preventing 1001 concurrency issues?
|
# while preventing 1001 concurrency issues?
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
#
|
#
|
||||||
# 'jsh prompt question answer' executes: js prompt('question','answer') )
|
# 'jsh prompt question answer' executes: js prompt('question','answer') )
|
||||||
|
|
||||||
|
source /mnt/profile.sh
|
||||||
|
|
||||||
to_js(){
|
to_js(){
|
||||||
printf "%s(" "$1"
|
printf "%s(" "$1"
|
||||||
shift
|
shift
|
||||||
|
@ -23,7 +25,7 @@ to_js(){
|
||||||
test -z "$1" || {
|
test -z "$1" || {
|
||||||
func=$(to_js "$@")
|
func=$(to_js "$@")
|
||||||
func=${func/,)/)}
|
func=${func/,)/)}
|
||||||
js "$func"
|
js "return $func"
|
||||||
hook "$@"
|
hook "$@"
|
||||||
exit 0
|
exit 0
|
||||||
}
|
}
|
||||||
|
@ -33,7 +35,7 @@ echo "jsh> type 'exit' or CTRL-C to quit"
|
||||||
echo "jsh> HINT: to run alert('foo') outside this REPL, run 'jsh alert foo'"
|
echo "jsh> HINT: to run alert('foo') outside this REPL, run 'jsh alert foo'"
|
||||||
echo "jsh>"
|
echo "jsh>"
|
||||||
while true; do
|
while true; do
|
||||||
echo -n -e "\r$(printf "\033[0m")jsh> $(printf "\033[0m")"
|
echo -n -e "\n$(printf "\033[0m")jsh> $(printf "\033[0m")"
|
||||||
read line
|
read line
|
||||||
test "$line" = exit && exit
|
test "$line" = exit && exit
|
||||||
js "$line"
|
js "$line"
|
||||||
|
|
|
@ -5,11 +5,12 @@
|
||||||
[38;5;129m _ _ / \ | | \/ \ Y / _ _ _ _ _ _ _
|
[38;5;129m _ _ / \ | | \/ \ Y / _ _ _ _ _ _ _
|
||||||
[38;5;165m . . /___/\ \ |____|_ /_______ /\___|_ /. . . . . . . .
|
[38;5;165m . . /___/\ \ |____|_ /_______ /\___|_ /. . . . . . . .
|
||||||
[38;5;201m . . . . . .\_/. . . . \/ . . . .\/ . . _ \/ . . . . . . . .
|
[38;5;201m . . . . . .\_/. . . . \/ . . . .\/ . . _ \/ . . . . . . . .
|
||||||
[38;5;165m ▬ ▬ ▬ https://xrsh.isvery.ninja ▬ ▬ ▬ ▬ ▬ ▬ ▬ ▬ ▬ ▬ ▬ ▬ ▬ ▬
|
[38;5;165m ▬▬▬▬ https://xrsh.isvery.ninja ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
|
||||||
[38;5;93m . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
|
|
||||||
|
|
||||||
[38;5;165mOpen, local-first, hackable & selfcontained XR apps.
|
[38;5;165mOpen, local-first, unix hackable & selfcontained XR apps.
|
||||||
|
|
||||||
credits: all FOSS devs | @lvk@mastodon.online
|
credits: NLnet | @nlnet@nlnet.nl https://nlnet.nl/project
|
||||||
copy.sh (v86) | @utopiah@mastodon.pirateparty.be
|
all FOSS devs | copy.sh (v86) aframe.io (AFRAME)
|
||||||
|
Leon van Kammen | @lvk@mastodon.online
|
||||||
|
Fabien Benetou | @utopiah@mastodon.pirateparty.be
|
||||||
|
|
||||||
|
|
|
@ -1,22 +1,41 @@
|
||||||
# install xrsh env
|
# source URL data from v86 (file created by v86 during boot)
|
||||||
|
test -f /mnt/profile.browser && source /mnt/profile.browser
|
||||||
|
|
||||||
|
# source xrsh env
|
||||||
source /mnt/profile.xrsh
|
source /mnt/profile.xrsh
|
||||||
|
|
||||||
|
# source shell functions
|
||||||
|
source /mnt/profile.sh
|
||||||
|
|
||||||
|
# source js functions
|
||||||
|
./.profile.js
|
||||||
|
|
||||||
## forward not-found commands to javascript (via jsh)
|
## forward not-found commands to javascript (via jsh)
|
||||||
command_not_found_handle(){
|
command_not_found_handle(){
|
||||||
echo "$1 not found, did you mean $1(...) (javascript?)"
|
echo "[!] '$1' not found, did you mean $1(...) (javascript?)"
|
||||||
alert '[XRSH TIPS]'
|
test -n "$ONBOARDING" && echo "[i] type 'help' for handy commands"
|
||||||
alert 'js console: ' "type 'jsh'"
|
test -n "$ONBOARDING" || help
|
||||||
alert 'js shellfunction:' "type 'alias $1=\"jsh $1\"' to run '$1 yo' as $1('yo')"
|
|
||||||
alert 'js logging: ' "type 'echo 0 > /dev/browser/console.tty' to disable"
|
|
||||||
alert 'js capture log: ' "type 'tail -f /dev/browser/console'"
|
|
||||||
alert 'jsh<->sh hooks: ' "type 'chmod +x ~/hook.d/alert/* && alert helloworld'"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# source javascript functions
|
help(){
|
||||||
#js "$(cat /mnt/profile.js)"
|
echo ""
|
||||||
|
echo 'TIPS'
|
||||||
|
echo '----'
|
||||||
|
echo 'js run ' "type 'js 'alert(\"hello\")'"
|
||||||
|
echo 'js console.log: ' "type 'console document.baseURI"
|
||||||
|
echo 'js function as cmd: ' "type 'alias $1=\"jsh $1\"' to run '$1 yo' as $1('yo')"
|
||||||
|
echo 'js inspect: ' "type 'js \"return document.baseURI\"'"
|
||||||
|
echo 'js console disable: ' "type 'echo 0 > /dev/browser/console.tty' to enable"
|
||||||
|
echo 'js capture console: ' "type 'tail -f /dev/browser/console'"
|
||||||
|
echo 'jsh<->sh hooks: ' "type 'chmod +x ~/hook.d/*/* && alert helloworld'"
|
||||||
|
echo 'include file into page' "type 'require <url.js|css>'"
|
||||||
|
echo 'create AFRAME a-entity' "type 'a_entity <componentname> [...]"
|
||||||
|
ONBOARDING=1
|
||||||
|
}
|
||||||
|
|
||||||
resize
|
resize
|
||||||
#clear
|
test $HOSTNAME = localhost || clear
|
||||||
cat /mnt/motd
|
cat /mnt/motd
|
||||||
export PATH=$PATH:/mnt
|
export PATH=$PATH:/mnt:~/bin
|
||||||
export PS1="\n[38;5;57mx[38;5;93mr[38;5;129ms[38;5;165mh [38;5;201m# \033[0m"
|
export PS1="\n\[\033[38;5;57m\]x\[\033[38;5;93m\]r\[\033[38;5;129m\]s\[\033[38;5;165m\]h \[\033[38;5;201m\]# \[\033[0m\]"
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
#!/bin/js
|
||||||
|
|
||||||
window.helloworld = function(){
|
window.helloworld = function(){
|
||||||
alert("hello world")
|
alert("hello world")
|
||||||
|
return "hello world"
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
hook(){
|
||||||
|
test -z "$1" && { echo "usage: hook <cmd_or_jsfunction> [args]"; return 0; }
|
||||||
|
cmd=$1
|
||||||
|
shift
|
||||||
|
test -d ~/hook.d/$cmd && {
|
||||||
|
find ~/hook.d/$cmd/ -type f -executable | while read hook; do
|
||||||
|
{ $hook "$@" || true; } | awk '{ gsub(/\/root\/\//,"",$1); $1 = sprintf("%-40s", $1)} 1'
|
||||||
|
done
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
alert(){
|
||||||
|
test -z "$1" && { echo "usage: alert <title> <message>"; return 0; }
|
||||||
|
title=$1
|
||||||
|
test -z "$1" || shift
|
||||||
|
msg="$*"
|
||||||
|
printf "[38;5;165m%s \033[0m%s\n" "$title" "$msg"
|
||||||
|
hook alert $title "$msg"
|
||||||
|
}
|
||||||
|
|
||||||
|
confirm(){
|
||||||
|
test -z "$1" && { echo "usage: confirm <question>"; return 0; }
|
||||||
|
read -p "$(printf "\033[0m")[?] [38;5;165m$1 [y/n] $(printf "\033[0m")" y
|
||||||
|
test $y = y && echo true && return 0
|
||||||
|
test $y = y || echo false
|
||||||
|
hook confirm $1 $y
|
||||||
|
}
|
||||||
|
|
||||||
|
prompt(){
|
||||||
|
test -z "$1" && { echo "usage: prompt <question> [answer_default]"; return 0; }
|
||||||
|
test -n "$2" && answer="[$2] " && answer_fallback="$2"
|
||||||
|
read -p "$(printf "\033[0m")[?] [38;5;165m$1: $answer $(printf "\033[0m")" answer
|
||||||
|
test -z "$answer" && answer="$answer_fallback"
|
||||||
|
echo "$answer"
|
||||||
|
hook prompt $1 $answer
|
||||||
|
}
|
||||||
|
|
||||||
|
console(){
|
||||||
|
js 'return '$1
|
||||||
|
}
|
||||||
|
|
||||||
|
require(){
|
||||||
|
file=$(basename "$1")
|
||||||
|
js '(async () => {
|
||||||
|
await AFRAME.utils.require({"'$file'": "'$1'"})
|
||||||
|
})()'
|
||||||
|
}
|
||||||
|
|
||||||
|
a_entity(){
|
||||||
|
code="let el = document.createElement('a-entity')"
|
||||||
|
for i in "$@"; do
|
||||||
|
code="$code;\nel.setAttribute('$i','')\n";
|
||||||
|
done
|
||||||
|
code="document.querySelector('a-scene').appendChild(el)"
|
||||||
|
echo "$code"
|
||||||
|
js "$code"
|
||||||
|
}
|
|
@ -3,39 +3,53 @@
|
||||||
test -d /dev/browser || {
|
test -d /dev/browser || {
|
||||||
|
|
||||||
setup_binaries(){
|
setup_binaries(){
|
||||||
for bin in /mnt/prompt /mnt/alert /mnt/confirm /mnt/hook /mnt/js* /mnt/v86pipe /mnt/xrsh; do
|
for bin in /mnt/js* /mnt/v86pipe /mnt/xrsh; do
|
||||||
chmod +x $bin
|
chmod +x $bin
|
||||||
ln -s $bin /bin/.
|
ln -s $bin /bin/.
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setup_links(){
|
||||||
|
ln -s /mnt/profile ~/.profile
|
||||||
|
ln -s /mnt/profile.js ~/.profile.js
|
||||||
|
ln -s /mnt/profile.browser ~/.profile.browser
|
||||||
|
ln -s /mnt/profile.sh ~/.profile.sh
|
||||||
|
ln -s /mnt/motd ~/.motd
|
||||||
|
ln -s ~/.profile.js ~/index.js
|
||||||
|
chmod +x ~/.profile.js
|
||||||
|
}
|
||||||
|
|
||||||
setup_browser_dev(){
|
setup_browser_dev(){
|
||||||
mkdir -p /mnt/dev/browser
|
mkdir -p /mnt/dev/browser
|
||||||
touch /mnt/dev/browser/js
|
touch /mnt/dev/browser/js
|
||||||
touch /mnt/dev/browser/html
|
|
||||||
touch /mnt/console.tty
|
touch /mnt/console.tty
|
||||||
ln -s /mnt/dev/browser /dev/browser
|
ln -s /mnt/dev/browser /dev/browser
|
||||||
# setup console goodies
|
# emulator.write_file() only writes to /mnt/. :(
|
||||||
ln -s /mnt/console.tty /dev/browser/console.tty # emulator.write_file() only writes to /mnt/. :(
|
# should be in /proc, but v86 gives 'no such file or dir' when creating it there
|
||||||
echo 1 > /dev/browser/console.tty # should be in /proc, but v86 gives 'no such file or dir' when creating it there
|
ln -s /mnt/console.tty /dev/browser/console.tty
|
||||||
v86pipe /mnt/console /dev/browser/console &
|
echo 1 > /dev/browser/console.tty
|
||||||
|
touch /mnt/console && ln -s /mnt/console /dev/browser/console
|
||||||
|
touch /mnt/index.html && ln -s /mnt/index.html /dev/browser/index.html
|
||||||
|
ln -s /dev/browser/index.html ~/index.html
|
||||||
|
|
||||||
test -f /etc/profile && rm /etc/profile
|
test -f /etc/profile && rm /etc/profile
|
||||||
ln -s /mnt/profile /etc/profile
|
ln -s /mnt/profile /etc/profile
|
||||||
}
|
}
|
||||||
|
|
||||||
setup_hook_dirs(){ # see /mnt/hook for usage
|
setup_hook_dirs(){ # see /mnt/hook for usage
|
||||||
|
mkdir ~/bin
|
||||||
mkdir -p ~/hook.d/alert
|
mkdir -p ~/hook.d/alert
|
||||||
mkdir -p ~/hook.d/confirm
|
|
||||||
mkdir -p ~/hook.d/prompt
|
|
||||||
echo -e "#!/bin/sh\necho hook.d/alert/yo: yo \$*" > ~/hook.d/alert/yo
|
echo -e "#!/bin/sh\necho hook.d/alert/yo: yo \$*" > ~/hook.d/alert/yo
|
||||||
echo -e "#!/bin/js\nalert(\"hook.d/alert/yo.js \"+args.slice(1).join(' '))" > ~/hook.d/alert/yo.js
|
echo -e "#!/bin/js\nstr = \"hook.d/alert/yo.js yo \"+args.slice(1).join(' ')\nalert(str)\nreturn str" > ~/hook.d/alert/yo.js
|
||||||
echo -e "#!/usr/bin/lua\nprint(\"hook.d/alert/yo.lua: yo \" .. arg[1])" > ~/hook.d/alert/yo.lua
|
echo -e "#!/usr/bin/env lua\nprint(\"hook.d/alert/yo.lua: yo \" .. arg[1])" > ~/hook.d/alert/yo.lua
|
||||||
|
echo -e "#!/usr/bin/awk -f\nBEGIN{\n\tprint \"hook.d/alert/yo.awk: yo \" ARGV[1]\n}" > ~/hook.d/alert/yo.awk
|
||||||
|
echo -e "#!/bin/sh\necho hello \$*" > ~/bin/hello
|
||||||
|
chmod +x ~/bin/hello
|
||||||
}
|
}
|
||||||
|
|
||||||
setup_network(){
|
setup_network(){
|
||||||
test -n "$BROWSER" || return 0
|
test -n "$BROWSER" || return 0
|
||||||
mount -a
|
#mount -a
|
||||||
udhcpc 1>>/var/log/network.log 2>>/var/log/network.log &
|
udhcpc 1>>/var/log/network.log 2>>/var/log/network.log &
|
||||||
echo 0 > /proc/sys/kernel/printk
|
echo 0 > /proc/sys/kernel/printk
|
||||||
}
|
}
|
||||||
|
@ -43,5 +57,7 @@ test -d /dev/browser || {
|
||||||
setup_binaries
|
setup_binaries
|
||||||
setup_browser_dev
|
setup_browser_dev
|
||||||
setup_hook_dirs
|
setup_hook_dirs
|
||||||
}
|
setup_links
|
||||||
|
setup_network
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
read -p "$(printf "\033[0m")[?] [38;5;165m$1: $(printf "\033[0m")" answer
|
|
||||||
echo "$answer"
|
|
|
@ -1,26 +0,0 @@
|
||||||
#!/bin/awk -f
|
|
||||||
|
|
||||||
BEGIN {
|
|
||||||
for (i = 1; i < ARGC; i++) {
|
|
||||||
options[i] = ARGV[i]
|
|
||||||
}
|
|
||||||
ARGC = 0
|
|
||||||
selected = 1
|
|
||||||
n = length(options)
|
|
||||||
|
|
||||||
while (1) {
|
|
||||||
printf "\r "
|
|
||||||
for (i = 1; i <= n; i++) {
|
|
||||||
if (i == selected)
|
|
||||||
printf "\033[44m%s\033[0m ", options[i]
|
|
||||||
else
|
|
||||||
printf "%s ", options[i]
|
|
||||||
}
|
|
||||||
if (c == 0) {
|
|
||||||
getline dir < "/dev/stdin"
|
|
||||||
print dir
|
|
||||||
if (dir == "up" && selected > 1) selected--
|
|
||||||
if (dir == "down" && selected < n) selected++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
Binary file not shown.
|
@ -0,0 +1,70 @@
|
||||||
|
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)
|
||||||
|
}
|
|
@ -1 +0,0 @@
|
||||||
buildroot.iso
|
|
|
@ -45,9 +45,12 @@ AFRAME.registerComponent('launcher', {
|
||||||
cols: { type:"number", "default": 5 }
|
cols: { type:"number", "default": 5 }
|
||||||
},
|
},
|
||||||
|
|
||||||
dependencies:['dom'],
|
dependencies:{
|
||||||
|
dom: "com/dom.js"
|
||||||
|
},
|
||||||
|
|
||||||
init: async function () {
|
init: async function () {
|
||||||
|
await AFRAME.utils.require(this.dependencies)
|
||||||
this.worldPosition = new THREE.Vector3()
|
this.worldPosition = new THREE.Vector3()
|
||||||
|
|
||||||
await AFRAME.utils.require({
|
await AFRAME.utils.require({
|
||||||
|
@ -377,7 +380,7 @@ AFRAME.registerSystem('launcher',{
|
||||||
|
|
||||||
updateLauncher: function(){
|
updateLauncher: function(){
|
||||||
let launcher = document.querySelector('[launcher]')
|
let launcher = document.querySelector('[launcher]')
|
||||||
if( launcher ) launcher.components['launcher'].render()
|
if( launcher ) launcher.components.launcher.render()
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
|
@ -6,13 +6,14 @@ AFRAME.registerComponent('window', {
|
||||||
uid: {type:'string'},
|
uid: {type:'string'},
|
||||||
attach: {type:'selector'},
|
attach: {type:'selector'},
|
||||||
dom: {type:'selector'},
|
dom: {type:'selector'},
|
||||||
|
max: {type:'boolean',"default":false},
|
||||||
|
min: {type:'boolean',"default":false},
|
||||||
x: {type:'string',"default":"center"},
|
x: {type:'string',"default":"center"},
|
||||||
y: {type:'string',"default":"center"}
|
y: {type:'string',"default":"center"}
|
||||||
},
|
},
|
||||||
|
|
||||||
dependencies:{
|
dependencies:{
|
||||||
dom: "com/dom.js",
|
dom: "com/dom.js",
|
||||||
html: "https://unpkg.com/aframe-htmlmesh@2.1.0/build/aframe-html.js", // html to AFRAME
|
|
||||||
winboxjs: "https://unpkg.com/winbox@0.2.82/dist/winbox.bundle.min.js", // deadsimple windows: https://nextapps-de.github.io/winbox
|
winboxjs: "https://unpkg.com/winbox@0.2.82/dist/winbox.bundle.min.js", // deadsimple windows: https://nextapps-de.github.io/winbox
|
||||||
//winboxcss: "https://unpkg.com/winbox@0.2.82/dist/css/winbox.min.css", // main theme
|
//winboxcss: "https://unpkg.com/winbox@0.2.82/dist/css/winbox.min.css", // main theme
|
||||||
},
|
},
|
||||||
|
@ -34,14 +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' )
|
||||||
setTimeout( () => this.el.setAttribute("html",`html:#${this.data.uid}; cursor:#cursor`), 1000)
|
|
||||||
// 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()
|
||||||
|
@ -52,9 +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()
|
||||||
debugger
|
|
||||||
this.el.parentElement.remove( this.el )
|
|
||||||
return false
|
return false
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -62,5 +63,9 @@ AFRAME.registerComponent('window', {
|
||||||
|
|
||||||
this.el.setAttribute("grabbable","")
|
this.el.setAttribute("grabbable","")
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
show: function(state){
|
||||||
|
this.el.dom.closest('.winbox').style.display = state ? '' : 'none'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
111
com/xd.js
111
com/xd.js
|
@ -1,111 +0,0 @@
|
||||||
AFRAME.registerComponent('xd', {
|
|
||||||
schema: {
|
|
||||||
foo: { type:"string"}
|
|
||||||
},
|
|
||||||
|
|
||||||
init: function () {
|
|
||||||
if( Object.keys(this.el.components).length > 1 ) return // we collect a-entities which wish to be toggled in this.showElements()
|
|
||||||
|
|
||||||
this.el.sceneEl.addEventListener('enter-vr',() => this.toggle(true) )
|
|
||||||
this.el.sceneEl.addEventListener('exit-vr', () => this.toggle(false) )
|
|
||||||
this.el.sceneEl.addEventListener('2D', () => this.showElements(false) )
|
|
||||||
this.el.sceneEl.addEventListener('3D', () => this.showElements(true) )
|
|
||||||
|
|
||||||
// toggle immersive with ESCAPE
|
|
||||||
document.body.addEventListener('keydown', (e) => e.key == 'Escape' && this.toggle() )
|
|
||||||
|
|
||||||
document.head.innerHTML += `<style type="text/css">
|
|
||||||
.XR #toggle_overlay{
|
|
||||||
background: transparent;
|
|
||||||
color: #3aacff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.XR #overlay{
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
</style>`
|
|
||||||
|
|
||||||
this.events.launcher = () => this.toggle()
|
|
||||||
},
|
|
||||||
|
|
||||||
showElements: function(state){
|
|
||||||
let els = [...document.querySelectorAll('[xd]')]
|
|
||||||
els = els.filter( (el) => el != this.el ? el : null ) // filter out self
|
|
||||||
els.map( (el) => el.setAttribute("visible", state ? "true" : false ) )
|
|
||||||
},
|
|
||||||
|
|
||||||
// draw a button so we can toggle apps between 2D / XR
|
|
||||||
toggle: function(state){
|
|
||||||
state = state != undefined ? state : state || !document.body.className.match(/XR/)
|
|
||||||
document.body.classList[ state ? 'add' : 'remove'](['XR'])
|
|
||||||
AFRAME.scenes[0].emit( state ? '3D' : '2D')
|
|
||||||
},
|
|
||||||
|
|
||||||
manifest: { // HTML5 manifest to identify app to xrsh
|
|
||||||
"short_name": "XD",
|
|
||||||
"name": "2D/3D switcher",
|
|
||||||
"icons": [],
|
|
||||||
"id": "/?source=pwa",
|
|
||||||
"start_url": "/?source=pwa",
|
|
||||||
"background_color": "#3367D6",
|
|
||||||
"display": "standalone",
|
|
||||||
"scope": "/",
|
|
||||||
"theme_color": "#3367D6",
|
|
||||||
"category":"system",
|
|
||||||
"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": "use ESC-key to toggle between 2D / 3D",
|
|
||||||
"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.
|
|
||||||
`
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
AFRAME.utils.XD = function(){
|
|
||||||
return document.body.classList.contains('XR') ? '3D' : '2D'
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
AFRAME.utils.XD.getPositionInFrontOfCamera = function(distance){
|
|
||||||
const camera = AFRAME.scenes[0].camera;
|
|
||||||
let pos = new THREE.Vector3()
|
|
||||||
let direction = new THREE.Vector3();
|
|
||||||
// Get camera's forward direction (without rotation)
|
|
||||||
camera.getWorldDirection(direction);
|
|
||||||
camera.getWorldPosition(pos)
|
|
||||||
direction.normalize();
|
|
||||||
// Scale the direction by 1 meter
|
|
||||||
if( !distance ) distance = 1.5
|
|
||||||
direction.multiplyScalar(distance);
|
|
||||||
// Add the camera's position to the scaled direction to get the target point
|
|
||||||
pos.add(direction);
|
|
||||||
return pos
|
|
||||||
}
|
|
|
@ -1,81 +0,0 @@
|
||||||
AFRAME.registerComponent('xrfragments', {
|
|
||||||
schema: {
|
|
||||||
url: { type:"string"}
|
|
||||||
},
|
|
||||||
|
|
||||||
init: function () {
|
|
||||||
},
|
|
||||||
|
|
||||||
events:{
|
|
||||||
|
|
||||||
launcher: async function(){
|
|
||||||
let url = prompt('enter URL to glb/fbx/json/obj/usdz asset', 'https://xrfragment.org/index.glb')
|
|
||||||
if( !url ) return
|
|
||||||
await AFRAME.utils.require({
|
|
||||||
xrfragments: "https://xrfragment.org/dist/xrfragment.aframe.js",
|
|
||||||
})
|
|
||||||
|
|
||||||
// remove objects which are marked to be removed from scene (with noxrf)
|
|
||||||
let els = [...document.querySelectorAll('[noxrf]') ]
|
|
||||||
els.map( (el) => el.remove() )
|
|
||||||
|
|
||||||
if( !this.el.getAttribute("xrf") ){
|
|
||||||
this.el.setAttribute("xrf", url )
|
|
||||||
let ARbutton = document.querySelector('.a-enter-ar-button')
|
|
||||||
if( ARbutton ){
|
|
||||||
ARbutton.addEventListener('click', () => {
|
|
||||||
AFRAME.XRF.reset()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}else AFRAME.XRF.navigator.to(url)
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
manifest: { // HTML5 manifest to identify app to xrsh
|
|
||||||
"short_name": "XRF",
|
|
||||||
"name": "XR Fragment URL",
|
|
||||||
"icons": [ ],
|
|
||||||
"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": "Hello world information",
|
|
||||||
"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.
|
|
||||||
`
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
|
@ -0,0 +1,261 @@
|
||||||
|
/*
|
||||||
|
* 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