Compare commits
No commits in common. "main" and "feat/handmenu" have entirely different histories.
main
...
feat/handm
23 changed files with 886 additions and 1394 deletions
49
README.md
49
README.md
|
|
@ -93,26 +93,25 @@ a Linux ISO image (via WASM).
|
||||||
|
|
||||||
> depends on [AFRAME.utils.require](com/require.js)
|
> depends on [AFRAME.utils.require](com/require.js)
|
||||||
|
|
||||||
| property | type | default | info |
|
| property | type | default | info |
|
||||||
|-------------------|-----------|------------------------|------|
|
|------------------|-----------|------------------------|------|
|
||||||
| `iso` | `string` | https`//forgejo.isvery.ninja/assets/xrsh-buildroot/main/xrsh.iso" | |
|
| `iso` | `string` | https`//forgejo.isvery.ninja/assets/xrsh-buildroot/main/xrsh.iso" | |
|
||||||
| `overlayfs` | `string` | '' | zip URL/file to autoextract on top of filesystem |
|
| `overlayfs` | `string` | *WORK-IN-PROGRESS* | |
|
||||||
| `width` | `number` | 800 ||
|
| `width` | `number` | 800 ||
|
||||||
| `height` | `number` | 600 ||
|
| `height` | `number` | 600 ||
|
||||||
| `depth` | `number` | 0.03 ||
|
| `depth` | `number` | 0.03 ||
|
||||||
| `lineHeight` | `number` | 18 ||
|
| `lineHeight` | `number` | 18 ||
|
||||||
| `bootMenu` | `string` | "" | character to auto-enter in bootMenu |
|
| `bootmenu` | `boolean` | true | give user choice [or boot straight into ISO ] |
|
||||||
| `bootMenuURL` | `string` | "" | character to auto-enter in bootMenu when URL has fragment (#foo.zip e.g.) |
|
| `padding` | `number`` | 18 | |
|
||||||
| `padding` | `number` | 18 | |
|
| `maximized` | `boolean` | false | |
|
||||||
| `maximized` | `boolean` | false | |
|
| `minimized` | `boolean` | false | |
|
||||||
| `minimized` | `boolean` | false | |
|
| `muteUntilPrompt`| `boolean` | true | mute stdout until a prompt is detected in ISO |
|
||||||
| `muteUntilPrompt` | `boolean` | true | mute stdout until a prompt is detected in ISO |
|
| `HUD` | `boolean` | false | link to camera movement |
|
||||||
| `HUD` | `boolean` | false | link to camera movement |
|
| `transparent` | `boolean` | false | heavy, needs good gpu |
|
||||||
| `transparent` | `boolean` | false | heavy, needs good gpu |
|
| `memory` | `number` | 60 | VM memory (in MB) [NOTE` quest or smartphone webworker might crash > 40mb ] |
|
||||||
| `memory` | `number` | 60 | VM memory (in MB) [NOTE` quest or smartphone webworker might crash > 40mb ] |
|
| `bufferLatency` | `number` | 1 | in ms` bufferlatency from webworker to term (batch-update every char to texture) |
|
||||||
| `bufferLatency` | `number` | 1 | in ms` bufferlatency from webworker to term (batch-update every char to texture) |
|
| `debug` | `boolean` | false | |
|
||||||
| `debug` | `boolean` | false | |
|
| `emulator` | `string` | fbterm | terminal emulator |
|
||||||
| `emulator` | `string` | fbterm | terminal emulator |
|
|
||||||
|
|
||||||
> for more info see [xrsh.isvery.ninja](https://xrsh.isvery.ninja)
|
> for more info see [xrsh.isvery.ninja](https://xrsh.isvery.ninja)
|
||||||
|
|
||||||
|
|
@ -150,18 +149,14 @@ NOTE: For convenience reasons, events are forwarded between com/isoterminal.js,
|
||||||
|
|
||||||
## [launcher](com/launcher.js)
|
## [launcher](com/launcher.js)
|
||||||
|
|
||||||
displays app (icons) in 2D and 3D handmenu (enduser can launch desktop-like 'apps')
|
displays app (icons) for enduser to launch
|
||||||
|
|
||||||
```html
|
|
||||||
<a-entity launcher>
|
|
||||||
<a-entity launch="component: helloworld; foo: bar"><a-entity>
|
|
||||||
</a-entity>
|
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
<a-scene launcher/>
|
||||||
```
|
```
|
||||||
|
|
||||||
| property | type | example |
|
| property | type | example |
|
||||||
|--------------|--------------------|----------------------------------------------------------------------------------------|
|
|--------------|--------------------|----------------------------------------------------------------------------------------|
|
||||||
| `attach` | `selector` | hand or object to attach menu to |
|
|
||||||
| `registries` | `array` of strings | `<a-entity launcher="registers: https://foo.com/index.json, ./index.json"/>` |
|
| `registries` | `array` of strings | `<a-entity launcher="registers: https://foo.com/index.json, ./index.json"/>` |
|
||||||
|
|
||||||
| event | target | info |
|
| event | target | info |
|
||||||
|
|
|
||||||
109
com/cast.js
109
com/cast.js
|
|
@ -4,8 +4,11 @@ AFRAME.registerComponent('cast', {
|
||||||
},
|
},
|
||||||
|
|
||||||
requires: {
|
requires: {
|
||||||
dom: "com/dom.js",
|
dom: "./com/dom.js", // interpret .dom object
|
||||||
window: "com/window.js",
|
xd: "./com/xd.js", // allow switching between 2D/3D
|
||||||
|
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", //
|
||||||
},
|
},
|
||||||
|
|
||||||
dom: {
|
dom: {
|
||||||
|
|
@ -20,7 +23,7 @@ AFRAME.registerComponent('cast', {
|
||||||
|
|
||||||
init: function () { },
|
init: function () { },
|
||||||
|
|
||||||
etInstallables: function(){
|
getInstallables: function(){
|
||||||
const installed = document.querySelector('[launcher]').components.launcher.system.getLaunchables()
|
const installed = document.querySelector('[launcher]').components.launcher.system.getLaunchables()
|
||||||
return this.data.comps.map( (c) => {
|
return this.data.comps.map( (c) => {
|
||||||
return installed[c] ? null : c
|
return installed[c] ? null : c
|
||||||
|
|
@ -34,54 +37,77 @@ AFRAME.registerComponent('cast', {
|
||||||
if( this.el.sceneEl.renderer.xr.isPresenting ){
|
if( this.el.sceneEl.renderer.xr.isPresenting ){
|
||||||
this.el.sceneEl.exitVR() // *FIXME* we need a gui
|
this.el.sceneEl.exitVR() // *FIXME* we need a gui
|
||||||
}
|
}
|
||||||
this.el.object3D.quaternion.copy( AFRAME.scenes[0].camera.quaternion ) // face towards camera
|
const el = document.querySelector('body');
|
||||||
// instance this component
|
const cropTarget = await CropTarget.fromElement(el);
|
||||||
this.el.setAttribute("dom","")
|
const stream = await navigator.mediaDevices.getDisplayMedia();
|
||||||
},
|
const [track] = stream.getVideoTracks();
|
||||||
|
this.track = track
|
||||||
DOMready: function(){
|
this.stream = stream
|
||||||
this.setupCast();
|
this.createWindow()
|
||||||
this.el.setAttribute("html-as-texture-in-xr", `domid: #${this.el.uid}; faceuser: true`)
|
}
|
||||||
},
|
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
setupCast: async function(){
|
createWindow: async function(){
|
||||||
let s = await AFRAME.utils.require(this.requires)
|
let s = await AFRAME.utils.require(this.requires)
|
||||||
|
|
||||||
const video = this.el.dom.querySelector('video')
|
// instance this component
|
||||||
|
const instance = this.el.cloneNode(false)
|
||||||
|
this.el.sceneEl.appendChild( instance )
|
||||||
|
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.object3D.quaternion.copy( AFRAME.scenes[0].camera.quaternion ) // face towards camera
|
||||||
|
instance.track = this.track
|
||||||
|
instance.stream = this.stream
|
||||||
|
|
||||||
video.addEventListener( "loadedmetadata", () => {
|
const setupWindow = () => {
|
||||||
|
instance.dom.style.display = 'none'
|
||||||
|
|
||||||
let width = Math.round(window.innerWidth*0.4)
|
const video = instance.dom.querySelector('video')
|
||||||
let factor = width / video.videoWidth
|
video.addEventListener( "loadedmetadata", function () {
|
||||||
let height = Math.round( video.videoHeight * factor)
|
let width = Math.round(window.innerWidth*0.4)
|
||||||
|
let factor = width / this.videoWidth
|
||||||
|
let height = Math.round(this.videoHeight * factor)
|
||||||
|
new WinBox("Casting Tab",{
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
x:"center",
|
||||||
|
y:"center",
|
||||||
|
id: instance.uid, // important hint for html-mesh
|
||||||
|
root: document.querySelector("#overlay"),
|
||||||
|
mount: instance.dom,
|
||||||
|
onclose: () => { instance.dom.style.display = 'none'; return false; },
|
||||||
|
oncreate: () => {
|
||||||
|
|
||||||
const createVideoTexture = () => {
|
// instance.setAttribute("html",`html:#${instance.uid}; cursor:#cursor`)
|
||||||
const texture = new THREE.VideoTexture( video );
|
}
|
||||||
texture.colorSpace = THREE.SRGBColorSpace;
|
});
|
||||||
const geometry = new THREE.PlaneGeometry( 16, 9 );
|
instance.dom.style.display = '' // show
|
||||||
geometry.scale( 0.2, 0.2, 0.2 );
|
|
||||||
const material = new THREE.MeshBasicMaterial( { map: texture } );
|
|
||||||
const mesh = new THREE.Mesh( geometry, material );
|
|
||||||
mesh.lookAt( AFRAME.scenes[0].camera.position );
|
|
||||||
this.el.object3D.add(mesh)
|
|
||||||
}
|
|
||||||
|
|
||||||
createVideoTexture.apply(this)
|
// hint grabbable's obb-collider to track the window-object
|
||||||
//this.el.setAttribute("window", `title: casting tab; uid: ${this.el.uid}; attach: #overlay; dom: #${this.el.dom.id}; width:${width}; height: ${height}`)
|
instance.components['obb-collider'].data.trackedObject3D = 'components.html.el.object3D.children.0'
|
||||||
})
|
instance.components['obb-collider'].update()
|
||||||
|
})
|
||||||
|
video.srcObject = instance.stream
|
||||||
|
video.play()
|
||||||
|
|
||||||
const el = document.querySelector('body');
|
this.createVideoTexture.apply(instance)
|
||||||
const cropTarget = await CropTarget.fromElement(el);
|
}
|
||||||
const stream = await navigator.mediaDevices.getDisplayMedia();
|
setTimeout( () => setupWindow(), 10 ) // give new components time to init
|
||||||
const [track] = stream.getVideoTracks();
|
},
|
||||||
this.track = track
|
|
||||||
this.stream = stream
|
|
||||||
|
|
||||||
video.srcObject = this.stream
|
|
||||||
video.play()
|
|
||||||
|
|
||||||
|
createVideoTexture: function(){
|
||||||
|
console.dir(this)
|
||||||
|
const texture = new THREE.VideoTexture( video );
|
||||||
|
texture.colorSpace = THREE.SRGBColorSpace;
|
||||||
|
const geometry = new THREE.PlaneGeometry( 16, 9 );
|
||||||
|
geometry.scale( 0.2, 0.2, 0.2 );
|
||||||
|
const material = new THREE.MeshBasicMaterial( { map: texture } );
|
||||||
|
const mesh = new THREE.Mesh( geometry, material );
|
||||||
|
mesh.lookAt( this.sceneEl.camera.position );
|
||||||
|
this.object3D.add(mesh)
|
||||||
},
|
},
|
||||||
|
|
||||||
manifest: { // HTML5 manifest to identify app to xrsh
|
manifest: { // HTML5 manifest to identify app to xrsh
|
||||||
|
|
@ -90,7 +116,6 @@ AFRAME.registerComponent('cast', {
|
||||||
"icons": [
|
"icons": [
|
||||||
{
|
{
|
||||||
"src": "https://css.gg/cast.svg",
|
"src": "https://css.gg/cast.svg",
|
||||||
"src": "data:image/svg+xml;base64,PHN2ZwogIHdpZHRoPSIyNCIKICBoZWlnaHQ9IjI0IgogIHZpZXdCb3g9IjAgMCAyNCAyNCIKICBmaWxsPSJub25lIgogIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKPgogIDxwYXRoCiAgICBkPSJNMjAgNkg0VjhIMlY2QzIgNC44OTU0MyAyLjg5NTQzIDQgNCA0SDIwQzIxLjEwNDYgNCAyMiA0Ljg5NTQzIDIyIDZWMThDMjIgMTkuMTA0NiAyMS4xMDQ2IDIwIDIwIDIwSDE1VjE4SDIwVjZaIgogICAgZmlsbD0iY3VycmVudENvbG9yIgogIC8+CiAgPHBhdGgKICAgIGQ9Ik0yIDEzQzUuODY1OTkgMTMgOSAxNi4xMzQgOSAyMEg3QzcgMTcuMjM4NiA0Ljc2MTQyIDE1IDIgMTVWMTNaIgogICAgZmlsbD0iY3VycmVudENvbG9yIgogIC8+CiAgPHBhdGggZD0iTTIgMTdDMy42NTY4NSAxNyA1IDE4LjM0MzEgNSAyMEgyVjE3WiIgZmlsbD0iY3VycmVudENvbG9yIiAvPgogIDxwYXRoCiAgICBkPSJNMiA5QzguMDc1MTMgOSAxMyAxMy45MjQ5IDEzIDIwSDExQzExIDE1LjAyOTQgNi45NzA1NiAxMSAyIDExVjlaIgogICAgZmlsbD0iY3VycmVudENvbG9yIgogIC8+Cjwvc3ZnPg==",
|
|
||||||
"type": "image/svg+xml",
|
"type": "image/svg+xml",
|
||||||
"sizes": "512x512"
|
"sizes": "512x512"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -50,9 +50,9 @@ if( !AFRAME.components.dom ){
|
||||||
|
|
||||||
this
|
this
|
||||||
.ensureOverlay()
|
.ensureOverlay()
|
||||||
|
.addCSS()
|
||||||
.createReactiveDOMElement()
|
.createReactiveDOMElement()
|
||||||
.assignUniqueID()
|
.assignUniqueID()
|
||||||
.addCSS()
|
|
||||||
.scaleDOMvsXR()
|
.scaleDOMvsXR()
|
||||||
.triggerKeyboardForInputs()
|
.triggerKeyboardForInputs()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,12 @@ AFRAME.registerComponent('helloworld-window', {
|
||||||
scale: 0.66,
|
scale: 0.66,
|
||||||
events: ['click','input'],
|
events: ['click','input'],
|
||||||
html: (me) => `<div class="htmlform">
|
html: (me) => `<div class="htmlform">
|
||||||
|
<fieldset>
|
||||||
|
<legend>Theme</legend>
|
||||||
|
<input type="radio" id="theme" name="theme" value="0" checked style=""><label for="theme" style="margin-right:15px;">Normal</label>
|
||||||
|
<input type="radio" id="themei" name="theme" value="1"><label for="themei">Invert</label><br>
|
||||||
|
</fieldset>
|
||||||
|
<br>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>Welcome to XR Shell</legend>
|
<legend>Welcome to XR Shell</legend>
|
||||||
A free offline-first morphable<br>
|
A free offline-first morphable<br>
|
||||||
|
|
@ -26,12 +32,6 @@ AFRAME.registerComponent('helloworld-window', {
|
||||||
<li>check the <a href="https://forgejo.isvery.ninja/xrsh/xrsh-buildroot/src/branch/main/buildroot-v86/board/v86/rootfs_overlay/root/manual.md" target="_blank">manual</a></li>
|
<li>check the <a href="https://forgejo.isvery.ninja/xrsh/xrsh-buildroot/src/branch/main/buildroot-v86/board/v86/rootfs_overlay/root/manual.md" target="_blank">manual</a></li>
|
||||||
</ol>
|
</ol>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<br>
|
|
||||||
<fieldset>
|
|
||||||
<legend>Icons</legend>
|
|
||||||
<input type="radio" id="small" name="icons" value="0.8" checked style=""><label for="small" style="margin-right:15px;">Small</label>
|
|
||||||
<input type="radio" id="big" name="icons" value="1.5"><label for="big">Big</label><br>
|
|
||||||
</fieldset>
|
|
||||||
<!--
|
<!--
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>Size</legend>
|
<legend>Size</legend>
|
||||||
|
|
@ -41,8 +41,7 @@ AFRAME.registerComponent('helloworld-window', {
|
||||||
-->
|
-->
|
||||||
</div>`,
|
</div>`,
|
||||||
|
|
||||||
css: (me) => `.htmlform { padding:11px; }
|
css: (me) => `.htmlform { padding:11px; }`
|
||||||
`
|
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -56,11 +55,8 @@ AFRAME.registerComponent('helloworld-window', {
|
||||||
input: function(e){
|
input: function(e){
|
||||||
if( !e.detail.target ) return
|
if( !e.detail.target ) return
|
||||||
if( e.detail.target.id == 'myRange' ) this.data.myvalue = e.detail.target.value // reactive demonstration
|
if( e.detail.target.id == 'myRange' ) this.data.myvalue = e.detail.target.value // reactive demonstration
|
||||||
|
if( e.detail.target.name == 'theme' ) document.body.style.filter = `invert(${e.detail.target.value})`
|
||||||
if( e.detail.target.name == 'cmenu' ) document.querySelector(".iconmenu").style.display = e.detail.target.value == 'on' ? '' : 'none';
|
if( e.detail.target.name == 'cmenu' ) document.querySelector(".iconmenu").style.display = e.detail.target.value == 'on' ? '' : 'none';
|
||||||
if( e.detail.target.name == 'icons' ){
|
|
||||||
document.querySelector('[launcher]').object3D.getObjectByProperty("HTMLMesh").scale.setScalar( e.detail.target.value )
|
|
||||||
document.querySelector('.iconmenu').style.transform = e.detail.target.value == '0.8' ? 'scale(1)' : 'scale(1.33)'
|
|
||||||
}
|
|
||||||
console.dir(e.detail)
|
console.dir(e.detail)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -68,20 +64,15 @@ AFRAME.registerComponent('helloworld-window', {
|
||||||
myvalue: function(e){ this.el.dom.querySelector('#myvalue').innerText = this.data.myvalue },
|
myvalue: function(e){ this.el.dom.querySelector('#myvalue').innerText = this.data.myvalue },
|
||||||
|
|
||||||
launcher: async function(){
|
launcher: async function(){
|
||||||
if( !this.el.getAttribute("dom") ){
|
let s = await AFRAME.utils.require(this.requires)
|
||||||
let s = await AFRAME.utils.require(this.requires)
|
|
||||||
|
|
||||||
// instance this component
|
// instance this component
|
||||||
this.el.setAttribute("dom", "")
|
this.el.setAttribute("dom", "")
|
||||||
this.el.object3D.quaternion.copy( AFRAME.scenes[0].camera.quaternion ) // face towards camera
|
this.el.object3D.quaternion.copy( AFRAME.scenes[0].camera.quaternion ) // face towards camera
|
||||||
}else{
|
|
||||||
// toggle visibility
|
|
||||||
this.el.winbox[ this.el.winbox.min ? 'restore' : 'minimize' ]()
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
DOMready: function(){
|
DOMready: function(){
|
||||||
this.el.setAttribute("window", `title: XRSH; uid: ${this.el.uid}; attach: #overlay; dom: #${this.el.dom.id}; class: no-min, no-max; width:250; height: 360`)
|
this.el.setAttribute("window", `title: Welcome; uid: ${this.el.uid}; attach: #overlay; dom: #${this.el.dom.id}; width:250; height: 360`)
|
||||||
|
|
||||||
// data2event demo
|
// data2event demo
|
||||||
this.el.setAttribute("data2event","")
|
this.el.setAttribute("data2event","")
|
||||||
|
|
@ -89,21 +80,6 @@ AFRAME.registerComponent('helloworld-window', {
|
||||||
this.data.foo = `this.el ${this.el.uid}: `
|
this.data.foo = `this.el ${this.el.uid}: `
|
||||||
setInterval( () => this.data.myvalue++, 500 )
|
setInterval( () => this.data.myvalue++, 500 )
|
||||||
|
|
||||||
// if you want your launch-icon to stick around after
|
|
||||||
// closing the window, then register it manually
|
|
||||||
window.launcher.register({
|
|
||||||
component: "helloworld-window",
|
|
||||||
...this.manifest,
|
|
||||||
icon: this.manifest.icons[0].src,
|
|
||||||
cb: () => {
|
|
||||||
const el = document.createElement("a-entity")
|
|
||||||
el.setAttribute("helloworld-window","")
|
|
||||||
el.setAttribute("pressable","")
|
|
||||||
el.setAttribute("position","0 1.6 0")
|
|
||||||
AFRAME.scenes[0].appendChild(el)
|
|
||||||
setTimeout( () => el.emit('launcher',null,false), 100 )
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
|
|
||||||
"window.oncreate": function(){
|
"window.oncreate": function(){
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@
|
||||||
*
|
*
|
||||||
* | property | type | default | info |
|
* | property | type | default | info |
|
||||||
* |-------------------|-----------|------------------------|------|
|
* |-------------------|-----------|------------------------|------|
|
||||||
* | `title` | `string` | 'xrsh.iso' | window title |
|
|
||||||
* | `iso` | `string` | https`//forgejo.isvery.ninja/assets/xrsh-buildroot/main/xrsh.iso" | |
|
* | `iso` | `string` | https`//forgejo.isvery.ninja/assets/xrsh-buildroot/main/xrsh.iso" | |
|
||||||
* | `overlayfs` | `string` | '' | zip URL/file to autoextract on top of filesystem |
|
* | `overlayfs` | `string` | '' | zip URL/file to autoextract on top of filesystem |
|
||||||
* | `width` | `number` | 800 ||
|
* | `width` | `number` | 800 ||
|
||||||
|
|
@ -21,8 +20,8 @@
|
||||||
* | `depth` | `number` | 0.03 ||
|
* | `depth` | `number` | 0.03 ||
|
||||||
* | `lineHeight` | `number` | 18 ||
|
* | `lineHeight` | `number` | 18 ||
|
||||||
* | `bootMenu` | `string` | "" | character to auto-enter in bootMenu |
|
* | `bootMenu` | `string` | "" | character to auto-enter in bootMenu |
|
||||||
* | `bootMenuURL` | `string` | "" | character to auto-enter in bootMenu when URL has fragment (#foo.zip e.g.) |
|
* | `bootMenuURL` | `string` | "" | character to auto-enter in bootmeun when URL has fragment (#foo.zip e.g.) |
|
||||||
* | `padding` | `number` | 18 | |
|
* | `padding` | `number`` | 18 | |
|
||||||
* | `maximized` | `boolean` | false | |
|
* | `maximized` | `boolean` | false | |
|
||||||
* | `minimized` | `boolean` | false | |
|
* | `minimized` | `boolean` | false | |
|
||||||
* | `muteUntilPrompt` | `boolean` | true | mute stdout until a prompt is detected in ISO |
|
* | `muteUntilPrompt` | `boolean` | true | mute stdout until a prompt is detected in ISO |
|
||||||
|
|
@ -73,7 +72,6 @@ if( typeof AFRAME != 'undefined '){
|
||||||
schema: {
|
schema: {
|
||||||
iso: { type:"string", "default":"https://forgejo.isvery.ninja/assets/xrsh-buildroot/main/xrsh.iso" },
|
iso: { type:"string", "default":"https://forgejo.isvery.ninja/assets/xrsh-buildroot/main/xrsh.iso" },
|
||||||
overlayfs: { type:"string"},
|
overlayfs: { type:"string"},
|
||||||
title: { type:"string", "default":"xrsh.iso"},
|
|
||||||
width: { type: 'number',"default": 800 },
|
width: { type: 'number',"default": 800 },
|
||||||
height: { type: 'number',"default": 600 },
|
height: { type: 'number',"default": 600 },
|
||||||
depth: { type: 'number',"default": 0.03 },
|
depth: { type: 'number',"default": 0.03 },
|
||||||
|
|
@ -102,7 +100,6 @@ if( typeof AFRAME != 'undefined '){
|
||||||
this.calculateDimension()
|
this.calculateDimension()
|
||||||
this.initHud()
|
this.initHud()
|
||||||
this.setupPasteDrop()
|
this.setupPasteDrop()
|
||||||
this.setupEvents()
|
|
||||||
|
|
||||||
fetch(this.data.iso,{method: 'HEAD'})
|
fetch(this.data.iso,{method: 'HEAD'})
|
||||||
.then( (res) => {
|
.then( (res) => {
|
||||||
|
|
@ -123,7 +120,7 @@ if( typeof AFRAME != 'undefined '){
|
||||||
v86: "com/isoterminal/libv86.js",
|
v86: "com/isoterminal/libv86.js",
|
||||||
// allow xrsh to selfcontain scene + itself
|
// allow xrsh to selfcontain scene + itself
|
||||||
xhook: "com/lib/xhook.min.js",
|
xhook: "com/lib/xhook.min.js",
|
||||||
//selfcontain: "com/selfcontainer.js",
|
selfcontain: "com/selfcontainer.js",
|
||||||
// html to texture
|
// html to texture
|
||||||
htmlinxr: "com/html-as-texture-in-xr.js",
|
htmlinxr: "com/html-as-texture-in-xr.js",
|
||||||
// isoterminal global features
|
// isoterminal global features
|
||||||
|
|
@ -143,9 +140,7 @@ if( typeof AFRAME != 'undefined '){
|
||||||
css: (me) => `
|
css: (me) => `
|
||||||
|
|
||||||
.isoterminal{
|
.isoterminal{
|
||||||
padding: ${me.com.data.padding}px;
|
padding: ${me.com.data.padding}px 0px 0px ${me.com.data.padding}px;
|
||||||
margin-top:-60px;
|
|
||||||
padding-bottom:60px;
|
|
||||||
width:100%;
|
width:100%;
|
||||||
height:99%;
|
height:99%;
|
||||||
resize: both;
|
resize: both;
|
||||||
|
|
@ -248,19 +243,20 @@ if( typeof AFRAME != 'undefined '){
|
||||||
-webkit-animation:none;
|
-webkit-animation:none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.winbox#${me.el.uid} .wb-header{
|
|
||||||
background: var(--xrsh-black) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wb-body:has(> .isoterminal){
|
.wb-body:has(> .isoterminal){
|
||||||
background: var(--xrsh-black);
|
background: #000C;
|
||||||
overflow:hidden;
|
overflow:hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.XR .isoterminal{
|
.XR .wb-body:has(> .isoterminal){
|
||||||
background: #000 !important;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.wb-control { margin-right:10px }
|
||||||
|
|
||||||
|
.XR .isoterminal{
|
||||||
|
background: #000;
|
||||||
|
}
|
||||||
.isoterminal *{
|
.isoterminal *{
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-family: "Cousine",Liberation Mono,DejaVu Sans Mono,Courier New,monospace;
|
font-family: "Cousine",Liberation Mono,DejaVu Sans Mono,Courier New,monospace;
|
||||||
|
|
@ -309,20 +305,15 @@ if( typeof AFRAME != 'undefined '){
|
||||||
remotekeyboard: "com/isoterminal/feat/remotekeyboard.js",
|
remotekeyboard: "com/isoterminal/feat/remotekeyboard.js",
|
||||||
indexhtml: "com/isoterminal/feat/index.html.js",
|
indexhtml: "com/isoterminal/feat/index.html.js",
|
||||||
indexjs: "com/isoterminal/feat/index.js.js",
|
indexjs: "com/isoterminal/feat/index.js.js",
|
||||||
|
autorestore: "com/isoterminal/feat/autorestore.js",
|
||||||
pastedropFeat: "com/isoterminal/feat/pastedrop.js",
|
pastedropFeat: "com/isoterminal/feat/pastedrop.js",
|
||||||
httpfs: "com/isoterminal/feat/httpfs.js",
|
httpfs: "com/isoterminal/feat/httpfs.js",
|
||||||
autorestore: "com/isoterminal/feat/autorestore.js",
|
|
||||||
}
|
|
||||||
if( document.location.hash.match(/#test/) || this.data.debug ){
|
|
||||||
features['tests'] = "tests/index.js"
|
|
||||||
}
|
}
|
||||||
if( this.data.emulator == 'fbterm' ){
|
if( this.data.emulator == 'fbterm' ){
|
||||||
features['fbtermjs'] = "com/isoterminal/term.js"
|
features['fbtermjs'] = "com/isoterminal/term.js"
|
||||||
features['fbterm'] = "com/isoterminal/feat/term.js"
|
features['fbterm'] = "com/isoterminal/feat/term.js"
|
||||||
}
|
}
|
||||||
await AFRAME.utils.require(features)
|
await AFRAME.utils.require(features)
|
||||||
// this one extends autorestore.js
|
|
||||||
await AFRAME.utils.require({remotestorage: "com/isoterminal/feat/remotestorage.js"})
|
|
||||||
|
|
||||||
this.el.setAttribute("selfcontainer","")
|
this.el.setAttribute("selfcontainer","")
|
||||||
|
|
||||||
|
|
@ -347,7 +338,7 @@ if( typeof AFRAME != 'undefined '){
|
||||||
this.term.emit('term_init', {instance, aEntity:this})
|
this.term.emit('term_init', {instance, aEntity:this})
|
||||||
//instance.winbox.resize(720,380)
|
//instance.winbox.resize(720,380)
|
||||||
let size = `width: ${this.data.width}; height: ${this.data.height}`
|
let size = `width: ${this.data.width}; height: ${this.data.height}`
|
||||||
instance.setAttribute("window", `title: ${this.data.title}; uid: ${instance.uid}; attach: #overlay; dom: #${instance.dom.id}; ${size}; min: ${this.data.minimized}; max: ${this.data.maximized}; class: no-full, no-min, no-close, no-max, no-resize; grabbable: components.html.el.object3D.children.${this.el.children.length}`)
|
instance.setAttribute("window", `title: xrsh; uid: ${instance.uid}; attach: #overlay; dom: #${instance.dom.id}; ${size}; min: ${this.data.minimized}; max: ${this.data.maximized}; class: no-full, no-max, no-resize, no-close; `)
|
||||||
})
|
})
|
||||||
|
|
||||||
instance.addEventListener('window.oncreate', (e) => {
|
instance.addEventListener('window.oncreate', (e) => {
|
||||||
|
|
@ -375,6 +366,7 @@ if( typeof AFRAME != 'undefined '){
|
||||||
this.term.addEventListener('ready', (e) => {
|
this.term.addEventListener('ready', (e) => {
|
||||||
instance.dom.classList.remove('blink')
|
instance.dom.classList.remove('blink')
|
||||||
this.term.emit('status',"running")
|
this.term.emit('status',"running")
|
||||||
|
if( this.data.debug ) this.runTests()
|
||||||
})
|
})
|
||||||
|
|
||||||
this.term.addEventListener('status', function(e){
|
this.term.addEventListener('status', function(e){
|
||||||
|
|
@ -382,7 +374,7 @@ if( typeof AFRAME != 'undefined '){
|
||||||
const w = instance.winbox
|
const w = instance.winbox
|
||||||
if(!w) return
|
if(!w) return
|
||||||
w.titleBak = w.titleBak || w.title
|
w.titleBak = w.titleBak || w.title
|
||||||
w.setTitle( `${w.titleBak} [${msg}]` )
|
w.setTitle( `${w.titleBak} ${msg ? "["+msg+"]" : ""}` )
|
||||||
})
|
})
|
||||||
|
|
||||||
instance.addEventListener('window.onclose', (e) => {
|
instance.addEventListener('window.onclose', (e) => {
|
||||||
|
|
@ -394,23 +386,6 @@ if( typeof AFRAME != 'undefined '){
|
||||||
instance.addEventListener('window.onmaximize', resize )
|
instance.addEventListener('window.onmaximize', resize )
|
||||||
|
|
||||||
const focus = (e) => {
|
const focus = (e) => {
|
||||||
|
|
||||||
if( event.target == document.activeElement ){
|
|
||||||
// if we're already focused, the keyboard is already triggerend
|
|
||||||
// therefore we blur() the element for correctness
|
|
||||||
// which allows the WebXR keyboard dissappear if implemented at all
|
|
||||||
// [Meta Quest 2 does not do this]
|
|
||||||
event.target.blur()
|
|
||||||
}
|
|
||||||
|
|
||||||
// calculate distance between thumb and indexfinger to detect pinch
|
|
||||||
// which should prevent focus-event (annoying to have keyboard popping up during pinch)
|
|
||||||
if( e.detail?.withEl?.components['hand-tracking-controls'] ){
|
|
||||||
const hand = e.detail.withEl.components['hand-tracking-controls']
|
|
||||||
const thumb = hand.bones.find( (b) => b.name == 'thumb-tip' )
|
|
||||||
const diff = thumb.position.distanceTo(hand.indexTipPosition)
|
|
||||||
if( diff < 0.02) return // pinching! don't trigger keyboard (focus)
|
|
||||||
}
|
|
||||||
this.el.emit('focus',e.detail)
|
this.el.emit('focus',e.detail)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -429,6 +404,14 @@ if( typeof AFRAME != 'undefined '){
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
runTests: async function(){
|
||||||
|
await AFRAME.utils.require({
|
||||||
|
"test_util": "tests/util.js",
|
||||||
|
"test_isoterminal":"tests/ISOTerminal.js"
|
||||||
|
})
|
||||||
|
console.test.run()
|
||||||
|
},
|
||||||
|
|
||||||
setupPasteDrop: function(){
|
setupPasteDrop: function(){
|
||||||
this.el.addEventListener('pasteFile', (e) => {
|
this.el.addEventListener('pasteFile', (e) => {
|
||||||
e.preventDefault() // prevent bubbling up to window (which is triggering this initially)
|
e.preventDefault() // prevent bubbling up to window (which is triggering this initially)
|
||||||
|
|
@ -448,22 +431,6 @@ if( typeof AFRAME != 'undefined '){
|
||||||
this.rows = Math.floor( (this.data.height*0.93)/this.data.lineHeight)-1
|
this.rows = Math.floor( (this.data.height*0.93)/this.data.lineHeight)-1
|
||||||
},
|
},
|
||||||
|
|
||||||
setupEvents: function(){
|
|
||||||
this.el.addEventListener('exec', (e) => this.term.exec( e.detail ) )
|
|
||||||
this.el.addEventListener('hook', (e) => this.term.hook( e.detail[0], e.detail[1] ) )
|
|
||||||
this.el.addEventListener('send', (e) => this.term.send( e.detail[0], e.detail[1] || 0 ) )
|
|
||||||
this.el.addEventListener('create_file', async (e) => await this.term.worker.create_file( e.detail[0], e.detail[1] ) )
|
|
||||||
this.el.addEventListener('create_file_from_url', async (e) => await this.term.worker.create_file_from_url( e.detail[0], e.detail[1] ) )
|
|
||||||
this.el.addEventListener('update_file', async (e) => await this.term.worker.update_file( e.detail[0], e.detail[1] ) )
|
|
||||||
this.el.addEventListener('append_file', async (e) => await this.term.worker.append_file( e.detail[0], e.detail[1] ) )
|
|
||||||
this.el.addEventListener('read_file', async (e) => {
|
|
||||||
const buf = await this.term.worker.read_file( e.detail[0] )
|
|
||||||
const str = new TextDecoder().decode(buf)
|
|
||||||
if( typeof e.detail[1] == 'function' ) e.detail[1](str,buf)
|
|
||||||
else console.log(str)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
events:{
|
events:{
|
||||||
|
|
||||||
// combined AFRAME+DOM reactive events
|
// combined AFRAME+DOM reactive events
|
||||||
|
|
@ -503,7 +470,6 @@ if( typeof AFRAME != 'undefined '){
|
||||||
"scope": "/",
|
"scope": "/",
|
||||||
"theme_color": "#3367D6",
|
"theme_color": "#3367D6",
|
||||||
"shortcuts": [
|
"shortcuts": [
|
||||||
/*
|
|
||||||
{
|
{
|
||||||
"name": "What is the latest news?",
|
"name": "What is the latest news?",
|
||||||
"cli":{
|
"cli":{
|
||||||
|
|
@ -518,9 +484,8 @@ if( typeof AFRAME != 'undefined '){
|
||||||
"url": "/today?source=pwa",
|
"url": "/today?source=pwa",
|
||||||
"icons": [{ "src": "/images/today.png", "sizes": "192x192" }]
|
"icons": [{ "src": "/images/today.png", "sizes": "192x192" }]
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
],
|
],
|
||||||
"description": "Runs an .iso file",
|
"description": "Hello world information",
|
||||||
"screenshots": [
|
"screenshots": [
|
||||||
{
|
{
|
||||||
"src": "/images/screenshot1.png",
|
"src": "/images/screenshot1.png",
|
||||||
|
|
@ -530,7 +495,7 @@ if( typeof AFRAME != 'undefined '){
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"help":`
|
"help":`
|
||||||
XRSH 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
|
||||||
|
|
|
||||||
|
|
@ -18,17 +18,14 @@ function ISOTerminal(instance,opts){
|
||||||
ISOTerminal.prototype.emit = function(event,data,sender){
|
ISOTerminal.prototype.emit = function(event,data,sender){
|
||||||
data = data || false
|
data = data || false
|
||||||
const evObj = new CustomEvent(event, {detail: data} )
|
const evObj = new CustomEvent(event, {detail: data} )
|
||||||
// forward event to worker/instance/AFRAME element or component-function
|
this.preventFrameDrop( () => {
|
||||||
// this feels complex, but actually keeps event- and function-names more concise in codebase
|
// forward event to worker/instance/AFRAME element or component-function
|
||||||
let fire = () => {
|
// this feels complex, but actually keeps event- and function-names more concise in codebase
|
||||||
this.dispatchEvent( evObj )
|
this.dispatchEvent( evObj )
|
||||||
if( sender != "instance" && this.instance ) this.instance.dispatchEvent(evObj)
|
if( sender != "instance" && this.instance ) this.instance.dispatchEvent(evObj)
|
||||||
if( sender != "worker" && this.worker ) this.worker.postMessage({event,data}, PromiseWorker.prototype.getTransferable(data) )
|
if( sender != "worker" && this.worker ) this.worker.postMessage({event,data}, PromiseWorker.prototype.getTransferable(data) )
|
||||||
if( sender !== undefined && typeof this[event] == 'function' ) this[event].apply(this, data && data.push ? data : [data] )
|
if( sender !== undefined && typeof this[event] == 'function' ) this[event].apply(this, data && data.push ? data : [data] )
|
||||||
}
|
})
|
||||||
if( event.match(/^serial/) ){
|
|
||||||
this.preventFrameDrop( fire )
|
|
||||||
}else fire()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ISOTerminal.addEventListener = (event,cb) => {
|
ISOTerminal.addEventListener = (event,cb) => {
|
||||||
|
|
@ -37,21 +34,13 @@ ISOTerminal.addEventListener = (event,cb) => {
|
||||||
ISOTerminal.listener[event].push(cb)
|
ISOTerminal.listener[event].push(cb)
|
||||||
}
|
}
|
||||||
|
|
||||||
ISOTerminal.prototype.exec = function(opts){
|
ISOTerminal.prototype.exec = function(shellscript){
|
||||||
const shellscript = opts[0];
|
this.send(`printf "\n\r"; ${shellscript}\n`,1)
|
||||||
const cb = opts[1];
|
|
||||||
let cmd = `printf "\n\r"; { sh<<EOF\n ${shellscript}; \nEOF\n} &> /mnt/exec;\n`
|
|
||||||
console.log(cmd)
|
|
||||||
if( cb ){
|
|
||||||
window.cb = cb
|
|
||||||
cmd += `js 'document.querySelector("[isoterminal]").emit("read_file", ["exec", window.cb ])';\n`
|
|
||||||
}
|
|
||||||
this.send( cmd, 1 )
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ISOTerminal.prototype.hook = function(hookname,args){
|
ISOTerminal.prototype.hook = function(hookname,args){
|
||||||
let cmd = `{ type hook || source /etc/profile.sh; }; hook ${hookname} "${args.join('" "')}"`
|
let cmd = `{ type hook || source /etc/profile.sh; }; hook ${hookname} "${args.join('" "')}"`
|
||||||
this.exec([cmd])
|
this.exec(cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
ISOTerminal.prototype.serial_input = 0; // can be set to 0,1,2,3 to define stdinput tty (xterm plugin)
|
ISOTerminal.prototype.serial_input = 0; // can be set to 0,1,2,3 to define stdinput tty (xterm plugin)
|
||||||
|
|
@ -142,14 +131,8 @@ ISOTerminal.prototype.start = function(opts){
|
||||||
},
|
},
|
||||||
cmdline: "rw root=host9p rootfstype=9p rootflags=trans=virtio,cache=loose modules=virtio_pci tsc=reliable init_on_freg|=on vga=ask", //vga=0x122",
|
cmdline: "rw root=host9p rootfstype=9p rootflags=trans=virtio,cache=loose modules=virtio_pci tsc=reliable init_on_freg|=on vga=ask", //vga=0x122",
|
||||||
net_device:{
|
net_device:{
|
||||||
//type:"ne2k",
|
relay_url:"fetch", // or websocket proxy "wss://relay.widgetry.org/",
|
||||||
relay_url:"fetch"
|
type:"virtio"
|
||||||
//relay_url:"wss://relay.widgetry.org/"
|
|
||||||
|
|
||||||
//local_http: true,
|
|
||||||
//type:"virtio",
|
|
||||||
//relay_url:"fetch",
|
|
||||||
//cors_proxy: "https://corsproxy.io/"
|
|
||||||
},
|
},
|
||||||
//bzimage_initrd_from_filesystem: true,
|
//bzimage_initrd_from_filesystem: true,
|
||||||
//filesystem: {
|
//filesystem: {
|
||||||
|
|
@ -266,7 +249,6 @@ ISOTerminal.prototype.startVM = function(opts){
|
||||||
this.v86opts = opts
|
this.v86opts = opts
|
||||||
|
|
||||||
this.addEventListener('emulator-started', async (e) => {
|
this.addEventListener('emulator-started', async (e) => {
|
||||||
if( this.boot.fromImage ) return this.emit('serial-output-string', "\r[!] downloading session...please wait\n\r[!] this could take a while depending on your connection..\n\r")
|
|
||||||
|
|
||||||
let line = ''
|
let line = ''
|
||||||
this.ready = false
|
this.ready = false
|
||||||
|
|
@ -289,17 +271,12 @@ ISOTerminal.prototype.startVM = function(opts){
|
||||||
}
|
}
|
||||||
|
|
||||||
ISOTerminal.prototype.bootISO = function(){
|
ISOTerminal.prototype.bootISO = function(){
|
||||||
const getImage = (str) => decodeURIComponent(
|
|
||||||
str.match(/\&img=/) ? str.replace(/.*img=/,'').replace(/\&.*/,'') : ''
|
|
||||||
)
|
|
||||||
let msglib = this.getLoaderMsg()
|
let msglib = this.getLoaderMsg()
|
||||||
this.emit('status',msglib.loadmsg)
|
this.emit('status',msglib.loadmsg)
|
||||||
let msg = "\n\r" + msglib.empowermsg + msglib.text_color + msglib.loadmsg + msglib.text_reset
|
let msg = "\n\r" + msglib.empowermsg + msglib.text_color + msglib.loadmsg + msglib.text_reset
|
||||||
this.emit('serial-output-string', msg)
|
this.emit('serial-output-string', msg)
|
||||||
if( getImage(this.boot.hash) ){
|
this.emit('runISO',{...this.v86opts, bufferLatency: this.opts.bufferLatency })
|
||||||
this.boot.fromImage = true
|
|
||||||
}
|
|
||||||
this.emit('runISO',{...this.v86opts, bufferLatency: this.opts.bufferLatency, img: getImage(this.boot.hash) })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -44,8 +44,7 @@ function PromiseWorker(file, onmessage){
|
||||||
this.resolvers = this.resolvers || {last:1,pending:{}}
|
this.resolvers = this.resolvers || {last:1,pending:{}}
|
||||||
msg.data.promiseId = this.resolvers.last++
|
msg.data.promiseId = this.resolvers.last++
|
||||||
// Send id and task to WebWorker
|
// Send id and task to WebWorker
|
||||||
let dataTransferable = PromiseWorker.prototype.getTransferable(msg.data)
|
worker.postMessage(msg, PromiseWorker.prototype.getTransferable(msg.data) )
|
||||||
worker.postMessage(msg, dataTransferable )
|
|
||||||
return new Promise( resolve => this.resolvers.pending[ msg.data.promiseId ] = resolve );
|
return new Promise( resolve => this.resolvers.pending[ msg.data.promiseId ] = resolve );
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -73,5 +72,6 @@ PromiseWorker.prototype.getTransferable = function(data){
|
||||||
for( var i in data ){
|
for( var i in data ){
|
||||||
if( isTransferable(data[i]) ) objs.push(data[i])
|
if( isTransferable(data[i]) ) objs.push(data[i])
|
||||||
}
|
}
|
||||||
|
if( objs.length ) debugger
|
||||||
return objs.length ? objs : undefined
|
return objs.length ? objs : undefined
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,20 +28,6 @@ emulator.fs9p.update_file = async function(file,data){
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
emulator.fs9p.create_file_from_url = async function(file,url){
|
|
||||||
const convert = ISOTerminal.prototype.convert
|
|
||||||
return fetch(url)
|
|
||||||
.then( (res) => res.arrayBuffer() )
|
|
||||||
.then( (buf) => {
|
|
||||||
let arr = new Uint8Array(buf)
|
|
||||||
return emulator.create_file(file, arr )
|
|
||||||
})
|
|
||||||
.catch( (e) => {
|
|
||||||
emulator.create_file(file, new Uint8Array() ) // empty file so at least other processes can check for error (v86 has no retcodes for fs9p)
|
|
||||||
console.error(e)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
emulator.fs9p.append_file = async function(file,data){
|
emulator.fs9p.append_file = async function(file,data){
|
||||||
const convert = ISOTerminal.prototype.convert
|
const convert = ISOTerminal.prototype.convert
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,8 @@
|
||||||
// this is restoring state to/from the v86 emulator
|
|
||||||
// however instead of passing the huge blob between webworker/browser
|
|
||||||
// we transfer it via localforage as a base64 string
|
|
||||||
|
|
||||||
if( typeof emulator != 'undefined' ){
|
if( typeof emulator != 'undefined' ){
|
||||||
// inside worker-thread
|
// inside worker-thread
|
||||||
importScripts("localforage.js") // we don't instance it again here (just use its functions)
|
importScripts("localforage.js") // we don't instance it again here (just use its functions)
|
||||||
|
|
||||||
this.restore_state = async function(data){
|
this.restore_state = async function(data){
|
||||||
// fastforward instance state
|
|
||||||
this.opts.muteUntilPrompt = false
|
|
||||||
this.ready = true
|
|
||||||
|
|
||||||
return new Promise( (resolve,reject) => {
|
return new Promise( (resolve,reject) => {
|
||||||
localforage.getItem("state", async (err,stateBase64) => {
|
localforage.getItem("state", async (err,stateBase64) => {
|
||||||
if( stateBase64 && !err ){
|
if( stateBase64 && !err ){
|
||||||
|
|
@ -23,20 +15,15 @@ if( typeof emulator != 'undefined' ){
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
this.save_state = async function(){
|
this.save_state = async function(){
|
||||||
return new Promise( async (resolve,reject ) => {
|
console.log("saving session")
|
||||||
console.log("saving session")
|
let state = await emulator.save_state()
|
||||||
let state = await emulator.save_state()
|
localforage.setDriver([
|
||||||
localforage.setDriver([
|
localforage.INDEXEDDB,
|
||||||
localforage.INDEXEDDB,
|
localforage.WEBSQL,
|
||||||
localforage.WEBSQL,
|
localforage.LOCALSTORAGE
|
||||||
localforage.LOCALSTORAGE
|
]).then( () => {
|
||||||
])
|
localforage.setItem("state", ISOTerminal.prototype.convert.arrayBufferToBase64(state) )
|
||||||
.then( () => {
|
console.log("state saved")
|
||||||
localforage.setItem("state", ISOTerminal.prototype.convert.arrayBufferToBase64(state) )
|
|
||||||
console.log("state saved")
|
|
||||||
resolve()
|
|
||||||
})
|
|
||||||
.catch( reject )
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -45,44 +32,33 @@ if( typeof emulator != 'undefined' ){
|
||||||
// inside browser-thread
|
// inside browser-thread
|
||||||
ISOTerminal.addEventListener('emulator-started', function(e){
|
ISOTerminal.addEventListener('emulator-started', function(e){
|
||||||
this.autorestore(e)
|
this.autorestore(e)
|
||||||
this.emit("autorestore-installed")
|
|
||||||
})
|
})
|
||||||
|
|
||||||
ISOTerminal.prototype.restore = async function(e){
|
ISOTerminal.prototype.autorestore = async function(e){
|
||||||
|
|
||||||
const onGetItem = (err,stateBase64) => {
|
localforage.setDriver([
|
||||||
const askConfirm = () => {
|
localforage.INDEXEDDB,
|
||||||
if( window.localStorage.getItem("restorestate") == "true" ) return true
|
localforage.WEBSQL,
|
||||||
try{
|
localforage.LOCALSTORAGE
|
||||||
const scene = document.querySelector('a-scene');
|
]).then( () => {
|
||||||
if( scene.is('ar-mode') ) scene.exitAR()
|
|
||||||
if( scene.is('vr-mode') ) scene.exitVR()
|
|
||||||
}catch(e){}
|
|
||||||
return confirm( "Continue old session?" )
|
|
||||||
}
|
|
||||||
|
|
||||||
if( stateBase64 && !err && document.location.hash.length < 2 && askConfirm() ){
|
localforage.getItem("state", async (err,stateBase64) => {
|
||||||
this.noboot = true // see feat/boot.js
|
if( stateBase64 && !err && confirm('continue last session?') ){
|
||||||
try{
|
this.noboot = true // see feat/boot.js
|
||||||
this.worker.restore_state()
|
try{
|
||||||
.then( () => {
|
await this.worker.restore_state()
|
||||||
// simulate / fastforward boot events
|
// simulate / fastforward boot events
|
||||||
this.postBoot( () => {
|
this.postBoot( () => {
|
||||||
// force redraw terminal issue
|
this.send("l\n")
|
||||||
this.send("l")
|
this.send("hook wakeup\n")
|
||||||
setTimeout( () => this.send("l"), 200 )
|
|
||||||
//this.send("12")
|
|
||||||
this.emit("exec",["source /etc/profile.sh; hook wakeup\n"])
|
|
||||||
this.emit("restored")
|
|
||||||
})
|
})
|
||||||
})
|
}catch(e){ console.error(e) }
|
||||||
}catch(e){ console.error(e) }
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
this.save = async () => {
|
||||||
|
await this.worker.save_state()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const doRestore = () => {
|
|
||||||
|
|
||||||
localforage.getItem("state", (err,stateBase64) => onGetItem(err,stateBase64) )
|
|
||||||
|
|
||||||
window.addEventListener("beforeunload", function (e) {
|
window.addEventListener("beforeunload", function (e) {
|
||||||
var confirmationMessage = "Sure you want to leave?\nTIP: enter 'save' to continue this session later";
|
var confirmationMessage = "Sure you want to leave?\nTIP: enter 'save' to continue this session later";
|
||||||
|
|
@ -90,17 +66,7 @@ if( typeof emulator != 'undefined' ){
|
||||||
return confirmationMessage; //Webkit, Safari, Chrome
|
return confirmationMessage; //Webkit, Safari, Chrome
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
})
|
||||||
|
|
||||||
localforage.setDriver([
|
|
||||||
localforage.INDEXEDDB,
|
|
||||||
localforage.WEBSQL,
|
|
||||||
localforage.LOCALSTORAGE
|
|
||||||
])
|
|
||||||
.then( () => doRestore() )
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ISOTerminal.prototype.autorestore = ISOTerminal.prototype.restore // alias to launch during boot
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
ISOTerminal.addEventListener('ready', function(e){
|
ISOTerminal.addEventListener('ready', function(e){
|
||||||
setTimeout( () => this.boot(), 50 ) // allow other features/plugins to settle first (autorestore.js e.g.)
|
setTimeout( () => this.boot(), 50 ) // because of autorestore.js
|
||||||
})
|
})
|
||||||
|
|
||||||
ISOTerminal.prototype.bootMenu = function(e){
|
ISOTerminal.prototype.bootMenu = function(e){
|
||||||
|
|
@ -17,13 +17,13 @@ ISOTerminal.prototype.bootMenu = function(e){
|
||||||
|
|
||||||
}else{ // autoboot
|
}else{ // autoboot
|
||||||
if( this.term ){
|
if( this.term ){
|
||||||
this.term.handler( String(e.detail.bootMenu || e.detail.bootMenuURL).charAt(0) )
|
this.term.handler( e.detail.bootMenu || e.detail.bootMenuURL )
|
||||||
this.term.handler("\n")
|
this.term.handler("\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ISOTerminal.addEventListener('bootMenu', function(e){this.bootMenu(e) })
|
ISOTerminal.addEventListener('bootMenu', function(e){ this.bootMenu(e) } )
|
||||||
|
|
||||||
ISOTerminal.prototype.boot = async function(e){
|
ISOTerminal.prototype.boot = async function(e){
|
||||||
// set environment
|
// set environment
|
||||||
|
|
@ -33,17 +33,10 @@ ISOTerminal.prototype.boot = async function(e){
|
||||||
'export BROWSER=1',
|
'export BROWSER=1',
|
||||||
]
|
]
|
||||||
for ( let i in document.location ){
|
for ( let i in document.location ){
|
||||||
if( typeof document.location[i] == 'string' && !String(i).match(/(hash|search)/) ){
|
if( typeof document.location[i] == 'string' ){
|
||||||
env.push( 'export '+String(i).toUpperCase()+'="'+decodeURIComponent( document.location[i]+'"') )
|
env.push( 'export '+String(i).toUpperCase()+'="'+decodeURIComponent( document.location[i]+'"') )
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// we export the cached hash/query (because they might be gone due to remotestorage plugin)
|
|
||||||
if( this.boot.hash.charAt(2) == '&' ){ // strip bootoption
|
|
||||||
this.boot.hashExBoot = `#` + this.boot.hash.substr(3)
|
|
||||||
}
|
|
||||||
env.push( 'export HASH="'+decodeURIComponent( this.boot.hashExBoot || this.boot.hash ) +'"' )
|
|
||||||
env.push( 'export QUERY="'+decodeURIComponent( this.boot.query ) +'"' )
|
|
||||||
await this.worker.create_file("profile.browser", this.convert.toUint8Array( env.join('\n') ) )
|
await this.worker.create_file("profile.browser", this.convert.toUint8Array( env.join('\n') ) )
|
||||||
|
|
||||||
if( this.serial_input == 0 ){
|
if( this.serial_input == 0 ){
|
||||||
|
|
@ -54,12 +47,8 @@ ISOTerminal.prototype.boot = async function(e){
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ISOTerminal.prototype.boot.fromImage = false
|
|
||||||
ISOTerminal.prototype.boot.menu = []
|
|
||||||
ISOTerminal.prototype.boot.hash = document.location.hash
|
|
||||||
ISOTerminal.prototype.boot.query = document.location.search
|
|
||||||
|
|
||||||
// here REPL's can be defined
|
// here REPL's can be defined
|
||||||
|
ISOTerminal.prototype.boot.menu = []
|
||||||
|
|
||||||
// REPL: iso
|
// REPL: iso
|
||||||
if( typeof window.PromiseWorker != 'undefined' ){ // if xrsh v86 is able to run in in worker
|
if( typeof window.PromiseWorker != 'undefined' ){ // if xrsh v86 is able to run in in worker
|
||||||
|
|
|
||||||
|
|
@ -20,38 +20,15 @@ if( typeof emulator != 'undefined' ){
|
||||||
}else{
|
}else{
|
||||||
// inside browser-thread
|
// inside browser-thread
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* here we're going to execute javascript in the browser,
|
|
||||||
* both a terrible and great idea depending on a corporate vs personal computing angle.
|
|
||||||
*
|
|
||||||
* if a function-string gets evaluated, and it returns a promise..then we assume async.
|
|
||||||
*/
|
|
||||||
ISOTerminal.addEventListener('javascript-eval', async function(e){
|
ISOTerminal.addEventListener('javascript-eval', async function(e){
|
||||||
const {script,PID} = e.detail
|
const {script,PID} = e.detail
|
||||||
let res;
|
let res;
|
||||||
let error = false;
|
|
||||||
|
|
||||||
const output = (res,PID) => {
|
|
||||||
if( res && typeof res != 'string' ) res = JSON.stringify(res,null,2)
|
|
||||||
// update output to 9p with PID as filename (in /mnt/run)
|
|
||||||
if( PID ){
|
|
||||||
this.worker.update_file(`run/${PID}.exit`, error ? "1" : "0")
|
|
||||||
this.worker.update_file(`run/${PID}`, this.convert.toUint8Array(res) )
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try{
|
try{
|
||||||
let f = new Function(`${script}`);
|
let f = new Function(`${script}`);
|
||||||
res = f();
|
res = f();
|
||||||
if( res && typeof res.then == 'function' ){ // if we got a promise
|
if( res && typeof res != 'string' ) res = JSON.stringify(res,null,2)
|
||||||
res.then( (res) => output(res,PID) )
|
|
||||||
res.catch( (e) => output(e,PID) )
|
|
||||||
}else{ // normal sync function
|
|
||||||
output(res,PID)
|
|
||||||
}
|
|
||||||
}catch(err){
|
}catch(err){
|
||||||
error = true
|
|
||||||
console.error(err)
|
console.error(err)
|
||||||
console.dir(err)
|
console.dir(err)
|
||||||
res = "error: "+err.toString()
|
res = "error: "+err.toString()
|
||||||
|
|
@ -64,7 +41,10 @@ if( typeof emulator != 'undefined' ){
|
||||||
res += script.split("\n")[lnr-1]
|
res += script.split("\n")[lnr-1]
|
||||||
}else console.dir(script)
|
}else console.dir(script)
|
||||||
console.error(res)
|
console.error(res)
|
||||||
output(res,PID)
|
}
|
||||||
|
// update output to 9p with PID as filename (in /mnt/run)
|
||||||
|
if( PID ){
|
||||||
|
this.worker.update_file(`run/${PID}`, this.convert.toUint8Array(res) )
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
ISOTerminal.prototype.redirectConsole = function(handler){
|
ISOTerminal.prototype.redirectConsole = function(handler){
|
||||||
const log = console._log = console.log;
|
const log = console.log;
|
||||||
const dir = console._dir = console.dir;
|
const dir = console.dir;
|
||||||
const err = console._error = console.error;
|
const err = console.error;
|
||||||
const warn = console._warn = console.warn;
|
const warn = console.warn;
|
||||||
const addLineFeeds = (str) => typeof str == 'string' ? str.replace(/\n/g,"\r\n") : str
|
const addLineFeeds = (str) => typeof str == 'string' ? str.replace(/\n/g,"\r\n") : str
|
||||||
|
|
||||||
console.log = (...args)=>{
|
console.log = (...args)=>{
|
||||||
|
|
@ -37,13 +37,12 @@ ISOTerminal.prototype.enableConsole = function(opts){
|
||||||
let _str = typeof str == 'string' ? str : JSON.stringify(str)
|
let _str = typeof str == 'string' ? str : JSON.stringify(str)
|
||||||
let finalStr = "";
|
let finalStr = "";
|
||||||
prefix = prefix ? prefix+' ' : ''
|
prefix = prefix ? prefix+' ' : ''
|
||||||
String(_str).trim().split("\n").map( (line) => {
|
_str.trim().split("\n").map( (line) => {
|
||||||
finalStr += `${opts.stdout ? '' : "\x1b[38;5;165m/dev/browser: \x1b[0m"}`+prefix+line+'\n'
|
finalStr += `${opts.stdout ? '' : "\x1b[38;5;165m/dev/browser: \x1b[0m"}`+prefix+line+'\n'
|
||||||
})
|
})
|
||||||
if( opts.stdout ){
|
if( opts.stdout ){
|
||||||
this.emit('serial-output-string', finalStr, "worker")
|
this.emit('serial-output-string', finalStr)
|
||||||
}else this.emit('append_file', ["/dev/browser/console",finalStr])
|
}else this.emit('append_file', ["/dev/browser/console",finalStr])
|
||||||
this.lastStr = finalStr
|
|
||||||
})
|
})
|
||||||
|
|
||||||
window.addEventListener('error', function(event) {
|
window.addEventListener('error', function(event) {
|
||||||
|
|
@ -96,12 +95,34 @@ ISOTerminal.addEventListener('init', function(){
|
||||||
}, 100 )
|
}, 100 )
|
||||||
},
|
},
|
||||||
keyHandler: function(ch){
|
keyHandler: function(ch){
|
||||||
|
let erase = false
|
||||||
|
// if( ch == '\x7F' ){
|
||||||
|
// ch = "\b \b" // why does write() not just support \x7F ?
|
||||||
|
// erase = true
|
||||||
|
// }
|
||||||
this.send(ch)
|
this.send(ch)
|
||||||
},
|
const reset = () => {
|
||||||
cmdHandler: function(cmd){
|
this.console = ""
|
||||||
this.send("\n\r")
|
setTimeout( () => {
|
||||||
eval(cmd)
|
if( this.boot.menu.selected ) this.send(this.prompt)
|
||||||
setTimeout( () => this.send(this.prompt) ,10) // because worker vs terminal
|
},100)
|
||||||
|
}
|
||||||
|
if( (ch == "\n" || ch == "\r") ){
|
||||||
|
try{
|
||||||
|
this.send("\n\r")
|
||||||
|
if( this.console ) eval(this.console)
|
||||||
|
reset()
|
||||||
|
}catch(e){
|
||||||
|
reset()
|
||||||
|
throw e // re throw
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
if( erase ){
|
||||||
|
this.console = this.console.split('').slice(0,-1).join('')
|
||||||
|
}else{
|
||||||
|
this.console += ch
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ ISOTerminal.prototype.enableRemoteKeyboard = function(opts){
|
||||||
this.send(clearScreen);
|
this.send(clearScreen);
|
||||||
this.send(`\n\rfor instructions\n\rsee ${document.location.origin}/index.html#Remote%20keyboard\n\n\r`)
|
this.send(`\n\rfor instructions\n\rsee ${document.location.origin}/index.html#Remote%20keyboard\n\n\r`)
|
||||||
this.send("enter 'm' for mainmenu\n\n\r")
|
this.send("enter 'm' for mainmenu\n\n\r")
|
||||||
this.send("[36mkeyboard ip-address> [0m")
|
this.send("[36mkeyboard ip-adress> [0m")
|
||||||
// autofill ip
|
// autofill ip
|
||||||
if( service.ip ){
|
if( service.ip ){
|
||||||
for( let i = 0; i < service.ip.length; i++ ) this.send(service.ip.charAt(i))
|
for( let i = 0; i < service.ip.length; i++ ) this.send(service.ip.charAt(i))
|
||||||
|
|
@ -26,12 +26,11 @@ ISOTerminal.prototype.enableRemoteKeyboard = function(opts){
|
||||||
},
|
},
|
||||||
server: (term) => {
|
server: (term) => {
|
||||||
try{
|
try{
|
||||||
service.addr = `wss://${service.ip}:9090/`;
|
service.addr = `ws://${service.ip}:9090/`;
|
||||||
service.ws = new WebSocket(service.addr)
|
service.ws = new WebSocket(service.addr)
|
||||||
service.ws.addEventListener("error", console.error )
|
service.ws.addEventListener("open", () => {
|
||||||
service.ws.addEventListener("open", (e) => {
|
|
||||||
if( service.state == 'listening' ){
|
if( service.state == 'listening' ){
|
||||||
this.send(`\n\rconnected! \\o/\n\r`)
|
this.send(`\n\rconnected to ${service.addr}! \\o/\n\r`)
|
||||||
this.bootMenu()
|
this.bootMenu()
|
||||||
}
|
}
|
||||||
service.state = 'receiving'
|
service.state = 'receiving'
|
||||||
|
|
@ -77,9 +76,9 @@ ISOTerminal.prototype.enableRemoteKeyboard = function(opts){
|
||||||
this.send("\n\r")
|
this.send("\n\r")
|
||||||
this.bootMenu()
|
this.bootMenu()
|
||||||
}else if( ch == '\n' || ch == '\r'){
|
}else if( ch == '\n' || ch == '\r'){
|
||||||
|
this.send("\n\rwaiting for connection..")
|
||||||
service.server(this)
|
service.server(this)
|
||||||
service.state = 'listening'
|
service.state = 'listening'
|
||||||
this.send("\n\rconnecting to "+service.addr)
|
|
||||||
}else{
|
}else{
|
||||||
service.ip = ch == '\b' ? service.ip.substr(0,this.service.ip.length-1)
|
service.ip = ch == '\b' ? service.ip.substr(0,this.service.ip.length-1)
|
||||||
: service.ip + ch
|
: service.ip + ch
|
||||||
|
|
|
||||||
|
|
@ -1,365 +0,0 @@
|
||||||
/* Remote storage feature
|
|
||||||
*
|
|
||||||
* NOTE: this feature extends the localstorage functions.
|
|
||||||
* this file can be excluded without crippling the
|
|
||||||
* core localstorage mechanism.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// see https://remotestorage.io/rs.js/docs/data-modules/
|
|
||||||
ISOTerminal.prototype.remoteStorageModule = {
|
|
||||||
name: 'xrsh',
|
|
||||||
builder: function(privateClient, publicClient) {
|
|
||||||
return {
|
|
||||||
exports: {
|
|
||||||
add: function(data,opts) {
|
|
||||||
if( !data || !opts.filename || !opts.mimetype) throw 'webxr.add() needs filedata + filename + mimetype'
|
|
||||||
const client = opts.client = opts.public ? publicClient : privateClient;
|
|
||||||
return client.storeFile(opts.mimetype, opts.filename,data)
|
|
||||||
},
|
|
||||||
|
|
||||||
getListing: function(a,opts){
|
|
||||||
opts = opts || {}
|
|
||||||
const client = opts.public ? publicClient : privateClient;
|
|
||||||
return client.getListing(a)
|
|
||||||
},
|
|
||||||
|
|
||||||
getFile: function(file,opts){
|
|
||||||
opts = opts || {}
|
|
||||||
const client = opts.public ? publicClient : privateClient;
|
|
||||||
return client.getFile(file)
|
|
||||||
},
|
|
||||||
|
|
||||||
remove: function(file,opts){
|
|
||||||
opts = opts || {}
|
|
||||||
const client = opts.public ? publicClient : privateClient;
|
|
||||||
return client.remove(file)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// this is the HTML which we append to the remotestorage widget:
|
|
||||||
// https://remotestorage.io/rs.js/docs/getting-started/connect-widget.html
|
|
||||||
const _rs_widget_html = `
|
|
||||||
<div class="form" id="remote">
|
|
||||||
<select class="remote" id="states"></select>
|
|
||||||
<button id="save">save</button>
|
|
||||||
|
|
||||||
<a class="btn remote" href="https://inspektor.5apps.com/?path=xrsh%2F" style="background:#888" target="_blank">filemanager</a>
|
|
||||||
<div id="result"></div>
|
|
||||||
</div>
|
|
||||||
<div class="form" id="local" style="position:relative">
|
|
||||||
<hr>
|
|
||||||
<div class="header">WebBrowser session:</div>
|
|
||||||
<div style="text-align:right">
|
|
||||||
<button id="saveLocal">save</button>
|
|
||||||
<button id="restoreLocal">restore</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- disabled for now -->
|
|
||||||
<div class="form" id="local" style="position:relative; display:none">
|
|
||||||
<hr>
|
|
||||||
<div class="header">File session:</div>
|
|
||||||
<div style="text-align:right">
|
|
||||||
<button id="saveLocalFile">save file</button>
|
|
||||||
<button id="restoreLocalFile">import file</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style type="text/css">
|
|
||||||
#remotestorage-widget{
|
|
||||||
position:fixed;
|
|
||||||
top:0;
|
|
||||||
right:0;
|
|
||||||
}
|
|
||||||
#remotestorage-widget h1,
|
|
||||||
#remotestorage-widget h2,
|
|
||||||
#remotestorage-widget h3,
|
|
||||||
#remotestorage-widget h4,
|
|
||||||
#remotestorage-widget h5{
|
|
||||||
color:#222;
|
|
||||||
}
|
|
||||||
#remotestorage-widget .rs-widget{
|
|
||||||
margin:25px;
|
|
||||||
}
|
|
||||||
#remotestorage-widget .rs-closed .form{
|
|
||||||
display:none !important;
|
|
||||||
}
|
|
||||||
#remotestorage-widget .form select#states{
|
|
||||||
padding: 5px;
|
|
||||||
margin-top: 10px;
|
|
||||||
margin-right: 10px;
|
|
||||||
border-radius: 5px;
|
|
||||||
}
|
|
||||||
#remotestorage-widget .form #result{
|
|
||||||
padding-top:10px;
|
|
||||||
}
|
|
||||||
#remotestorage-widget .form select,
|
|
||||||
#remotestorage-widget .form button,
|
|
||||||
#remotestorage-widget .form a{
|
|
||||||
height:35px;
|
|
||||||
margin-bottom:0;
|
|
||||||
}
|
|
||||||
#remotestorage-widget *{
|
|
||||||
font-size:15px;
|
|
||||||
}
|
|
||||||
#remotestorage-widget div.header{
|
|
||||||
position:absolute;
|
|
||||||
display:inline;
|
|
||||||
top:15px;
|
|
||||||
left:0;
|
|
||||||
font-weight:bold;
|
|
||||||
}
|
|
||||||
#remotestorage-widget .form .btn {
|
|
||||||
text-decoration: none;
|
|
||||||
color: white;
|
|
||||||
margin-top: 10px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
display: inline-block;
|
|
||||||
line-height: 15px;
|
|
||||||
}
|
|
||||||
#remotestorage-widget .form{
|
|
||||||
margin-top:10px;
|
|
||||||
}
|
|
||||||
#remotestorage-widget .btn:hover,
|
|
||||||
#remotestorage-widget button:hover {
|
|
||||||
background:#CCC;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
`
|
|
||||||
|
|
||||||
const widgetForm = function(opts){
|
|
||||||
let el = document.querySelector("#remotestorage-widget select#states")
|
|
||||||
const $widget = document.querySelector("#remotestorage-widget .rs-widget")
|
|
||||||
const $result = document.querySelector("#remotestorage-widget #result")
|
|
||||||
if( !el ){
|
|
||||||
|
|
||||||
let div = document.createElement('div')
|
|
||||||
div.innerHTML = _rs_widget_html
|
|
||||||
$widget.appendChild(div)
|
|
||||||
$widget.querySelector("h3").innerText = "Click here to connect your storage"
|
|
||||||
|
|
||||||
el = $widget.querySelector("select#states")
|
|
||||||
el.addEventListener('change', () => {
|
|
||||||
if( el.options.selectedIndex == 0 ) return; // ignore default option
|
|
||||||
this.remoteStorage.widget.filename = el.value
|
|
||||||
opts.status = "loading..."
|
|
||||||
$widget.classList.add(["blink"])
|
|
||||||
this.remoteStorage.xrsh.getFile(el.value)
|
|
||||||
.then( (data,err) => {
|
|
||||||
this.restore({remotestorage:true, data:data.data})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
this.setupStorageListeners($widget,opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.updateStorageForm(el,$widget,$result,opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
ISOTerminal.prototype.remoteStorageWidget = function(){
|
|
||||||
if( this.remoteStorageWidget.installed ) return // only do once
|
|
||||||
let apis = {
|
|
||||||
//dropbox: "ce876ce",
|
|
||||||
//googledrive: "c3983c3"
|
|
||||||
}
|
|
||||||
const modules = [ this.remoteStorageModule ]
|
|
||||||
const remoteStorage = this.remoteStorage = new RemoteStorage({logging: true, modules })
|
|
||||||
remoteStorage.setApiKeys(apis)
|
|
||||||
|
|
||||||
remoteStorage.on('not-connected', (e) => {
|
|
||||||
this.remoteStorage.connected = false
|
|
||||||
this.remoteStorage.widget.form({show:false})
|
|
||||||
})
|
|
||||||
remoteStorage.on('ready', (e) => { })
|
|
||||||
remoteStorage.on('connected', (e) => {
|
|
||||||
this.remoteStorage.connected = true
|
|
||||||
this.remoteStorage.widget.form({update:true,show:true})
|
|
||||||
})
|
|
||||||
|
|
||||||
remoteStorage.access.claim( `xrsh`, 'rw'); // our data dir
|
|
||||||
remoteStorage.caching.enable( `/xrsh/` ) // local-first, remotestorage-second
|
|
||||||
remoteStorage.caching.enable( `/public/xrsh/` ) // local-first, remotestorage-second
|
|
||||||
|
|
||||||
// create widget
|
|
||||||
let opts = {}
|
|
||||||
opts.modalBackdrop = false
|
|
||||||
opts.leaveOpen = false
|
|
||||||
const widget = remoteStorage.widget = new window.Widget(remoteStorage, opts)
|
|
||||||
widget.attach();
|
|
||||||
widget.form = widgetForm.bind(this)
|
|
||||||
|
|
||||||
this.remoteStorageWidget.installed = true
|
|
||||||
}
|
|
||||||
|
|
||||||
ISOTerminal.prototype.setupStorageListeners = function($widget,opts){
|
|
||||||
|
|
||||||
this.addEventListener("ready", () => {
|
|
||||||
opts.loading = false
|
|
||||||
opts.status = "session loaded"
|
|
||||||
})
|
|
||||||
|
|
||||||
// setup events
|
|
||||||
$widget.querySelector("button#save").addEventListener("click", () => {
|
|
||||||
this.save({remotestorage:true})
|
|
||||||
})
|
|
||||||
$widget.querySelector("button#saveLocal").addEventListener("click", async () => {
|
|
||||||
if( confirm("save current session?") ){
|
|
||||||
$widget.classList.add(['blink'])
|
|
||||||
await this.worker.save_state()
|
|
||||||
$widget.classList.remove(['blink'])
|
|
||||||
alert("succesfully saved session")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
$widget.querySelector("button#restoreLocal").addEventListener("click", async () => {
|
|
||||||
this.restore({localstorage:true})
|
|
||||||
})
|
|
||||||
$widget.querySelector("button#saveLocalFile").addEventListener("click", () => {
|
|
||||||
alert("saveFile")
|
|
||||||
})
|
|
||||||
$widget.querySelector("button#restoreLocalFile").addEventListener("click", () => {
|
|
||||||
alert("saveFile")
|
|
||||||
})
|
|
||||||
$widget.addEventListener("click", () => this.remoteStorage.widget.open() )
|
|
||||||
this.addEventListener("restored", () => this.remoteStorage.widget.form({loading:false, status:"session restored"}) )
|
|
||||||
}
|
|
||||||
|
|
||||||
ISOTerminal.prototype.updateStorageForm = function(el,$widget,$result,opts){
|
|
||||||
|
|
||||||
if( typeof opts.show != 'undefined' ){
|
|
||||||
$widget.querySelector('.form#remote').style.display = opts.show ? 'block' : 'none'
|
|
||||||
}
|
|
||||||
|
|
||||||
if( typeof opts.status != 'undefined'){
|
|
||||||
$result.innerText = opts.status
|
|
||||||
}
|
|
||||||
|
|
||||||
if( opts.public ){
|
|
||||||
const link = this.remoteStorage.remote.href + '/public/xrsh/' + this.remoteStorage.widget.filename
|
|
||||||
const linkWebView = document.location.href.replace(/(\?|#).*/,'') + `#1&img=${link}`
|
|
||||||
$result.innerHTML += `<br><a class="btn" href="${linkWebView}" target="_blank">public weblink</a>`
|
|
||||||
}
|
|
||||||
|
|
||||||
if( typeof opts.loading != 'undefined' ){
|
|
||||||
$widget.classList[ opts.loading ? 'add' : 'remove' ](["blink"])
|
|
||||||
}
|
|
||||||
|
|
||||||
if( opts.update ){
|
|
||||||
|
|
||||||
const createOption = (opt) => {
|
|
||||||
let option = document.createElement("option")
|
|
||||||
option.value = opt.value
|
|
||||||
option.innerText = opt.name || opt.value
|
|
||||||
el.appendChild(option)
|
|
||||||
}
|
|
||||||
|
|
||||||
el.innerHTML = ''
|
|
||||||
createOption({value:"-- snapshots --"})
|
|
||||||
this.remoteStorage.xrsh.getListing()
|
|
||||||
.then( (data,err) => {
|
|
||||||
for( let file in data ){
|
|
||||||
createOption({value:file})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const requireFiles = () => AFRAME.utils.require({remotestorageWidget:"assets/aframe-remotestorage.min.js"})
|
|
||||||
const getForage = () => localforage.setDriver([
|
|
||||||
localforage.INDEXEDDB,
|
|
||||||
localforage.WEBSQL,
|
|
||||||
localforage.LOCALSTORAGE
|
|
||||||
])
|
|
||||||
|
|
||||||
// we extend the autorestore feature at the init-event
|
|
||||||
ISOTerminal.addEventListener('init', function(e){
|
|
||||||
|
|
||||||
const decorateSave = () => {
|
|
||||||
|
|
||||||
let localSave = this.save
|
|
||||||
|
|
||||||
this.save = async (opts) => {
|
|
||||||
requireFiles()
|
|
||||||
.then( getForage )
|
|
||||||
.then( async () => {
|
|
||||||
|
|
||||||
if( opts.localstorage ){
|
|
||||||
this.save.localstorage(opts) // default behaviour
|
|
||||||
}
|
|
||||||
|
|
||||||
this.remoteStorageWidget()
|
|
||||||
|
|
||||||
if( opts.remotestorage ){
|
|
||||||
this.remoteStorage.widget.open() // force open dialog
|
|
||||||
|
|
||||||
let filename = String(this.remoteStorage.widget.filename || "snapshot").replace(/\.bin/,'')
|
|
||||||
filename = prompt("please name your snapshot:", filename )
|
|
||||||
filename = filename.replace(/\.bin*/,'') + '.bin'
|
|
||||||
|
|
||||||
// make it public or not (disabled for now as it does not work flawlessly)
|
|
||||||
const public = false // confirm('create a public link?')
|
|
||||||
this.remoteStorage.widget.filename = filename
|
|
||||||
|
|
||||||
this.remoteStorage.widget.form({loading:true,status: "saving.."})
|
|
||||||
|
|
||||||
await this.worker.save_state()
|
|
||||||
localforage.getItem("state", async (err,stateBase64) => {
|
|
||||||
this.remoteStorage.xrsh.add( stateBase64, {filename, mimetype: 'application/x-v86-base64', public})
|
|
||||||
.then( () => {
|
|
||||||
console.log("saved to remotestorage")
|
|
||||||
setTimeout(
|
|
||||||
() => this.remoteStorage.widget.form({update:true, loading:false, status:"saved succesfully", public})
|
|
||||||
,500 )
|
|
||||||
})
|
|
||||||
.catch( console.error )
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
}
|
|
||||||
this.save.localstorage = localSave
|
|
||||||
}
|
|
||||||
|
|
||||||
const decorateRestore = () => {
|
|
||||||
|
|
||||||
let localRestore = this.restore
|
|
||||||
|
|
||||||
this.restore = async (opts) => {
|
|
||||||
requireFiles()
|
|
||||||
.then( getForage )
|
|
||||||
.then( async () => {
|
|
||||||
|
|
||||||
document.querySelector("#remotestorage-widget .rs-widget").classList.add(['blink'])
|
|
||||||
if( opts.localstorage ){
|
|
||||||
this.restore.localstorage.apply(this,opts) // default behaviour
|
|
||||||
}
|
|
||||||
if( opts.remotestorage ){
|
|
||||||
localforage.setItem( "state", opts.data )
|
|
||||||
.then( () => this.restore.localstorage.call(this,opts) ) // now trigger default behaviour
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
}
|
|
||||||
this.restore.localstorage = localRestore
|
|
||||||
this.autorestore = this.restore // reroute automapping
|
|
||||||
}
|
|
||||||
|
|
||||||
// decorate save() and restore() of autorestore.js after it's declared (hence the setTimeout)
|
|
||||||
this.addEventListener('autorestore-installed', () => {
|
|
||||||
decorateSave()
|
|
||||||
decorateRestore()
|
|
||||||
})
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
// decorate autorestore
|
|
||||||
const autorestoreLocalStorage = ISOTerminal.prototype.autorestore;
|
|
||||||
ISOTerminal.prototype.autorestore = function(data){
|
|
||||||
requireFiles().then( async () => {
|
|
||||||
this.remoteStorageWidget()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -13,9 +13,9 @@ ISOTerminal.prototype.TermInit = function(){
|
||||||
rows: aEntity.rows,
|
rows: aEntity.rows,
|
||||||
el_or_id: el,
|
el_or_id: el,
|
||||||
scrollback: aEntity.rows*3,
|
scrollback: aEntity.rows*3,
|
||||||
fontSize: null,
|
fontSize: null //
|
||||||
//rainbow: [Term.COLOR_MAGENTA, Term.COLOR_CYAN ],
|
//rainbow: [Term.COLOR_MAGENTA, Term.COLOR_CYAN ],
|
||||||
isWebXRKeyboard: () => AFRAME.scenes[0].renderer.xr.isPresenting && AFRAME.scenes[0].renderer.xr.getSession().isSystemKeyboardSupported, // naive way
|
//xr: AFRAME.scenes[0].renderer.xr,
|
||||||
//map: {
|
//map: {
|
||||||
// 'ArrowRight': { ch: false, ctrl: '\x1b\x66' }, // this triggers ash-shell forward-word
|
// 'ArrowRight': { ch: false, ctrl: '\x1b\x66' }, // this triggers ash-shell forward-word
|
||||||
// 'ArrowLeft': { ch: false, ctrl: '\x1b\x62' } // backward-word
|
// 'ArrowLeft': { ch: false, ctrl: '\x1b\x62' } // backward-word
|
||||||
|
|
@ -25,6 +25,12 @@ ISOTerminal.prototype.TermInit = function(){
|
||||||
// patch Term-class
|
// patch Term-class
|
||||||
Term.prototype.move_textarea = function(){} /* *TODO* *FIXME* does not work in winbox */
|
Term.prototype.move_textarea = function(){} /* *TODO* *FIXME* does not work in winbox */
|
||||||
|
|
||||||
|
Term.prototype.pasteHandler = function(original){
|
||||||
|
return function (ev){
|
||||||
|
original.apply(this,[ev])
|
||||||
|
}
|
||||||
|
}( Term.prototype.pasteHandler )
|
||||||
|
|
||||||
Term.prototype.keyDownHandler = function(original){
|
Term.prototype.keyDownHandler = function(original){
|
||||||
return function (e){
|
return function (e){
|
||||||
if ((e.ctrlKey || e.metaKey) && e.key === 'v') {
|
if ((e.ctrlKey || e.metaKey) && e.key === 'v') {
|
||||||
|
|
@ -65,39 +71,27 @@ ISOTerminal.prototype.TermInit = function(){
|
||||||
// but instead extend/override ISOTerminal.prototype.boot.menu
|
// but instead extend/override ISOTerminal.prototype.boot.menu
|
||||||
// as demonstrated in index.html
|
// as demonstrated in index.html
|
||||||
this.term.setKeyHandler( (ch) => {
|
this.term.setKeyHandler( (ch) => {
|
||||||
let erase = false
|
let erase = false
|
||||||
const isEnter = ch == '\n' || ch == '\r'
|
|
||||||
if( ch == '\x7F' ){
|
if( ch == '\x7F' ){
|
||||||
ch = "\b \b" // why does write() not just support \x7F ?
|
ch = "\b \b" // why does write() not just support \x7F ?
|
||||||
erase = true
|
erase = true
|
||||||
}
|
}
|
||||||
if( this.boot.menu.selected ){
|
if( this.boot.menu.selected ){
|
||||||
this.boot.menu.selected.keyHandler.call(this,ch)
|
this.boot.menu.selected.keyHandler.call(this,ch)
|
||||||
if( isEnter ){
|
}else if( (ch == "\n" || ch == "\r") ){
|
||||||
if( this.boot.menu.selected.cmdHandler ){
|
|
||||||
this.boot.menu.selected.cmdHandler.call(this,this.lastCmd)
|
|
||||||
}
|
|
||||||
this.lastCmd = ""
|
|
||||||
}
|
|
||||||
}else if( isEnter ){
|
|
||||||
let menuitem = this.boot.menu.find( (m) => m.key == this.lastChar )
|
let menuitem = this.boot.menu.find( (m) => m.key == this.lastChar )
|
||||||
if( menuitem ){
|
if( menuitem ){
|
||||||
this.boot.menu.selected = menuitem
|
this.boot.menu.selected = menuitem
|
||||||
this.lastCmd = ""
|
|
||||||
menuitem.init.call(this, () => {
|
menuitem.init.call(this, () => {
|
||||||
this.term.write("\n\r")
|
this.term.write("\n\r")
|
||||||
this.bootMenu()
|
this.bootMenu()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
ch.split("").map( (ch) => this.term.write( ch ) )
|
this.term.write( ch )
|
||||||
}
|
}
|
||||||
if( !erase ){
|
if( !erase ) this.lastChar = ch
|
||||||
this.lastChar = ch
|
|
||||||
this.lastCmd = this.lastCmd ? this.lastCmd + ch : ch
|
|
||||||
}else this.lastCmd = this.lastCmd ? this.lastCmd.substr(0, this.lastCmd.length-1) : ""
|
|
||||||
})
|
})
|
||||||
|
|
||||||
aEntity.el.addEventListener('focus', () => {
|
aEntity.el.addEventListener('focus', () => {
|
||||||
let textarea = el.querySelector("textarea")
|
let textarea = el.querySelector("textarea")
|
||||||
textarea.focus()
|
textarea.focus()
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -31,7 +31,6 @@ function Term(options)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
this.options = options
|
|
||||||
width = options.cols ? options.cols : 80;
|
width = options.cols ? options.cols : 80;
|
||||||
height = options.rows ? options.rows : 25;
|
height = options.rows ? options.rows : 25;
|
||||||
scrollback = options.scrollback ? options.scrollback : 0;
|
scrollback = options.scrollback ? options.scrollback : 0;
|
||||||
|
|
@ -102,9 +101,6 @@ function Term(options)
|
||||||
this.linux_console = true;
|
this.linux_console = true;
|
||||||
|
|
||||||
this.textarea_has_focus = false;
|
this.textarea_has_focus = false;
|
||||||
|
|
||||||
// remember last data (to help WebXRKeyboard decide whether onblur should imply 'enter')
|
|
||||||
this.last = { keydown: '', input: '', inputValue: ''}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Term.prototype.setKeyHandler = function(handler)
|
Term.prototype.setKeyHandler = function(handler)
|
||||||
|
|
@ -1158,7 +1154,6 @@ Term.prototype.keyDownHandler = function (ev)
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
this.last.keydown = str
|
|
||||||
// console.log("keydown: keycode=" + ev.keyCode + " charcode=" + ev.charCode + " str=" + str + " ctrl=" + ev.ctrlKey + " alt=" + ev.altKey + " meta=" + ev.metaKey);
|
// console.log("keydown: keycode=" + ev.keyCode + " charcode=" + ev.charCode + " str=" + str + " ctrl=" + ev.ctrlKey + " alt=" + ev.altKey + " meta=" + ev.metaKey);
|
||||||
if (str) {
|
if (str) {
|
||||||
if (ev.stopPropagation)
|
if (ev.stopPropagation)
|
||||||
|
|
@ -1209,13 +1204,7 @@ Term.prototype.to_utf8 = function(s)
|
||||||
Term.prototype.inputHandler = function (ev)
|
Term.prototype.inputHandler = function (ev)
|
||||||
{
|
{
|
||||||
var str;
|
var str;
|
||||||
// edge-case: Meta's WebXRKeyboard implementation requires us to use .value
|
str = this.textarea_el.value;
|
||||||
str = this.last.input = this.textarea_el.value.split("").pop();
|
|
||||||
// edge-case: detect backspace based on length-change [Meta's WebXRKeyboard]
|
|
||||||
if( (this.textarea_el.value.length + 1) == this.last.inputValue.length ){
|
|
||||||
str = '\b'
|
|
||||||
}
|
|
||||||
this.last.inputValue = this.textarea_el.value
|
|
||||||
if (str) {
|
if (str) {
|
||||||
this.textarea_el.value = "";
|
this.textarea_el.value = "";
|
||||||
this.show_cursor();
|
this.show_cursor();
|
||||||
|
|
@ -1254,7 +1243,6 @@ Term.prototype.blurHandler = function (ev)
|
||||||
/* allow unloading the page */
|
/* allow unloading the page */
|
||||||
window.onbeforeunload = null;
|
window.onbeforeunload = null;
|
||||||
this.textarea_has_focus = false;
|
this.textarea_has_focus = false;
|
||||||
if( this.last.keydown != "\n" && this.last.input && this.options.isWebXRKeyboard() ) this.handler("\n")
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Term.prototype.pasteHandler = function (ev)
|
Term.prototype.pasteHandler = function (ev)
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -3,7 +3,7 @@ importScripts("ISOTerminal.js") // we don't instance it again here (just use it
|
||||||
|
|
||||||
this.runISO = async function(opts){
|
this.runISO = async function(opts){
|
||||||
this.opts = opts
|
this.opts = opts
|
||||||
if( opts.debug ) console.dir(opts)
|
if( opts.debug ) console.dir(opts)
|
||||||
|
|
||||||
if( opts.cdrom && !opts.cdrom.url.match(/^http/) ) opts.cdrom.url = "../../"+opts.cdrom.url
|
if( opts.cdrom && !opts.cdrom.url.match(/^http/) ) opts.cdrom.url = "../../"+opts.cdrom.url
|
||||||
if( opts.bzimage && !opts.cdrom.url.match(/^http/) ) opts.bzimage.url = "../../"+opts.bzimage.url
|
if( opts.bzimage && !opts.cdrom.url.match(/^http/) ) opts.bzimage.url = "../../"+opts.bzimage.url
|
||||||
|
|
@ -40,7 +40,6 @@ this.runISO = async function(opts){
|
||||||
emulator.add_listener("emulator-started", function(){
|
emulator.add_listener("emulator-started", function(){
|
||||||
importScripts("feat/9pfs_utils.js")
|
importScripts("feat/9pfs_utils.js")
|
||||||
this.postMessage({event:"emulator-started",data:false});
|
this.postMessage({event:"emulator-started",data:false});
|
||||||
if( opts.img ) this.restoreImage(opts)
|
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -52,12 +51,11 @@ this.runISO = async function(opts){
|
||||||
arr[0] = String(arr[0]).replace(/^\/mnt/,'')
|
arr[0] = String(arr[0]).replace(/^\/mnt/,'')
|
||||||
return arr
|
return arr
|
||||||
}
|
}
|
||||||
this.create_file = async function(){ return emulator.create_file.apply(emulator, stripMountDir(arguments[0]) ) }
|
this.create_file = async function(){ return emulator.create_file.apply(emulator, stripMountDir(arguments[0]) ) }
|
||||||
this.create_file_from_url = async function(){ return emulator.fs9p.create_file_from_url.apply(emulator, stripMountDir(arguments[0]) ) }
|
this.read_file = async function(){ return emulator.read_file.apply(emulator, stripMountDir(arguments[0]) ) }
|
||||||
this.read_file = async function(){ return emulator.read_file.apply(emulator, stripMountDir(arguments[0]) ) }
|
this.read_file_world = async function(){ return emulator.fs9p.read_file_world.apply(emulator.fs9p, stripMountDir(arguments[0]) ) }
|
||||||
this.read_file_world = async function(){ return emulator.fs9p.read_file_world.apply(emulator.fs9p, stripMountDir(arguments[0]) ) }
|
this.append_file = async function(){ emulator.fs9p.append_file.apply(emulator.fs9p, stripMountDir(arguments[0])) }
|
||||||
this.append_file = async function(){ emulator.fs9p.append_file.apply(emulator.fs9p, stripMountDir(arguments[0])) }
|
this.update_file = async function(){ emulator.fs9p.update_file.apply(emulator.fs9p, stripMountDir(arguments[0])) }
|
||||||
this.update_file = async function(){ emulator.fs9p.update_file.apply(emulator.fs9p, stripMountDir(arguments[0])) }
|
|
||||||
|
|
||||||
// filename will be read from 9pfs: "/mnt/"+filename
|
// filename will be read from 9pfs: "/mnt/"+filename
|
||||||
emulator.readFromPipe = function(filename,cb){
|
emulator.readFromPipe = function(filename,cb){
|
||||||
|
|
@ -68,12 +66,13 @@ this.runISO = async function(opts){
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
importScripts("feat/javascript.js")
|
importScripts("feat/javascript.js")
|
||||||
importScripts("feat/index.html.js")
|
importScripts("feat/index.html.js")
|
||||||
importScripts("feat/autorestore.js")
|
importScripts("feat/autorestore.js")
|
||||||
|
|
||||||
if( opts.overlayfs ) await this.addOverlayFS(opts)
|
if( opts.overlayfs ) await this.addOverlayFS(opts)
|
||||||
|
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
* forward events/functions so non-worker world can reach them
|
* forward events/functions so non-worker world can reach them
|
||||||
|
|
@ -107,28 +106,9 @@ this.addOverlayFS = async function(opts){
|
||||||
fetch(opts.overlayfs)
|
fetch(opts.overlayfs)
|
||||||
.then( (f) => {
|
.then( (f) => {
|
||||||
f.arrayBuffer().then( (buf) => {
|
f.arrayBuffer().then( (buf) => {
|
||||||
let filename = opts.overlayfs.match(/\.zip$/) ? 'overlayfs.zip' : '.env'
|
this.emulator.create_file('overlayfs.zip', new Uint8Array(buf) )
|
||||||
this.emulator.create_file( filename, new Uint8Array(buf) )
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
this.restoreImage = function(opts){
|
|
||||||
|
|
||||||
this.postMessage({event:"serial0-output-string",data:`\n\r[!] loading session image:\n\r[!] ${opts.img}\n\r[!] please wait...internet speed matters here..\n`})
|
|
||||||
|
|
||||||
fetch( opts.img )
|
|
||||||
.then( (res) => {
|
|
||||||
this.postMessage({event:"serial0-output-string",data:`\r[v] image downloaded..restoring..\n\r`})
|
|
||||||
return res
|
|
||||||
})
|
|
||||||
.then( (res) => res.arrayBuffer() )
|
|
||||||
.then( async (buf) => {
|
|
||||||
await this.emulator.restore_state(buf)
|
|
||||||
this.postMessage({event:"serial0-output-string",data:`[v] restored\n\r`})
|
|
||||||
|
|
||||||
})
|
|
||||||
.catch( console.error )
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,6 @@
|
||||||
*
|
*
|
||||||
* displays app (icons) in 2D and 3D handmenu (enduser can launch desktop-like 'apps')
|
* displays app (icons) in 2D and 3D handmenu (enduser can launch desktop-like 'apps')
|
||||||
*
|
*
|
||||||
* They can be temporary processes (icon disappears after component is removed) or permanent icons
|
|
||||||
* when registered via .register({...})
|
|
||||||
*
|
|
||||||
* ```html
|
* ```html
|
||||||
* <a-entity launcher>
|
* <a-entity launcher>
|
||||||
* <a-entity launch="component: helloworld; foo: bar"><a-entity>
|
* <a-entity launch="component: helloworld; foo: bar"><a-entity>
|
||||||
|
|
@ -102,7 +99,6 @@ AFRAME.registerComponent('launcher', {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
height: 50px;
|
height: 50px;
|
||||||
width:764px;
|
|
||||||
overflow:hidden;
|
overflow:hidden;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 10px;
|
bottom: 10px;
|
||||||
|
|
@ -311,8 +307,6 @@ AFRAME.registerSystem('launcher',{
|
||||||
register: function(launchable){
|
register: function(launchable){
|
||||||
try{
|
try{
|
||||||
let {name, description, cb} = launchable
|
let {name, description, cb} = launchable
|
||||||
const exist = this.registered.find( (r) => r.manifest.name == name )
|
|
||||||
if( exist ) return // already registered
|
|
||||||
this.registered.push({
|
this.registered.push({
|
||||||
manifest: {name, description, icons: launchable.icon ? [{src:launchable.icon}] : [] },
|
manifest: {name, description, icons: launchable.icon ? [{src:launchable.icon}] : [] },
|
||||||
launcher: cb
|
launcher: cb
|
||||||
|
|
@ -343,10 +337,7 @@ AFRAME.registerSystem('launcher',{
|
||||||
];
|
];
|
||||||
|
|
||||||
// collect manually registered launchables
|
// collect manually registered launchables
|
||||||
this.registered.map( (launchable) => {
|
this.registered.map( (launchable) => this.launchables.push(launchable) )
|
||||||
if( launchable.component ) seen[ launchable.component ] = true
|
|
||||||
this.launchables.push(launchable)
|
|
||||||
})
|
|
||||||
|
|
||||||
// collect launchables in aframe dom elements
|
// collect launchables in aframe dom elements
|
||||||
this.dom = els.filter( (el) => {
|
this.dom = els.filter( (el) => {
|
||||||
|
|
@ -355,11 +346,8 @@ AFRAME.registerSystem('launcher',{
|
||||||
for( let i in el.components ){
|
for( let i in el.components ){
|
||||||
if( el.components[i].events && el.components[i].events[searchEvent] && !seen[i] ){
|
if( el.components[i].events && el.components[i].events[searchEvent] && !seen[i] ){
|
||||||
let com = hasEvent = seen[i] = el.components[i]
|
let com = hasEvent = seen[i] = el.components[i]
|
||||||
let alreadyAdded = this.launchables.find( (l) => l.manifest.name == com.manifest.name )
|
com.launcher = () => com.el.emit('launcher',null,false) // important: no bubble
|
||||||
if( !alreadyAdded ){
|
this.launchables.push(com)
|
||||||
com.launcher = () => com.el.emit('launcher',null,false) // important: no bubble
|
|
||||||
this.launchables.push({manifest: com.manifest, launcher: com.launcher})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
|
|
||||||
;
|
|
||||||
(function (three) {
|
(function (three) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
|
@ -678,5 +676,4 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
})(THREE);
|
})(THREE);
|
||||||
|
//# sourceMappingURL=aframe-html.js.map
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
AFRAME.registerComponent('pressable', {
|
AFRAME.registerComponent('pressable', {
|
||||||
schema: {
|
schema: {
|
||||||
pressDistance: { default: 0.008 },
|
pressDistance: { default: 0.005 },
|
||||||
pressDuration: { default: 300 },
|
pressDuration: { default: 300 },
|
||||||
immersiveOnly: { default: true }
|
immersiveOnly: { default: true }
|
||||||
},
|
},
|
||||||
|
|
@ -32,53 +32,50 @@ AFRAME.registerComponent('pressable', {
|
||||||
}, y )
|
}, y )
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
detectPress: function(){
|
detectPress: function(){
|
||||||
if( !this.el.sceneEl.renderer.xr.isPresenting ) return // ignore events in desktop mode
|
if( this.handEls.length == 0 ){
|
||||||
|
this.handEls = document.querySelectorAll('[hand-tracking-controls]');
|
||||||
if( this.handEls.length == 0 ){
|
|
||||||
this.handEls = document.querySelectorAll('[hand-tracking-controls]');
|
|
||||||
}
|
|
||||||
var handEls = this.handEls;
|
|
||||||
var handEl;
|
|
||||||
let minDistance = 5
|
|
||||||
|
|
||||||
// compensate for an object inside a group
|
|
||||||
let object3D = this.el.object3D.type == "Group" ? this.el.object3D.children[0] : this.el.object3D
|
|
||||||
if( !object3D ) return
|
|
||||||
|
|
||||||
for (var i = 0; i < handEls.length; i++) {
|
|
||||||
handEl = handEls[i];
|
|
||||||
let indexTip = handEl.components['hand-tracking-controls'] ?
|
|
||||||
handEl.components['hand-tracking-controls'].indexTipPosition :
|
|
||||||
false
|
|
||||||
if( ! indexTip ) return // nothing to do here
|
|
||||||
|
|
||||||
this.raycaster.far = this.data.pressDistance
|
|
||||||
|
|
||||||
// Create a direction vector to negative Z
|
|
||||||
const direction = new THREE.Vector3(0,0,-1.0);
|
|
||||||
direction.normalize()
|
|
||||||
this.raycaster.set(indexTip, direction)
|
|
||||||
intersects = this.raycaster.intersectObjects([object3D],true)
|
|
||||||
|
|
||||||
object3D.getWorldPosition(this.worldPosition)
|
|
||||||
distance = indexTip.distanceTo(this.worldPosition)
|
|
||||||
minDistance = distance < minDistance ? distance : minDistance
|
|
||||||
|
|
||||||
if (intersects.length ){
|
|
||||||
this.i = this.i || 0;
|
|
||||||
if( !this.pressed ){
|
|
||||||
this.el.emit('pressedstarted', intersects);
|
|
||||||
this.el.emit('click', {intersection: intersects[0]});
|
|
||||||
this.pressed = setTimeout( () => {
|
|
||||||
this.el.emit('pressedended', intersects);
|
|
||||||
this.pressed = null
|
|
||||||
}, this.data.pressDuration )
|
|
||||||
}
|
}
|
||||||
}
|
var handEls = this.handEls;
|
||||||
}
|
var handEl;
|
||||||
this.distance = minDistance
|
let minDistance = 5
|
||||||
},
|
|
||||||
|
// compensate for an object inside a group
|
||||||
|
let object3D = this.el.object3D.type == "Group" ? this.el.object3D.children[0] : this.el.object3D
|
||||||
|
if( !object3D ) return
|
||||||
|
|
||||||
|
for (var i = 0; i < handEls.length; i++) {
|
||||||
|
handEl = handEls[i];
|
||||||
|
let indexTip = handEl.object3D.getObjectByName('index-finger-tip')
|
||||||
|
if( ! indexTip ) return // nothing to do here
|
||||||
|
|
||||||
|
this.raycaster.far = this.data.pressDistance
|
||||||
|
|
||||||
|
// Create a direction vector to negative Z
|
||||||
|
const direction = new THREE.Vector3(0,0,-1.0);
|
||||||
|
direction.normalize()
|
||||||
|
this.raycaster.set(indexTip.position, direction)
|
||||||
|
intersects = this.raycaster.intersectObjects([object3D],true)
|
||||||
|
|
||||||
|
object3D.getWorldPosition(this.worldPosition)
|
||||||
|
|
||||||
|
distance = indexTip.position.distanceTo(this.worldPosition)
|
||||||
|
minDistance = distance < minDistance ? distance : minDistance
|
||||||
|
|
||||||
|
if (intersects.length ){
|
||||||
|
this.i = this.i || 0;
|
||||||
|
if( !this.pressed ){
|
||||||
|
this.el.emit('pressedstarted', intersects);
|
||||||
|
this.el.emit('click', intersects);
|
||||||
|
this.pressed = setTimeout( () => {
|
||||||
|
this.el.emit('pressedended', intersects);
|
||||||
|
this.pressed = null
|
||||||
|
}, this.data.pressDuration )
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.distance = minDistance
|
||||||
|
},
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,6 @@ AFRAME.registerComponent('window', {
|
||||||
height: {type:'string',"default":'260px'},
|
height: {type:'string',"default":'260px'},
|
||||||
uid: {type:'string'},
|
uid: {type:'string'},
|
||||||
attach: {type:'selector'},
|
attach: {type:'selector'},
|
||||||
grabbable: {type:'string', "default":"components.html.el.object3D.children.0"},
|
|
||||||
dom: {type:'selector'},
|
dom: {type:'selector'},
|
||||||
max: {type:'boolean',"default":false},
|
max: {type:'boolean',"default":false},
|
||||||
min: {type:'boolean',"default":false},
|
min: {type:'boolean',"default":false},
|
||||||
|
|
@ -53,6 +52,16 @@ AFRAME.registerComponent('window', {
|
||||||
setupWindow: async function(){
|
setupWindow: async function(){
|
||||||
await AFRAME.utils.require(this.dependencies)
|
await AFRAME.utils.require(this.dependencies)
|
||||||
if( !this.el.dom ) return console.error('window element requires dom-component as dependency')
|
if( !this.el.dom ) return console.error('window element requires dom-component as dependency')
|
||||||
|
|
||||||
|
const close = () => {
|
||||||
|
let e = {halt:false}
|
||||||
|
this.el.emit('window.onclose',e)
|
||||||
|
if( e.halt ) return true
|
||||||
|
this.data.dom.style.display = 'none';
|
||||||
|
if( this.el.parentNode ) this.el.remove() //parentElement.remove( this.el )
|
||||||
|
this.data.dom.parentElement.remove()
|
||||||
|
return false
|
||||||
|
}
|
||||||
this.el.addEventListener('close', () => {
|
this.el.addEventListener('close', () => {
|
||||||
close()
|
close()
|
||||||
this.el.winbox.close()
|
this.el.winbox.close()
|
||||||
|
|
@ -78,15 +87,11 @@ AFRAME.registerComponent('window', {
|
||||||
setTimeout( () => {
|
setTimeout( () => {
|
||||||
if( !this.data.max && this.data.autoresize ) winbox.resize( this.el.dom.offsetWidth+'px', this.el.dom.offsetHeight+'px' )
|
if( !this.data.max && this.data.autoresize ) winbox.resize( this.el.dom.offsetWidth+'px', this.el.dom.offsetHeight+'px' )
|
||||||
// hint grabbable's obb-collider to track the window-object
|
// hint grabbable's obb-collider to track the window-object
|
||||||
this.el.components['obb-collider'].data.trackedObject3D = this.data.grabbable
|
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()
|
||||||
},1000)
|
},1000)
|
||||||
|
|
||||||
this.patchButtons(e)
|
|
||||||
},
|
},
|
||||||
onminimize: this.onminimize,
|
onclose: close
|
||||||
onrestore: this.onrestore,
|
|
||||||
onclose: this.onclose.bind(this),
|
|
||||||
|
|
||||||
});
|
});
|
||||||
this.data.dom.style.display = '' // show
|
this.data.dom.style.display = '' // show
|
||||||
|
|
@ -106,54 +111,13 @@ AFRAME.registerComponent('window', {
|
||||||
|
|
||||||
show: function(state){
|
show: function(state){
|
||||||
this.el.dom.closest('.winbox').style.display = state ? '' : 'none'
|
this.el.dom.closest('.winbox').style.display = state ? '' : 'none'
|
||||||
},
|
|
||||||
|
|
||||||
onminimize: function(e){
|
|
||||||
if( AFRAME.scenes[0].renderer.xr.isPresenting ){
|
|
||||||
this.window.style.display = 'none'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onrestore: function(e){
|
|
||||||
if( AFRAME.scenes[0].renderer.xr.isPresenting ){
|
|
||||||
this.window.style.display = ''
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onclose: function(){
|
|
||||||
let e = {halt:false}
|
|
||||||
this.el.emit('window.onclose',e)
|
|
||||||
if( e.halt ) return true
|
|
||||||
this.data.dom.style.display = 'none';
|
|
||||||
if( this.el.parentNode ) this.el.remove() //parentElement.remove( this.el )
|
|
||||||
this.data.dom.parentElement.remove()
|
|
||||||
return false
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
// the buttons don't work in XR because HTMLMesh does not understand onclick on divs
|
|
||||||
patchButtons: function(e){
|
|
||||||
let wEl = e.mount;
|
|
||||||
let controls = [...wEl.closest(".winbox").querySelectorAll(".wb-control span")]
|
|
||||||
controls.map( (c) => {
|
|
||||||
if( c.className.match(/wb-(close|min)/) ){
|
|
||||||
let btn = document.createElement("button")
|
|
||||||
btn.className = `xr xr-${c.className}`
|
|
||||||
btn.innerText = c.className == "wb-close" ? "x" : "_"
|
|
||||||
btn.addEventListener("click", (e) => { } )// this will bubble up (to click ancestor in XR)
|
|
||||||
c.appendChild(btn)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
AFRAME.utils.positionObjectNextToNeighbor = function positionObjectNextToNeighbor(object, lastNeighbor = null, margin ){
|
AFRAME.utils.positionObjectNextToNeighbor = function positionObjectNextToNeighbor(object, lastNeighbor = null, margin ){
|
||||||
if( lastNeighbor == null || object == null) return
|
|
||||||
// *FIXME* this could be more sophisticated :)
|
// *FIXME* this could be more sophisticated :)
|
||||||
object.position.x = lastNeighbor.position.x + margin
|
object.position.x = lastNeighbor.position.x + margin
|
||||||
object.position.y = lastNeighbor.position.y - margin
|
object.position.y = lastNeighbor.position.y - margin
|
||||||
object.position.z = lastNeighbor.position.z + margin
|
object.position.z = lastNeighbor.position.z + margin
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue