Compare commits
No commits in common. "main" and "feat/vt100" have entirely different histories.
main
...
feat/vt100
|
@ -4,8 +4,8 @@ AFRAME.registerComponent('codemirror', {
|
||||||
schema: {
|
schema: {
|
||||||
file: { type:"string"},
|
file: { type:"string"},
|
||||||
term: { type:"selector", default: "[isoterminal]" },
|
term: { type:"selector", default: "[isoterminal]" },
|
||||||
width: { type:"number", default:700},
|
width: { type:"number", default:900},
|
||||||
height: { type:"number", default:500},
|
height: { type:"number", default:700},
|
||||||
},
|
},
|
||||||
|
|
||||||
init: function () {
|
init: function () {
|
||||||
|
@ -109,8 +109,8 @@ AFRAME.registerComponent('codemirror', {
|
||||||
// we don't do via shellcmd: isoterminal.exec(`echo '${str}' > ${file}`,1)
|
// we don't do via shellcmd: isoterminal.exec(`echo '${str}' > ${file}`,1)
|
||||||
// as it would require all kindof ugly stringescaping
|
// as it would require all kindof ugly stringescaping
|
||||||
console.log("updating "+file)
|
console.log("updating "+file)
|
||||||
await this.isoterminal.worker.update_file(file, this.isoterminal.convert.toUint8Array(str) )
|
console.log(str)
|
||||||
this.isoterminal.exec("touch "+file) // *FIXME* notify filesystem (why does inotifyd need this? v86's 9pfees is cached?)
|
await this.isoterminal.worker['emulator.update_file'](file, term.convert.toUint8Array(str) )
|
||||||
},
|
},
|
||||||
|
|
||||||
events:{
|
events:{
|
||||||
|
@ -118,7 +118,7 @@ AFRAME.registerComponent('codemirror', {
|
||||||
// component events
|
// component events
|
||||||
DOMready: function(e){
|
DOMready: function(e){
|
||||||
|
|
||||||
this.isoterminal.worker.read_file(this.data.file)
|
this.isoterminal.worker['emulator.read_file'](this.data.file)
|
||||||
.then( this.isoterminal.convert.Uint8ArrayToString )
|
.then( this.isoterminal.convert.Uint8ArrayToString )
|
||||||
.then( (str) => {
|
.then( (str) => {
|
||||||
console.log("creating editor")
|
console.log("creating editor")
|
||||||
|
|
|
@ -21,7 +21,7 @@ if( !AFRAME.components['html-as-texture-in-xr'] ){
|
||||||
this.el.setAttribute("html",`html: ${this.data.domid}; cursor:#cursor; xrlayer: true`)
|
this.el.setAttribute("html",`html: ${this.data.domid}; cursor:#cursor; xrlayer: true`)
|
||||||
this.el.setAttribute("visible", AFRAME.utils.XD() == '3D' ? 'true' : 'false' )
|
this.el.setAttribute("visible", AFRAME.utils.XD() == '3D' ? 'true' : 'false' )
|
||||||
if( this.data.faceuser ){
|
if( this.data.faceuser ){
|
||||||
this.el.setAttribute("position", AFRAME.utils.XD.getPositionInFrontOfCamera(0.4) )
|
this.el.setAttribute("position", AFRAME.utils.XD.getPositionInFrontOfCamera(0.8) )
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -40,6 +40,7 @@ if( typeof AFRAME != 'undefined '){
|
||||||
depth: { type: 'number',"default": 0.03 },
|
depth: { type: 'number',"default": 0.03 },
|
||||||
lineHeight: { type: 'number',"default": 18 },
|
lineHeight: { type: 'number',"default": 18 },
|
||||||
padding: { type: 'number',"default": 18 },
|
padding: { type: 'number',"default": 18 },
|
||||||
|
minimized: { type: 'boolean',"default":false},
|
||||||
maximized: { type: 'boolean',"default":true},
|
maximized: { type: 'boolean',"default":true},
|
||||||
muteUntilPrompt:{ type: 'boolean',"default":true}, // mute stdout until a prompt is detected in ISO
|
muteUntilPrompt:{ type: 'boolean',"default":true}, // mute stdout until a prompt is detected in ISO
|
||||||
HUD: { type: 'boolean',"default":false}, // link to camera movement
|
HUD: { type: 'boolean',"default":false}, // link to camera movement
|
||||||
|
@ -55,7 +56,6 @@ if( typeof AFRAME != 'undefined '){
|
||||||
this.calculateDimension()
|
this.calculateDimension()
|
||||||
this.initHud()
|
this.initHud()
|
||||||
this.setupBox()
|
this.setupBox()
|
||||||
this.setupPasteDrop()
|
|
||||||
|
|
||||||
fetch(this.data.iso,{method: 'HEAD'})
|
fetch(this.data.iso,{method: 'HEAD'})
|
||||||
.then( (res) => {
|
.then( (res) => {
|
||||||
|
@ -72,11 +72,10 @@ if( typeof AFRAME != 'undefined '){
|
||||||
requires:{
|
requires:{
|
||||||
com: "com/dom.js",
|
com: "com/dom.js",
|
||||||
window: "com/window.js",
|
window: "com/window.js",
|
||||||
pastedrop: "com/pastedrop.js",
|
|
||||||
v86: "com/isoterminal/libv86.js",
|
v86: "com/isoterminal/libv86.js",
|
||||||
vt100: "com/isoterminal/VT100.js",
|
vt100: "com/isoterminal/VT100.js",
|
||||||
// allow xrsh to selfcontain scene + itself
|
// allow xrsh to selfcontain scene + itself
|
||||||
xhook: "com/lib/xhook.min.js",
|
xhook: "https://jpillora.com/xhook/dist/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",
|
||||||
|
@ -98,7 +97,7 @@ if( typeof AFRAME != 'undefined '){
|
||||||
css: (me) => `.isoterminal{
|
css: (me) => `.isoterminal{
|
||||||
padding: ${me.com.data.padding}px;
|
padding: ${me.com.data.padding}px;
|
||||||
width:100%;
|
width:100%;
|
||||||
height:90%;
|
height:100%;
|
||||||
position:relative;
|
position:relative;
|
||||||
}
|
}
|
||||||
.isoterminal div{
|
.isoterminal div{
|
||||||
|
@ -134,17 +133,6 @@ if( typeof AFRAME != 'undefined '){
|
||||||
box-shadow:none;
|
box-shadow:none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cursor {
|
|
||||||
background: #70F !important;
|
|
||||||
animation:fade 1000ms infinite;
|
|
||||||
-webkit-animation:fade 1000ms infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
.XR .cursor {
|
|
||||||
animation:none;
|
|
||||||
-webkit-animation:none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wb-body:has(> .isoterminal){
|
.wb-body:has(> .isoterminal){
|
||||||
background: #000C;
|
background: #000C;
|
||||||
overflow:hidden;
|
overflow:hidden;
|
||||||
|
@ -204,8 +192,6 @@ if( typeof AFRAME != 'undefined '){
|
||||||
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",
|
autorestore: "com/isoterminal/feat/autorestore.js",
|
||||||
pastedropFeat: "com/isoterminal/feat/pastedrop.js",
|
|
||||||
httpfs: "com/isoterminal/feat/httpfs.js",
|
|
||||||
})
|
})
|
||||||
|
|
||||||
this.el.setAttribute("selfcontainer","")
|
this.el.setAttribute("selfcontainer","")
|
||||||
|
@ -234,7 +220,7 @@ if( typeof AFRAME != 'undefined '){
|
||||||
},100)
|
},100)
|
||||||
//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-resize, no-move`)
|
instance.setAttribute("window", `title: xrsh.iso; uid: ${instance.uid}; attach: #overlay; dom: #${instance.dom.id}; ${size}; min: ${this.data.minimized}; max: ${this.data.maximized}`)
|
||||||
})
|
})
|
||||||
|
|
||||||
instance.addEventListener('window.oncreate', (e) => {
|
instance.addEventListener('window.oncreate', (e) => {
|
||||||
|
@ -249,7 +235,6 @@ if( typeof AFRAME != 'undefined '){
|
||||||
})
|
})
|
||||||
|
|
||||||
instance.setAttribute("dom", "")
|
instance.setAttribute("dom", "")
|
||||||
instance.setAttribute("pastedrop", "")
|
|
||||||
|
|
||||||
this.term.addEventListener('ready', (e) => {
|
this.term.addEventListener('ready', (e) => {
|
||||||
instance.dom.classList.remove('blink')
|
instance.dom.classList.remove('blink')
|
||||||
|
@ -274,10 +259,10 @@ if( typeof AFRAME != 'undefined '){
|
||||||
instance.addEventListener('window.onmaximize', resize )
|
instance.addEventListener('window.onmaximize', resize )
|
||||||
|
|
||||||
const focus = (showdom) => (e) => {
|
const focus = (showdom) => (e) => {
|
||||||
this.el.emit('focus',e.detail)
|
|
||||||
if( this.el.components.window && this.data.renderer == 'canvas'){
|
if( this.el.components.window && this.data.renderer == 'canvas'){
|
||||||
this.el.components.window.show( showdom )
|
this.el.components.window.show( showdom )
|
||||||
}
|
}
|
||||||
|
this.el.emit('focus',e.detail)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.el.addEventListener('obbcollisionstarted', focus(false) )
|
this.el.addEventListener('obbcollisionstarted', focus(false) )
|
||||||
|
@ -307,27 +292,21 @@ if( typeof AFRAME != 'undefined '){
|
||||||
|
|
||||||
setupVT100: function(instance){
|
setupVT100: function(instance){
|
||||||
const el = this.el.dom.querySelector('#term')
|
const el = this.el.dom.querySelector('#term')
|
||||||
this.term.opts.vt100 = {
|
const opts = {
|
||||||
cols: this.cols,
|
cols: this.cols,
|
||||||
rows: this.rows,
|
rows: this.rows,
|
||||||
el_or_id: el,
|
el_or_id: el,
|
||||||
max_scroll_lines: this.rows,
|
max_scroll_lines: 100,
|
||||||
nodim: true,
|
nodim: true
|
||||||
rainbow: [VT100.COLOR_MAGENTA, VT100.COLOR_CYAN ],
|
|
||||||
xr: AFRAME.scenes[0].renderer.xr,
|
|
||||||
map: {
|
|
||||||
'ArrowRight': { ch: false, ctrl: '\x1b\x66' }, // this triggers ash-shell forward-word
|
|
||||||
'ArrowLeft': { ch: false, ctrl: '\x1b\x62' } // backward-word
|
|
||||||
}
|
}
|
||||||
}
|
this.vt100 = new VT100( opts )
|
||||||
this.term.emit('initVT100',this)
|
|
||||||
this.vt100 = new VT100( this.term.opts.vt100 )
|
|
||||||
this.vt100.el = el
|
this.vt100.el = el
|
||||||
this.vt100.curs_set( 1, true)
|
this.vt100.curs_set( 1, true)
|
||||||
this.vt100.focus()
|
el.focus()
|
||||||
this.el.addEventListener('focus', () => this.vt100.focus() )
|
this.el.addEventListener('focus', () => el.focus())
|
||||||
this.vt100.getch( (ch,t) => {
|
this.vt100.getch( (ch,t) => {
|
||||||
this.term.send( ch )
|
this.term.send( ch )
|
||||||
|
this.vt100.curs_set( 0, true)
|
||||||
})
|
})
|
||||||
|
|
||||||
this.el.addEventListener('serial-output-byte', (e) => {
|
this.el.addEventListener('serial-output-byte', (e) => {
|
||||||
|
@ -339,23 +318,13 @@ if( typeof AFRAME != 'undefined '){
|
||||||
this.vt100.write(e.detail)
|
this.vt100.write(e.detail)
|
||||||
})
|
})
|
||||||
|
|
||||||
// translate file upload into pasteFile
|
|
||||||
this.vt100.upload.addEventListener('change', (e) => {
|
|
||||||
const file = this.vt100.upload.files[0];
|
|
||||||
const item = {...file, getAsFile: () => file }
|
|
||||||
this.el.emit('pasteFile', { item, type: file.type });
|
|
||||||
})
|
|
||||||
|
|
||||||
return this
|
//this.el.dom.querySelector('input').addEventListener('keyup', (e) => {
|
||||||
},
|
// VT100.handle_onkeypress_( {charCode : e.charCode || e.keyCode, keyCode: e.keyCode}, (chars) => {
|
||||||
|
// debugger
|
||||||
setupPasteDrop: function(){
|
// chars.map( (c) => this.term.send(str) )
|
||||||
this.el.addEventListener('pasteFile', (e) => {
|
// })
|
||||||
e.preventDefault() // prevent bubbling up to window (which is triggering this initially)
|
//})
|
||||||
if( !this.term.pasteFile ) return // skip if feat/pastedrop.js is not loaded
|
|
||||||
this.term.pasteFile(e.detail)
|
|
||||||
})
|
|
||||||
return this
|
|
||||||
},
|
},
|
||||||
|
|
||||||
setupBox: function(){
|
setupBox: function(){
|
||||||
|
@ -370,13 +339,13 @@ if( typeof AFRAME != 'undefined '){
|
||||||
},
|
},
|
||||||
|
|
||||||
calculateDimension: function(){
|
calculateDimension: function(){
|
||||||
if( this.data.width == -1 ) this.data.width = document.body.offsetWidth;
|
if( this.data.width == -1 ) this.data.width = document.body.offsetWidth
|
||||||
if( this.data.height == -1 ) this.data.height = Math.floor( document.body.offsetHeight - 30 )
|
if( this.data.height == -1 ) this.data.height = document.body.offsetHeight
|
||||||
if( this.data.height > this.data.width ) this.data.height = this.data.width // mobile smartphone fix
|
if( this.data.height > this.data.width ) this.data.height = this.data.width // mobile smartphone fix
|
||||||
this.data.width -= this.data.padding*2
|
this.data.width -= this.data.padding*2
|
||||||
this.data.height -= this.data.padding*2
|
this.data.height -= this.data.padding*2
|
||||||
this.cols = Math.floor(this.data.width/this.data.lineHeight*2)
|
this.cols = Math.floor(this.data.width/this.data.lineHeight*1.9)
|
||||||
this.rows = Math.floor(this.data.height*0.53/this.data.lineHeight*1.7)
|
this.rows = Math.floor(this.data.height*0.5/this.data.lineHeight*1.7) // keep extra height for mobile browser bottom-bar (android)
|
||||||
},
|
},
|
||||||
|
|
||||||
events:{
|
events:{
|
||||||
|
|
|
@ -17,14 +17,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} )
|
||||||
this.preventFrameDrop( () => {
|
//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
|
||||||
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] )
|
||||||
})
|
//})
|
||||||
}
|
}
|
||||||
|
|
||||||
ISOTerminal.addEventListener = (event,cb) => {
|
ISOTerminal.addEventListener = (event,cb) => {
|
||||||
|
@ -37,10 +37,6 @@ ISOTerminal.prototype.exec = function(shellscript){
|
||||||
this.send(shellscript+"\n",1)
|
this.send(shellscript+"\n",1)
|
||||||
}
|
}
|
||||||
|
|
||||||
ISOTerminal.prototype.hook = function(hookname,args){
|
|
||||||
this.exec(`{ type hook || source /etc/profile.sh; }; hook ${hookname} "${args.join('" "')}"`)
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
ISOTerminal.prototype.send = function(str, ttyNr){
|
ISOTerminal.prototype.send = function(str, ttyNr){
|
||||||
|
@ -52,9 +48,7 @@ ISOTerminal.prototype.send = function(str, ttyNr){
|
||||||
}else{
|
}else{
|
||||||
this.convert.toUint8Array( str ).map( (c) => {
|
this.convert.toUint8Array( str ).map( (c) => {
|
||||||
this.preventFrameDrop(
|
this.preventFrameDrop(
|
||||||
() => {
|
() => this.worker.postMessage({event:`serial${ttyNr}-input`,data:c})
|
||||||
this.worker.postMessage({event:`serial${ttyNr}-input`,data:c})
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -193,6 +187,7 @@ ISOTerminal.prototype.startVM = function(opts){
|
||||||
"Learned helplessness fades when we realize tech isn’t too complex to understand",
|
"Learned helplessness fades when we realize tech isn’t too complex to understand",
|
||||||
"FOSS empowers users to customize and improve their tools",
|
"FOSS empowers users to customize and improve their tools",
|
||||||
"Engaging with FOSS helps build confidence and self-reliance in tech",
|
"Engaging with FOSS helps build confidence and self-reliance in tech",
|
||||||
|
"FOSS tools are accessible and often better than closed alternatives",
|
||||||
"FOSS shows that anyone can shape the digital world with curiosity and effort",
|
"FOSS shows that anyone can shape the digital world with curiosity and effort",
|
||||||
"Linux can revive old computers, extending their life and reducing e-waste",
|
"Linux can revive old computers, extending their life and reducing e-waste",
|
||||||
"Many lightweight Linux distributions run smoothly on older hardware",
|
"Many lightweight Linux distributions run smoothly on older hardware",
|
||||||
|
|
|
@ -13,8 +13,6 @@
|
||||||
//
|
//
|
||||||
// Released under the GNU LGPL v2.1, by Frank Bi <bi@zompower.tk>
|
// Released under the GNU LGPL v2.1, by Frank Bi <bi@zompower.tk>
|
||||||
//
|
//
|
||||||
//
|
|
||||||
// 2024-08-xx upgraded things to work in WebXR (xterm.js too heavy)
|
|
||||||
// 2007-08-12 - refresh():
|
// 2007-08-12 - refresh():
|
||||||
// - factor out colour code to html_colours_()
|
// - factor out colour code to html_colours_()
|
||||||
// - fix handling of A_REVERSE | A_DIM
|
// - fix handling of A_REVERSE | A_DIM
|
||||||
|
@ -120,8 +118,6 @@ function VT100(opts)
|
||||||
this.redraw_[r] = 1;
|
this.redraw_[r] = 1;
|
||||||
}
|
}
|
||||||
this.scr_ = scr;
|
this.scr_ = scr;
|
||||||
this.scr_.style.display = 'inline'
|
|
||||||
this.setupTouchInputFallback() // smartphone/android
|
|
||||||
this.cursor_vis_ = true;
|
this.cursor_vis_ = true;
|
||||||
this.cursor_key_mode_ = VT100.CK_CURSOR;
|
this.cursor_key_mode_ = VT100.CK_CURSOR;
|
||||||
this.grab_events_ = false;
|
this.grab_events_ = false;
|
||||||
|
@ -135,6 +131,8 @@ function VT100(opts)
|
||||||
|
|
||||||
// rate limit this.refresh
|
// rate limit this.refresh
|
||||||
this.refresh = this.throttleSmart( VT100.prototype.refresh.bind(this), 100)
|
this.refresh = this.throttleSmart( VT100.prototype.refresh.bind(this), 100)
|
||||||
|
|
||||||
|
this.setupTouchInputFallback() // smartphone
|
||||||
}
|
}
|
||||||
|
|
||||||
// public constants -- colours and colour pairs
|
// public constants -- colours and colour pairs
|
||||||
|
@ -218,7 +216,7 @@ VT100.handle_onkeypress_ = function VT100_handle_onkeypress(event,cb)
|
||||||
ch = '\n';
|
ch = '\n';
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
switch (event.code) {
|
switch (event.key) {
|
||||||
case "Backspace":
|
case "Backspace":
|
||||||
ch = '\b';
|
ch = '\b';
|
||||||
break;
|
break;
|
||||||
|
@ -266,21 +264,14 @@ VT100.handle_onkeypress_ = function VT100_handle_onkeypress(event,cb)
|
||||||
return true
|
return true
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// custom map override
|
|
||||||
if( vt.opts.map[ event.code ].ch ) ch = vt.opts.map[ event.code ].ch
|
|
||||||
if( vt.opts.map[ event.code ].ctrl && event.ctrlKey ) ch = vt.opts.map[ event.code ].ctrl
|
|
||||||
}
|
}
|
||||||
|
// Stop the event from doing anything else.
|
||||||
// Workaround: top the event from doing anything else.
|
event.preventDefault();
|
||||||
// (prevent input from adding characters instead of via VM)
|
|
||||||
event.preventDefault()
|
|
||||||
vt.key_buf_.push(ch);
|
vt.key_buf_.push(ch);
|
||||||
|
|
||||||
if( cb ){
|
if( cb ){
|
||||||
cb(vt.key_buf_)
|
cb(vt.key_buf_)
|
||||||
vt.key_buf_ = []
|
vt.key_buf_ = []
|
||||||
}else setTimeout(VT100.go_getch_, 0);
|
}else setTimeout(VT100.go_getch_, 0);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -294,7 +285,6 @@ VT100.handle_onkeydown_ = function VT100_handle_onkeydown()
|
||||||
default:
|
default:
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
event.preventDefault()
|
|
||||||
vt.key_buf_.push(ch);
|
vt.key_buf_.push(ch);
|
||||||
setTimeout(VT100.go_getch_, 0);
|
setTimeout(VT100.go_getch_, 0);
|
||||||
return false;
|
return false;
|
||||||
|
@ -559,28 +549,24 @@ VT100.prototype.clearpos = function VT100_clearpos(row, col)
|
||||||
this.redraw_[row] = 1;
|
this.redraw_[row] = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
VT100.prototype.curs_set = function(vis, grab, offscreenKB)
|
VT100.prototype.curs_set = function(vis, grab, eventist)
|
||||||
{
|
{
|
||||||
// offscreenKB is a div which receives keys from physical kb's
|
|
||||||
// but not from touch keyboards (they require an input-field)
|
|
||||||
// hence setupTouchInputFallback()..this is how we seperate the two
|
|
||||||
this.info("curs_set:: vis: " + vis + ", grab: " + grab);
|
this.info("curs_set:: vis: " + vis + ", grab: " + grab);
|
||||||
if (vis !== undefined){
|
if (vis !== undefined)
|
||||||
this.cursor_vis_ = (vis > 0);
|
this.cursor_vis_ = (vis > 0);
|
||||||
}
|
if (eventist === undefined)
|
||||||
if (offscreenKB === undefined)
|
eventist = this.scr_;
|
||||||
offscreenKB = this.scr_;
|
|
||||||
if (grab === true || grab === false) {
|
if (grab === true || grab === false) {
|
||||||
if (grab === this.grab_events_)
|
if (grab === this.grab_events_)
|
||||||
return;
|
return;
|
||||||
if (grab) {
|
if (grab) {
|
||||||
this.grab_events_ = true;
|
this.grab_events_ = true;
|
||||||
VT100.the_vt_ = this;
|
VT100.the_vt_ = this;
|
||||||
offscreenKB.addEventListener("keypress", VT100.handle_onkeypress_, false);
|
eventist.addEventListener("keypress", VT100.handle_onkeypress_, false);
|
||||||
offscreenKB.addEventListener("keydown", VT100.handle_onkeypress_, false);
|
eventist.addEventListener("keydown", VT100.handle_onkeypress_, false);
|
||||||
} else {
|
} else {
|
||||||
offscreenKB.removeEventListener("keypress", VT100.handle_onkeypress_, false);
|
eventist.removeEventListener("keypress", VT100.handle_onkeypress_, false);
|
||||||
offscreenKB.removeEventListener("keydown", VT100.handle_onkeypress_, false);
|
eventist.removeEventListener("keydown", VT100.handle_onkeypress_, false);
|
||||||
this.grab_events_ = false;
|
this.grab_events_ = false;
|
||||||
VT100.the_vt_ = undefined;
|
VT100.the_vt_ = undefined;
|
||||||
}
|
}
|
||||||
|
@ -670,8 +656,7 @@ VT100.prototype.refresh = function VT100_refresh()
|
||||||
for (c = 0; c < wd; ++c) {
|
for (c = 0; c < wd; ++c) {
|
||||||
added_end_tag = false;
|
added_end_tag = false;
|
||||||
n_at = this.attr_[r][c];
|
n_at = this.attr_[r][c];
|
||||||
const drawCursor = cv && r == cr && c == cc
|
if (cv && r == cr && c == cc) {
|
||||||
if (drawCursor){
|
|
||||||
// Draw the cursor here.
|
// Draw the cursor here.
|
||||||
n_at = this._cloneAttr(n_at);
|
n_at = this._cloneAttr(n_at);
|
||||||
n_at.mode ^= VT100.A_REVERSE;
|
n_at.mode ^= VT100.A_REVERSE;
|
||||||
|
@ -696,9 +681,7 @@ VT100.prototype.refresh = function VT100_refresh()
|
||||||
start_tag += ';font-weight: bolder';
|
start_tag += ';font-weight: bolder';
|
||||||
if (n_at.mode & VT100.A_UNDERLINE)
|
if (n_at.mode & VT100.A_UNDERLINE)
|
||||||
start_tag += ';text-decoration:underline';
|
start_tag += ';text-decoration:underline';
|
||||||
if ( drawCursor )
|
start_tag += ';">';
|
||||||
start_tag += '" class="cursor'
|
|
||||||
start_tag += '">';
|
|
||||||
row_html += start_tag;
|
row_html += start_tag;
|
||||||
end_tag = "</span>" + end_tag;
|
end_tag = "</span>" + end_tag;
|
||||||
at = n_at;
|
at = n_at;
|
||||||
|
@ -738,7 +721,6 @@ VT100.prototype.refresh = function VT100_refresh()
|
||||||
div_element.innerHTML = row_html;
|
div_element.innerHTML = row_html;
|
||||||
//dump("adding row html: " + row_html + "\n");
|
//dump("adding row html: " + row_html + "\n");
|
||||||
}
|
}
|
||||||
this.curs_set(1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
VT100.prototype.set_max_scroll_lines = function(max_lines)
|
VT100.prototype.set_max_scroll_lines = function(max_lines)
|
||||||
|
@ -1085,10 +1067,6 @@ VT100.prototype.write = function VT100_write(stuff)
|
||||||
case 'm':
|
case 'm':
|
||||||
for (j=0; j<this.csi_parms_.length; ++j) {
|
for (j=0; j<this.csi_parms_.length; ++j) {
|
||||||
x = this.csi_parms_[j];
|
x = this.csi_parms_[j];
|
||||||
if( x > 89 && x < 98 && this.opts.rainbow ){
|
|
||||||
const rainbow = this.opts.rainbow
|
|
||||||
this.fgset( rainbow[ x % rainbow.length ] )
|
|
||||||
}
|
|
||||||
switch (x) {
|
switch (x) {
|
||||||
case 0:
|
case 0:
|
||||||
this.standend();
|
this.standend();
|
||||||
|
@ -1188,7 +1166,6 @@ VT100.prototype.write = function VT100_write(stuff)
|
||||||
default:
|
default:
|
||||||
this.warn(" unknown command: " + ch);
|
this.warn(" unknown command: " + ch);
|
||||||
this.csi_parms_ = [];
|
this.csi_parms_ = [];
|
||||||
return
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -1332,90 +1309,38 @@ VT100.prototype.throttleSmart = function throttleSmart(fn, wait) {
|
||||||
}
|
}
|
||||||
|
|
||||||
VT100.prototype.setupTouchInputFallback = function(){
|
VT100.prototype.setupTouchInputFallback = function(){
|
||||||
|
this.scr_.addEventListener('touchend', () => {
|
||||||
if( !this.input ){
|
if( !this.input ){
|
||||||
this.upload = document.createElement("input")
|
|
||||||
this.upload.setAttribute("type", "file")
|
|
||||||
this.upload.style.opacity = '0'
|
|
||||||
this.upload.style.position = 'absolute'
|
|
||||||
this.upload.style.left = '-9999px'
|
|
||||||
|
|
||||||
this.input = document.createElement("input")
|
|
||||||
this.input.setAttribute("type", "text")
|
|
||||||
this.input.setAttribute("cols", this.opts.cols )
|
|
||||||
this.input.setAttribute("rows", this.opts.rows )
|
|
||||||
this.input.style.opacity = '0'
|
|
||||||
this.input.style.position = 'absolute'
|
|
||||||
this.input.style.left = '-9999px'
|
|
||||||
|
|
||||||
this.form = document.createElement("form")
|
this.form = document.createElement("form")
|
||||||
this.form.addEventListener("submit", (e) => {
|
this.form.addEventListener("submit", (e) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
this.key_buf_.push('\n')
|
this.key_buf_.push('\n')
|
||||||
setTimeout(VT100.go_getch_, 0);
|
setTimeout(VT100.go_getch_, 0);
|
||||||
return false
|
|
||||||
})
|
})
|
||||||
this.form.appendChild(this.upload)
|
this.input = document.createElement("input")
|
||||||
|
this.input.setAttribute("cols", this.opts.cols )
|
||||||
|
this.input.setAttribute("rows", this.opts.rows )
|
||||||
|
this.input.style.opacity = '0'
|
||||||
|
this.input.style.position = 'absolute'
|
||||||
|
this.input.style.left = '-9999px'
|
||||||
this.form.appendChild(this.input)
|
this.form.appendChild(this.input)
|
||||||
this.scr_.parentElement.appendChild(this.form)
|
this.scr_.parentElement.appendChild(this.form)
|
||||||
|
|
||||||
this.input.addEventListener('blur', () => {
|
this.input.handler = () => {
|
||||||
if( this.input.value != '' ){
|
let ch = this.input.value
|
||||||
ch = '\n'
|
|
||||||
this.key_buf_.push(ch);
|
|
||||||
setTimeout(VT100.go_getch_, 0);
|
|
||||||
this.input.value = ''
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
this.input.addEventListener("keydown", VT100.handle_onkeypress_, false);
|
|
||||||
|
|
||||||
this.input.handler = (e) => {
|
|
||||||
let ch
|
|
||||||
let isEnter = String(e?.key).toLowerCase() == "enter" || e?.code == 13
|
|
||||||
let isBackspace = String(e?.key).toLowerCase() == "backspace" || e?.code == 8
|
|
||||||
if( isEnter ){
|
|
||||||
ch = '\n'
|
|
||||||
}else if( isBackspace ){
|
|
||||||
ch = '\b' // naive
|
|
||||||
}else{
|
|
||||||
ch = this.input.value.substr(-1)
|
|
||||||
}
|
|
||||||
// detect backspace
|
// detect backspace
|
||||||
|
//if( e.inputType == 'deleteContentBackward' ) ch = '\b'
|
||||||
|
this.input.value = ''
|
||||||
if( !ch ) return
|
if( !ch ) return
|
||||||
this.key_buf_.push(ch);
|
this.key_buf_.push(ch);
|
||||||
setTimeout(VT100.go_getch_, 0);
|
setTimeout(VT100.go_getch_, 0);
|
||||||
this.input.lastValue = this.input.value
|
this.input.valueLast = this.input.value
|
||||||
}
|
}
|
||||||
this.input.addEventListener('input', (e) => this.input.handler(e) )
|
this.input.addEventListener('input', this.input.handler )
|
||||||
|
|
||||||
this.scr_.addEventListener('touchend', (e) => this.focus() )
|
|
||||||
this.scr_.addEventListener('click', (e) => this.focus() )
|
|
||||||
|
|
||||||
}
|
}
|
||||||
this.useFallbackInput = true
|
setTimeout( () => this.input.focus(), 10 )
|
||||||
this.focus()
|
})
|
||||||
}
|
|
||||||
|
|
||||||
VT100.prototype.focus = function(){
|
|
||||||
setTimeout( () => {
|
|
||||||
const el = this[ this.useFallbackInput ? 'input' : 'scr_' ]
|
|
||||||
el.focus()
|
|
||||||
}, 10 )
|
|
||||||
}
|
|
||||||
|
|
||||||
window.keyboard = function(n){
|
|
||||||
let msg = 'unknown keyboard'
|
|
||||||
if( n == 0 ){
|
|
||||||
msg = "using onscreen keyboard"
|
|
||||||
VT100.the_vt_.useFallbackInput = true
|
|
||||||
VT100.the_vt_.focus()
|
|
||||||
}
|
|
||||||
if( n == 1 ){
|
|
||||||
msg = "using offscreen keyboard"
|
|
||||||
VT100.the_vt_.useFallbackInput = false
|
|
||||||
VT100.the_vt_.focus()
|
|
||||||
}
|
|
||||||
return msg
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function dump(x) {
|
function dump(x) {
|
||||||
|
|
|
@ -21,6 +21,7 @@ emulator.fs9p.update_file = async function(file,data){
|
||||||
inode.size = buf.length
|
inode.size = buf.length
|
||||||
const now = Math.round(Date.now() / 1000);
|
const now = Math.round(Date.now() / 1000);
|
||||||
inode.atime = inode.mtime = now;
|
inode.atime = inode.mtime = now;
|
||||||
|
me.postMessage({event:'exec',data:[`touch /mnt/${file}`]}) // update inode
|
||||||
return new Promise( (resolve,reject) => resolve(buf) )
|
return new Promise( (resolve,reject) => resolve(buf) )
|
||||||
}catch(e){
|
}catch(e){
|
||||||
console.error({file,data})
|
console.error({file,data})
|
||||||
|
@ -49,47 +50,3 @@ emulator.fs9p.append_file = async function(file,data){
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
emulator.fs9p.read_file_world = async function(file){
|
|
||||||
const p = this.SearchPath(file);
|
|
||||||
|
|
||||||
if(p.id === -1)
|
|
||||||
{
|
|
||||||
return Promise.resolve(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
const inode = this.GetInode(p.id);
|
|
||||||
const perms = this.parseFilePermissions(inode.mode)
|
|
||||||
|
|
||||||
if( !perms.world.read ){
|
|
||||||
return Promise.resolve(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.Read(p.id, 0, inode.size);
|
|
||||||
}
|
|
||||||
|
|
||||||
emulator.fs9p.parseFilePermissions = function(permissionInt) {
|
|
||||||
// Convert the permission integer to octal
|
|
||||||
const octalPermissions = permissionInt.toString(8);
|
|
||||||
|
|
||||||
// Extract the permission bits (last 3 digits in octal)
|
|
||||||
const permissionBits = octalPermissions.slice(-3);
|
|
||||||
|
|
||||||
|
|
||||||
function parsePermission(digit) {
|
|
||||||
const num = parseInt(digit, 10);
|
|
||||||
|
|
||||||
return {
|
|
||||||
read: Boolean(num & 4), // 4 = read
|
|
||||||
write: Boolean(num & 2), // 2 = write
|
|
||||||
execute: Boolean(num & 1) // 1 = execute
|
|
||||||
};
|
|
||||||
}
|
|
||||||
// Decode the permissions
|
|
||||||
const permissions = {
|
|
||||||
owner: parsePermission(permissionBits[0]),
|
|
||||||
group: parsePermission(permissionBits[1]),
|
|
||||||
world: parsePermission(permissionBits[2]),
|
|
||||||
};
|
|
||||||
|
|
||||||
return permissions;
|
|
||||||
}
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ 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['emulator.restore_state'] = async function(data){
|
||||||
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 ){
|
||||||
|
@ -14,7 +14,7 @@ if( typeof emulator != 'undefined' ){
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
this.save_state = async function(){
|
this['emulator.save_state'] = async function(){
|
||||||
console.log("saving session")
|
console.log("saving session")
|
||||||
let state = await emulator.save_state()
|
let state = await emulator.save_state()
|
||||||
localforage.setDriver([
|
localforage.setDriver([
|
||||||
|
@ -25,6 +25,7 @@ if( typeof emulator != 'undefined' ){
|
||||||
localforage.setItem("state", ISOTerminal.prototype.convert.arrayBufferToBase64(state) )
|
localforage.setItem("state", ISOTerminal.prototype.convert.arrayBufferToBase64(state) )
|
||||||
console.log("state saved")
|
console.log("state saved")
|
||||||
})
|
})
|
||||||
|
console.dir(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -46,7 +47,7 @@ if( typeof emulator != 'undefined' ){
|
||||||
if( stateBase64 && !err && confirm('continue last session?') ){
|
if( stateBase64 && !err && confirm('continue last session?') ){
|
||||||
this.noboot = true // see feat/boot.js
|
this.noboot = true // see feat/boot.js
|
||||||
try{
|
try{
|
||||||
await this.worker.restore_state()
|
await this.worker['emulator.restore_state']()
|
||||||
// simulate / fastforward boot events
|
// simulate / fastforward boot events
|
||||||
this.postBoot( () => {
|
this.postBoot( () => {
|
||||||
this.send("l\n")
|
this.send("l\n")
|
||||||
|
@ -57,7 +58,7 @@ if( typeof emulator != 'undefined' ){
|
||||||
})
|
})
|
||||||
|
|
||||||
this.save = async () => {
|
this.save = async () => {
|
||||||
await this.worker.save_state()
|
await this.worker['emulator.save_state']()
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener("beforeunload", function (e) {
|
window.addEventListener("beforeunload", function (e) {
|
||||||
|
|
|
@ -14,7 +14,7 @@ ISOTerminal.prototype.boot = async function(e){
|
||||||
env.push( 'export '+String(i).toUpperCase()+'="'+decodeURIComponent( document.location[i]+'"') )
|
env.push( 'export '+String(i).toUpperCase()+'="'+decodeURIComponent( document.location[i]+'"') )
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.worker.create_file("profile.browser", this.convert.toUint8Array( env.join('\n') ) )
|
await this.emit("emulator.create_file", ["profile.browser", this.convert.toUint8Array( env.join('\n') ) ] )
|
||||||
|
|
||||||
if( this.serial_input == 0 ){
|
if( this.serial_input == 0 ){
|
||||||
if( !this.noboot ){
|
if( !this.noboot ){
|
||||||
|
|
|
@ -1,30 +0,0 @@
|
||||||
if( typeof emulator != 'undefined' ){
|
|
||||||
|
|
||||||
}else{
|
|
||||||
|
|
||||||
ISOTerminal.addEventListener('ready', function(e){
|
|
||||||
|
|
||||||
// listen for http request to the filesystem ( file://host/path )
|
|
||||||
xhook.before( (request,callback) => {
|
|
||||||
|
|
||||||
if (request.url.match(/^file:\/\/xrsh\/mnt\/.*/) ){
|
|
||||||
let response
|
|
||||||
let file = request.url.replace(/^file:\/\/xrsh\/mnt\//,'')
|
|
||||||
this.worker.read_file_world(file)
|
|
||||||
.then( (data) => {
|
|
||||||
response = new Response( new Blob( [data] ) ) // wrap Uint8Array into array
|
|
||||||
response.status = 200
|
|
||||||
callback(response)
|
|
||||||
})
|
|
||||||
.catch( (e) => {
|
|
||||||
response = new Response()
|
|
||||||
response.status = 404
|
|
||||||
callback(response)
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
callback()
|
|
||||||
})
|
|
||||||
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -6,7 +6,7 @@ if( typeof emulator != 'undefined' ){
|
||||||
const convert = ISOTerminal.prototype.convert
|
const convert = ISOTerminal.prototype.convert
|
||||||
const buf = await this.emulator.read_file("dev/browser/js")
|
const buf = await this.emulator.read_file("dev/browser/js")
|
||||||
const script = convert.Uint8ArrayToString(buf)
|
const script = convert.Uint8ArrayToString(buf)
|
||||||
let PID=null
|
let PID="?"
|
||||||
try{
|
try{
|
||||||
if( script.match(/^PID/) ){
|
if( script.match(/^PID/) ){
|
||||||
PID = script.match(/^PID=([0-9]+);/)[1]
|
PID = script.match(/^PID=([0-9]+);/)[1]
|
||||||
|
@ -35,9 +35,7 @@ if( typeof emulator != 'undefined' ){
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// update output to 9p with PID as filename (in /mnt/run)
|
// update output to 9p with PID as filename (in /mnt/run)
|
||||||
if( PID ){
|
this.emit('fs9p.update_file', [`run/${PID}`, this.convert.toUint8Array(res)] )
|
||||||
this.worker.update_file(`run/${PID}`, this.convert.toUint8Array(res) )
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,32 +0,0 @@
|
||||||
if( typeof emulator != 'undefined' ){
|
|
||||||
// inside worker-thread
|
|
||||||
|
|
||||||
}else{
|
|
||||||
// inside browser-thread
|
|
||||||
//
|
|
||||||
ISOTerminal.prototype.pasteWriteFile = async function(data,type,filename){
|
|
||||||
this.pasteWriteFile.fileCount = this.pasteWriteFile.fileCount || 0
|
|
||||||
const file = `clipboard/`+ ( filename || `user-paste-${this.pasteWriteFile.fileCount}`)
|
|
||||||
await this.worker.create_file(file, data )
|
|
||||||
// run the xrsh hook
|
|
||||||
this.hook("clipboard", [ `/mnt/${file}`, type ] )
|
|
||||||
console.log("clipboard paste: /mnt/"+file)
|
|
||||||
this.pasteWriteFile.fileCount += 1
|
|
||||||
}
|
|
||||||
|
|
||||||
ISOTerminal.prototype.pasteFile = async function(data){
|
|
||||||
const {type,item,pastedText} = data
|
|
||||||
if( pastedText){
|
|
||||||
this.pasteWriteFile( this.convert.toUint8Array(pastedText) ,type)
|
|
||||||
}else{
|
|
||||||
const file = item.getAsFile();
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.onload = (e) => {
|
|
||||||
const arr = new Uint8Array(e.target.result)
|
|
||||||
this.pasteWriteFile( arr, type, file.name ); // or use readAsDataURL for images
|
|
||||||
};
|
|
||||||
reader.readAsArrayBuffer(file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
|
@ -42,11 +42,10 @@ this.runISO = function(opts){
|
||||||
/*
|
/*
|
||||||
* forward events/functions so non-worker world can reach them
|
* forward events/functions so non-worker world can reach them
|
||||||
*/
|
*/
|
||||||
this.create_file = async function(){ return emulator.create_file.apply(emulator, arguments[0]) }
|
this['emulator.create_file'] = async function(){ return emulator.create_file.apply(emulator, arguments[0]) }
|
||||||
this.read_file = async function(){ return emulator.read_file.apply(emulator, arguments[0]) }
|
this['emulator.read_file'] = async function(){ return emulator.read_file.apply(emulator, arguments[0]) }
|
||||||
this.read_file_world = async function(){ return emulator.fs9p.read_file_world.apply(emulator.fs9p, arguments[0]) }
|
this['emulator.append_file'] = async function(){ emulator.fs9p.append_file.apply(emulator.fs9p, arguments[0]) }
|
||||||
this.append_file = async function(){ emulator.fs9p.append_file.apply(emulator.fs9p, arguments[0]) }
|
this['emulator.update_file'] = async function(){ emulator.fs9p.update_file.apply(emulator.fs9p, arguments[0]) }
|
||||||
this.update_file = async function(){ emulator.fs9p.update_file.apply(emulator.fs9p, 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){
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,179 @@
|
||||||
|
AFRAME.registerComponent('paste', {
|
||||||
|
schema: {
|
||||||
|
foo: { type:"string"}
|
||||||
|
},
|
||||||
|
|
||||||
|
init: function () {
|
||||||
|
this.el.object3D.visible = false
|
||||||
|
//this.el.innerHTML = ` `
|
||||||
|
},
|
||||||
|
|
||||||
|
requires:{
|
||||||
|
osbutton: "com/osbutton.js"
|
||||||
|
},
|
||||||
|
|
||||||
|
events:{
|
||||||
|
|
||||||
|
// component events
|
||||||
|
somecomponent: function( ){ console.log("component requirement mounted") },
|
||||||
|
ready: function(e){ console.log("requires are loaded") },
|
||||||
|
|
||||||
|
launcher: function(e){
|
||||||
|
const paste = () => {
|
||||||
|
navigator.clipboard.readText()
|
||||||
|
.then( (base64) => {
|
||||||
|
let mimetype = base64.replace(/;base64,.*/,'')
|
||||||
|
let data = base64.replace(/.*;base64,/,'')
|
||||||
|
let type = this.textHeuristic(data)
|
||||||
|
console.log("type="+type)
|
||||||
|
switch( this.textHeuristic(data) ){
|
||||||
|
case "aframe": this.insertAFRAME(data); break;
|
||||||
|
default: this.insertText(data); break;
|
||||||
|
}
|
||||||
|
this.count += 1
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
navigator.permissions.query({ name: 'clipboard-read' })
|
||||||
|
.then( (permission) => {
|
||||||
|
if( permission.state != 'granted' ){
|
||||||
|
this.el.sceneEl.exitVR()
|
||||||
|
setTimeout( () => paste(), 500 )
|
||||||
|
return
|
||||||
|
}else paste()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
textHeuristic: function(text){
|
||||||
|
// Script type identification clues
|
||||||
|
const bashClues = ["|", "if ", "fi", "cat"];
|
||||||
|
const htmlClues = ["/>", "href=", "src="];
|
||||||
|
const aframeClues = ["<a-entity", "/>", "position="];
|
||||||
|
const jsClues = ["var ", "let ", "function ", "setTimeout","console."];
|
||||||
|
// Count occurrences of clues for each script type
|
||||||
|
const bashCount = bashClues.reduce((acc, clue) => acc + (text.includes(clue) ? 1 : 0), 0);
|
||||||
|
const htmlCount = htmlClues.reduce((acc, clue) => acc + (text.includes(clue) ? 1 : 0), 0);
|
||||||
|
const aframeCount = aframeClues.reduce((acc, clue) => acc + (text.includes(clue) ? 1 : 0), 0);
|
||||||
|
const jsCount = jsClues.reduce((acc, clue) => acc + (text.includes(clue) ? 1 : 0), 0);
|
||||||
|
|
||||||
|
// Identify the script with the most clues or return unknown if inconclusive
|
||||||
|
const maxCount = Math.max(bashCount, htmlCount, jsCount, aframeCount);
|
||||||
|
if (maxCount === 0) {
|
||||||
|
return "unknown";
|
||||||
|
} else if (bashCount === maxCount) {
|
||||||
|
return "bash";
|
||||||
|
} else if (htmlCount === maxCount) {
|
||||||
|
return "html";
|
||||||
|
} else if (jsCount === maxCount) {
|
||||||
|
return "javascript";
|
||||||
|
} else {
|
||||||
|
return "aframe";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
insertAFRAME: function(data){
|
||||||
|
let scene = document.createElement('a-entity')
|
||||||
|
scene.id = "embedAframe"
|
||||||
|
scene.innerHTML = data
|
||||||
|
let el = document.createElement('a-text')
|
||||||
|
el.setAttribute("value",data)
|
||||||
|
el.setAttribute("color","white")
|
||||||
|
el.setAttribute("align","center")
|
||||||
|
el.setAttribute("anchor","align")
|
||||||
|
let osbutton = this.wrapOSButton(el,"aframe",data)
|
||||||
|
AFRAME.scenes[0].appendChild(osbutton)
|
||||||
|
console.log(data)
|
||||||
|
},
|
||||||
|
|
||||||
|
insertText: function(data){
|
||||||
|
let el = document.createElement('a-text')
|
||||||
|
el.setAttribute("value",data)
|
||||||
|
el.setAttribute("color","white")
|
||||||
|
el.setAttribute("align","center")
|
||||||
|
el.setAttribute("anchor","align")
|
||||||
|
let osbutton = this.wrapOSButton(el,"text",data)
|
||||||
|
AFRAME.scenes[0].appendChild(osbutton)
|
||||||
|
console.log(data)
|
||||||
|
},
|
||||||
|
|
||||||
|
wrapOSButton: function(el,type,data){
|
||||||
|
let osbutton = document.createElement('a-entity')
|
||||||
|
let height = type == 'aframe' ? 0.3 : 0.1
|
||||||
|
let depth = type == 'aframe' ? 0.3 : 0.05
|
||||||
|
osbutton.setAttribute("osbutton",`width:0.3; height: ${height}; depth: ${depth}; color:blue `)
|
||||||
|
osbutton.appendChild(el)
|
||||||
|
osbutton.object3D.position.copy( this.getPositionInFrontOfCamera() )
|
||||||
|
return osbutton
|
||||||
|
},
|
||||||
|
|
||||||
|
getPositionInFrontOfCamera: function(distance){
|
||||||
|
const camera = this.el.sceneEl.camera;
|
||||||
|
let pos = new THREE.Vector3()
|
||||||
|
let direction = new THREE.Vector3();
|
||||||
|
// Get camera's forward direction (without rotation)
|
||||||
|
camera.getWorldDirection(direction);
|
||||||
|
camera.getWorldPosition(pos)
|
||||||
|
direction.normalize();
|
||||||
|
// Scale the direction by 1 meter
|
||||||
|
if( !distance ) distance = 1.5
|
||||||
|
direction.multiplyScalar(distance);
|
||||||
|
// Add the camera's position to the scaled direction to get the target point
|
||||||
|
pos.add(direction);
|
||||||
|
return pos
|
||||||
|
},
|
||||||
|
|
||||||
|
manifest: { // HTML5 manifest to identify app to xrsh
|
||||||
|
"short_name": "Paste",
|
||||||
|
"name": "Paste",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "https://css.gg/clipboard.svg",
|
||||||
|
"type": "image/svg+xml",
|
||||||
|
"sizes": "512x512"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": "/?source=pwa",
|
||||||
|
"start_url": "/?source=pwa",
|
||||||
|
"background_color": "#3367D6",
|
||||||
|
"display": "standalone",
|
||||||
|
"scope": "/",
|
||||||
|
"theme_color": "#3367D6",
|
||||||
|
"shortcuts": [
|
||||||
|
{
|
||||||
|
"name": "What is the latest news?",
|
||||||
|
"cli":{
|
||||||
|
"usage": "helloworld <type> [options]",
|
||||||
|
"example": "helloworld news",
|
||||||
|
"args":{
|
||||||
|
"--latest": {type:"string"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"short_name": "Today",
|
||||||
|
"description": "View weather information for today",
|
||||||
|
"url": "/today?source=pwa",
|
||||||
|
"icons": [{ "src": "/images/today.png", "sizes": "192x192" }]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Paste the clipboard",
|
||||||
|
"screenshots": [
|
||||||
|
{
|
||||||
|
"src": "/images/screenshot1.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "540x720",
|
||||||
|
"form_factor": "narrow"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"help":`
|
||||||
|
Helloworld application
|
||||||
|
|
||||||
|
This is a help file which describes the application.
|
||||||
|
It will be rendered thru troika text, and will contain
|
||||||
|
headers based on non-punctualized lines separated by linebreaks,
|
||||||
|
in above's case "\nHelloworld application\n" will qualify as header.
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
115
com/pastedrop.js
115
com/pastedrop.js
|
@ -1,115 +0,0 @@
|
||||||
AFRAME.registerComponent('pastedrop', {
|
|
||||||
schema: {
|
|
||||||
foo: { type:"string"}
|
|
||||||
},
|
|
||||||
|
|
||||||
init: function () {
|
|
||||||
|
|
||||||
window.addEventListener('paste', this.onPaste.bind(this) )
|
|
||||||
|
|
||||||
document.body.addEventListener('dragover',(e) => e.preventDefault() )
|
|
||||||
document.body.addEventListener('drop', this.onDrop.bind(this) )
|
|
||||||
},
|
|
||||||
|
|
||||||
initClipboard: function(){
|
|
||||||
navigator.permissions.query({ name: 'clipboard-read' })
|
|
||||||
.then( (permission) => {
|
|
||||||
if( permission.state != 'granted' ){
|
|
||||||
this.el.sceneEl.exitVR()
|
|
||||||
setTimeout( () => this.paste(), 500 )
|
|
||||||
return
|
|
||||||
}else this.paste()
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
//getClipboard: function(){
|
|
||||||
// navigator.clipboard.readText()
|
|
||||||
// .then( async (base64) => {
|
|
||||||
// let mimetype = base64.replace(/;base64,.*/,'')
|
|
||||||
// let data = base64.replace(/.*;base64,/,'')
|
|
||||||
// let type = this.textHeuristic(data)
|
|
||||||
// const term = document.querySelector('[isoterminal]').components.isoterminal.term
|
|
||||||
// this.el.emit('pasteFile',{}) /*TODO* data incompatible */
|
|
||||||
// })
|
|
||||||
//},
|
|
||||||
|
|
||||||
onDrop: function(e){
|
|
||||||
e.preventDefault()
|
|
||||||
this.onPaste({...e, type: "paste", clipboardData: e.dataTransfer})
|
|
||||||
},
|
|
||||||
|
|
||||||
onPaste: function(e){
|
|
||||||
if( e.type != "paste" ) return
|
|
||||||
|
|
||||||
const clipboardData = e.clipboardData || navigator.clipboard;
|
|
||||||
const items = clipboardData.items;
|
|
||||||
|
|
||||||
for (let i = 0; i < items.length; i++) {
|
|
||||||
const item = items[i];
|
|
||||||
const type = item.type;
|
|
||||||
|
|
||||||
// Check if the item is a file
|
|
||||||
if (item.kind === "file") {
|
|
||||||
this.el.emit('pasteFile',{item,type})
|
|
||||||
} else if (type === "text/plain") {
|
|
||||||
const pastedText = clipboardData.getData("text/plain");
|
|
||||||
const newType = "text" // let /root/hook.d/mimetype/text further decide whether this is text/plain (or something else)
|
|
||||||
this.el.emit('pasteFile',{item,type:newType,pastedText})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
manifest: { // HTML5 manifest to identify app to xrsh
|
|
||||||
"short_name": "Paste",
|
|
||||||
"name": "Paste",
|
|
||||||
"icons": [
|
|
||||||
{
|
|
||||||
"src": "https://css.gg/clipboard.svg",
|
|
||||||
"type": "image/svg+xml",
|
|
||||||
"sizes": "512x512"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"id": "/?source=pwa",
|
|
||||||
"start_url": "/?source=pwa",
|
|
||||||
"background_color": "#3367D6",
|
|
||||||
"display": "standalone",
|
|
||||||
"scope": "/",
|
|
||||||
"theme_color": "#3367D6",
|
|
||||||
"shortcuts": [
|
|
||||||
{
|
|
||||||
"name": "What is the latest news?",
|
|
||||||
"cli":{
|
|
||||||
"usage": "helloworld <type> [options]",
|
|
||||||
"example": "helloworld news",
|
|
||||||
"args":{
|
|
||||||
"--latest": {type:"string"}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"short_name": "Today",
|
|
||||||
"description": "View weather information for today",
|
|
||||||
"url": "/today?source=pwa",
|
|
||||||
"icons": [{ "src": "/images/today.png", "sizes": "192x192" }]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "Paste the clipboard",
|
|
||||||
"screenshots": [
|
|
||||||
{
|
|
||||||
"src": "/images/screenshot1.png",
|
|
||||||
"type": "image/png",
|
|
||||||
"sizes": "540x720",
|
|
||||||
"form_factor": "narrow"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"help":`
|
|
||||||
Helloworld application
|
|
||||||
|
|
||||||
This is a help file which describes the application.
|
|
||||||
It will be rendered thru troika text, and will contain
|
|
||||||
headers based on non-punctualized lines separated by linebreaks,
|
|
||||||
in above's case "\nHelloworld application\n" will qualify as header.
|
|
||||||
`
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
|
@ -34,22 +34,16 @@ AFRAME.registerComponent('selfcontainer', {
|
||||||
|
|
||||||
installProxyServer: function(){
|
installProxyServer: function(){
|
||||||
if( !window.store ) window.store = {}
|
if( !window.store ) window.store = {}
|
||||||
|
|
||||||
// selfcontain every webrequest to store (and serve if stored)
|
// selfcontain every webrequest to store (and serve if stored)
|
||||||
let curry = function(me){
|
let curry = function(me){
|
||||||
return function(request, response, cb){
|
return function(request, response, cb){
|
||||||
|
|
||||||
let data = request ? window.store[ request.url ] || false : false
|
let data = request ? window.store[ request.url ] || false : false
|
||||||
if( data ){ // return inline version
|
if( data ){ // return inline version
|
||||||
console.log('selfcontainer.js: serving '+request.url+' from cache')
|
console.log('selfcontained cache: '+request.url)
|
||||||
let res = new Response()
|
let res = new Response()
|
||||||
res[ data.binary ? 'data' : 'text' ] = data.binary ? () => me.convert.base64ToArrayBuffer(data.text) : data.text
|
res[ data.binary ? 'data' : 'text' ] = data.binary ? () => me.convert.base64ToArrayBuffer(data.text) : data.text
|
||||||
cb(res)
|
cb(res)
|
||||||
}else{
|
}else{
|
||||||
|
|
||||||
if( request.url.match(/(^file:\/\/xrsh)/) ) return cb(response)
|
|
||||||
|
|
||||||
console.log("selfcontainer.js: caching "+request.url)
|
|
||||||
if( response.text ){
|
if( response.text ){
|
||||||
data = {text: response.text}
|
data = {text: response.text}
|
||||||
}else{
|
}else{
|
||||||
|
|
|
@ -9,8 +9,7 @@ AFRAME.registerComponent('window', {
|
||||||
max: {type:'boolean',"default":false},
|
max: {type:'boolean',"default":false},
|
||||||
min: {type:'boolean',"default":false},
|
min: {type:'boolean',"default":false},
|
||||||
x: {type:'string',"default":"center"},
|
x: {type:'string',"default":"center"},
|
||||||
y: {type:'string',"default":"center"},
|
y: {type:'string',"default":"center"}
|
||||||
"class": {type:'array',"default":[]},
|
|
||||||
},
|
},
|
||||||
|
|
||||||
dependencies:{
|
dependencies:{
|
||||||
|
@ -29,7 +28,6 @@ AFRAME.registerComponent('window', {
|
||||||
|
|
||||||
this.el.dom.style.display = 'none'
|
this.el.dom.style.display = 'none'
|
||||||
let winbox = this.el.winbox = new WinBox( this.data.title, {
|
let winbox = this.el.winbox = new WinBox( this.data.title, {
|
||||||
class: this.data.class,
|
|
||||||
height:this.data.height,
|
height:this.data.height,
|
||||||
width:this.data.width,
|
width:this.data.width,
|
||||||
x: this.data.x,
|
x: this.data.x,
|
||||||
|
@ -72,7 +70,7 @@ AFRAME.registerComponent('window', {
|
||||||
if( els.length < 2 ) return
|
if( els.length < 2 ) return
|
||||||
let current = els[ els.length-1 ]
|
let current = els[ els.length-1 ]
|
||||||
let last = els[ els.length-2 ]
|
let last = els[ els.length-2 ]
|
||||||
AFRAME.utils.positionObjectNextToNeighbor( current.object3D , last.object3D, 0.02 )
|
AFRAME.utils.positionObjectNextToNeighbor( current.object3D , last.object3D, els.length )
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -81,10 +79,11 @@ AFRAME.registerComponent('window', {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
AFRAME.utils.positionObjectNextToNeighbor = function positionObjectNextToNeighbor(object, lastNeighbor = null, margin ){
|
AFRAME.utils.positionObjectNextToNeighbor = function positionObjectNextToNeighbor(object, lastNeighbor = null, neighbours, margin = 0.45, degree = 20) {
|
||||||
// *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 + ((neighbours-1) * margin)
|
||||||
object.position.y = lastNeighbor.position.y - margin
|
object.position.y = lastNeighbor.position.y
|
||||||
object.position.z = lastNeighbor.position.z + margin
|
object.position.z = lastNeighbor.position.z
|
||||||
|
//object.rotation.y += THREE.MathUtils.degToRad( (neighbours-1) * degree);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue