CLIte/clite/bios.js
2025-08-30 11:48:57 +10:00

848 lines
21 KiB
JavaScript

(function() {
var bios = {
data:{
version:'0.1.4',
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);
},
localfile:function(name,callback) {
bios.io.write(3,{file:name,fn:callback});
},
file:function(name,callback) {
if (window.location.protocol == 'file:') {
bios.core.load.localfile(name,callback);
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;
case 2:
return bios.storage.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;
case 3:
bios.input.upload.data.callback = d.fn;
bios.input.upload.data.file = d.file;
bios.input.setState(2);
break;
case 4:
bios.boot.shutdown(!d);
if (d == true)
setTimeout(bios.boot.reboot,10);
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;
}
},
storage:{
data:{
files:[]
},
internal:{
flush:function() {
var list = bios.storage.data.files.join(':');
window.localStorage.setItem('files',list);
}
},
io:{
addFile:function(fn,data) {
if (bios.storage.data.files.indexOf(fn) < 0)
bios.storage.data.files.push(fn);
window.localStorage.setItem(fn,data);
bios.storage.internal.flush();
},
delFile:function(fn) {
var ind = bios.storage.data.files.indexOf(fn)
if (ind > -1)
bios.storage.data.files.splice(ind,1);
window.localStorage.removeItem(fn);
bios.storage.internal.flush();
},
getList:function() {
return bios.storage.data.files;
},
getFile:function(fn) {
var ind = bios.storage.data.files.indexOf(fn)
if (ind < 0)
return null;
return window.localStorage.getItem(fn).toString();
},
clear:function() {
window.localStorage.clear();
}
},
init:function() {
let f = window.localStorage.getItem('files');
if (f)
bios.storage.data.files = f.split(':');
}
},
input:{
el:null,
state:0,
setState:function(v) { // 1 for normal keyboard input, 2 for file access
if (bios.input.state == v)
return;
switch (bios.input.state) {
case 1:
bios.input.keyboard.disable();
break;
case 2:
bios.input.upload.disable();
break;
default:;
}
switch (v) {
case 2:
bios.input.upload.enable();
break;
default:
bios.input.keyboard.enable();
v = 1;
}
bios.input.state = v;
},
init:function() {
bios.input.el = document.getElementById('input');
bios.input.keyboard.init();
bios.input.upload.init();
bios.input.setState(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);
}
},
shutdown:function(complete) {
var s = Array.from(document.getElementsByTagName('src'));
s.forEach(function(el,i) {
if (i>0)
el.parentNode.removeChild(el);
});
if (!complete)
return;
document.body.innerHTML = 'The system has shutdown';
bios = null;
},
reboot:function() {
setTimeout(bios.boot.preBoot,1000);
}
},
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(3,5,'Storage Devices:');
bios.storage.init();
bios.core.writeStringVGA(25,5,'Done',2);
bios.core.writeStringVGA(0,7,'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);
},
setCursor:function(x,y,enabled) {
if (typeof enabled !== 'boolean')
return;
var c = document.getElementById('vga-cursor');
if (!c)
return;
c.data.x = x;
c.data.y = y;
c.data.enabled = enabled;
if (!enabled) {
c.style.display = 'none';
return;
}
c.style.display = 'block';
c.style.top = (y*bios.video.text.data.screen.nh)+'px';
c.style.left = (x*bios.video.text.data.screen.nw)+'px';
},
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;
case 3:
bios.video.text.io.setCursor(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.style.zIndex = '5';
c.data = {bg:'0',fg:'F',bk:false};
c.id = 'vga-'+y+'-'+x;
bios.video.text.data.el.appendChild(c);
}
}
// the cursor
var c = document.createElement('div');
c.className = 'vga-cursor vga-blink vga-bt vga-fF';
c.style.position = 'absolute';
c.style.top = '0px';
c.style.left = '0px';
c.style.width = xpx+'px';
c.style.height = ypx+'px';
c.style.zIndex = '10';
c.id = 'vga-cursor';
c.textContent = '_';
c.data = {};
c.data.x = x;
c.data.y = y;
c.data.enabled = false;
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';
}
}
var c = document.getElementById('vga-cursor');
if (c) {
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,
enabled:false
},
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;
e.preventDefault();
bios.input.keyboard.internal.resetFocus();
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.keyboard.internal.resetFocus();
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.char = -3;
k.lchr = k.char;
k.code = -3;
break;
case "Up":
case "ArrowUp":
k.char = -4;
k.lchr = k.char;
k.code = -4;
break;
case "Left":
case "ArrowLeft":
k.char = -5;
k.lchr = k.char;
k.code = -5;
break;
case "Right":
case "ArrowRight":
k.char = -6;
k.lchr = k.char;
k.code = -6;
break;
case "Enter":
k.char = String.fromCharCode(10);
k.lchr = k.char;
k.code = 10;
break;
case "Esc":
case "Escape":
k.char = String.fromCharCode(27);
k.lchr = k.char;
k.code = 27;
break;
case "Shift":
k.char = -2;
k.lchr = k.char;
k.code = -2;
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;
case "Backspace":
k.char = String.fromCharCode(8);
k.lchr = k.char;
k.code = 8;
break;
case 'Control':
k.char = -1;
k.lchar = k.char;
k.code = -1;
break;
case 'PageUp':
k.char = -7;
k.lchr = k.char;
k.code = -7;
break;
case 'PageDown':
k.char = -8;
k.lchr = k.char;
k.code = -8;
break;
case 'Home':
k.char = -9;
k.lchr = k.char;
k.code = -9;
break;
case 'End':
k.char = -10;
k.lchr = k.char;
k.code = -10;
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);
if (bios.input.keyboard.data.enabled) {
bios.input.keyboard.data.el.addEventListener('focusout',bios.input.keyboard.internal.resetFocus);
bios.input.keyboard.internal.resetFocus();
}
return bios.input.keyboard.data.el;
},
init:function() {
var el = bios.input.keyboard.getElement();
bios.input.el.appendChild(el);
},
enable:function() {
bios.input.keyboard.data.enabled = true;
bios.input.keyboard.getElement();
},
disable:function() {
bios.input.keyboard.data.enabled = false;
bios.input.keyboard.getElement();
bios.input.keyboard.data.el.removeEventListener('focusout',bios.input.keyboard.internal.resetFocus);
}
};
bios.input.upload = {
data:{
el:null,
enabled:false,
reader:null,
callback:null,
file:null,
panel:null
},
internal:{
createUploadPanel:function() {
if (bios.video.mode.current != 1 || bios.input.upload.data.file == null)
return;
var l = 28;
if (bios.input.upload.data.file.length > l)
l = bios.input.upload.data.file.length;
var w = l+4;
var h = 6;
var x = 40-(w/2);
var y = 9;
bios.input.upload.data.panel = {w:w,h:h,x:x,y:y,d:[]};
for (var i=0; i<h; i++) {
bios.input.upload.data.panel.d[i] = [];
}
for (var i=0; i<w; i++) {
var p = bios.video.text.internal.getPix(x+i,y);
bios.input.upload.data.panel.d[y-9][i] = {
bg:p.data.bg,
fg:p.data.fg,
c:p.textContent
};
bios.video.io.write(x+i,y,' ');
bios.video.io.setBG(x+i,y,15);
}
for (var j=0; j<4; j++) {
y++;
var p = bios.video.text.internal.getPix(x,y);
bios.input.upload.data.panel.d[y-9][0] = {
bg:p.data.bg,
fg:p.data.fg,
c:p.textContent
};
bios.video.io.write(x,y,' ');
bios.video.io.setBG(x,y,15);
for (var i=1; i<w-1; i++) {
p = bios.video.text.internal.getPix(x+i,y);
bios.input.upload.data.panel.d[y-9][i] = {
bg:p.data.bg,
fg:p.data.fg,
c:p.textContent
};
bios.video.io.write(x+i,y,' ');
bios.video.io.setBG(x+i,y,0);
}
p = bios.video.text.internal.getPix(x+i,y);
bios.input.upload.data.panel.d[y-9][w-1] = {
bg:p.data.bg,
fg:p.data.fg,
c:p.textContent
};
bios.video.io.write(x+w-1,y,' ');
bios.video.io.setBG(x+w-1,y,15);
}
y++;
for (var i=0; i<w; i++) {
var p = bios.video.text.internal.getPix(x+i,y);
bios.input.upload.data.panel.d[y-9][i] = {
bg:p.data.bg,
fg:p.data.fg,
c:p.textContent
};
bios.video.io.write(x+i,y,' ');
bios.video.io.setBG(x+i,y,15);
}
var ho = x+2;
var fo = x+2;
if (l == 28) {
fo += ((28-bios.input.upload.data.file.length)/2);
}else{
ho += ((bios.input.upload.data.file.length-28)/2);
}
bios.core.writeStringVGA(ho,11,'Press Enter and Select File:',15);
bios.core.writeStringVGA(fo,12,bios.input.upload.data.file,15);
},
removeUploadPanel:function() {
if (bios.video.mode.current != 1 || bios.input.upload.data.panel == null)
return;
for (var i=0; i<bios.input.upload.data.panel.h; i++) {
for (var j=0; j<bios.input.upload.data.panel.w; j++) {
var p = bios.video.text.internal.getPix(bios.input.upload.data.panel.x+j,bios.input.upload.data.panel.y+i);
p.data.bg = bios.input.upload.data.panel.d[i][j].bg;
p.data.fg = bios.input.upload.data.panel.d[i][j].fg;
p.textContent = bios.input.upload.data.panel.d[i][j].c;
p.className = bios.video.text.internal.dataToClass(p.data);
}
}
bios.input.upload.data.panel = null;
},
resetFocus:function(e) {
setTimeout(function() {
try{
document.getElementById('lfs').focus();
} catch(err) {}
},10);
return false;
}
},
getElement:function() {
bios.input.upload.data.el = document.getElementById('lfs');
if (typeof bios.input.upload.data.el === 'undefined' || bios.input.upload.data.el == null) {
bios.input.upload.data.el = document.createElement('input');
bios.input.upload.data.el.id = 'lfs';
bios.input.upload.data.el.className = 'lfs';
//bios.input.upload.data.el.style.zIndex = '100';
bios.input.upload.data.el.style.display = 'block';
bios.input.upload.data.el.type = 'file';
}
bios.input.upload.data.el.onchange = function(e) {
bios.input.upload.data.reader = new FileReader();
bios.input.upload.data.reader.onload = function() {
bios.input.setState(1);
bios.input.upload.data.callback(bios.input.upload.data.reader.result);
};
bios.input.upload.data.reader.onerror = function() {
bios.input.setState(1);
bios.input.upload.data.callback(null);
};
bios.input.upload.data.reader.readAsText(e.target.files[0]);
}
if (bios.input.upload.data.enabled) {
bios.input.upload.data.el.addEventListener('focusout',bios.input.upload.internal.resetFocus);
bios.input.upload.internal.resetFocus();
}
return bios.input.upload.data.el;
},
init:function() {
var el = bios.input.upload.getElement();
bios.input.el.appendChild(el);
},
enable:function() {
bios.input.upload.data.enabled = true;
bios.input.upload.getElement();
bios.input.upload.internal.createUploadPanel();
},
disable:function() {
bios.input.upload.data.enabled = false;
bios.input.upload.getElement();
bios.input.upload.data.el.removeEventListener('focusout',bios.input.upload.internal.resetFocus);
bios.input.upload.internal.removeUploadPanel();
}
};
bios.core.expose('init',bios.init);
})();