tty part 1

This commit is contained in:
Lisa Milne 2023-12-12 16:18:20 +10:00
parent b72764963d
commit b1e45d7627
4 changed files with 808 additions and 273 deletions

490
clite/bios.js Normal file
View file

@ -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<v.length; i++) {
if (x+i >= 80)
break;
bios.video.text.io.write(x+i,y,v[i]);
}
if (typeof fg === 'undefined')
return;
for (var i=0; i<v.length; i++) {
if (x+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);
})();

View file

@ -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;}

View file

@ -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<str.length; i++) {
var ch = str[i];
var cc = ch.charCodeAt(0);
// TODO: escape/control sequences
if (cc < 32 || cc > 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<str.length; i++) {
if (str[i] == '\n') {
x = 0;
y++;
continue;
}
if (x >= 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);

View file

@ -12,6 +12,6 @@
<div class="input" id="input"></div>
<div class="output" id="output"></div>
</div>
<script type="text/javascript">bios.power.off();</script>
<script type="text/javascript">window.BIOSinit();</script>
</body>
</html>