Compare commits
7 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 10bbfd27e2 | |||
| c3d9b7aaed | |||
| 55883df288 | |||
| 2b3c41fa6e | |||
| b3fdd67ee3 | |||
| c207b2da2a | |||
| c97875e3e5 |
13 changed files with 194 additions and 128 deletions
109
com/cast.js
109
com/cast.js
|
|
@ -4,11 +4,8 @@ AFRAME.registerComponent('cast', {
|
||||||
},
|
},
|
||||||
|
|
||||||
requires: {
|
requires: {
|
||||||
dom: "./com/dom.js", // interpret .dom object
|
dom: "com/dom.js",
|
||||||
xd: "./com/xd.js", // allow switching between 2D/3D
|
window: "com/window.js",
|
||||||
html: "https://unpkg.com/aframe-htmlmesh@2.1.0/build/aframe-html.js", // html to AFRAME
|
|
||||||
winboxjs: "https://unpkg.com/winbox@0.2.82/dist/winbox.bundle.min.js", // deadsimple windows: https://nextapps-de.github.io/winbox
|
|
||||||
winboxcss: "https://unpkg.com/winbox@0.2.82/dist/css/winbox.min.css", //
|
|
||||||
},
|
},
|
||||||
|
|
||||||
dom: {
|
dom: {
|
||||||
|
|
@ -23,7 +20,7 @@ AFRAME.registerComponent('cast', {
|
||||||
|
|
||||||
init: function () { },
|
init: function () { },
|
||||||
|
|
||||||
getInstallables: function(){
|
etInstallables: 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
|
||||||
|
|
@ -37,77 +34,54 @@ 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
|
||||||
}
|
}
|
||||||
const el = document.querySelector('body');
|
this.el.object3D.quaternion.copy( AFRAME.scenes[0].camera.quaternion ) // face towards camera
|
||||||
const cropTarget = await CropTarget.fromElement(el);
|
// instance this component
|
||||||
const stream = await navigator.mediaDevices.getDisplayMedia();
|
this.el.setAttribute("dom","")
|
||||||
const [track] = stream.getVideoTracks();
|
},
|
||||||
this.track = track
|
|
||||||
this.stream = stream
|
DOMready: function(){
|
||||||
this.createWindow()
|
this.setupCast();
|
||||||
}
|
this.el.setAttribute("html-as-texture-in-xr", `domid: #${this.el.uid}; faceuser: true`)
|
||||||
|
},
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
createWindow: async function(){
|
setupCast: async function(){
|
||||||
let s = await AFRAME.utils.require(this.requires)
|
let s = await AFRAME.utils.require(this.requires)
|
||||||
|
|
||||||
// instance this component
|
const video = this.el.dom.querySelector('video')
|
||||||
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
|
|
||||||
|
|
||||||
const setupWindow = () => {
|
video.addEventListener( "loadedmetadata", () => {
|
||||||
instance.dom.style.display = 'none'
|
|
||||||
|
|
||||||
const video = instance.dom.querySelector('video')
|
let width = Math.round(window.innerWidth*0.4)
|
||||||
video.addEventListener( "loadedmetadata", function () {
|
let factor = width / video.videoWidth
|
||||||
let width = Math.round(window.innerWidth*0.4)
|
let height = Math.round( video.videoHeight * factor)
|
||||||
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: () => {
|
|
||||||
|
|
||||||
// instance.setAttribute("html",`html:#${instance.uid}; cursor:#cursor`)
|
const createVideoTexture = () => {
|
||||||
}
|
const texture = new THREE.VideoTexture( video );
|
||||||
});
|
texture.colorSpace = THREE.SRGBColorSpace;
|
||||||
instance.dom.style.display = '' // show
|
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( AFRAME.scenes[0].camera.position );
|
||||||
|
this.el.object3D.add(mesh)
|
||||||
|
}
|
||||||
|
|
||||||
// hint grabbable's obb-collider to track the window-object
|
createVideoTexture.apply(this)
|
||||||
instance.components['obb-collider'].data.trackedObject3D = 'components.html.el.object3D.children.0'
|
//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'].update()
|
})
|
||||||
})
|
|
||||||
video.srcObject = instance.stream
|
|
||||||
video.play()
|
|
||||||
|
|
||||||
this.createVideoTexture.apply(instance)
|
const el = document.querySelector('body');
|
||||||
}
|
const cropTarget = await CropTarget.fromElement(el);
|
||||||
setTimeout( () => setupWindow(), 10 ) // give new components time to init
|
const stream = await navigator.mediaDevices.getDisplayMedia();
|
||||||
},
|
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
|
||||||
|
|
@ -116,6 +90,7 @@ 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()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,8 @@ AFRAME.registerComponent('helloworld-window', {
|
||||||
-->
|
-->
|
||||||
</div>`,
|
</div>`,
|
||||||
|
|
||||||
css: (me) => `.htmlform { padding:11px; }`
|
css: (me) => `.htmlform { padding:11px; }
|
||||||
|
`
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -67,15 +68,20 @@ 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(){
|
||||||
let s = await AFRAME.utils.require(this.requires)
|
if( !this.el.getAttribute("dom") ){
|
||||||
|
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}; width:250; height: 360`)
|
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`)
|
||||||
|
|
||||||
// data2event demo
|
// data2event demo
|
||||||
this.el.setAttribute("data2event","")
|
this.el.setAttribute("data2event","")
|
||||||
|
|
@ -83,6 +89,21 @@ 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,6 +13,7 @@
|
||||||
*
|
*
|
||||||
* | 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 ||
|
||||||
|
|
@ -72,6 +73,7 @@ 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 },
|
||||||
|
|
@ -142,6 +144,8 @@ if( typeof AFRAME != 'undefined '){
|
||||||
|
|
||||||
.isoterminal{
|
.isoterminal{
|
||||||
padding: ${me.com.data.padding}px;
|
padding: ${me.com.data.padding}px;
|
||||||
|
margin-top:-60px;
|
||||||
|
padding-bottom:60px;
|
||||||
width:100%;
|
width:100%;
|
||||||
height:99%;
|
height:99%;
|
||||||
resize: both;
|
resize: both;
|
||||||
|
|
@ -244,18 +248,19 @@ 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: #000C;
|
background: var(--xrsh-black);
|
||||||
overflow:hidden;
|
overflow:hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.XR .wb-body:has(> .isoterminal){
|
.XR .isoterminal{
|
||||||
background: transparent;
|
background: #000 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.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;
|
||||||
|
|
@ -342,7 +347,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: xrsh.iso; 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; grabbable: components.html.el.object3D.children.${this.el.children.length}`)
|
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.addEventListener('window.oncreate', (e) => {
|
instance.addEventListener('window.oncreate', (e) => {
|
||||||
|
|
@ -390,6 +395,14 @@ if( typeof AFRAME != 'undefined '){
|
||||||
|
|
||||||
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
|
// calculate distance between thumb and indexfinger to detect pinch
|
||||||
// which should prevent focus-event (annoying to have keyboard popping up during pinch)
|
// which should prevent focus-event (annoying to have keyboard popping up during pinch)
|
||||||
if( e.detail?.withEl?.components['hand-tracking-controls'] ){
|
if( e.detail?.withEl?.components['hand-tracking-controls'] ){
|
||||||
|
|
@ -439,9 +452,10 @@ if( typeof AFRAME != 'undefined '){
|
||||||
this.el.addEventListener('exec', (e) => this.term.exec( e.detail ) )
|
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('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('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], this.term.convert.toUint8Array(e.detail[1]) ) )
|
this.el.addEventListener('create_file', async (e) => await this.term.worker.create_file( e.detail[0], e.detail[1] ) )
|
||||||
this.el.addEventListener('update_file', async (e) => await this.term.worker.update_file( e.detail[0], this.term.convert.toUint8Array(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('append_file', async (e) => await this.term.worker.append_file( e.detail[0], this.term.convert.toUint8Array(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) => {
|
this.el.addEventListener('read_file', async (e) => {
|
||||||
const buf = await this.term.worker.read_file( e.detail[0] )
|
const buf = await this.term.worker.read_file( e.detail[0] )
|
||||||
const str = new TextDecoder().decode(buf)
|
const str = new TextDecoder().decode(buf)
|
||||||
|
|
@ -489,6 +503,7 @@ 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":{
|
||||||
|
|
@ -503,8 +518,9 @@ 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": "Hello world information",
|
"description": "Runs an .iso file",
|
||||||
"screenshots": [
|
"screenshots": [
|
||||||
{
|
{
|
||||||
"src": "/images/screenshot1.png",
|
"src": "/images/screenshot1.png",
|
||||||
|
|
@ -514,7 +530,7 @@ if( typeof AFRAME != 'undefined '){
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"help":`
|
"help":`
|
||||||
Helloworld application
|
XRSH 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,14 +18,17 @@ 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} )
|
||||||
this.preventFrameDrop( () => {
|
// forward event to worker/instance/AFRAME element or component-function
|
||||||
// forward event to worker/instance/AFRAME element or component-function
|
// this feels complex, but actually keeps event- and function-names more concise in codebase
|
||||||
// this feels complex, but actually keeps event- and function-names more concise in codebase
|
let fire = () => {
|
||||||
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) => {
|
||||||
|
|
@ -38,6 +41,7 @@ ISOTerminal.prototype.exec = function(opts){
|
||||||
const shellscript = opts[0];
|
const shellscript = opts[0];
|
||||||
const cb = opts[1];
|
const cb = opts[1];
|
||||||
let cmd = `printf "\n\r"; { sh<<EOF\n ${shellscript}; \nEOF\n} &> /mnt/exec;\n`
|
let cmd = `printf "\n\r"; { sh<<EOF\n ${shellscript}; \nEOF\n} &> /mnt/exec;\n`
|
||||||
|
console.log(cmd)
|
||||||
if( cb ){
|
if( cb ){
|
||||||
window.cb = cb
|
window.cb = cb
|
||||||
cmd += `js 'document.querySelector("[isoterminal]").emit("read_file", ["exec", window.cb ])';\n`
|
cmd += `js 'document.querySelector("[isoterminal]").emit("read_file", ["exec", window.cb ])';\n`
|
||||||
|
|
@ -47,7 +51,7 @@ ISOTerminal.prototype.exec = function(opts){
|
||||||
|
|
||||||
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)
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,8 @@ 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
|
||||||
worker.postMessage(msg, PromiseWorker.prototype.getTransferable(msg.data) )
|
let dataTransferable = 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 );
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -72,6 +73,5 @@ 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,6 +28,20 @@ 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,8 +1,8 @@
|
||||||
ISOTerminal.prototype.redirectConsole = function(handler){
|
ISOTerminal.prototype.redirectConsole = function(handler){
|
||||||
const log = console.log;
|
const log = console._log = console.log;
|
||||||
const dir = console.dir;
|
const dir = console._dir = console.dir;
|
||||||
const err = console.error;
|
const err = console._error = console.error;
|
||||||
const warn = console.warn;
|
const warn = console._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,12 +37,13 @@ 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+' ' : ''
|
||||||
_str.trim().split("\n").map( (line) => {
|
String(_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)
|
this.emit('serial-output-string', finalStr, "worker")
|
||||||
}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) {
|
||||||
|
|
|
||||||
|
|
@ -25,16 +25,9 @@ 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') {
|
||||||
debugger
|
|
||||||
return true; // bubble up to pasteHandler (see pastedrop.js)
|
return true; // bubble up to pasteHandler (see pastedrop.js)
|
||||||
}
|
}
|
||||||
original.apply(this,[e])
|
original.apply(this,[e])
|
||||||
|
|
|
||||||
|
|
@ -1259,7 +1259,6 @@ Term.prototype.blurHandler = function (ev)
|
||||||
|
|
||||||
Term.prototype.pasteHandler = function (ev)
|
Term.prototype.pasteHandler = function (ev)
|
||||||
{
|
{
|
||||||
debugger
|
|
||||||
var c, str;
|
var c, str;
|
||||||
if (!this.textarea_has_focus) {
|
if (!this.textarea_has_focus) {
|
||||||
c = ev.clipboardData;
|
c = ev.clipboardData;
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -52,11 +52,12 @@ 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.read_file = async function(){ return emulator.read_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_world = async function(){ return emulator.fs9p.read_file_world.apply(emulator.fs9p, stripMountDir(arguments[0]) ) }
|
this.read_file = async function(){ return emulator.read_file.apply(emulator, stripMountDir(arguments[0]) ) }
|
||||||
this.append_file = async function(){ emulator.fs9p.append_file.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.update_file = async function(){ emulator.fs9p.update_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])) }
|
||||||
|
|
||||||
// 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){
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,9 @@
|
||||||
*
|
*
|
||||||
* 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>
|
||||||
|
|
@ -340,7 +343,10 @@ AFRAME.registerSystem('launcher',{
|
||||||
];
|
];
|
||||||
|
|
||||||
// collect manually registered launchables
|
// collect manually registered launchables
|
||||||
this.registered.map( (launchable) => this.launchables.push(launchable) )
|
this.registered.map( (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) => {
|
||||||
|
|
@ -349,8 +355,11 @@ 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]
|
||||||
com.launcher = () => com.el.emit('launcher',null,false) // important: no bubble
|
let alreadyAdded = this.launchables.find( (l) => l.manifest.name == com.manifest.name )
|
||||||
this.launchables.push(com)
|
if( !alreadyAdded ){
|
||||||
|
com.launcher = () => com.el.emit('launcher',null,false) // important: no bubble
|
||||||
|
this.launchables.push({manifest: com.manifest, launcher: com.launcher})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,16 +53,6 @@ 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()
|
||||||
|
|
@ -91,8 +81,12 @@ AFRAME.registerComponent('window', {
|
||||||
this.el.components['obb-collider'].data.trackedObject3D = this.data.grabbable
|
this.el.components['obb-collider'].data.trackedObject3D = this.data.grabbable
|
||||||
this.el.components['obb-collider'].update()
|
this.el.components['obb-collider'].update()
|
||||||
},1000)
|
},1000)
|
||||||
|
|
||||||
|
this.patchButtons(e)
|
||||||
},
|
},
|
||||||
onclose: close
|
onminimize: this.onminimize,
|
||||||
|
onrestore: this.onrestore,
|
||||||
|
onclose: this.onclose.bind(this),
|
||||||
|
|
||||||
});
|
});
|
||||||
this.data.dom.style.display = '' // show
|
this.data.dom.style.display = '' // show
|
||||||
|
|
@ -112,7 +106,46 @@ 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 ){
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue