tweaks for fbterm
/ mirror_to_github (push) Successful in 21s Details
/ test (push) Successful in 3s Details

This commit is contained in:
Leon van Kammen 2025-01-15 18:02:30 +01:00
parent e8d6d0a8c1
commit 4125f90f06
6 changed files with 1661 additions and 57 deletions

View File

@ -35,8 +35,8 @@ if( typeof AFRAME != 'undefined '){
schema: {
iso: { type:"string", "default":"https://forgejo.isvery.ninja/assets/xrsh-buildroot/main/xrsh.iso" },
overlayfs: { type:"string"},
width: { type: 'number',"default": 700 },
height: { type: 'number',"default": 500 },
width: { type: 'number',"default": 800 },
height: { type: 'number',"default": 600 },
depth: { type: 'number',"default": 0.03 },
lineHeight: { type: 'number',"default": 18 },
padding: { type: 'number',"default": 18 },
@ -46,9 +46,9 @@ if( typeof AFRAME != 'undefined '){
HUD: { type: 'boolean',"default":false}, // link to camera movement
transparent: { type:'boolean', "default":false }, // need good gpu
memory: { type: 'number', "default":60 }, // VM memory (in MB) [NOTE: quest or smartphone might crash > 40mb ]
bufferLatency: { type: 'number', "default":1 }, // in ms: bufferlatency from webworker to xterm (batch-update every char to texture)
bufferLatency: { type: 'number', "default":1 }, // in ms: bufferlatency from webworker to xterm (batch-update every char to texture)
debug: { type: 'boolean', "default":false },
emulator: { type: 'string', "default": "vt100" }
emulator: { type: 'string', "default": "fbterm" }// terminal emulator
},
init: function(){
@ -59,7 +59,6 @@ if( typeof AFRAME != 'undefined '){
this.calculateDimension()
this.initHud()
this.setupBox()
this.setupPasteDrop()
fetch(this.data.iso,{method: 'HEAD'})
@ -94,26 +93,18 @@ if( typeof AFRAME != 'undefined '){
scale: 0.66,
events: ['click','keydown'],
html: (me) => `<div class="isoterminal">
<div style="white-space: pre;"></div>
<canvas style="display: none"></canvas>
<div id="term" tabindex="0">
<pre></pre>
</div>
<input type="file" id="pastedrop" style="position:absolute; left:-9999px;opacity:0"></input>
<div id="term" tabindex="0"></div>
</div>`,
css: (me) => `.isoterminal{
css: (me) => `
.isoterminal{
padding: ${me.com.data.padding}px;
width:100%;
height:90%;
position:relative;
}
.isoterminal div{
display:block;
position:relative;
line-height: ${me.com.data.lineHeight}px;
}
#term {
outline: none !important;
height:99%;
resize: both;
overflow: hidden;
}
@font-face {
font-family: 'Cousine';
@ -128,6 +119,73 @@ if( typeof AFRAME != 'undefined '){
src: url(./com/isoterminal/assets/CousineBold.ttf) format('truetype');
}
.isoterminal *{
outline:none;
box-shadow:none;
}
.term {
font-family: 'Cousine';
line-height: ${me.com.data.lineHeight}px;
font-weight: normal;
font-variant-ligatures: none;
color: #f0f0f0;
overflow: hidden;
white-space: nowrap;
}
.term_content a {
color: inherit;
text-decoration: underline;
color:#2AFF;
}
.term_content a span{
text-shadow: 0px 0px 10px #F07A;
}
.term_content a:hover {
color: inherit;
text-decoration: underline;
animation:fade 1000ms infinite;
-webkit-animation:fade 1000ms infinite;
}
.term_cursor {
color: #000000;
background: #70f;
animation:fade 1000ms infinite;
-webkit-animation:fade 1000ms infinite;
}
.term_char_size {
display: inline-block;
visibility: hidden;
position: absolute;
top: 0px;
left: -1000px;
padding: 0px;
}
.term_textarea {
position: absolute;
top: 0px;
left: 0px;
width: 0px;
height: 0px;
padding: 0px;
border: 0px;
margin: 0px;
opacity: 0;
resize: none;
}
.term_scrollbar { background: transparent url(images/bg-scrollbar-track-y.png) no-repeat 0 0; position: relative; background-position: 0 0; float: right; height: 100%; }
.term_track { background: transparent url(images/bg-scrollbar-trackend-y.png) no-repeat 0 100%; height: 100%; width:13px; position: relative; padding: 0 1px; }
.term_thumb { background: transparent url(images/bg-scrollbar-thumb-y.png) no-repeat 50% 100%; height: 20px; width: 25px; cursor: pointer; overflow: hidden; position: absolute; top: 0; left: -5px; }
.term_thumb .term_end { background: transparent url(images/bg-scrollbar-thumb-y.png) no-repeat 50% 0; overflow: hidden; height: 5px; width: 25px; }
.noSelect { user-select: none; -o-user-select: none; -moz-user-select: none; -khtml-user-select: none; -webkit-user-select: none; }
.isoterminal style{ display:none }
blink{
@ -140,12 +198,6 @@ if( typeof AFRAME != 'undefined '){
box-shadow:none;
}
.cursor {
background: #70F !important;
animation:fade 1000ms infinite;
-webkit-animation:fade 1000ms infinite;
}
.XR .cursor {
animation:none;
-webkit-animation:none;
@ -214,10 +266,10 @@ if( typeof AFRAME != 'undefined '){
pastedropFeat: "com/isoterminal/feat/pastedrop.js",
httpfs: "com/isoterminal/feat/httpfs.js",
}
if( this.data.emulator == "vt100" ){
features['VT100js'] = "com/isoterminal/VT100.js"
features['vt100'] = "com/isoterminal/feat/vt100.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.el.setAttribute("selfcontainer","")
@ -325,25 +377,14 @@ if( typeof AFRAME != 'undefined '){
return this
},
setupBox: function(){
// setup slightly bigger black backdrop (this.el.getObject3D("mesh"))
const w = this.data.width/950;
const h = this.data.height/950;
this.el.box = document.createElement('a-entity')
this.el.box.setAttribute("geometry",`primitive: box; width:${w}; height:${h}; depth: -${this.data.depth}`)
this.el.box.setAttribute("material","shader:flat; color:black; opacity:0.9; transparent:true; ")
this.el.box.setAttribute("position",`0 0 ${(this.data.depth/2)-0.001}`)
this.el.appendChild(this.el.box)
},
calculateDimension: function(){
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 > this.data.width ) this.data.height = this.data.width // mobile smartphone fix
this.data.width -= this.data.padding*2
this.data.height -= this.data.padding*2
this.cols = Math.floor(this.data.width/this.data.lineHeight*2)
this.rows = Math.floor( (this.data.height*0.93)/this.data.lineHeight)
this.cols = Math.floor(this.data.width/this.data.lineHeight*2)-1
this.rows = Math.floor( (this.data.height*0.93)/this.data.lineHeight)-1
},
events:{

View File

@ -34,11 +34,12 @@ ISOTerminal.addEventListener = (event,cb) => {
}
ISOTerminal.prototype.exec = function(shellscript){
this.send(shellscript+"\n",1)
this.send(`printf "\n\r"; ${shellscript}\n`,1)
}
ISOTerminal.prototype.hook = function(hookname,args){
this.exec(`{ 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)
}
ISOTerminal.prototype.serial_input = 0; // can be set to 0,1,2,3 to define stdinput tty (xterm plugin)
@ -53,7 +54,7 @@ ISOTerminal.prototype.send = function(str, ttyNr){
this.convert.toUint8Array( str ).map( (c) => {
this.preventFrameDrop(
() => {
this.worker.postMessage({event:`serial${ttyNr}-input`,data:c})
this.worker.postMessage({event:`serial${ttyNr}-input`,data:c})
}
)
})
@ -189,15 +190,14 @@ ISOTerminal.prototype.startVM = function(opts){
const empower = [
"FOSS gives users control over their software, offering freedom to modify and share",
"Feeling powerless with tech? FOSS escapes a mindset known as learned helplessness",
"Feeling powerless? FOSS escapes a mindset known as learned helplessness",
"FOSS breaks this cycle by showing that anyone can learn and contribute",
"Proprietary software can make users dependent, but FOSS offers real choices",
"FOSS communities provide support and encourage users to develop new skills",
"Learned helplessness fades when we realize tech isnt too complex to understand",
"FOSS empowers users to customize and improve their tools",
"Engaging with FOSS helps build confidence and self-reliance in tech",
"FOSS 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 reduces e-waste",
"Many lightweight Linux distributions run smoothly on older hardware",
"Installing Linux on aging devices keeps them functional instead of sending them to the landfill",
"Linux uses fewer resources, making it ideal for reusing older machines",
@ -216,16 +216,17 @@ ISOTerminal.prototype.startVM = function(opts){
\r . . . / \\ | | \\/ \\ Y / .
\r . . ./___/\\ \\ |____|_ /_______ /\\___|_ /. .
\r . . . . . .\\_/. . . . \\/ . . . .\\/ . . _ \\/ . .
\r https://xrsh.isvery.ninja ▬▬▬▬▬▬▬▬▬▬▬▬
\r https://xrsh.isvery.ninja ▬▬▬▬▬▬▬▬▬▬▬▬
\r local-first, polyglot, unixy WebXR IDE & runtime
\r
\r
\r credits
\r -------
\r @nlnet@nlnet.nl
\r @lvk@mastodon.online
\r @utopiah@mastodon.pirateparty.be
\r @utopiah@mastodon.pirateparty.be 
\r https://www.w3.org/TR/webxr
\r https://three.org
\r https://xrfragment.org
\r https://threejs.org
\r https://aframe.org
\r https://busybox.net
\r https://buildroot.org
@ -233,7 +234,7 @@ ISOTerminal.prototype.startVM = function(opts){
const text_color = "\r"
const text_reset = "\033[0m"
const loadmsg = "\n\r "+loading[ Math.floor(Math.random()*1000) % loading.length ] + "..[please wait]"
const loadmsg = "\n\r "+loading[ Math.floor(Math.random()*1000) % loading.length ] + "..please wait"
const empowermsg = "\n\r "+text_reset+'"'+empower[ Math.floor(Math.random()*1000) % empower.length ] + '"\n\r'
this.emit('status',loadmsg)
this.emit('serial-output-string', motd + empowermsg + text_color + loadmsg + text_reset+"\n\r")

View File

@ -14,7 +14,7 @@ ISOTerminal.prototype.boot = async function(e){
env.push( 'export '+String(i).toUpperCase()+'="'+decodeURIComponent( document.location[i]+'"') )
}
}
this.worker.create_file("profile.browser", this.convert.toUint8Array( env.join('\n') ) )
await this.worker.create_file("profile.browser", this.convert.toUint8Array( env.join('\n') ) )
if( this.serial_input == 0 ){
if( !this.noboot ){

View File

@ -17,7 +17,7 @@ if( typeof emulator != 'undefined' ){
ISOTerminal.prototype.pasteFile = async function(data){
const {type,item,pastedText} = data
if( pastedText){
this.pasteWriteFile( this.convert.toUint8Array(pastedText) ,type)
this.pasteWriteFile( this.convert.toUint8Array(pastedText) ,type, null, true)
}else{
const file = item.getAsFile();
const reader = new FileReader();
@ -29,4 +29,18 @@ if( typeof emulator != 'undefined' ){
}
}
ISOTerminal.prototype.pasteInit = function(opts){
// bind upload input
const {instance, aEntity} = opts
const el = aEntity.el.dom.querySelector('#pastedrop') // upload input
el.addEventListener('change', (e) => {
const file = el.files[0];
const item = {...file, getAsFile: () => file }
this.el.emit('pasteFile', { item, type: file.type });
})
}
ISOTerminal.addEventListener('init', function(){
this.addEventListener('term_init', (opts) => this.pasteInit(opts.detail) )
})
}

View File

@ -0,0 +1,88 @@
ISOTerminal.addEventListener('init', function(){
this.TermInit()
})
ISOTerminal.prototype.TermInit = function(){
const setupTerm = (opts) => {
if( !opts ) return
const {instance, aEntity} = opts
const el = aEntity.el.dom.querySelector('#term')
opts.termOpts = {
cols: aEntity.cols,
rows: aEntity.rows,
el_or_id: el,
scrollback: aEntity.rows*3,
fontSize: null //
//rainbow: [Term.COLOR_MAGENTA, Term.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
//}
}
// 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') {
return true; // bubble up to pasteHandler (see pastedrop.js)
}
original.apply(this,[e])
}
}( Term.prototype.keyDownHandler )
Term.prototype.href = (a) => {
if( a.href ){
this.exec(`source /etc/profile.sh; hook href "${a.href}"`)
}
return false
}
this.term = new Term( opts.termOpts )
this.term.colors = [
/* normal */
"#000000",
"#2FA",
"#7700ff",
"#555555",
"#0000ff",
"#aa00aa",
"#ff00aa",
"#aaaaaa",
/* bright */
"#555555",
"#ff5555",
"#2CF",
"#aa00ff",
"#5555ff",
"#ff55ff",
"#55ffff",
"#ffffff"
];
this.term.open(el)
this.term.el = el
this.term.setKeyHandler( (ch) => this.send(ch) )
aEntity.el.addEventListener('focus', () => el.querySelector("textarea").focus() )
aEntity.el.addEventListener('serial-output-string', (e) => {
this.term.write(e.detail)
})
//aEntity.term.emit('initTerm',this)
//aEntity.el.addEventListener('focus', () => this.vt100.focus() )
//aEntity.el.addEventListener('serial-output-string', (e) => {
// this.vt100.write(e.detail)
//})
}
this.addEventListener('term_init', (opts) => setupTerm(opts.detail) )
}

1460
com/isoterminal/term.js Normal file

File diff suppressed because it is too large Load Diff