Compare commits

...
Sign in to create a new pull request.

33 commits

Author SHA1 Message Date
0407a23747 typofix urls
Some checks failed
/ mirror_to_github (push) Failing after 2m24s
/ test (push) Successful in 8s
2025-12-22 15:44:11 +01:00
cbc9daa8cd rerwote forgejo links to codeberg
Some checks failed
/ mirror_to_github (push) Failing after 4m26s
/ test (push) Has been cancelled
2025-12-22 15:39:55 +01:00
92590d6ecc new version
Some checks failed
/ mirror_to_github (push) Has been cancelled
/ test (push) Has been cancelled
2025-10-23 18:08:23 +02:00
e486111105 ignore remotestorage.js for vanilla terminal
Some checks are pending
/ mirror_to_github (push) Waiting to run
/ test (push) Waiting to run
2025-10-23 18:06:57 +02:00
11674a1509 added FoT talk
Some checks are pending
/ mirror_to_github (push) Waiting to run
/ test (push) Waiting to run
2025-10-22 17:25:09 +02:00
06ea1c56c8 added 'features' tag to gesture management page
Some checks failed
/ mirror_to_github (push) Has been cancelled
/ test (push) Has been cancelled
2025-07-18 16:11:33 +02:00
Fabien Benetou
6b2b9834ac gesture documentation
Some checks are pending
/ mirror_to_github (push) Waiting to run
/ test (push) Waiting to run
2025-07-18 15:21:21 +02:00
468cee87af added multibrowser cast
Some checks failed
/ mirror_to_github (push) Has been cancelled
/ test (push) Has been cancelled
2025-07-14 16:14:32 +02:00
8ac69c4aed window buttons in XR (better)
Some checks failed
/ mirror_to_github (push) Failing after 5m44s
/ test (push) Successful in 13s
2025-07-01 16:14:56 +02:00
f2d0c36f09 window buttons in XR (better) 2025-07-01 16:12:33 +02:00
a38fe0a16d window-buttons in XR
All checks were successful
/ test (push) Successful in 16s
2025-06-30 20:54:56 +02:00
a74e1dd60d wip: window close bugfix
All checks were successful
/ test (push) Successful in 16s
2025-06-27 11:49:29 +02:00
628357019c index.html: added more to XR hands page
Some checks failed
/ mirror_to_github (push) Failing after 2m24s
/ test (push) Successful in 13s
2025-06-26 15:18:25 +02:00
2cf3cb6808 added keyboard handfocus page
Some checks failed
/ mirror_to_github (push) Failing after 2m37s
/ test (push) Successful in 12s
2025-06-26 14:51:54 +02:00
310d7304a4 new build
Some checks failed
/ mirror_to_github (push) Failing after 2m22s
/ test (push) Successful in 18s
2025-06-25 18:54:04 +02:00
6f8dfc7bc3 new build + docs
Some checks failed
/ mirror_to_github (push) Failing after 2m17s
/ test (push) Successful in 12s
2025-06-24 12:48:07 +02:00
c17c2e4808 run server with vanilla redbean to prevent loading iso in xrsh.com 2025-06-24 12:47:56 +02:00
277bc79e78 added packages.zip video
Some checks failed
/ mirror_to_github (push) Failing after 2m5s
/ test (push) Successful in 11s
2025-06-20 18:23:32 +02:00
d2698c5e8e bugfix: array-less leftover exec() call
Some checks failed
/ mirror_to_github (push) Failing after 2m18s
/ test (push) Successful in 10s
2025-06-20 17:20:12 +02:00
95c91c72d8 added new build
Some checks failed
/ mirror_to_github (push) Failing after 1m18s
/ test (push) Successful in 10s
2025-06-20 16:45:29 +02:00
d424328c9a updated docs 2025-06-20 16:45:00 +02:00
aca83f7235 wip: adding more tests
All checks were successful
/ test (push) Successful in 13s
2025-06-13 23:50:32 +02:00
bd9de85ffc wip: adding more tests
All checks were successful
/ test (push) Successful in 12s
2025-06-10 15:08:11 +02:00
ab5efe4a33 new build
Some checks failed
/ mirror_to_github (push) Failing after 2m5s
/ test (push) Successful in 10s
2025-05-23 18:05:33 +02:00
9a9994a105 update components
Some checks failed
/ mirror_to_github (push) Failing after 2m10s
/ test (push) Has been cancelled
2025-05-23 18:02:56 +02:00
e72d296726 better async js execution page
Some checks failed
/ mirror_to_github (push) Failing after 2m22s
/ test (push) Successful in 10s
2025-05-23 12:49:43 +02:00
139f12031f new build
Some checks failed
/ mirror_to_github (push) Failing after 1m55s
/ test (push) Successful in 11s
2025-05-22 20:00:49 +02:00
05a2858ef7 upgrade xrfragment build 2025-05-22 20:00:26 +02:00
14bdd5fff3 index.html: update video of remotestorage page
Some checks failed
/ mirror_to_github (push) Failing after 2m3s
/ test (push) Successful in 9s
2025-05-22 18:37:13 +02:00
b48f9f47cc added assets
Some checks failed
/ mirror_to_github (push) Failing after 2m31s
/ test (push) Successful in 11s
2025-05-22 16:26:22 +02:00
5a35281c33 updated build + docs with remotestorage 2025-05-22 16:26:08 +02:00
5a3de2604d added remotestorage 2025-05-22 16:25:39 +02:00
f9ca00efb8 bugfix: properly escape exec command-strings
Some checks failed
/ mirror_to_github (push) Failing after 2m34s
/ test (push) Successful in 13s
2025-04-18 15:22:41 +02:00
15 changed files with 8445 additions and 60286 deletions

File diff suppressed because one or more lines are too long

6
make
View file

@ -68,6 +68,7 @@ standalone(){ # build standalone xrsh.com binary
echo " fbterm: true,"
echo " fbtermjs: true,"
echo " remotekeyboard: true"
echo " remotestorage: true"
echo " };"
} > xrsh.js
ls -lh xrsh.js
@ -80,9 +81,10 @@ standalone(){ # build standalone xrsh.com binary
dev(){ # start dev http server
set -x
cd "$DIR"
test -f xrsh.com || standalone
wget "$REDBEAN_VERSION" -O /tmp/server.com
chmod +x /tmp/server.com
test -f /tmp/cert.pem || openssl req -newkey rsa:2048 -new -nodes -x509 -days 3650 -keyout /tmp/key.pem -out /tmp/cert.pem
./xrsh.com -c 0 -C /tmp/cert.pem -K /tmp/key.pem -D . "$@"
/tmp/server.com -c 0 -C /tmp/cert.pem -K /tmp/key.pem -D . "$@"
}
shell(){ # run xrsh shell via docker

416
src/assets/aframe-remotestorage.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

View file

@ -8,7 +8,7 @@
*/
:root {
--xrsh-primary: #3a51ff;
--xrsh-primary: #00AA;
--xrsh-primary-fg: #FFF;
--xrsh-light-primary: #00a3Ff;
--xrsh-secondary: #872eff;
@ -17,9 +17,11 @@
--xrsh-dark-gray: #343334;
--xrsh-gray: #424280;
--xrsh-white: #fdfdfd;
--xrsh-black: #001c;
--xrsh-light-gray: #efefef;
--xrsh-lighter-gray: #e4e2fb96;
--xrsh-window-radius: 10px;
--xrsh-control-radius: 7px;
--xrsh-font-sans-serif: system-ui, -apple-system, segoe ui, roboto, ubuntu, helvetica, cantarell, noto sans, sans-serif;
--xrsh-font-monospace: menlo, monaco, lucida console, liberation mono, dejavu sans mono, bitstream vera sans mono, courier new, monospace, serif;
--xrsh-font-size-0: 12px;
@ -54,7 +56,7 @@ a,a:visited,a:active{
*/
button,.btn,input[type=submit]{
border-radius:7px;
border-radius: var(--xrsh-control-radius);
background: var(--xrsh-primary);
color: var(--xrsh-primary-fg);
transition:0.3s;
@ -98,31 +100,55 @@ button,input,.btn{
*/
.winbox {
box-shadow:none !important;
background:transparent !important;
background: transparent !important;
}
.winbox .wb-body{
background: var(--xrsh-dark-gray);
margin-top: -6px !important;
/*margin-top: -6px !important;*/
z-index: 100;
border: 1px solid #FFF3;
border-radius:7px;
border-radius:0px 0px var(--xrsh-window-radius) var(--xrsh-window-radius);
}
.winbox .wb-header{
background:transparent !important;
background: var(--xrsh-dark-gray);
z-index:1000;
font-weight: bold;
border-radius: var(--xrsh-window-radius) var(--xrsh-window-radius) 0px 0px;
line-height:36px;
}
.winbox.max .wb-header{
border-radius:0px;
}
.XR .winbox .wb-header{
border-radius:0px !important;
margin-top:0px;
}
.XR .winbox .wb-control .xr {
display:block;
}
.winbox .wb-control .xr {
display:none;
background:transparent !important;
position: absolute;
top: -2px;
cursor:pointer;
}
.winbox .wb-control .xr.xr-wb-close,
.winbox .wb-control .xr.xr-wb-min{
right: 2px;
}
.winbox .wb-body{
top:0px;
top: 35 !important;
padding-top:35px;
}
.winbox .wb-control{
padding-right:5px;
height:29px;
color:#FFF;
z-index:1001;
}
.winbox .wb-drag{
padding-left:19px;
@ -160,7 +186,6 @@ button,input,.btn{
background: var(--xrsh-white);
}
/*
* icons for non-XR (please source from https://css.gg
*

View file

@ -6,6 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<link rel="stylesheet" href="./index.css"></link>
<script src="./assets/aframe.min.js"></script>
<script src="./assets/xrfragment.aframe.js"></script>
<script src="com/require.js"></script>
<script src="com/isoterminal.js"></script>

View file

@ -1,37 +1,56 @@
term = document.querySelector('[isoterminal]').components.isoterminal.term
convert = ISOTerminal.prototype.convert
isoterminal = document.querySelector("[isoterminal]")
console.test("com/isoterminal/ISOTerminal.js", async () => {
isoterminal.addEventListener("ready", function(){
term = isoterminal.components.isoterminal.term
convert = ISOTerminal.prototype.convert
console.test.tests = {} // empty previous tests
console.test("com/isoterminal/ISOTerminal.js", async () => {
term.worker.create_file("foo", convert.toUint8Array("hello") )
.then( (res) => {
term.worker.read_file("foo")
.then( (res) => {
try{
let data = convert.Uint8ArrayToString( res )
console.assert( data == "hello", "worker.create_file")
}catch(e){
console.assert( false, "worker.create_file")
}
})
})
.catch(console.error)
.finally(console.dir)
term.worker.read_file("root/.profile")
.then( (res) => {
try{
let data = convert.Uint8ArrayToString( res )
console.assert( data.length, "worker.postMessage.promise()")
}catch(e){
console.assert( false, "worker.postMessage.promise()")
}
})
.catch(console.error)
.finally(console.dir)
})
console.test("com/isoterminal/ISOTerminal.js", async () => {
console.test("com/isoterminal/ISOTerminal.js", async () => {
term.worker.create_file("foo", convert.toUint8Array("hello") )
.then( (res) => {
term.worker.read_file("foo")
.then( (res) => {
try{
let data = convert.Uint8ArrayToString( res )
console.assert( data == "hello", "worker.create_file")
console.assert( data.length && data == "hello", "worker.postMessage.promise()")
}catch(e){
console.assert( false, "worker.create_file")
console.assert( false, "worker.postMessage.promise()")
}
})
.catch(console.error)
.finally(console.dir)
})
.catch(console.error)
.finally(console.dir)
console.test.run()
})
console.test("com/isoterminal.js", async () => {
console.assert( AFRAME.components.isoterminal != undefined, "AFRAME.components.isoterminal")
})
console.test("xrsh.iso", async () => {
console.assert( false, "create testsuite.zip package which tests iso")
})

10
src/tests/index.js Normal file
View file

@ -0,0 +1,10 @@
const run = async function(){
await AFRAME.utils.require({
"test_util": "tests/util.js",
"test_isoterminal":"tests/ISOTerminal.js"
})
console.test.run()
}
run()

View file

@ -1,20 +1,23 @@
// minimalistc testrunner
console.test = async (name,cb) => {
window.console.test = async (name,cb) => {
let tests = console.test.tests = console.test.tests || {}
tests[name] = tests[name] || []
tests[name].push(cb)
console.test.run = async () => {
for( let name in tests ){
console.log("\x1b[34m\x1b[36;49m"+name+"\x1B[m")
for( let test in tests[name] ){
await tests[name][test]()
}
}
}
}
console.assert = ((assert) => (a,reason,data) => {
window.console.test.run = async (cb) => {
const tests = window.console.test.tests
for( let name in tests ){
console.log("\x1b[34m\x1b[36;49m"+name+"\x1B[m")
for( let test in tests[name] ){
await tests[name][test]()
}
}
if( cb ) cb()
}
window.console.assert = ((assert) => (a,reason,data) => {
console.log("\x1b[34m\x1b[36;49m♥ \x1B[m"+reason)
assert.call( console, a, {reason} )
if( !a ){

@ -1 +1 @@
Subproject commit 9d549454d62671f52b007fa91098453008c37768
Subproject commit 10bbfd27e21a1b7368c38432178309263662aef6

File diff suppressed because one or more lines are too long

BIN
xrsh.com

Binary file not shown.

View file

@ -1 +1 @@
d25cabf0091a73f78e13c6a3135aad6178dc4708c02733446ab659b018cdfe96 xrsh.com
e791dba58adf568d34648eeed71e047d60b126b3dd5a6d81306a4307c5b7c3ad xrsh.com

253
xrsh.js
View file

@ -14,7 +14,7 @@ document.head.innerHTML += `<style type='text/css'>
*/
:root {
--xrsh-primary: #3a51ff;
--xrsh-primary: #00AA;
--xrsh-primary-fg: #FFF;
--xrsh-light-primary: #00a3Ff;
--xrsh-secondary: #872eff;
@ -23,9 +23,11 @@ document.head.innerHTML += `<style type='text/css'>
--xrsh-dark-gray: #343334;
--xrsh-gray: #424280;
--xrsh-white: #fdfdfd;
--xrsh-black: #001c;
--xrsh-light-gray: #efefef;
--xrsh-lighter-gray: #e4e2fb96;
--xrsh-window-radius: 10px;
--xrsh-control-radius: 7px;
--xrsh-font-sans-serif: system-ui, -apple-system, segoe ui, roboto, ubuntu, helvetica, cantarell, noto sans, sans-serif;
--xrsh-font-monospace: menlo, monaco, lucida console, liberation mono, dejavu sans mono, bitstream vera sans mono, courier new, monospace, serif;
--xrsh-font-size-0: 12px;
@ -60,7 +62,7 @@ a,a:visited,a:active{
*/
button,.btn,input[type=submit]{
border-radius:7px;
border-radius: var(--xrsh-control-radius);
background: var(--xrsh-primary);
color: var(--xrsh-primary-fg);
transition:0.3s;
@ -104,31 +106,55 @@ button,input,.btn{
*/
.winbox {
box-shadow:none !important;
background:transparent !important;
background: transparent !important;
}
.winbox .wb-body{
background: var(--xrsh-dark-gray);
margin-top: -6px !important;
/*margin-top: -6px !important;*/
z-index: 100;
border: 1px solid #FFF3;
border-radius:7px;
border-radius:0px 0px var(--xrsh-window-radius) var(--xrsh-window-radius);
}
.winbox .wb-header{
background:transparent !important;
background: var(--xrsh-dark-gray);
z-index:1000;
font-weight: bold;
border-radius: var(--xrsh-window-radius) var(--xrsh-window-radius) 0px 0px;
line-height:36px;
}
.winbox.max .wb-header{
border-radius:0px;
}
.XR .winbox .wb-header{
border-radius:0px !important;
margin-top:0px;
}
.XR .winbox .wb-control .xr {
display:block;
}
.winbox .wb-control .xr {
display:none;
background:transparent !important;
position: absolute;
top: -2px;
cursor:pointer;
}
.winbox .wb-control .xr.xr-wb-close,
.winbox .wb-control .xr.xr-wb-min{
right: 2px;
}
.winbox .wb-body{
top:0px;
top: 35 !important;
padding-top:35px;
}
.winbox .wb-control{
padding-right:5px;
height:29px;
color:#FFF;
z-index:1001;
}
.winbox .wb-drag{
padding-left:19px;
@ -166,7 +192,6 @@ button,input,.btn{
background: var(--xrsh-white);
}
/*
* icons for non-XR (please source from https://css.gg
*
@ -251,6 +276,7 @@ e.removeClass=function(a){this.g.classList.remove(a);return this};e.toggleClass=
*
* | property | type | default | info |
* |-------------------|-----------|------------------------|------|
* | `title` | `string` | 'xrsh.iso' | window title |
* | `iso` | `string` | https`//forgejo.isvery.ninja/assets/xrsh-buildroot/main/xrsh.iso" | |
* | `overlayfs` | `string` | '' | zip URL/file to autoextract on top of filesystem |
* | `width` | `number` | 800 ||
@ -310,6 +336,7 @@ if( typeof AFRAME != 'undefined '){
schema: {
iso: { type:"string", "default":"https://forgejo.isvery.ninja/assets/xrsh-buildroot/main/xrsh.iso" },
overlayfs: { type:"string"},
title: { type:"string", "default":"xrsh.iso"},
width: { type: 'number',"default": 800 },
height: { type: 'number',"default": 600 },
depth: { type: 'number',"default": 0.03 },
@ -380,6 +407,8 @@ if( typeof AFRAME != 'undefined '){
.isoterminal{
padding: ${me.com.data.padding}px;
margin-top:-60px;
padding-bottom:60px;
width:100%;
height:99%;
resize: both;
@ -482,18 +511,19 @@ if( typeof AFRAME != 'undefined '){
-webkit-animation:none;
}
.wb-body:has(> .isoterminal){
background: #000C;
.winbox#${me.el.uid} .wb-header{
background: var(--xrsh-black) !important;
}
.wb-body:has(> .isoterminal){
background: var(--xrsh-black);
overflow:hidden;
}
.XR .wb-body:has(> .isoterminal){
background: transparent;
.XR .isoterminal{
background: #000 !important;
}
.XR .isoterminal{
background: #000;
}
.isoterminal *{
font-size: 14px;
font-family: "Cousine",Liberation Mono,DejaVu Sans Mono,Courier New,monospace;
@ -542,15 +572,20 @@ if( typeof AFRAME != 'undefined '){
remotekeyboard: "com/isoterminal/feat/remotekeyboard.js",
indexhtml: "com/isoterminal/feat/index.html.js",
indexjs: "com/isoterminal/feat/index.js.js",
autorestore: "com/isoterminal/feat/autorestore.js",
pastedropFeat: "com/isoterminal/feat/pastedrop.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' ){
features['fbtermjs'] = "com/isoterminal/term.js"
features['fbterm'] = "com/isoterminal/feat/term.js"
}
await AFRAME.utils.require(features)
// this one extends autorestore.js
await AFRAME.utils.require({remotestorage: "com/isoterminal/feat/remotestorage.js"})
this.el.setAttribute("selfcontainer","")
@ -575,7 +610,7 @@ if( typeof AFRAME != 'undefined '){
this.term.emit('term_init', {instance, aEntity:this})
//instance.winbox.resize(720,380)
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) => {
@ -603,7 +638,6 @@ if( typeof AFRAME != 'undefined '){
this.term.addEventListener('ready', (e) => {
instance.dom.classList.remove('blink')
this.term.emit('status',"running")
if( this.data.debug ) this.runTests()
})
this.term.addEventListener('status', function(e){
@ -623,6 +657,23 @@ if( typeof AFRAME != 'undefined '){
instance.addEventListener('window.onmaximize', resize )
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)
}
@ -641,14 +692,6 @@ 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(){
this.el.addEventListener('pasteFile', (e) => {
e.preventDefault() // prevent bubbling up to window (which is triggering this initially)
@ -672,9 +715,10 @@ if( typeof AFRAME != 'undefined '){
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], this.term.convert.toUint8Array(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('append_file', async (e) => await this.term.worker.append_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('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)
@ -722,6 +766,7 @@ if( typeof AFRAME != 'undefined '){
"scope": "/",
"theme_color": "#3367D6",
"shortcuts": [
/*
{
"name": "What is the latest news?",
"cli":{
@ -736,8 +781,9 @@ if( typeof AFRAME != 'undefined '){
"url": "/today?source=pwa",
"icons": [{ "src": "/images/today.png", "sizes": "192x192" }]
}
*/
],
"description": "Hello world information",
"description": "Runs an .iso file",
"screenshots": [
{
"src": "/images/screenshot1.png",
@ -747,7 +793,7 @@ if( typeof AFRAME != 'undefined '){
}
],
"help":`
Helloworld application
XRSH application
This is a help file which describes the application.
It will be rendered thru troika text, and will contain
@ -860,9 +906,9 @@ if( !AFRAME.components.dom ){
this
.ensureOverlay()
.addCSS()
.createReactiveDOMElement()
.assignUniqueID()
.addCSS()
.scaleDOMvsXR()
.triggerKeyboardForInputs()
@ -1395,16 +1441,6 @@ AFRAME.registerComponent('window', {
setupWindow: async function(){
await AFRAME.utils.require(this.dependencies)
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', () => {
close()
this.el.winbox.close()
@ -1433,8 +1469,12 @@ AFRAME.registerComponent('window', {
this.el.components['obb-collider'].data.trackedObject3D = this.data.grabbable
this.el.components['obb-collider'].update()
},1000)
this.patchButtons(e)
},
onclose: close
onminimize: this.onminimize,
onrestore: this.onrestore,
onclose: this.onclose.bind(this),
});
this.data.dom.style.display = '' // show
@ -1454,7 +1494,46 @@ AFRAME.registerComponent('window', {
show: function(state){
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 ){
@ -2172,14 +2251,17 @@ function ISOTerminal(instance,opts){
ISOTerminal.prototype.emit = function(event,data,sender){
data = data || false
const evObj = new CustomEvent(event, {detail: data} )
this.preventFrameDrop( () => {
// forward event to worker/instance/AFRAME element or component-function
// this feels complex, but actually keeps event- and function-names more concise in codebase
// forward event to worker/instance/AFRAME element or component-function
// this feels complex, but actually keeps event- and function-names more concise in codebase
let fire = () => {
this.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 !== 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) => {
@ -2191,20 +2273,18 @@ ISOTerminal.addEventListener = (event,cb) => {
ISOTerminal.prototype.exec = function(opts){
const shellscript = opts[0];
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
this.send(`printf "\n\r"; ${shellscript} &> /mnt/exec; js '
document.querySelector("[isoterminal]").emit("read_file", ["exec", window.cb ])
';
\n`,1)
}else{
this.send(`printf "\n\r"; ${shellscript} \n`,1)
cmd += `js 'document.querySelector("[isoterminal]").emit("read_file", ["exec", window.cb ])';\n`
}
this.send( cmd, 1 )
}
ISOTerminal.prototype.hook = function(hookname,args){
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)
@ -2295,8 +2375,14 @@ 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",
net_device:{
relay_url:"fetch", // or websocket proxy "wss://relay.widgetry.org/",
type:"virtio"
//type:"ne2k",
relay_url:"fetch"
//relay_url:"wss://relay.widgetry.org/"
//local_http: true,
//type:"virtio",
//relay_url:"fetch",
//cors_proxy: "https://corsproxy.io/"
},
//bzimage_initrd_from_filesystem: true,
//filesystem: {
@ -2413,6 +2499,7 @@ ISOTerminal.prototype.startVM = function(opts){
this.v86opts = opts
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 = ''
this.ready = false
@ -2435,12 +2522,17 @@ ISOTerminal.prototype.startVM = function(opts){
}
ISOTerminal.prototype.bootISO = function(){
const getImage = (str) => decodeURIComponent(
str.match(/\&img=/) ? str.replace(/.*img=/,'').replace(/\&.*/,'') : ''
)
let msglib = this.getLoaderMsg()
this.emit('status',msglib.loadmsg)
let msg = "\n\r" + msglib.empowermsg + msglib.text_color + msglib.loadmsg + msglib.text_reset
this.emit('serial-output-string', msg)
this.emit('runISO',{...this.v86opts, bufferLatency: this.opts.bufferLatency })
if( getImage(this.boot.hash) ){
this.boot.fromImage = true
}
this.emit('runISO',{...this.v86opts, bufferLatency: this.opts.bufferLatency, img: getImage(this.boot.hash) })
}
@ -3971,7 +4063,7 @@ Term.prototype.resizePixel = function (new_width, new_height)
return true;
}
ISOTerminal.addEventListener('ready', function(e){
setTimeout( () => this.boot(), 50 ) // because of autorestore.js
setTimeout( () => this.boot(), 50 ) // allow other features/plugins to settle first (autorestore.js e.g.)
})
ISOTerminal.prototype.bootMenu = function(e){
@ -3989,13 +4081,13 @@ ISOTerminal.prototype.bootMenu = function(e){
}else{ // autoboot
if( this.term ){
this.term.handler( e.detail.bootMenu || e.detail.bootMenuURL )
this.term.handler( String(e.detail.bootMenu || e.detail.bootMenuURL).charAt(0) )
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){
// set environment
@ -4005,10 +4097,17 @@ ISOTerminal.prototype.boot = async function(e){
'export BROWSER=1',
]
for ( let i in document.location ){
if( typeof document.location[i] == 'string' ){
if( typeof document.location[i] == 'string' && !String(i).match(/(hash|search)/) ){
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') ) )
if( this.serial_input == 0 ){
@ -4019,8 +4118,12 @@ ISOTerminal.prototype.boot = async function(e){
}
// here REPL's can be defined
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
// REPL: iso
if( typeof window.PromiseWorker != 'undefined' ){ // if xrsh v86 is able to run in in worker
@ -4046,10 +4149,10 @@ if( typeof window.PromiseWorker != 'undefined' ){ // if xrsh v86 is able to run
}
ISOTerminal.prototype.redirectConsole = function(handler){
const log = console.log;
const dir = console.dir;
const err = console.error;
const warn = console.warn;
const log = console._log = console.log;
const dir = console._dir = console.dir;
const err = console._error = console.error;
const warn = console._warn = console.warn;
const addLineFeeds = (str) => typeof str == 'string' ? str.replace(/\n/g,"\r\n") : str
console.log = (...args)=>{
@ -4084,12 +4187,13 @@ ISOTerminal.prototype.enableConsole = function(opts){
let _str = typeof str == 'string' ? str : JSON.stringify(str)
let finalStr = "";
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'
})
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])
this.lastStr = finalStr
})
window.addEventListener('error', function(event) {
@ -4180,12 +4284,6 @@ ISOTerminal.prototype.TermInit = function(){
// patch Term-class
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){
return function (e){
if ((e.ctrlKey || e.metaKey) && e.key === 'v') {
@ -4400,5 +4498,6 @@ AFRAME.utils.require.required = {
httpfs: true,
fbterm: true,
fbtermjs: true,
remotekeyboard: true
remotekeyboard: true,
remotestorage: true
};