diff --git a/com/isoterminal.js b/com/isoterminal.js
index 057f67e..e2eb21b 100644
--- a/com/isoterminal.js
+++ b/com/isoterminal.js
@@ -1,475 +1,258 @@
-AFRAME.registerComponent('isoterminal', {
- schema: {
- iso: { type:"string", "default":"com/isoterminal/images/buildroot-bzimage.bin" },
- cols: { type: 'number',"default": 120 },
- rows: { type: 'number',"default": 30 },
- padding:{ type: 'number',"default": 18 },
- transparent: { type:'boolean', "default":false } // need good gpu
- },
+function ISOTerminal(){
+ // create a neutral isoterminal object which can be decorated
+ // with prototype functions and has addListener() and dispatchEvent()
+ let obj = new EventTarget()
+ // register default event listeners (enable file based features like isoterminal/jsconsole.js e.g.)
+ for( let event in ISOTerminal.listener )
+ for( let cb in ISOTerminal.listener[event] )
+ obj.addEventListener( event, ISOTerminal.listener[event][cb] )
+ // compose object with functions
+ for( let i in ISOTerminal.prototype ) obj[i] = ISOTerminal.prototype[i]
+ obj.emit('init')
+ return obj
+}
- init: function(){
- this.el.object3D.visible = false
- },
+ISOTerminal.prototype.emit = function(event,data){
+ data = data || false
+ this.dispatchEvent( new CustomEvent(event, {detail: data} ) )
+}
- requires:{
- 'window': "com/window.js",
- xtermjs: "https://unpkg.com/@xterm/xterm@5.5.0/lib/xterm.js",
- xtermcss: "https://unpkg.com/@xterm/xterm@5.5.0/css/xterm.css",
- v86: "com/isoterminal/libv86.js"
- //axterm: "https://unpkg.com/aframe-xterm-component/aframe-xterm-component.js"
- },
+ISOTerminal.addEventListener = (event,cb) => {
+ ISOTerminal.listener = ISOTerminal.listener || {}
+ ISOTerminal.listener[event] = ISOTerminal.listener[event] || []
+ ISOTerminal.listener[event].push(cb)
+}
- dom: {
- scale: 0.7,
- events: ['click','keydown'],
- html: (me) => `
`,
+// ISOTerminal has defacto support for AFRAME
+// but can be decorated to work without it as well
- css: (me) => `.isoterminal{
- padding: ${me.com.data.padding}px;
- width:100%;
- height:100%;
- }
- .isoterminal *{
- white-space: pre;
- font-size: 14px;
- font-family: Liberation Mono,DejaVu Sans Mono,Courier New,monospace;
- font-weight:700;
- display:inline;
- overflow: hidden;
- }
+if( typeof AFRAME != 'undefined '){
- .isoterminal style{ display:none }
-
- .wb-body:has(> .isoterminal){
- background: #000c;
- overflow:hidden;
- }
-
- .isoterminal div{ display:block; }
- .isoterminal span{ display: inline }
-
- @keyframes fade {
- from { opacity: 1.0; }
- 50% { opacity: 0.5; }
- to { opacity: 1.0; }
- }
-
- @-webkit-keyframes fade {
- from { opacity: 1.0; }
- 50% { opacity: 0.5; }
- to { opacity: 1.0; }
- }
-
- .blink {
- animation:fade 1000ms infinite;
- -webkit-animation:fade 1000ms infinite;
- }
- `
- },
-
- toUint8Array: function(str) {
- // Create a new Uint8Array with the same length as the input string
- const uint8Array = new Uint8Array(str.length);
-
- // Iterate over the string and populate the Uint8Array
- for (let i = 0; i < str.length; i++) {
- uint8Array[i] = str.charCodeAt(i);
- }
- return uint8Array;
- },
-
- runISO: function(dom,instance){
- //var term = new Terminal()
- //term.open(dom)
- //term.write('Hello from \x1B[1;3;31mxterm.js\x1B[0m $ ')``
- if( typeof Terminal == undefined ) throw 'xterm terminal not loaded'
- // monkeypatch Xterm (which V86 initializes) so we can add our own constructor args
- window._Terminal = window.Terminal
- window.Terminal = function(opts){
- const term = new window._Terminal({ ...opts,
- cursorBlink:true,
- onSelectionChange: function(e){
- debugger
- }
- })
-
- term.onSelectionChange( () => {
- document.execCommand('copy')
- term.select(0, 0, 0)
- instance.setStatus('copied to clipboard')
- })
- return term
- }
-
- instance.setStatus = (msg) => {
- const w = instance.winbox
- w.titleBak = w.titleBak || w.title
- instance.winbox.setTitle( `${w.titleBak} [${msg}]` )
- }
-
- let image = {}
- if( this.data.iso.match(/\.iso$/) ) image.cdrom = { url: this.data.iso }
- if( this.data.iso.match(/\.bin$/) ) image.bzimage = { url: this.data.iso }
-
- var emulator = window.emulator = dom.emulator = new V86({ ...image,
- wasm_path: "com/isoterminal/v86.wasm",
- memory_size: 32 * 1024 * 1024,
- vga_memory_size: 2 * 1024 * 1024,
- serial_container_xtermjs: dom,
- //screen_container: dom, //this.canvas.parentElement,
- bios: {
- url: "com/isoterminal/bios/seabios.bin",
- },
- vga_bios: {
- url: "com/isoterminal/bios/vgabios.bin",
- },
- network_relay_url: "wss://relay.widgetry.org/",
- cmdline: "rw root=host9p rootfstype=9p rootflags=trans=virtio,cache=loose modules=virtio_pci tsc=reliable init_on_free=on",
- //bzimage_initrd_from_filesystem: true,
- //filesystem: {
- // baseurl: "com/isoterminal/v86/images/alpine-rootfs-flat",
- // basefs: "com/isoterminal/v86/images/alpine-fs.json",
- // },
- //screen_dummy: true,
- //disable_jit: false,
- filesystem: {},
- autostart: true,
- });
-
- const loading = [
- 'loading quantum bits and bytes',
- 'preparing quantum flux capacitors',
- 'crunching peanuts and chakras',
- 'preparing parallel universe',
- 'loading quantum state fluctuations',
- 'preparing godmode',
- 'loading cat pawns and cuteness',
- 'beaming up scotty',
- 'still faster than Windows update',
- 'loading a microlinux',
- 'figuring out meaning of life',
- 'Aligning your chakras now',
- 'Breathing in good vibes',
- 'Finding inner peace soon',
- 'Centering your Zen energy',
- 'Awakening third eye powers',
- 'Tuning into the universe',
- 'Balancing your cosmic karma',
- 'Stretching time and space',
- 'Recharging your soul battery',
- 'Transcending earthly limits'
- ]
-
- let motd = "\n\r"
- motd += "[38;5;57m " + ' ____ _____________ _________ ___ ___ ' + "\n\r"
- motd += "[38;5;93m " + ' \\ \\/ /\\______ \\/ _____// | \\ ' + "\n\r"
- motd += "[38;5;93m " + ' \\ / | _/\\_____ \\/ ~ \\ ' + "\n\r"
- motd += "[38;5;129m " + ' / \\ | | \\/ \\ Y / ' + "\n\r"
- motd += "[38;5;165m " + ' /___/\\ \\ |____|_ /_______ /\\___|_ / ' + "\n\r"
- motd += "[38;5;201m " + ' \\_/ \\/ \\/ \\/ ' + "\n\r"
- motd += " \n\r"
- motd += `${loading[ Math.floor(Math.random()*1000) % loading.length-1 ]}, please wait..\n\r\n\r`
- motd += "\033[0m"
-
- const files = [
- "com/isoterminal/mnt/js",
- "com/isoterminal/mnt/jsh",
- "com/isoterminal/mnt/xrsh",
- "com/isoterminal/mnt/profile",
- "com/isoterminal/mnt/profile.sh",
- "com/isoterminal/mnt/profile.xrsh",
- "com/isoterminal/mnt/profile.js",
- "com/isoterminal/mnt/motd",
- "com/isoterminal/mnt/v86pipe"
- ]
-
- const redirectConsole = (handler) => {
- const log = console.log;
- const dir = console.dir;
- const err = console.error;
- const warn = console.warn;
- console.log = (...args)=>{
- const textArg = args[0];
- handler(textArg+'\n');
- log.apply(log, args);
- };
- console.error = (...args)=>{
- const textArg = args[0].message?args[0].message:args[0];
- handler( textArg+'\n', '\x1b[31merror\x1b[0m');
- err.apply(log, args);
- };
- console.dir = (...args)=>{
- const textArg = args[0].message?args[0].message:args[0];
- handler( JSON.stringify(textArg,null,2)+'\n');
- dir.apply(log, args);
- };
- console.warn = (...args)=>{
- const textArg = args[0].message?args[0].message:args[0];
- handler(textArg+'\n','\x1b[38;5;208mwarn\x1b[0m');
- err.apply(log, args);
- };
- }
-
- emulator.bus.register("emulator-started", async () => {
- emulator.serial_adapter.term.element.querySelector('.xterm-viewport').style.background = 'transparent'
- emulator.serial_adapter.term.clear()
- emulator.serial_adapter.term.write(motd)
-
- emulator.create_file("motd", this.toUint8Array(motd) )
- emulator.create_file("js", this.toUint8Array(`#!/bin/sh
- cat /mnt/motd
- cat > /dev/null
- `))
-
- redirectConsole( (str,prefix) => {
- if( emulator.log_to_tty ){
- prefix = prefix ? prefix+' ' : ' '
- str.trim().split("\n").map( (line) => {
- emulator.serial_adapter.term.write( '\r\x1b[38;5;165m/dev/browser: \x1b[0m'+prefix+line+'\n' )
- })
- emulator.serial_adapter.term.write( '\r' )
- }
- emulator.create_file( "console", this.toUint8Array( str ) )
- })
-
- let p = files.map( (f) => fetch(f) )
- Promise.all(p)
- .then( (files) => {
- files.map( (f) => {
- f.arrayBuffer().then( (buf) => {
- emulator.create_file( f.url.replace(/.*mnt\//,''), new Uint8Array(buf) )
- })
- })
- })
-
- //emulator.serial0_send('chmod +x /mnt/js')
- //emulator.serial0_send()
- let line = ''
- let ready = false
- emulator.add_listener("serial0-output-byte", async (byte) => {
- var chr = String.fromCharCode(byte);
- if(chr < " " && chr !== "\n" && chr !== "\t" || chr > "~")
- {
- return;
- }
-
- if(chr === "\n")
- {
- var new_line = line;
- line = "";
- }
- else if(chr >= " " && chr <= "~")
- {
- line += chr;
- }
-
- //if(!ran_command && line.endsWith("~% "))
- //{
- // ran_command = true;
- // emulator.serial0_send("chmod +x /mnt/test-i386\n");
- // emulator.serial0_send("/mnt/test-i386 > /mnt/result\n");
- // emulator.serial0_send("echo test fini''shed\n");
- //}
- //console.dir({line,new_line})
- if( !ready && line.match(/^(\/ #|~%)/) ){
- instance.dom.classList.remove('blink')
- // set environment
- let env = ['export BROWSER=1']
- for ( let i in document.location ){
- if( typeof document.location[i] == 'string' )
- env.push( 'export '+String(i).toUpperCase()+'="'+document.location[i]+'"')
- }
- await emulator.create_file("profile.browser", this.toUint8Array( env.join('\n') ) )
- let boot = `source /mnt/profile ; js "$(cat /mnt/profile.js)"`
- // exec hash as extra boot cmd
- if( document.location.hash.length > 1 ){
- boot += ` ; cmd='${decodeURI(document.location.hash.substr(1))}' && $cmd`
- }
- console.dir(boot)
- emulator.serial0_send(boot+"\n")
- instance.winbox.maximize()
- emulator.serial_adapter.term.focus()
- ready = true
- //emulator.serial0_send("root\n")
- //emulator.serial0_send("mv /mnt/js . && chmod +x js\n")
- }
- });
-
- // unix to js device
- emulator.add_listener("9p-write-end", async (opts) => {
- const decoder = new TextDecoder('utf-8');
-
- if ( opts[0] == 'js' ){
- const buf = await emulator.read_file("dev/browser/js")
- const script = decoder.decode(buf)
- try{
- let res = (new Function(`${script}`))()
- if( res && typeof res != 'string' ) res = JSON.stringify(res,null,2)
- }catch(e){
- console.error(e)
- }
- }
- })
-
- // enable/disable logging file (echo 1 > mnt/console.tty)
- emulator.add_listener("9p-write-end", async (opts) => {
- const decoder = new TextDecoder('utf-8');
- if ( opts[0] == 'console.tty' ){
- const buf = await emulator.read_file("console.tty")
- const val = decoder.decode(buf)
- emulator.log_to_tty = ( String(val).trim() == '1')
- }
- })
-
- });
-
- },
-
- events:{
-
- // combined AFRAME+DOM reactive events
- click: function(e){ }, //
- keydown: function(e){ },
-
- // reactive events for this.data updates
- myvalue: function(e){ this.el.dom.querySelector('b').innerText = this.data.myvalue },
-
- ready: function( ){
- this.el.dom.style.display = 'none'
+ AFRAME.registerComponent('isoterminal', {
+ schema: {
+ iso: { type:"string", "default":"com/isoterminal/images/buildroot-bzimage.bin" },
+ cols: { type: 'number',"default": 120 },
+ rows: { type: 'number',"default": 30 },
+ padding:{ type: 'number',"default": 18 },
+ transparent: { type:'boolean', "default":false } // need good gpu
},
- launcher: async function(){
- if( this.instance ){
- const el = document.querySelector('.isoterminal')
- return console.warn('TODO: allow multiple terminals (see v86 examples)')
- }
+ init: function(){
+ this.el.object3D.visible = false
+ },
- let s = await AFRAME.utils.require(this.requires)
- // instance this component
- const instance = this.instance = this.el.cloneNode(false)
- this.el.sceneEl.appendChild( instance )
+ requires:{
+ 'window': "com/window.js",
+ xtermjs: "https://unpkg.com/@xterm/xterm@5.5.0/lib/xterm.js",
+ xtermcss: "https://unpkg.com/@xterm/xterm@5.5.0/css/xterm.css",
+ v86: "com/isoterminal/libv86.js",
+ // isoterminal features
+ core: "com/isoterminal/core.js",
+ utils_9p: "com/isoterminal/feat/9pfs_utils.js",
+ boot: "com/isoterminal/feat/boot.js",
+ xterm: "com/isoterminal/feat/xterm.js",
+ jsconsole: "com/isoterminal/feat/jsconsole.js",
+ javascript: "com/isoterminal/feat/javascript.js",
+ },
- instance.addEventListener('DOMready', () => {
- this.runISO(instance.dom, instance)
- instance.setAttribute("window", `title: ${this.data.iso}; uid: ${instance.uid}; attach: #overlay; dom: #${instance.dom.id}`)
- })
+ dom: {
+ scale: 0.7,
+ events: ['click','keydown'],
+ html: (me) => ``,
- instance.addEventListener('window.oncreate', (e) => {
- instance.dom.classList.add('blink')
- })
+ css: (me) => `.isoterminal{
+ padding: ${me.com.data.padding}px;
+ width:100%;
+ height:100%;
+ }
+ .isoterminal *{
+ white-space: pre;
+ font-size: 14px;
+ font-family: Liberation Mono,DejaVu Sans Mono,Courier New,monospace;
+ font-weight:700;
+ display:inline;
+ overflow: hidden;
+ }
- instance.addEventListener('window.onclose', (e) => {
- if( !confirm('do you want to kill this virtual machine and all its processes?') ) e.halt = true
- })
+ .isoterminal style{ display:none }
- const resize = (w,h) => {
- if( instance.dom.emulator && instance.dom.emulator.serial_adapter ){
- setTimeout( () => {
- this.autoResize(instance.dom.emulator.serial_adapter.term,instance,-5)
- },800) // wait for resize anim
+ .wb-body:has(> .isoterminal){
+ background: #000c;
+ overflow:hidden;
+ }
+
+ .isoterminal div{ display:block; }
+ .isoterminal span{ display: inline }
+
+ @keyframes fade {
+ from { opacity: 1.0; }
+ 50% { opacity: 0.5; }
+ to { opacity: 1.0; }
+ }
+
+ @-webkit-keyframes fade {
+ from { opacity: 1.0; }
+ 50% { opacity: 0.5; }
+ to { opacity: 1.0; }
+ }
+
+ .blink {
+ animation:fade 1000ms infinite;
+ -webkit-animation:fade 1000ms infinite;
+ }
+ `
+ },
+
+
+ events:{
+
+ // combined AFRAME+DOM reactive events
+ click: function(e){ }, //
+ keydown: function(e){ },
+
+ // reactive events for this.data updates
+ myvalue: function(e){ this.el.dom.querySelector('b').innerText = this.data.myvalue },
+
+ ready: function( ){
+ this.el.dom.style.display = 'none'
+ },
+
+ launcher: async function(){
+ if( this.instance ){
+ const el = document.querySelector('.isoterminal')
+ return console.warn('TODO: allow multiple terminals (see v86 examples)')
}
+
+ let s = await AFRAME.utils.require(this.requires)
+
+ // instance this component
+ const instance = this.instance = this.el.cloneNode(false)
+ this.el.sceneEl.appendChild( instance )
+
+ // init isoterminal
+ this.isoterminal = new ISOTerminal()
+
+ instance.addEventListener('DOMready', () => {
+ instance.setAttribute("window", `title: ${this.data.iso}; uid: ${instance.uid}; attach: #overlay; dom: #${instance.dom.id}`)
+ })
+
+ instance.addEventListener('window.oncreate', (e) => {
+ instance.dom.classList.add('blink')
+ // run iso
+ let opts = {dom:instance.dom}
+ for( let i in this.data ) opts[i] = this.data[i]
+ this.isoterminal.runISO(opts)
+ })
+
+ this.isoterminal.addEventListener('ready', function(e){
+ instance.dom.classList.remove('blink')
+ instance.winbox.maximize()
+ })
+
+ this.isoterminal.addEventListener('status', function(e){
+ let msg = e.detail
+ const w = instance.winbox
+ if(!w) return
+ w.titleBak = w.titleBak || w.title
+ instance.winbox.setTitle( `${w.titleBak} [${msg}]` )
+ })
+
+ instance.addEventListener('window.onclose', (e) => {
+ if( !confirm('do you want to kill this virtual machine and all its processes?') ) e.halt = true
+ })
+
+ const resize = (w,h) => {
+ if( this.isoterminal.emulator && this.isoterminal.emulator.serial_adapter ){
+ setTimeout( () => {
+ this.isoterminal.xtermAutoResize(this.isoterminal.emulator.serial_adapter.term,instance,-5)
+ },800) // wait for resize anim
+ }
+ }
+ instance.addEventListener('window.onresize', resize )
+ instance.addEventListener('window.onmaximize', resize )
+
+ instance.setAttribute("dom", "")
+ instance.setAttribute("xd", "") // allows flipping between DOM/WebGL when toggling XD-button
+ instance.setAttribute("visible", AFRAME.utils.XD() == '3D' ? 'true' : 'false' )
+ instance.setAttribute("position", AFRAME.utils.XD.getPositionInFrontOfCamera(0.5) )
+
+ const focus = () => document.querySelector('canvas.a-canvas').focus()
+ instance.addEventListener('obbcollisionstarted', focus )
+ this.el.sceneEl.addEventListener('enter-vr', focus )
+
+ instance.object3D.quaternion.copy( AFRAME.scenes[0].camera.quaternion ) // face towards camera
}
- instance.addEventListener('window.onresize', resize )
- instance.addEventListener('window.onmaximize', resize )
- instance.setAttribute("dom", "")
- instance.setAttribute("xd", "") // allows flipping between DOM/WebGL when toggling XD-button
- instance.setAttribute("visible", AFRAME.utils.XD() == '3D' ? 'true' : 'false' )
- instance.setAttribute("position", AFRAME.utils.XD.getPositionInFrontOfCamera(0.5) )
+ },
- const focus = () => document.querySelector('canvas.a-canvas').focus()
- instance.addEventListener('obbcollisionstarted', focus )
- this.el.sceneEl.addEventListener('enter-vr', focus )
+ manifest: { // HTML5 manifest to identify app to xrsh
+ "iso": "linux-x64-4.15.iso",
+ "short_name": "ISOTerm",
+ "name": "terminal",
+ "icons": [
+ {
+ "src": "https://css.gg/terminal.svg",
+ "src": "data:image/svg+xml;base64,PHN2ZwogIHdpZHRoPSIyNCIKICBoZWlnaHQ9IjI0IgogIHZpZXdCb3g9IjAgMCAyNCAyNCIKICBmaWxsPSJub25lIgogIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKPgogIDxwYXRoCiAgICBkPSJNNS4wMzMzIDE0LjgyODRMNi40NDc1MSAxNi4yNDI2TDEwLjY5MDIgMTJMNi40NDc1MSA3Ljc1NzMzTDUuMDMzMyA5LjE3MTU1TDcuODYxNzIgMTJMNS4wMzMzIDE0LjgyODRaIgogICAgZmlsbD0iY3VycmVudENvbG9yIgogIC8+CiAgPHBhdGggZD0iTTE1IDE0SDExVjE2SDE1VjE0WiIgZmlsbD0iY3VycmVudENvbG9yIiAvPgogIDxwYXRoCiAgICBmaWxsLXJ1bGU9ImV2ZW5vZGQiCiAgICBjbGlwLXJ1bGU9ImV2ZW5vZGQiCiAgICBkPSJNMiAyQzAuODk1NDMxIDIgMCAyLjg5NTQzIDAgNFYyMEMwIDIxLjEwNDYgMC44OTU0MyAyMiAyIDIySDIyQzIzLjEwNDYgMjIgMjQgMjEuMTA0NiAyNCAyMFY0QzI0IDIuODk1NDMgMjMuMTA0NiAyIDIyIDJIMlpNMjIgNEgyTDIgMjBIMjJWNFoiCiAgICBmaWxsPSJjdXJyZW50Q29sb3IiCiAgLz4KPC9zdmc+",
+ "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 [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": "Hello world information",
+ "screenshots": [
+ {
+ "src": "/images/screenshot1.png",
+ "type": "image/png",
+ "sizes": "540x720",
+ "form_factor": "narrow"
+ }
+ ],
+ "help":`
+ Helloworld application
- instance.object3D.quaternion.copy( AFRAME.scenes[0].camera.quaternion ) // face towards camera
+ 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.
+ `
}
- },
+ });
- autoResize: function(term,instance,rowoffset){
- if( !term.element ) return
+ // reflect HTML changes to /dev/browser/html
+ AFRAME.registerSystem('isoterminal',{
- const defaultScrollWidth = 24;
- const MINIMUM_COLS = 2;
- const MINIMUM_ROWS = 2;
+ init: function(){
+ this.components = []
+ // observe HTML changes in
+ observer = new MutationObserver( (a,b) => {
- const dims = term._core._renderService.dimensions;
- const scrollbarWidth = (term.options.scrollback === 0
- ? 0
- : (term.options.overviewRuler?.width || defaultScrollWidth ));
+ console.log("change")
+ })
+ observer.observe( this.sceneEl, {characterData: false, childList: true, attributes: false});
+ }
- const parentElementStyle = window.getComputedStyle(instance.dom);
- const parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height'));
- const parentElementWidth = Math.max(0, parseInt(parentElementStyle.getPropertyValue('width')));
- const elementStyle = window.getComputedStyle(term.element);
- const elementPadding = {
- top: parseInt(elementStyle.getPropertyValue('padding-top')),
- bottom: parseInt(elementStyle.getPropertyValue('padding-bottom')),
- right: parseInt(elementStyle.getPropertyValue('padding-right')),
- left: parseInt(elementStyle.getPropertyValue('padding-left'))
- };
- const elementPaddingVer = elementPadding.top + elementPadding.bottom;
- const elementPaddingHor = elementPadding.right + elementPadding.left;
- const availableHeight = parentElementHeight - elementPaddingVer;
- const availableWidth = parentElementWidth - elementPaddingHor - scrollbarWidth;
- const geometry = {
- cols: Math.max(MINIMUM_COLS, Math.floor(availableWidth / dims.css.cell.width)),
- rows: Math.max(MINIMUM_ROWS, Math.floor(availableHeight / dims.css.cell.height))
- };
- term.resize(geometry.cols, geometry.rows + (rowoffset||0) );
- },
+ })
+}
- manifest: { // HTML5 manifest to identify app to xrsh
- "iso": "linux-x64-4.15.iso",
- "short_name": "ISOTerm",
- "name": "terminal",
- "icons": [
- {
- "src": "https://css.gg/terminal.svg",
- "src": "data:image/svg+xml;base64,PHN2ZwogIHdpZHRoPSIyNCIKICBoZWlnaHQ9IjI0IgogIHZpZXdCb3g9IjAgMCAyNCAyNCIKICBmaWxsPSJub25lIgogIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKPgogIDxwYXRoCiAgICBkPSJNNS4wMzMzIDE0LjgyODRMNi40NDc1MSAxNi4yNDI2TDEwLjY5MDIgMTJMNi40NDc1MSA3Ljc1NzMzTDUuMDMzMyA5LjE3MTU1TDcuODYxNzIgMTJMNS4wMzMzIDE0LjgyODRaIgogICAgZmlsbD0iY3VycmVudENvbG9yIgogIC8+CiAgPHBhdGggZD0iTTE1IDE0SDExVjE2SDE1VjE0WiIgZmlsbD0iY3VycmVudENvbG9yIiAvPgogIDxwYXRoCiAgICBmaWxsLXJ1bGU9ImV2ZW5vZGQiCiAgICBjbGlwLXJ1bGU9ImV2ZW5vZGQiCiAgICBkPSJNMiAyQzAuODk1NDMxIDIgMCAyLjg5NTQzIDAgNFYyMEMwIDIxLjEwNDYgMC44OTU0MyAyMiAyIDIySDIyQzIzLjEwNDYgMjIgMjQgMjEuMTA0NiAyNCAyMFY0QzI0IDIuODk1NDMgMjMuMTA0NiAyIDIyIDJIMlpNMjIgNEgyTDIgMjBIMjJWNFoiCiAgICBmaWxsPSJjdXJyZW50Q29sb3IiCiAgLz4KPC9zdmc+",
- "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 [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": "Hello world information",
- "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.
- `
- }
-
-});
diff --git a/com/isoterminal/core.js b/com/isoterminal/core.js
new file mode 100644
index 0000000..e7730e1
--- /dev/null
+++ b/com/isoterminal/core.js
@@ -0,0 +1,160 @@
+ISOTerminal.prototype.toUint8Array = function(str) {
+ str = String(str) || String("")
+ // Create a new Uint8Array with the same length as the input string
+ const uint8Array = new Uint8Array(str.length);
+
+ // Iterate over the string and populate the Uint8Array
+ for (let i = 0; i < str.length; i++) {
+ uint8Array[i] = str.charCodeAt(i);
+ }
+ return uint8Array;
+},
+
+ISOTerminal.prototype.runISO = function(opts){
+ this.opts = opts
+ let image = {}
+ if( opts.iso.match(/\.iso$/) ) image.cdrom = { url: opts.iso }
+ if( opts.iso.match(/\.bin$/) ) image.bzimage = { url: opts.iso }
+
+ let emulator = this.emulator = new V86({ ...image,
+ wasm_path: "com/isoterminal/v86.wasm",
+ memory_size: 32 * 1024 * 1024,
+ vga_memory_size: 2 * 1024 * 1024,
+ serial_container_xtermjs: opts.dom,
+ //screen_container: dom, //this.canvas.parentElement,
+ bios: {
+ url: "com/isoterminal/bios/seabios.bin",
+ },
+ vga_bios: {
+ url: "com/isoterminal/bios/vgabios.bin",
+ },
+ network_relay_url: "wss://relay.widgetry.org/",
+ cmdline: "rw root=host9p rootfstype=9p rootflags=trans=virtio,cache=loose modules=virtio_pci tsc=reliable init_on_free=on",
+ //bzimage_initrd_from_filesystem: true,
+ //filesystem: {
+ // baseurl: "com/isoterminal/v86/images/alpine-rootfs-flat",
+ // basefs: "com/isoterminal/v86/images/alpine-fs.json",
+ // },
+ //screen_dummy: true,
+ //disable_jit: false,
+ filesystem: {},
+ autostart: true,
+ });
+
+
+ const loading = [
+ 'loading quantum bits and bytes',
+ 'preparing quantum flux capacitors',
+ 'crunching peanuts and chakras',
+ 'preparing parallel universe',
+ 'loading quantum state fluctuations',
+ 'preparing godmode',
+ 'loading cat pawns and cuteness',
+ 'beaming up scotty',
+ 'still faster than Windows update',
+ 'loading a microlinux',
+ 'figuring out meaning of life',
+ 'Aligning your chakras now',
+ 'Breathing in good vibes',
+ 'Finding inner peace soon',
+ 'Centering your Zen energy',
+ 'Awakening third eye powers',
+ 'Tuning into the universe',
+ 'Balancing your cosmic karma',
+ 'Stretching time and space',
+ 'Recharging your soul battery',
+ 'Transcending earthly limits'
+ ]
+
+ let loadmsg = loading[ Math.floor(Math.random()*1000) % loading.length-1 ]
+ this.emit('status',loadmsg)
+
+ let motd = "\n\r"
+ motd += "[38;5;57m " + ' ____ _____________ _________ ___ ___ ' + "\n\r"
+ motd += "[38;5;93m " + ' \\ \\/ /\\______ \\/ _____// | \\ ' + "\n\r"
+ motd += "[38;5;93m " + ' \\ / | _/\\_____ \\/ ~ \\ ' + "\n\r"
+ motd += "[38;5;129m " + ' / \\ | | \\/ \\ Y / ' + "\n\r"
+ motd += "[38;5;165m " + ' /___/\\ \\ |____|_ /_______ /\\___|_ / ' + "\n\r"
+ motd += "[38;5;201m " + ' \\_/ \\/ \\/ \\/ ' + "\n\r"
+ motd += " \n\r"
+ motd += `${loadmsg}, please wait..\n\r\n\r`
+ motd += "\033[0m"
+
+ const files = [
+ "com/isoterminal/mnt/js",
+ "com/isoterminal/mnt/jsh",
+ "com/isoterminal/mnt/xrsh",
+ "com/isoterminal/mnt/profile",
+ "com/isoterminal/mnt/profile.sh",
+ "com/isoterminal/mnt/profile.xrsh",
+ "com/isoterminal/mnt/profile.js",
+ "com/isoterminal/mnt/motd",
+ "com/isoterminal/mnt/v86pipe"
+ ]
+
+ emulator.bus.register("emulator-started", async (e) => {
+ this.emit('emulator-started',e)
+ emulator.serial_adapter.term.clear()
+ emulator.serial_adapter.term.write(motd)
+
+ emulator.create_file("motd", this.toUint8Array(motd) )
+ emulator.create_file("js", this.toUint8Array(`#!/bin/sh
+ cat /mnt/motd
+ cat > /dev/null
+ `))
+
+ let p = files.map( (f) => fetch(f) )
+ Promise.all(p)
+ .then( (files) => {
+ files.map( (f) => {
+ f.arrayBuffer().then( (buf) => {
+ emulator.create_file( f.url.replace(/.*mnt\//,''), new Uint8Array(buf) )
+ })
+ })
+ })
+
+ //emulator.serial0_send('chmod +x /mnt/js')
+ //emulator.serial0_send()
+ let line = ''
+ let ready = false
+ emulator.add_listener("serial0-output-byte", async (byte) => {
+ this.emit('serial0-output-byte',byte)
+ var chr = String.fromCharCode(byte);
+ if(chr < " " && chr !== "\n" && chr !== "\t" || chr > "~")
+ {
+ return;
+ }
+
+ if(chr === "\n")
+ {
+ var new_line = line;
+ line = "";
+ }
+ else if(chr >= " " && chr <= "~")
+ {
+ line += chr;
+ }
+
+ if( !ready && line.match(/^(\/ #|~%)/) ){
+ this.emit('ready')
+ ready = true
+ //emulator.serial0_send("root\n")
+ //emulator.serial0_send("mv /mnt/js . && chmod +x js\n")
+ }
+ });
+ });
+
+}
+
+ISOTerminal.prototype.readFromPipe = function(filename,cb){
+
+ this.emulator.add_listener("9p-write-end", async (opts) => {
+ const decoder = new TextDecoder('utf-8');
+ if ( opts[0] == filename.replace(/.*\//,'') ){
+ const buf = await this.emulator.read_file("console.tty")
+ const val = decoder.decode(buf)
+ cb(val)
+ }
+ })
+
+}
diff --git a/com/isoterminal/feat/9pfs_utils.js b/com/isoterminal/feat/9pfs_utils.js
new file mode 100644
index 0000000..18ca419
--- /dev/null
+++ b/com/isoterminal/feat/9pfs_utils.js
@@ -0,0 +1,45 @@
+ISOTerminal.addEventListener('emulator-started', function(){
+ let emulator = this.emulator
+ let isoterminal = this
+
+ emulator.fs9p.update_file = async function(file,data){
+
+ const p = this.SearchPath(file);
+
+ if(p.id === -1)
+ {
+ return Promise.resolve(null);
+ }
+
+ const inode = this.GetInode(p.id);
+ const buf = typeof data == 'string' ? isoterminal.toUint8Array(data) : data
+ await this.Write(p.id,0, buf.length, buf )
+ // update inode
+ inode.size = buf.length
+ const now = Math.round(Date.now() / 1000);
+ inode.atime = inode.mtime = now;
+ return new Promise( (resolve,reject) => resolve(buf) )
+
+ }
+
+ emulator.fs9p.append_file = async function(file,data){
+
+ const p = this.SearchPath(file);
+
+ if(p.id === -1)
+ {
+ return Promise.resolve(null);
+ }
+
+ const inode = this.GetInode(p.id);
+ const buf = typeof data == 'string' ? isoterminal.toUint8Array(data) : data
+ await this.Write(p.id, inode.size, buf.length, buf )
+ // update inode
+ inode.size = inode.size + buf.length
+ const now = Math.round(Date.now() / 1000);
+ inode.atime = inode.mtime = now;
+ return new Promise( (resolve,reject) => resolve(buf) )
+
+ }
+
+})
diff --git a/com/isoterminal/feat/boot.js b/com/isoterminal/feat/boot.js
new file mode 100644
index 0000000..dd9019a
--- /dev/null
+++ b/com/isoterminal/feat/boot.js
@@ -0,0 +1,21 @@
+ISOTerminal.addEventListener('ready', function(){
+ this.boot()
+})
+
+ISOTerminal.prototype.boot = async function(){
+ // set environment
+ let env = ['export BROWSER=1']
+ for ( let i in document.location ){
+ if( typeof document.location[i] == 'string' )
+ env.push( 'export '+String(i).toUpperCase()+'="'+document.location[i]+'"')
+ }
+ await this.emulator.create_file("profile.browser", this.toUint8Array( env.join('\n') ) )
+ let boot = `source /mnt/profile ; js "$(cat /mnt/profile.js)"`
+ // exec hash as extra boot cmd
+ if( document.location.hash.length > 1 ){
+ boot += ` ; cmd='${decodeURI(document.location.hash.substr(1))}' && $cmd`
+ }
+ this.emulator.serial0_send(boot+"\n")
+ this.emulator.serial_adapter.term.focus()
+}
+
diff --git a/com/isoterminal/feat/javascript.js b/com/isoterminal/feat/javascript.js
new file mode 100644
index 0000000..edabe84
--- /dev/null
+++ b/com/isoterminal/feat/javascript.js
@@ -0,0 +1,23 @@
+ISOTerminal.addEventListener('init', function(){
+
+ this.addEventListener('emulator-started', function(e){
+
+ const emulator = this.emulator
+
+ // unix to js device
+ this.readFromPipe( '/mnt/js', async (data) => {
+ const buf = await emulator.read_file("dev/browser/js")
+ const decoder = new TextDecoder('utf-8');
+ const script = decoder.decode(buf)
+ try{
+ let res = (new Function(`${script}`))()
+ if( res && typeof res != 'string' ) res = JSON.stringify(res,null,2)
+ }catch(e){
+ console.error(e)
+ }
+ })
+
+ })
+
+})
+
diff --git a/com/isoterminal/feat/jsconsole.js b/com/isoterminal/feat/jsconsole.js
new file mode 100644
index 0000000..0545759
--- /dev/null
+++ b/com/isoterminal/feat/jsconsole.js
@@ -0,0 +1,47 @@
+ISOTerminal.prototype.redirectConsole = function(handler){
+ const log = console.log;
+ const dir = console.dir;
+ const err = console.error;
+ const warn = console.warn;
+ console.log = (...args)=>{
+ const textArg = args[0];
+ handler(textArg+'\n');
+ log.apply(log, args);
+ };
+ console.error = (...args)=>{
+ const textArg = args[0].message?args[0].message:args[0];
+ handler( textArg+'\n', '\x1b[31merror\x1b[0m');
+ err.apply(log, args);
+ };
+ console.dir = (...args)=>{
+ const textArg = args[0].message?args[0].message:args[0];
+ handler( JSON.stringify(textArg,null,2)+'\n');
+ dir.apply(log, args);
+ };
+ console.warn = (...args)=>{
+ const textArg = args[0].message?args[0].message:args[0];
+ handler(textArg+'\n','\x1b[38;5;208mwarn\x1b[0m');
+ err.apply(log, args);
+ };
+}
+
+ISOTerminal.addEventListener('emulator-started', function(){
+ let emulator = this.emulator
+
+ this.redirectConsole( (str,prefix) => {
+ if( emulator.log_to_tty ){
+ prefix = prefix ? prefix+' ' : ' '
+ str.trim().split("\n").map( (line) => {
+ emulator.serial_adapter.term.write( '\r\x1b[38;5;165m/dev/browser: \x1b[0m'+prefix+line+'\n' )
+ })
+ emulator.serial_adapter.term.write( '\r' )
+ }
+ emulator.fs9p.append_file( "console", str )
+ })
+
+ // enable/disable logging file (echo 1 > mnt/console.tty)
+ this.readFromPipe( '/mnt/console.tty', (data) => {
+ emulator.log_to_tty = ( String(data).trim() == '1')
+ })
+
+})
diff --git a/com/isoterminal/feat/xterm.js b/com/isoterminal/feat/xterm.js
new file mode 100644
index 0000000..d7a0e13
--- /dev/null
+++ b/com/isoterminal/feat/xterm.js
@@ -0,0 +1,61 @@
+ISOTerminal.addEventListener('init', function(){
+ if( typeof Terminal != 'undefined' ) this.xtermInit()
+})
+
+ISOTerminal.prototype.xtermInit = function(){
+ let isoterm = this
+ // monkeypatch Xterm (which V86 initializes) so we can add our own constructor args
+ window._Terminal = window.Terminal
+ window.Terminal = function(opts){
+ const term = new window._Terminal({ ...opts,
+ cursorBlink:true,
+ onSelectionChange: function(e){
+ debugger
+ }
+ })
+
+ term.onSelectionChange( () => {
+ document.execCommand('copy')
+ term.select(0, 0, 0)
+ isoterm.emit('status','copied to clipboard')
+ })
+ return term
+ }
+
+ this.addEventListener('emulator-started', function(){
+ this.emulator.serial_adapter.term.element.querySelector('.xterm-viewport').style.background = 'transparent'
+ })
+}
+
+ISOTerminal.prototype.xtermAutoResize = function(term,instance,rowoffset){
+ if( !term.element ) return
+
+ const defaultScrollWidth = 24;
+ const MINIMUM_COLS = 2;
+ const MINIMUM_ROWS = 2;
+
+ const dims = term._core._renderService.dimensions;
+ const scrollbarWidth = (term.options.scrollback === 0
+ ? 0
+ : (term.options.overviewRuler?.width || defaultScrollWidth ));
+
+ const parentElementStyle = window.getComputedStyle(instance.dom);
+ const parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height'));
+ const parentElementWidth = Math.max(0, parseInt(parentElementStyle.getPropertyValue('width')));
+ const elementStyle = window.getComputedStyle(term.element);
+ const elementPadding = {
+ top: parseInt(elementStyle.getPropertyValue('padding-top')),
+ bottom: parseInt(elementStyle.getPropertyValue('padding-bottom')),
+ right: parseInt(elementStyle.getPropertyValue('padding-right')),
+ left: parseInt(elementStyle.getPropertyValue('padding-left'))
+ };
+ const elementPaddingVer = elementPadding.top + elementPadding.bottom;
+ const elementPaddingHor = elementPadding.right + elementPadding.left;
+ const availableHeight = parentElementHeight - elementPaddingVer;
+ const availableWidth = parentElementWidth - elementPaddingHor - scrollbarWidth;
+ const geometry = {
+ cols: Math.max(MINIMUM_COLS, Math.floor(availableWidth / dims.css.cell.width)),
+ rows: Math.max(MINIMUM_ROWS, Math.floor(availableHeight / dims.css.cell.height))
+ };
+ term.resize(geometry.cols, geometry.rows + (rowoffset||0) );
+}
diff --git a/com/isoterminal/mnt/profile b/com/isoterminal/mnt/profile
index a2286fa..e587a0f 100644
--- a/com/isoterminal/mnt/profile
+++ b/com/isoterminal/mnt/profile
@@ -18,7 +18,7 @@ command_not_found_handle(){
echo '----'
echo 'js console: ' "type 'jsh'"
echo 'js shellfunction:' "type 'alias $1=\"jsh $1\"' to run '$1 yo' as $1('yo')"
- echo 'js logging: ' "type 'echo 0 > /dev/browser/console.tty' to disable"
+ echo 'js log to tty: ' "type 'echo 1 > /dev/browser/console.tty' to enable"
echo 'js capture log: ' "type 'tail -f /dev/browser/console'"
echo 'jsh<->sh hooks: ' "type 'chmod +x ~/hook.d/*/* && alert helloworld'"
}
@@ -27,4 +27,5 @@ resize
test $HOSTNAME = localhost || clear
cat /mnt/motd
export PATH=$PATH:/mnt
-export PS1="\n[38;5;57mx[38;5;93mr[38;5;129ms[38;5;165mh [38;5;201m# \033[0m"
+export PS1="\n\[\033[38;5;57m\]x\[\033[38;5;93m\]r\[\033[38;5;129m\]s\[\033[38;5;165m\]h \[\033[38;5;201m\]# \[\033[0m\]"
+
diff --git a/com/isoterminal/mnt/profile.xrsh b/com/isoterminal/mnt/profile.xrsh
index c69ad19..abeae4b 100644
--- a/com/isoterminal/mnt/profile.xrsh
+++ b/com/isoterminal/mnt/profile.xrsh
@@ -23,10 +23,12 @@ test -d /dev/browser || {
touch /mnt/dev/browser/html
touch /mnt/console.tty
ln -s /mnt/dev/browser /dev/browser
+ ln -s /dev/browser/html ~/index.html
# setup console goodies
ln -s /mnt/console.tty /dev/browser/console.tty # emulator.write_file() only writes to /mnt/. :(
- echo 1 > /dev/browser/console.tty # should be in /proc, but v86 gives 'no such file or dir' when creating it there
- v86pipe /mnt/console /dev/browser/console &
+ echo 0 > /dev/browser/console.tty # should be in /proc, but v86 gives 'no such file or dir' when creating it there
+ touch /mnt/console
+ ln -s /mnt/console /dev/browser/console
test -f /etc/profile && rm /etc/profile
ln -s /mnt/profile /etc/profile
@@ -34,11 +36,10 @@ test -d /dev/browser || {
setup_hook_dirs(){ # see /mnt/hook for usage
mkdir -p ~/hook.d/alert
- mkdir -p ~/hook.d/confirm
- mkdir -p ~/hook.d/prompt
- echo -e "#!/bin/sh\necho hook.d/alert/yo: yo \$*" > ~/hook.d/alert/yo
- echo -e "#!/bin/js\nalert(\"hook.d/alert/yo.js \"+args.slice(1).join(' '))" > ~/hook.d/alert/yo.js
- echo -e "#!/usr/bin/lua\nprint(\"hook.d/alert/yo.lua: yo \" .. arg[1])" > ~/hook.d/alert/yo.lua
+ echo -e "#!/bin/sh\necho hook.d/alert/yo: yo \$*" > ~/hook.d/alert/yo
+ echo -e "#!/bin/js\nalert(\"hook.d/alert/yo.js \"+args.slice(1).join(' '))" > ~/hook.d/alert/yo.js
+ echo -e "#!/usr/bin/env lua\nprint(\"hook.d/alert/yo.lua: yo \" .. arg[1])" > ~/hook.d/alert/yo.lua
+ echo -e "#!/usr/bin/awk -f\nBEGIN{\n\tprint \"hook.d/alert/yo.awk: yo \" ARGV[1]\n}" > ~/hook.d/alert/yo.awk
}
setup_network(){
@@ -52,5 +53,6 @@ test -d /dev/browser || {
setup_browser_dev
setup_hook_dirs
setup_links
+ setup_network
}
diff --git a/com/isoterminal/mnt/test.awk b/com/isoterminal/mnt/test.awk
deleted file mode 100755
index 8f8a47e..0000000
--- a/com/isoterminal/mnt/test.awk
+++ /dev/null
@@ -1,26 +0,0 @@
-#!/bin/awk -f
-
-BEGIN {
- for (i = 1; i < ARGC; i++) {
- options[i] = ARGV[i]
- }
- ARGC = 0
- selected = 1
- n = length(options)
-
- while (1) {
- printf "\r "
- for (i = 1; i <= n; i++) {
- if (i == selected)
- printf "\033[44m%s\033[0m ", options[i]
- else
- printf "%s ", options[i]
- }
- if (c == 0) {
- getline dir < "/dev/stdin"
- print dir
- if (dir == "up" && selected > 1) selected--
- if (dir == "down" && selected < n) selected++
- }
- }
-}