From b1e45d7627aadc441b00ca312432d394356b4656 Mon Sep 17 00:00:00 2001 From: Lisa Milne Date: Tue, 12 Dec 2023 16:18:20 +1000 Subject: [PATCH] tty part 1 --- clite/bios.js | 490 ++++++++++++++++++++++++++++++++++++++++++ clite/core.css | 17 +- clite/core.js | 572 +++++++++++++++++++++++++++---------------------- index.html | 2 +- 4 files changed, 808 insertions(+), 273 deletions(-) create mode 100644 clite/bios.js diff --git a/clite/bios.js b/clite/bios.js new file mode 100644 index 0000000..81b7962 --- /dev/null +++ b/clite/bios.js @@ -0,0 +1,490 @@ +(function() { +var bios = { + data:{ + version:'0.1', + id:'BIOS', + bootable:'clite/core.js', + entry:null + }, + core:{ + getHex:function(s) { + if (typeof s === 'string') + s = parseInt(s); + if (typeof s !== 'number') + return '0'; + return s.toString(16).toUpperCase(); + }, + writeStringVGA:function(x,y,v,fg) { + for (var i=0; i= 80) + break; + bios.video.text.io.write(x+i,y,v[i]); + } + if (typeof fg === 'undefined') + return; + for (var i=0; i= 80) + break; + bios.video.text.io.writeSet(1,x+i,y,fg); + } + }, + expose:function(name,fn) { + window['BIOS'+name] = function() { + try{ + fn.apply(null,arguments); + } catch(err) {} + setTimeout(function() {window['BIOS'+name] = null;},10); + } + }, + load:{ + script:function(name,callback) { + var head = document.getElementsByTagName('head')[0]; + var script = document.createElement('script'); + script.type = 'text/javascript'; + script.src = name+'?d='+(new Date).getTime(); + script.onload = callback; + head.appendChild(script); + }, + file:function(name,callback) { + if (window.location.protocol == 'file:') { // this is a dirty hack and I hate it, just let me open a file: path! + clite.term.setCustom({type:'file',callback:callback}); + clite.term.writeLine('open file: '+name); + return; + } + + fetch(name+'?d='+(new Date).getTime()) + .then(function(response) { + return response.text(); + }) + .then(function(data) { + callback(data); + }); + } + }, + }, + io:{ + read:function(c) { + switch (c) { + case 0: + return bios.data.id; + break; + case 1: + return bios.video.io; + break; + default:; + } + return null; + }, + write:function(c,d) { + switch (c) { + case 0: + bios.data.id = d; + document.title = d; + break; + case 1: + bios.video.setMode(d); + break; + case 2: + // writing to the keyboard handler without a callback flushes the buffer + if (typeof d === 'undefined') { + bios.input.keybuffer.buff = []; + return; + } + bios.input.keybuffer.rcvr = d; + break; + default:; + } + }, + }, + video:{ + mode:{ + current:0, + modes:[null], + }, + viewport:{ + width:0, + height:0 + }, + viewportResize:function() { + bios.video.viewport.width = window.innerWidth; + bios.video.viewport.height = window.innerHeight; + if (bios.video.mode.current == 0) + return; + try{ + bios.video.mode.modes[bios.video.mode.current].resize(); + }catch(err) {} + }, + init:function() { + window.addEventListener('resize',bios.video.viewportResize); + bios.video.viewportResize(); + bios.video.mode.modes[1] = bios.video.text; + bios.video.mode.modes[2] = bios.video.graphic; + bios.video.setMode(1); + }, + io:{}, + setMode:function(m) { + if (m < 0 || m >= bios.video.mode.modes.length) + return false; + if (m == bios.video.mode.current) + return true; + try{ + bios.video.mode.modes[bios.video.mode.current].exit(); + } catch(err) {} + bios.video.mode.current = m; + try{ + bios.video.mode.modes[bios.video.mode.current].init(); + } catch(err) { + bios.video.io = null; + return false; + } + return true; + } + }, + input:{ + el:null, + state:0, + init:function() { + bios.input.el = document.getElementById('input'); + bios.input.keyboard.init(); + bios.input.state = 1; + }, + keybuffer:{ + rcvr:null, + buff:[], + input:function(key) { + if (bios.input.state == 1) { + try{ + bios.input.keybuffer.rcvr(key); + return; + } catch(err) {} + } + bios.input.keybuffer.buff.push(key); + } + } + }, + boot:{ + setEntry:function(fn) { + bios.data.entry = fn; + setTimeout(bios.boot.doBoot,10); + }, + preBoot:function() { + bios.core.expose('entry',bios.boot.setEntry); + bios.core.load.script(bios.data.bootable,null); + }, + doBoot:function() { + try{ + bios.data.entry(bios); + }catch(err) { + bios.core.writeStringVGA(1,7,'Error in bootable entry point',4); + bios.core.writeStringVGA(1,8,err.message,6); + } + } + }, + init:function() { + bios.io.write(0,bios.data.id); + bios.video.init(); + bios.core.writeStringVGA(2,0,'JS Basic Input Output System'); + var v = bios.data.version.toString(); + bios.core.writeStringVGA(78-v.length,0,v); + for (var i=0; i<80; i++) { + bios.video.io.writeSet(0,i,0,15); + bios.video.io.writeSet(1,i,0,0); + } + bios.core.writeStringVGA(3,1,'Initialising...'); + bios.core.writeStringVGA(3,3,'Video Graphics Array:'); + bios.core.writeStringVGA(25,3,'Done',2); + + bios.core.writeStringVGA(3,4,'Input Devices:'); + bios.input.init(); + bios.core.writeStringVGA(25,4,'Done',2); + + bios.core.writeStringVGA(0,6,'Booting...'); + + bios.boot.preBoot(); + } +}; + +bios.video.text = { + data:{ + el:null, + isinit:false, + screen:{ + width:0, + height:0, + nw:0, + nh:0, + f:0 + } + }, + internal:{ + getElement:function() { + bios.video.text.data.el = document.getElementById('vga'); + if (typeof bios.video.text.data.el !== 'undefined' && bios.video.text.data.el != null) + return bios.video.text.data.el; + bios.video.text.data.el = document.createNode('div'); + bios.video.text.data.el.id = 'vga'; + bios.video.text.data.el.className = 'vga'; + document.body.appendChild(bios.video.text.data.el); + return bios.video.data.text.el; + }, + getPix:function(x,y) { + return document.getElementById('vga-'+y+'-'+x); + }, + dataToClass:function(d) { + var c = 'vga-b'+d.bg+' vga-f'+d.fg; + if (d.bk) + c += ' vga-blink'; + return c; + } + }, + io:{ + write:function(x,y,v) { + if (x < 0 || x > 79 || y < 0 || y > 24) + return; + var c = bios.video.text.internal.getPix(x,y); + c.textContent = v; + }, + setBG:function(x,y,id) { + if (id < 0 || id > 15) + return; + var c = bios.video.text.internal.getPix(x,y); + c.data.bg = bios.core.getHex(id); + c.className = bios.video.text.internal.dataToClass(c.data); + }, + setFG:function(x,y,id) { + if (id < 0 || id > 15) + return; + var c = bios.video.text.internal.getPix(x,y); + c.data.fg = bios.core.getHex(id); + c.className = bios.video.text.internal.dataToClass(c.data); + }, + setBlink:function(x,y,id) { + if (typeof id !== 'boolean') + return; + var c = bios.video.text.internal.getPix(x,y); + c.data.bk = id; + c.className = bios.video.text.internal.dataToClass(c.data); + }, + writeSet:function(name,x,y,id) { + if (x < 0 || x > 79 || y < 0 || y > 24) + return; + switch(name) { + case 0: + bios.video.text.io.setBG(x,y,id); + break; + case 1: + bios.video.text.io.setFG(x,y,id); + break; + case 2: + bios.video.text.io.setBlink(x,y,id); + break; + default:; + } + } + }, + init:function() { + if (!bios.video.text.internal.getElement()) + return false; + + bios.video.io = bios.video.text.io; + + bios.video.text.data.el.innerHTML = ''; + var ypx; + var xpx; + var c = document.createElement('span'); + c.textContent = 'W'; + bios.video.text.data.el.appendChild(c); + ypx = c.offsetHeight; + xpx = c.offsetWidth; + bios.video.text.data.el.innerHTML = ''; + bios.video.text.data.el.style.width = (xpx*80)+'px'; + bios.video.text.data.el.style.height = (ypx*25)+'px'; + bios.video.text.data.el.style.position = 'relative'; + for (var y=0; y<25; y++) { + for (var x=0; x<80; x++) { + var c = document.createElement('div'); + c.className = 'vga-b0 vga-fF'; + c.style.position = 'absolute'; + c.style.top = (y*ypx)+'px'; + c.style.left = (x*xpx)+'px'; + c.style.width = xpx+'px'; + c.style.height = ypx+'px'; + c.data = {bg:'0',fg:'F',bk:false}; + c.id = 'vga-'+y+'-'+x; + bios.video.text.data.el.appendChild(c); + } + } + bios.video.text.resize(); + bios.video.text.data.isinit = true; + return bios.video.text.data.isinit; + }, + exit:function() { + if (!bios.video.text.internal.getElement()) + return; + bios.video.text.data.el.innerHTML = ''; + }, + resize:function() { + var w = bios.video.viewport.width; + var h = bios.video.viewport.height; + + if (h/1.42 > w) { + h = w/1.42; + }else{ + w = h*1.42; + } + + bios.video.text.data.screen.height = parseInt(h); + bios.video.text.data.screen.width = parseInt(w); + bios.video.text.data.screen.nh = parseInt(h/25); + bios.video.text.data.screen.nw = parseInt(w/80); + bios.video.text.data.screen.f = parseInt(bios.video.text.data.screen.nh/1.2); + + var e = bios.video.text.internal.getElement(); + e.style.width = bios.video.text.data.screen.width+'px'; + e.style.height = bios.video.text.data.screen.height+'px'; + for (var y=0; y<25; y++) { + for (var x=0; x<80; x++) { + var c = bios.video.text.internal.getPix(x,y); + c.style.top = (y*bios.video.text.data.screen.nh)+'px'; + c.style.left = (x*bios.video.text.data.screen.nw)+'px'; + c.style.width = bios.video.text.data.screen.nw+'px'; + c.style.height = bios.video.text.data.screen.nh+'px'; + c.style.fontSize = bios.video.text.data.screen.f+'px'; + } + } + } +}; + +bios.video.graphic = { + init:function() { + }, + exit:function() { + } +}; + +bios.input.keyboard = { + data:{ + el:null + }, + internal:{ + rawDown:function(e) { + if (e.isComposing) + return; + var k = { + down:true, + code:0, + char:0, + lchr:0 + }; + if (e.key.length == 1) { + k.char = e.key; + k.lchr = e.key.toLowerCase(); + k.code = k.lchr.charCodeAt(0); + }else{ + bios.input.keyboard.internal.getKeyData(e.key,k); + } + if (k.code == 0) + return false; + bios.input.keybuffer.input(k); + return false; + }, + rawUp:function(e) { + if (e.isComposing) + return; + var k = { + down:false, + code:0, + char:0, + lchr:0 + }; + if (e.key.length == 1) { + k.char = e.key; + k.lchr = e.key.toLowerCase(); + k.code = k.lchr.charCodeAt(0); + }else{ + bios.input.keyboard.internal.getKeyData(e.key,k); + } + if (k.code == 0) + return false; + e.target.value = ''; + bios.input.keybuffer.input(k); + return false; + }, + resetFocus:function(e) { + setTimeout(function() { + try{ + document.getElementById('kbd').focus(); + } catch(err) {} + },10); + return false; + }, + getKeyData:function(key,k) { + // arrow keys are assigned the ascii device control 1/2/3/4 code + // this will be changed by the tty + switch (key) { + case "Down": + case "ArrowDown": + k.code = 17; + break; + case "Up": + case "ArrowUp": + k.code = 18; + break; + case "Left": + case "ArrowLeft": + k.code = 19; + break; + case "Right": + case "ArrowRight": + k.code = 20; + break; + case "Enter": + k.char = String.fromCharCode(10); + k.lchr = k.char; + k.code = 10; + break; + case "Esc": + case "Escape": + k.code = 27; + break; + case "Shift": + k.code = 15; + break; + case "Tab": + k.char = String.fromCharCode(9); + k.lchr = k.char; + k.code = 9; + break; + case "Delete": + k.char = String.fromCharCode(127); + k.lchr = k.char; + k.code = 127; + break; + default: + k.code = key; + } + } + }, + getElement:function() { + bios.input.keyboard.data.el = document.getElementById('kbd'); + if (typeof bios.input.keyboard.data.el === 'undefined' || bios.input.keyboard.data.el == null) { + bios.input.keyboard.data.el = document.createElement('textarea'); + bios.input.keyboard.data.el.id = 'kbd'; + bios.input.keyboard.data.el.className = 'kbd'; + bios.input.keyboard.data.el.style.zIndex = '100'; + bios.input.keyboard.data.el.style.display = 'block'; + } + bios.input.keyboard.data.el.addEventListener('keydown',bios.input.keyboard.internal.rawDown); + bios.input.keyboard.data.el.addEventListener('keyup',bios.input.keyboard.internal.rawUp); + bios.input.keyboard.data.el.addEventListener('focusout',bios.input.keyboard.internal.resetFocus); + return bios.input.keyboard.data.el; + }, + init:function() { + var el = bios.input.keyboard.getElement(); + bios.input.el.appendChild(el); + bios.input.keyboard.internal.resetFocus(); + } +}; + +bios.core.expose('init',bios.init); +})(); diff --git a/clite/core.css b/clite/core.css index 485780d..f4c3ccb 100644 --- a/clite/core.css +++ b/clite/core.css @@ -1,18 +1,7 @@ -html,body {margin:0; padding:0; overflow:hidden; width:100%; height:100%; max-height:100%; background-color:#000000; color:#FFFFFF; font-family:monospace; font-size:14px; line-height:20px;} -body {display:flex; flex-direction:column;} - -div, header, section, article, p, form, h1 {display:block; margin:0; padding:0;} -header {padding:10px;} -header h1 {line-height:40px; font-size:30px;} -section {} -section.content {position:relative; overflow:hidden; width:100%; height:100%;} -section.content div#terminal {width:100%; min-width: 500px; max-width:1000px; min-height:100px; height:100%; font-size:14px; line-height:20px; margin:0 auto; overflow:hidden; overflow-y:auto; scrollbar-width:none;} -section.content div#termalt {width:100%; min-width: 500px; max-width:1000px; min-height:100px; height:100%; font-size:14px; line-height:20px; margin:0 auto; overflow:hidden; overflow-y:auto; scrollbar-width:none; z-index:99; background:#000000;} -section.content article {unicode-bidi: embed; white-space: pre-wrap;} -section.content form {display:flex;} -section.content form label, section.content form input, section.content form input:focus {display:block; border:none; margin:0; padding:0; font-family:monospace; font-size:14px; line-height:20px; background-color:#000000; color:#FFFFFF; outline:none;} -section.content form input, section.content form input:focus {flex-grow:100;} +html,body {margin:0; padding:0; overflow:auto; width:100%; height:100%; background-color:black; color:white; font-family:monospace; font-size:14px;} +body {display:grid; align-items:center;} +div {display:block; margin:0; padding:0;} div.vga {margin:auto; z-index:10; background-color:#000000;} div.io {position:absolute; top:0px; left:0px; width:100px; height:100px; z-index:3;} div.ioblank {position:absolute; top:0px; left:0px; width:100px; height:100px; z-index:9; background-color:black;} diff --git a/clite/core.js b/clite/core.js index fa38b8d..aa94732 100644 --- a/clite/core.js +++ b/clite/core.js @@ -2,7 +2,8 @@ var clite = { state:{ version:'0.4.0', isinit:false, - runlevel:1 + runlevel:1, + bios:null, }, includes:{ // a list of program/command files to load @@ -51,8 +52,8 @@ var clite = { }, file:function(name,callback) { if (window.location.protocol == 'file:') { // this is a dirty hack and I hate it, just let me open a file: path! - clite.term.setCustom({type:'file',callback:callback}); - clite.term.writeLine('open file: '+name); + //clite.term.setCustom({type:'file',callback:callback}); + //clite.term.writeLine('open file: '+name); return; } @@ -92,19 +93,34 @@ var clite = { l('clite/core.js',function() {setTimeout('clite.init();',500)}); },10); }, + shutdown:function() { + clite.log.write('The system is shutting down'); + setTimeout(function() { + var s = Array.from(document.getElementsByTagName('src')); + s.forEach(function(el) { + el.parentNode.removeChild(el); + }); + clite.term.ttyctrl('hide'); + delete clite; + clite = null; + },10); + }, hostname:function() { if (window.location.protocol != 'file:') return window.location.hostname; return 'localhost'; } }, - init:function() { - if (this.state.isinit) + init:function(bios) { + if (clite.state.isinit) return; - this.state.isinit = true; + clite.state.isinit = true; - this.core.execSafeAsync(function() { - clite.term.clear(); + clite.state.bios = bios; + clite.state.bios.io.write(0,'CLIte '+clite.state.version); + + clite.core.execSafeAsync(function() { + clite.console.init(); var vfsapi = null; function defaultDevices() { @@ -187,15 +203,7 @@ var clite = { case 0: clite.state.runlevel = rli; n.data.content.data = 0; - setTimeout(function() { - var s = Array.from(document.getElementsByTagName('src')); - s.forEach(function(el) { - el.parentNode.removeChild(el); - }); - clite.term.ttyctrl('hide'); - delete clite; - clite = null; - },10); + clite.core.shutdown(); break; case 1: // essentially the booting runlevel clite.state.runlevel = rli; @@ -228,8 +236,8 @@ var clite = { return false; } n.data.content = { - read:null, - write:null + read:clite.console.read, + write:clite.console.write }; n.perms = 'crw-rw-rw-'; n.data.isdev = true; @@ -243,35 +251,9 @@ var clite = { return false; } n.data.content = { - receive:{ - callback:null, - buffer:[], - input:function(str) { - if (n.data.content.receive.callback != null) { - var fn = n.data.content.receive.callback; - clite.core.execSafeAsync(function() { - fn(str); - }); - n.data.content.receive.callback = null; - return; - } - n.data.content.receive.buffer.push(str); - } - }, - read:function(cb) { - if (n.data.content.receive.buffer.length > 0) { - var str = n.data.content.receive.buffer.shift(); - cb(str); - return true; - } - n.data.content.receive.callback = cb; - clite.term.genForm(); - clite.term.data.field.focus(); - return true; - }, - write:clite.term.writeLine + read:null, + write:null }; - clite.term.data.handler = n.data.content.receive.input; n.perms = 'crw-rw-rw-'; n.data.isdev = true; n.data.istty = true; @@ -449,6 +431,8 @@ cat /etc/greeting clite.log.write('Setting up VFS'); clite.vfs.init(); vfsapi = clite.vfs.getApi(); + clite.log.write('Setting up TTY'); + clite.tty.init(vfsapi); clite.log.init(vfsapi); clite.log.write('Mounting wfs on /'); // mount core (root) filesystem using data/filesys.txt @@ -659,6 +643,7 @@ clite.proc = { gid:0, // the group id this process is running as gpid:mapCPID(cpid), // the group pid (parent process id) of this process pid:data.nextid++, // the pid (process id) of this process + ctty:0, // the id of the controlling tty for this process func:fn, // the function this process executes waits:[] // array of wait() calls pending for this process on exit }); @@ -666,6 +651,7 @@ clite.proc = { if (parent) { proc.uid = parent.uid; proc.gid = parent.gid; + proc.ctty = parent.ctty; } data.procs.push(proc); vfsapi.mkFile(0,'/proc/'+proc.pid); @@ -767,12 +753,19 @@ clite.proc = { return 0; return proc.gid; } + clite.proc.getTTY = function(pid) { + var proc = getProc(pid); + if (!proc) + return -1; + return proc.ctty; + } clite.proc.setLogin = function(pid,uid) { var proc = getProc(pid); if (!proc) return false; proc.uid = uid; proc.gid = clite.user.getGID(uid); + return true; } clite.proc.init = null; return true; @@ -787,6 +780,8 @@ clite.proc = { addGroup:function() {return false;}, getUID:function(pid) {return 0;}, getGID:function(pid) {return 0;}, + getTTY:function(pid) {return 0;}, + setLogin:function(pid,uid) {return false;} }; clite.user = { @@ -923,14 +918,7 @@ clite.user = { return 0; return udata.gid; } - function setLogin(uid) { - var udata = getUser(uid); - if (!udata) - return false; - return true; - } clite.user.genGuest = function() { - clite.term.clear(); vfsapi.mkDir(0,'/usr/home/guest'); var n = vfsapi.getNode(0,'/usr/home/guest'); if (n) { @@ -942,17 +930,15 @@ clite.user = { vfsapi.mkFile(1,'/usr/home/guest/.shrc'); n = vfsapi.getNode(1,'/usr/home/guest/.shrc'); - if (n) { - n.perms = '-rwx------'; - n.uid = 1; - n.gid = 1; - n.data.content = ` + if (!n) + return; + n.perms = '-rwx------'; + n.uid = 1; + n.gid = 1; + n.data.content = ` #!/bin/sh cat -l /usr/share/introduction `; - } - - setLogin(1); } clite.user.getEnv = function(uid) { var udata = getUser(uid); @@ -1224,7 +1210,12 @@ clite.io = { if (fd.node.data.istty) { try{ if (typeof cb === 'function') { - fd.node.data.content.read(cb); + if (fd.node.data.content.read.length == 2) { + var ctty = clite.proc.getTTY(pid); + fd.node.data.content.read(ctty,cb); + }else{ + fd.node.data.content.read(cb); + } return true; } } catch(err) {} @@ -1254,7 +1245,12 @@ clite.io = { if (fd.node.data.istty) { try{ if (typeof cb === 'function') { - fd.node.data.content.read(cb); + if (fd.node.data.content.read.length == 2) { + var ctty = clite.proc.getTTY(pid); + fd.node.data.content.read(ctty,cb); + }else{ + fd.node.data.content.read(cb); + } return true; } } catch(err) {} @@ -1308,6 +1304,12 @@ clite.io = { if (typeof fd.node.data.content != 'string') { fd.node.time.modify = clite.time.sec(); try{ + if (fd.node.data.content.write.length == 2) { + var ctty = clite.proc.getTTY(pid); + fd.node.data.content.write(ctty,cb); + }else{ + fd.node.data.content.write(cb); + } return fd.node.data.content.write(data); } catch(err) { return false; @@ -2138,7 +2140,7 @@ clite.log = { clite.log.write = function(txt) { if (clite.state.runlevel != 3) { try { - clite.term.writeLine(txt); + clite.console.write(txt+'\n'); } catch(e) {} } var n = vfsapi.getNode(0,'/var/logs'); @@ -2154,215 +2156,267 @@ clite.log = { write:function(txt) { clite.log.logs.push(txt); try { - clite.term.writeLine(txt); + clite.console.write(txt+'\n'); } catch(e) {} console.log(txt); } }; -clite.term = { +clite.tty = { data:{ - type:'text', - show:false, - isalt:false, - form:null, - field:null, - handler:null, - prompt:'', - buff:'', - alt:{ - israwOut:false, - israwIn:false, - echo:true, - lines:0, - cols:0 + active:null, + activeID:-1, + ttys:[] + }, + internal:{ + genTTY:function() {return null;}, + write:function(id,ch) { + var cc = ch.charCodeAt(0); + var updateLine = false; + var updateAll = false; + var updateX = clite.tty.data.ttys[id].x; + var updateY = clite.tty.data.ttys[id].y; + + switch (cc) { + case 10: //LF + clite.tty.data.ttys[id].y++; + break; + case 13: //CR + clite.tty.data.ttys[id].x = 0; + updateLine = true; + break; + default: + clite.tty.data.ttys[id].data[updateY][updateX].ch = ch; + clite.tty.data.ttys[id].x++; + } + + if (clite.tty.data.ttys[id].x > 79) { + clite.tty.data.ttys[id].y++; + clite.tty.data.ttys[id].x = 0; + } + // theoretically this should be fine, in practice it may not + if (clite.tty.data.ttys[id].y > 24) { + var l = clite.tty.data.ttys[id].data.shift() + for (var i=0; i<80; i++) { + l[i].ch = ' '; + l[i].fg = clite.tty.data.ttys[id].fg; + l[i].bg = clite.tty.data.ttys[id].bg; + } + clite.tty.data.ttys[id].y = 24; + clite.tty.data.ttys[id].data.push(l); + updateAll = true; + } + + if (clite.tty.data.activeID != id) + return; + + if (updateAll) { + clite.console.drawBuff(clite.tty.data.ttys[id].data); + }else if (updateLine) { + clite.console.drawLine(updateY,clite.tty.data.ttys[id].data[updateY]); + }else{ + clite.console.drawAt(updateX,updateY,clite.tty.data.ttys[id].data[updateY][updateX]); + } } }, - events:{ - keydown:function(e) { - if (e.key == 'Tab') { - clite.term.events.refocus(); - return false; - } - return true; - }, - keyup:function(e) { - if (clite.term.data.isalt && clite.term.data.alt.israwIn) { - if (clite.term.data.handler != null) { - if (e.key == 'Tab') { - clite.term.data.handler('\t'); - }else{ - clite.term.data.handler(e.key); - } - } - if (!clite.term.data.alt.echo) { - e.target.value = ''; - clite.term.data.buff = ''; - return false; - } - }else if (e.key.length > 1 && e.key != 'Spacebar' && clite.term.data.handler != null) { - if (e.key == 'Enter') { - clite.term.data.buff = ''; - clite.term.data.handler(e.target.value); - return false; - }else{ - clite.term.data.handler('\1'+e.key); + init:function(vfsapi) { + clite.tty.internal.genTTY = function() { + var l = []; + for (var y=0; y<25; y++) { + l[y] = []; + for (var x=0; x<80; x++) { + l[y].push({bg:0,fg:15,bk:false,ch:' '}); } } - clite.term.data.buff = e.target.value; - return false; - }, - refocus:function(e) { - clite.core.execSafeAsync(function() { - try{ - clite.term.data.field.focus() - } catch(err) { - clite.term.genForm(); - } - }); + var id = clite.tty.data.ttys.length; + var t = {id:id,x:0,y:0,fg:15,bg:0,data:l}; + clite.tty.data.ttys.push(t); + + + vfsapi.mkFile(0,'/dev/tty'+id); + let n = vfsapi.getNode(0,'/dev/tty'+id); + if (!n) { + clite.log.write('tty creation failure (/dev/tty'+id+')'); + return null; + } + n.data.content = { + read:null, + write:null + }; + n.perms = 'crw-rw-rw-'; + n.data.isdev = true; + n.data.istty = true; + + clite.tty.data.ttys.push(t); + + return t; } + clite.tty.internal.genTTY(); + }, + input:function(key) { + }, + read:function(id,cb) { + }, + write:function(id,str) { + if (id<0 || id>=clite.tty.data.ttys.length) + return; + + for (var i=0; i 126) { + switch (cc) { + case 10: // NL/LF to CR+LF + clite.tty.internal.write(id,String.fromCharCode(13)); + clite.tty.internal.write(id,String.fromCharCode(10)); + break; + default: + clite.tty.internal.write(id,'^'); + } + }else{ + clite.tty.internal.write(id,ch); + } + } + } +}; + +clite.console = { + data:{ + vgaio:null, + state:0, + size:[80,25], + cursor:[0,0,0,0] + }, + init:function() { + // set graphics to text mode, and get the VGA api + clite.state.bios.io.write(1,1); + clite.console.data.vgaio = clite.state.bios.io.read(1); + // set a keyboard handler, then flush the buffer + clite.state.bios.io.write(2,clite.tty.input); + clite.state.bios.io.write(2); + clite.console.data.state = 1; + clite.console.clear(); }, clear:function() { - if (clite.term.data.isalt) { - document.getElementById('termalt').innerHTML = ''; - return; - } - document.getElementById('terminal').innerHTML = ''; - }, - writeLine:function(txt) { - if (clite.term.data.isalt) { - var t = document.getElementById('termalt'); - if (!t) - t = clite.term.genAlt(); - if (clite.term.data.alt.israwOut) { - t.innerHTML += txt; - if (clite.term.data.show) - clite.term.genForm(); - return; - } - var a = document.createElement('article'); - a.innerHTML = clite.lib.htmlEncode(txt); - t.appendChild(a); - if (clite.term.data.show) - clite.term.genForm(); - return; - } - var a = document.createElement('article'); - a.innerHTML = clite.lib.htmlEncode(txt); - var t = document.getElementById('terminal'); - if (!t) - return; - t.appendChild(a); - if (clite.term.data.show) - clite.term.genForm(); - t.scrollTop = t.scrollHeight; - }, - genAlt:function() { - var t = document.getElementById('termalt'); - if (!t) { - t = document.createElement('div'); - t.id = 'termalt'; - } - - var term = document.getElementById('terminal'); - var p = term.parentNode; - - t.style.width = term.offsetWidth+'px'; - if (p.offsetHeight > term.offsetHeight) { - t.style.minHeight = p.offsetHeight+'px'; - }else{ - t.style.minHeight = term.offsetHeight+'px'; - } - t.style.position = 'absolute'; - t.style.top = term.offsetTop+'px'; - t.style.left = term.offsetLeft+'px'; - p.appendChild(t); - - // some calculations to help with putting content in - var s = document.createElement('span'); - s.innerText = 'W'; - t.appendChild(s); - // 20 should not be hardcoded - clite.term.data.alt.lines = parseInt((t.offsetHeight/20)-1); - // TODO: shouldn't need the 0.8 thing, but urgh - clite.term.data.alt.cols = parseInt((t.offsetWidth/s.offsetWidth)*0.8); - t.removeChild(s); - - clite.term.data.buff = ''; - - return t; - }, - genForm:function() { - var f = document.getElementById('form'); - if (!clite.term.data.show) { - if (f) - f.parentNode.removeChild(f); - return; - } - if (!f) - f = document.createElement('form'); - f.id = 'form'; - f.innerHTML = ''; - if (!clite.term.data.isalt) { - var l = document.createElement('label'); - l.innerHTML = clite.lib.htmlEncode(clite.term.data.prompt); - f.appendChild(l); - } - var i = document.createElement('input'); - i.type = clite.term.getType(); - i.value = clite.term.data.buff; - f.appendChild(i); - clite.term.data.form = f; - clite.term.data.field = i; - if (clite.term.data.isalt) { - var t = document.getElementById('termalt'); - if (!t) - t = clite.term.genAlt(); - t.appendChild(f); - }else{ - var t = document.getElementById('terminal'); - if (!t) - return; - t.appendChild(f); - } - i.onkeydown = clite.term.events.keydown; - i.onkeyup = clite.term.events.keyup; - i.onfocusout = clite.term.events.refocus; - i.onblur = clite.term.events.refocus; - if (i.type == 'file') { // this is only needed due to dirty hacks to read in a file: path - i.onchange = function(e) { - var reader = new FileReader(); - reader.onload = function() { - var cb = clite.term.data.type.callback; - clite.term.setPass(false); - clite.term.writeLine('loaded'); - cb(reader.result); - }; - reader.onerror = function() { - var cb = clite.term.data.type.callback; - clite.term.setPass(false); - clite.term.writeLine('load failed'); - cb(null); - }; - reader.readAsText(e.target.files[0]); + if (clite.console.data.state == 1) { + for (var y=0; y<25; y++) { + for (var x=0; x<80; x++) { + clite.console.data.vgaio.write(x,y,' '); + clite.console.data.vgaio.writeSet(0,x,y,0); + clite.console.data.vgaio.writeSet(1,x,y,15); + clite.console.data.vgaio.writeSet(2,x,y,false); + } } } - f.onsubmit = function() {return false;}; - clite.term.events.refocus(); }, - setPass:function(is) { - clite.term.data.type = (!!is) ? 'password' : 'text'; + drawBuff:function(buff) { + if (clite.console.data.state == 1) { + for (var y=0; y<25; y++) { + for (var x=0; x<80; x++) { + unix.vga.io.write(x,y,buff[y][x].ch); + unix.vga.io.writeSet(0,x,y,buff[y][x].bg); + unix.vga.io.writeSet(1,x,y,buff[y][x].fg); + unix.vga.io.writeSet(2,x,y,buff[y][x].bk); + } + } + } }, - setCustom:function(type) { - clite.term.data.type = type; + drawLine:function(y,buff) { + if (clite.console.data.state == 1) { + for (var x=0; x<80; x++) { + unix.vga.io.write(x,y,buff[x].ch); + unix.vga.io.writeSet(0,x,y,buff[x].bg); + unix.vga.io.writeSet(1,x,y,buff[x].fg); + unix.vga.io.writeSet(2,x,y,buff[x].bk); + } + } }, - getType:function() { - if (typeof clite.term.data.type == 'string') - return clite.term.data.type; - if (typeof clite.term.data.type == 'object' && typeof clite.term.data.type.type == 'string') - return clite.term.data.type.type; - return 'text'; + drawAt:function(x,y,data) { + if (clite.console.data.state == 1) { + if (x<0 || x>79 || y<0 || y>24) + return; + clite.console.data.vgaio.write(x,y,data.ch); + clite.console.data.vgaio.writeSet(0,x,y,data.bg); + clite.console.data.vgaio.writeSet(1,x,y,data.fg); + clite.console.data.vgaio.writeSet(2,x,y,data.bk); + } }, + moveCursor:function() { + if ( + clite.console.data.cursor[0] == clite.console.data.cursor[2] + && clite.console.data.cursor[1] == clite.console.data.cursor[3] + ) + return; + clite.console.data.vgaio.writeSet(2,clite.console.data.cursor[0],clite.console.data.cursor[1],false); + clite.console.data.cursor[0] = clite.console.data.cursor[2]; + clite.console.data.cursor[1] = clite.console.data.cursor[3]; + clite.console.data.vgaio.writeSet(2,clite.console.data.cursor[0],clite.console.data.cursor[1],true); + }, + setCursor:function(x,y) { + clite.console.data.cursor[2] = x; + clite.console.data.cursor[3] = y; + clite.console.moveCursor(); + }, + read:function() { + }, + write:function(str) { + var d = { + ch:'', + bg:0, + fg:15, + bk:false + }; + clite.console.moveCursor(); + var x = clite.console.data.cursor[0]; + var y = clite.console.data.cursor[1]; + for (var i=0; i= 80) { + x = 0; + y++; + } + d.ch = str[i]; + clite.console.drawAt(x,y,d); + x++; + } + clite.console.setCursor(x,y); + } +} + +clite.term = { + //events:{ + //keyup:function(e) { + //if (clite.term.data.isalt && clite.term.data.alt.israwIn) { + //if (clite.term.data.handler != null) { + //if (e.key == 'Tab') { + //clite.term.data.handler('\t'); + //}else{ + //clite.term.data.handler(e.key); + //} + //} + //if (!clite.term.data.alt.echo) { + //e.target.value = ''; + //clite.term.data.buff = ''; + //return false; + //} + //}else if (e.key.length > 1 && e.key != 'Spacebar' && clite.term.data.handler != null) { + //if (e.key == 'Enter') { + //clite.term.data.buff = ''; + //clite.term.data.handler(e.target.value); + //return false; + //}else{ + //clite.term.data.handler('\1'+e.key); + //} + //} + //clite.term.data.buff = e.target.value; + //return false; + //}, + //}, ttyctrl:function(fn,v) { switch (fn) { case 'iget': // returns the current input string - bypassing tty read @@ -2398,7 +2452,7 @@ clite.term = { break; case 'show': // turns on input clite.term.data.show = true; - clite.term.genForm(); + //clite.term.genForm(); break; case 'alt': // switch to the alternate frame buffer if (typeof v === 'boolean') { @@ -2409,9 +2463,9 @@ clite.term = { if (t) t.parentNode.removeChild(t); }else{ - clite.term.genAlt(); + //clite.term.genAlt(); } - clite.term.genForm(); + //clite.term.genForm(); return clite.term.data.isalt; break; case 'rawout': @@ -2586,3 +2640,5 @@ clite.lib = { clite.proc.exit(pid); } } + +window.BIOSentry(clite.init); diff --git a/index.html b/index.html index 8d26cba..7fdb813 100644 --- a/index.html +++ b/index.html @@ -12,6 +12,6 @@
- +