mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-06-10 23:04:04 +09:00
tvdos bootable disk image builder
This commit is contained in:
@@ -1,567 +0,0 @@
|
||||
let PROMPT_TEXT = ">";
|
||||
let CURRENT_DRIVE = "A";
|
||||
let shell_pwd = [""];
|
||||
|
||||
let goInteractive = false;
|
||||
let goFancy = false;
|
||||
|
||||
let DEBUG_PRINT = true;
|
||||
|
||||
let errorlevel = 0;
|
||||
|
||||
const termWidth = con.getmaxyx()[1];
|
||||
const termHeight = con.getmaxyx()[0];
|
||||
const welcome_text = (termWidth > 40) ? "TSVM 한글 DOS, 버전 " + _TVDOS.VERSION
|
||||
: "TSVM 한글 DOS " + _TVDOS.VERSION;
|
||||
const greetLeftPad = (termWidth - welcome_text.length - 6) >> 1;
|
||||
const greetRightPad = termWidth - greetLeftPad - welcome_text.length - 6;
|
||||
|
||||
function makeHash() {
|
||||
let e = "YBNDRFG8EJKMCPQXOTLVWIS2A345H769";
|
||||
let m = e.length;
|
||||
return e[Math.floor(Math.random()*m)] + e[Math.floor(Math.random()*m)] + e[Math.floor(Math.random()*m)] + e[Math.floor(Math.random()*m)] + e[Math.floor(Math.random()*m)]
|
||||
}
|
||||
|
||||
const shellID = makeHash();
|
||||
|
||||
function print_prompt_text() {
|
||||
if (goFancy) {
|
||||
con.color_pair(239,161);
|
||||
print(" "+CURRENT_DRIVE+":");
|
||||
con.color_pair(161,253);
|
||||
con.addch(16);con.curs_right();
|
||||
con.color_pair(0,253);
|
||||
print(" /"+shell_pwd.join("/").substring(1)+" ");
|
||||
if (errorlevel != 0) {
|
||||
con.color_pair(166,253);
|
||||
print("["+errorlevel+"] ");
|
||||
}
|
||||
con.color_pair(253,255);
|
||||
con.addch(16);con.curs_right();
|
||||
con.addch(32);con.curs_right();
|
||||
con.color_pair(253,255);
|
||||
}
|
||||
else {
|
||||
// con.color_pair(253,255);
|
||||
if (errorlevel != 0)
|
||||
print(CURRENT_DRIVE + ":/" + shell_pwd.join("/") + " [" + errorlevel + "]" + PROMPT_TEXT);
|
||||
else
|
||||
print(CURRENT_DRIVE + ":/" + shell_pwd.join("/") + PROMPT_TEXT);
|
||||
}
|
||||
}
|
||||
|
||||
function greet() {
|
||||
if (goFancy) {
|
||||
con.color_pair(239,255);
|
||||
con.clear();
|
||||
con.color_pair(253,255);
|
||||
print(' ');con.addch(17);con.curs_right();
|
||||
con.color_pair(0,253);
|
||||
print(" ".repeat(greetLeftPad)+welcome_text+" ".repeat(greetRightPad));
|
||||
con.color_pair(253,255);
|
||||
con.addch(16);con.curs_right();print(' ');
|
||||
con.move(3,1);
|
||||
}
|
||||
else
|
||||
println(welcome_text);
|
||||
}
|
||||
|
||||
/*uninterruptible*/ function sendLcdMsg(s) {
|
||||
// making it uninterruptible
|
||||
sys.poke(-1025, (s === undefined) ? 0 : s.charCodeAt(0)|0);
|
||||
sys.poke(-1026, (s === undefined) ? 0 : s.charCodeAt(1)|0);
|
||||
sys.poke(-1027, (s === undefined) ? 0 : s.charCodeAt(2)|0);
|
||||
sys.poke(-1028, (s === undefined) ? 0 : s.charCodeAt(3)|0);
|
||||
sys.poke(-1029, (s === undefined) ? 0 : s.charCodeAt(4)|0);
|
||||
sys.poke(-1030, (s === undefined) ? 0 : s.charCodeAt(5)|0);
|
||||
sys.poke(-1031, (s === undefined) ? 0 : s.charCodeAt(6)|0);
|
||||
sys.poke(-1032, (s === undefined) ? 0 : s.charCodeAt(7)|0);
|
||||
sys.poke(-1033, (s === undefined) ? 0 : s.charCodeAt(8)|0);
|
||||
sys.poke(-1034, (s === undefined) ? 0 : s.charCodeAt(9)|0);
|
||||
sys.poke(-1035, (s === undefined) ? 0 : s.charCodeAt(10)|0);
|
||||
sys.poke(-1036, (s === undefined) ? 0 : s.charCodeAt(11)|0);
|
||||
sys.poke(-1037, (s === undefined) ? 0 : s.charCodeAt(12)|0);
|
||||
sys.poke(-1038, (s === undefined) ? 0 : s.charCodeAt(13)|0);
|
||||
sys.poke(-1039, (s === undefined) ? 0 : s.charCodeAt(14)|0);
|
||||
sys.poke(-1040, (s === undefined) ? 0 : s.charCodeAt(15)|0);
|
||||
sys.poke(-1041, (s === undefined) ? 0 : s.charCodeAt(16)|0);
|
||||
sys.poke(-1042, (s === undefined) ? 0 : s.charCodeAt(17)|0);
|
||||
sys.poke(-1043, (s === undefined) ? 0 : s.charCodeAt(18)|0);
|
||||
sys.poke(-1044, (s === undefined) ? 0 : s.charCodeAt(19)|0);
|
||||
sys.poke(-1045, (s === undefined) ? 0 : s.charCodeAt(20)|0);
|
||||
sys.poke(-1046, (s === undefined) ? 0 : s.charCodeAt(21)|0);
|
||||
sys.poke(-1047, (s === undefined) ? 0 : s.charCodeAt(22)|0);
|
||||
sys.poke(-1048, (s === undefined) ? 0 : s.charCodeAt(23)|0);
|
||||
}
|
||||
|
||||
function trimStartRevSlash(s) {
|
||||
var cnt = 0;
|
||||
while (cnt < s.length) {
|
||||
var chr = s[cnt];
|
||||
|
||||
if (chr != '\\') break;
|
||||
|
||||
cnt += 1;
|
||||
}
|
||||
|
||||
return s.substring(cnt);
|
||||
}
|
||||
|
||||
let shell = {};
|
||||
shell.replaceVarCall = function(value) {
|
||||
// syntax:
|
||||
// line = literal [varcall] [literal] ;
|
||||
// varcall = "$" ident ;
|
||||
// ident = ? regex: [A-Za-z_]+ ? ;
|
||||
// literal = ? you know what it is ? ;
|
||||
let replaceMap = [];
|
||||
let varMode = false;
|
||||
let sb = '';
|
||||
for (let i=0; i<value.length; i++) {
|
||||
let char = value.charAt(i);
|
||||
let cp = value.charCodeAt(i);
|
||||
if (!varMode && char == '$') {
|
||||
replaceMap.push({s:sb,r:false});
|
||||
sb = ''; varMode = true;
|
||||
}
|
||||
else if (varMode && !(cp >= 48 && cp <= 57 || cp >= 65 && cp <= 90 || cp == 95 || cp >= 97 && cp <= 122)) {
|
||||
replaceMap.push({s:sb,r:true});
|
||||
sb = ''+char; varMode = false;
|
||||
}
|
||||
else sb += char;
|
||||
}; replaceMap.push({s:sb,r:(varMode)});
|
||||
|
||||
return replaceMap.map(it => (it.r) ? _TVDOS.variables[it.s] : it.s).join('');
|
||||
}
|
||||
shell.getPwd = function() { return shell_pwd; }
|
||||
shell.getPwdString = function() { return "\\" + (shell_pwd.concat([""])).join("\\"); }
|
||||
shell.getCurrentDrive = function() { return CURRENT_DRIVE; }
|
||||
// example input: echo "the string" > subdir\test.txt
|
||||
shell.parse = function(input) {
|
||||
var tokens = [];
|
||||
var stringBuffer = "";
|
||||
var mode = "LITERAL"; // LITERAL, QUOTE, ESCAPE, LIMBO
|
||||
var i = 0
|
||||
while (i < input.length) {
|
||||
const c = input[i];
|
||||
/*digraph g {
|
||||
LITERAL -> QUOTE [label="\""]
|
||||
LITERAL -> LIMBO [label="space"]
|
||||
LITERAL -> LITERAL [label=else]
|
||||
|
||||
QUOTE -> LIMBO [label="\""]
|
||||
QUOTE -> ESCAPE [label="\\"]
|
||||
QUOTE -> QUOTE [label=else]
|
||||
|
||||
ESCAPE -> QUOTE
|
||||
|
||||
LIMBO -> LITERAL [label="not space"]
|
||||
LIMBO -> QUOTE [label="\""]
|
||||
LIMBO -> LIMBO [label="space"]
|
||||
}*/
|
||||
if ("LITERAL" == mode) {
|
||||
if (' ' == c) {
|
||||
tokens.push(stringBuffer); stringBuffer = "";
|
||||
mode = "LIMBO";
|
||||
}
|
||||
else if ('"' == c) {
|
||||
tokens.push(stringBuffer); stringBuffer = "";
|
||||
mode = "QUOTE";
|
||||
}
|
||||
else {
|
||||
stringBuffer += c;
|
||||
}
|
||||
}
|
||||
else if ("LIMBO" == mode) {
|
||||
if ('"' == c) {
|
||||
mode = "QUOTE";
|
||||
}
|
||||
else if (c != ' ') {
|
||||
mode = "LITERAL";
|
||||
stringBuffer += c;
|
||||
}
|
||||
}
|
||||
else if ("QUOTE" == mode) {
|
||||
if ('"' == c) {
|
||||
tokens.push(stringBuffer); stringBuffer = "";
|
||||
mode = "LIMBO";
|
||||
}
|
||||
else if ('^' == c) {
|
||||
mode = "ESCAPE";
|
||||
}
|
||||
else {
|
||||
stringBuffer += c;
|
||||
}
|
||||
}
|
||||
else if ("ESCAPE" == mode) {
|
||||
TODO();
|
||||
}
|
||||
|
||||
i += 1;
|
||||
}
|
||||
|
||||
if (stringBuffer.length > 0) {
|
||||
tokens.push(stringBuffer);
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
shell.resolvePathInput = function(input) {
|
||||
// replace revslashes into rslashes
|
||||
var pathstr = input.replaceAll('\\','/');
|
||||
var startsWithSlash = input.startsWith('/');
|
||||
var newPwd = [];
|
||||
|
||||
// split them into an array while filtering empty elements except for the root 'head'
|
||||
var ipwd = (startsWithSlash ? [""] : shell_pwd).concat(pathstr.split("/").filter(function(it) { return (it.length > 0); }));
|
||||
|
||||
serial.println("command.js > resolvePathInput > ipwd = "+ipwd);
|
||||
serial.println("command.js > resolvePathInput > newPwd = "+newPwd);
|
||||
|
||||
// process dots
|
||||
ipwd.forEach(function(it) {
|
||||
serial.println("command.js > resolvePathInput > ipwd.forEach > it = "+it);
|
||||
if (it === ".." && newPwd[1] !== undefined) {
|
||||
newPwd.pop();
|
||||
}
|
||||
else if (it !== ".." && it !== ".") {
|
||||
newPwd.push(it);
|
||||
}
|
||||
serial.println("command.js > resolvePathInput > newPwd = "+newPwd);
|
||||
});
|
||||
|
||||
// construct new pathstr from pwd arr so it will be sanitised
|
||||
pathstr = newPwd.join('/').substring(1);
|
||||
|
||||
return { string: pathstr, pwd: newPwd };
|
||||
}
|
||||
shell.coreutils = {
|
||||
/* Args follow this format:
|
||||
* <command-name> <1st arg> <2nd arg> ...
|
||||
* NOTE:
|
||||
* even if there's no 1st arg, length of args may not be 1, therefore don't:
|
||||
* if (args.length < 2)
|
||||
* but do instead:
|
||||
* if (args[1] === undefined)
|
||||
*/
|
||||
cd: function(args) {
|
||||
if (args[1] === undefined) {
|
||||
println(CURRENT_DRIVE+":"+shell_pwd.join("/"));
|
||||
return
|
||||
}
|
||||
var path = shell.resolvePathInput(args[1])
|
||||
if (DEBUG_PRINT) serial.println("command.js > cd > pathstr = "+path.string);
|
||||
|
||||
// check if path is valid
|
||||
var dirOpenedStatus = filesystem.open(CURRENT_DRIVE, path.string, 'R');
|
||||
var isDir = filesystem.isDirectory(CURRENT_DRIVE); // open a dir; if path is nonexistent, file won't actually be opened
|
||||
if (!isDir) { printerrln("디렉토리 '"+path.string+"'가 없습니다."); return dirOpenedStatus; } // if file is not opened, IO error code will be returned
|
||||
|
||||
shell_pwd = path.pwd;
|
||||
},
|
||||
mkdir: function(args) {
|
||||
if (args[1] === undefined) {
|
||||
printerrln("Syntax error");
|
||||
return
|
||||
}
|
||||
var path = shell.resolvePathInput(args[1])
|
||||
if (DEBUG_PRINT) serial.println("command.js > mkdir > pathstr = "+path.string);
|
||||
|
||||
// check if path is valid
|
||||
var dirOpenedStatus = filesystem.open(CURRENT_DRIVE, path.string, 'W');
|
||||
var mkdird = filesystem.mkDir(CURRENT_DRIVE);
|
||||
if (!mkdird) { printerrln("디렉토리 생성 실패: '"+path.string+"'"); return dirOpenedStatus; }
|
||||
},
|
||||
cls: function(args) {
|
||||
con.clear();
|
||||
graphics.clearPixels(255);
|
||||
},
|
||||
exit: function(args) {
|
||||
cmdExit = true;
|
||||
},
|
||||
ver: function(args) {
|
||||
println(welcome_text);
|
||||
},
|
||||
echo: function(args) {
|
||||
if (args[1] !== undefined) {
|
||||
args.forEach(function(it,i) { if (i > 0) print(shell.replaceVarCall(it)+" ") });
|
||||
}
|
||||
println();
|
||||
},
|
||||
rem: function(args) {
|
||||
return 0;
|
||||
},
|
||||
set: function(args) {
|
||||
// print all the env vars
|
||||
if (args[1] === undefined) {
|
||||
Object.entries(_TVDOS.variables).forEach(function(a) { println(a[0]+"="+a[1]); })
|
||||
}
|
||||
else {
|
||||
// parse key-value pair with splitter '='
|
||||
var key = undefined; var value = undefined;
|
||||
// if syntax "<key> = <value>" is used?
|
||||
if ('=' == args[2]) {
|
||||
key = args[1].toUpperCase(); value = args[3];
|
||||
}
|
||||
else if (args[2] === undefined) {
|
||||
var pair = args[1].split('=');
|
||||
key = pair[0].toUpperCase(); value = pair[1];
|
||||
}
|
||||
|
||||
if (key == undefined) throw SyntaxError("Input format must be 'key=value'");
|
||||
|
||||
// if value is undefined, show what envvar[key] has
|
||||
if (value === undefined) {
|
||||
if (_TVDOS.variables[key] === undefined)
|
||||
println("환경변수 '"+key+"'이(가) 없습니다.");
|
||||
else
|
||||
println(_TVDOS.variables[key])
|
||||
}
|
||||
else {
|
||||
_TVDOS.variables[key] = shell.replaceVarCall(value);
|
||||
|
||||
// if key is KEYBOARD, reload the keyboard layout
|
||||
if ("KEYBOARD" == key)
|
||||
input.changeKeyLayout(_TVDOS.variables.KEYBOARD || "us_qwerty");
|
||||
}
|
||||
}
|
||||
},
|
||||
dir: function(args) {
|
||||
var pathstr = (args[1] !== undefined) ? args[1] : shell.getPwdString();
|
||||
|
||||
// check if path is valid
|
||||
var pathOpenedStatus = filesystem.open(CURRENT_DRIVE, pathstr, 'R');
|
||||
if (pathOpenedStatus != 0) { printerrln("파일이 없습니다"); return pathOpenedStatus; }
|
||||
|
||||
var port = filesystem._toPorts(CURRENT_DRIVE)[0]
|
||||
com.sendMessage(port, "LIST");
|
||||
println(com.pullMessage(port));
|
||||
},
|
||||
cat: function(args) {
|
||||
var pathstr = (args[1] !== undefined) ? args[1] : shell.getPwdString();
|
||||
|
||||
var pathOpenedStatus = filesystem.open(CURRENT_DRIVE, pathstr, 'R');
|
||||
if (pathOpenedStatus != 0) { printerrln("파일이 없습니다"); return pathOpenedStatus; }
|
||||
let contents = filesystem.readAll(CURRENT_DRIVE);
|
||||
// TODO just print out what's there
|
||||
print(contents);
|
||||
}
|
||||
};
|
||||
shell.coreutils.chdir = shell.coreutils.cd;
|
||||
Object.freeze(shell.coreutils);
|
||||
shell.execute = function(line) {
|
||||
if (0 == line.size) return;
|
||||
var tokens = shell.parse(line);
|
||||
var cmd = tokens[0];
|
||||
if (cmd === undefined || cmd === '') return 0;
|
||||
|
||||
// handle Ctrl-C
|
||||
if (con.hitterminate()) return 1;
|
||||
|
||||
if (shell.coreutils[cmd.toLowerCase()] !== undefined) {
|
||||
var retval = shell.coreutils[cmd.toLowerCase()](tokens);
|
||||
return retval|0; // return value of undefined will cast into 0
|
||||
}
|
||||
else {
|
||||
// search through PATH for execution
|
||||
|
||||
var fileExists = false;
|
||||
var searchDir = (cmd.startsWith("/")) ? [""] : ["/"+shell_pwd.join("/")].concat(_TVDOS.getPath());
|
||||
|
||||
var pathExt = []; // it seems Nashorn does not like 'let' too much? this line gets ignored sometimes
|
||||
// fill pathExt using %PATHEXT% but also capitalise them
|
||||
if (cmd.split(".")[1] === undefined)
|
||||
_TVDOS.variables.PATHEXT.split(';').forEach(function(it) { pathExt.push(it); pathExt.push(it.toUpperCase()); });
|
||||
else
|
||||
pathExt.push(""); // final empty extension
|
||||
|
||||
searchLoop:
|
||||
for (var i = 0; i < searchDir.length; i++) {
|
||||
for (var j = 0; j < pathExt.length; j++) {
|
||||
var search = searchDir[i]; if (!search.endsWith('\\')) search += '\\';
|
||||
var path = trimStartRevSlash(search + cmd + pathExt[j]);
|
||||
|
||||
if (DEBUG_PRINT) {
|
||||
serial.println("[command.js > shell.execute] file search path: "+path);
|
||||
}
|
||||
|
||||
if (0 == filesystem.open(CURRENT_DRIVE, path, "R")) {
|
||||
fileExists = true;
|
||||
break searchLoop;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!fileExists) {
|
||||
printerrln('명령어 또는 파일 이름이 틀립니다: "'+cmd+'"');
|
||||
return 127;
|
||||
}
|
||||
else {
|
||||
var programCode = filesystem.readAll(CURRENT_DRIVE);
|
||||
var extension = undefined;
|
||||
// get proper extension
|
||||
var dotSepTokens = cmd.split('.');
|
||||
if (dotSepTokens.length > 1) extension = dotSepTokens[dotSepTokens.length - 1].toUpperCase();
|
||||
|
||||
if ("BAT" == extension) {
|
||||
// parse and run as batch file
|
||||
var lines = programCode.split('\n').filter(function(it) { return it.length > 0; });
|
||||
lines.forEach(function(line) {
|
||||
shell.execute(line);
|
||||
});
|
||||
}
|
||||
else {
|
||||
let gotError = false;
|
||||
|
||||
try {
|
||||
errorlevel = 0; // reset the number
|
||||
|
||||
if (_G.shellProgramTitles === undefined) _G.shellProgramTitles = [];
|
||||
_G.shellProgramTitles.push(cmd.toUpperCase())
|
||||
sendLcdMsg(_G.shellProgramTitles[_G.shellProgramTitles.length - 1]);
|
||||
//serial.println(_G.shellProgramTitles);
|
||||
|
||||
errorlevel = execApp(programCode, tokens); // return value of undefined will cast into 0
|
||||
}
|
||||
catch (e) {
|
||||
gotError = true;
|
||||
|
||||
serial.printerr(`[command.js] program quit with ${e}:\n${e.stack || '(stack trace unavailable)'}`);
|
||||
|
||||
if (`${e}`.startsWith("InterruptedException"))
|
||||
errorlevel = SIGTERM.name;
|
||||
else if (e instanceof IllegalAccessException || `${e}`.startsWith("net.torvald.tsvm.ErrorIllegalAccess"))
|
||||
errorlevel = SIGSEGV.name;
|
||||
|
||||
// exception catched means something went wrong, so if errorlevel is found to be zero, force set to 1.
|
||||
if (errorlevel === 0 || errorlevel == undefined)
|
||||
errorlevel = 1;
|
||||
}
|
||||
finally {
|
||||
// sometimes no-error program may return nothing as the errorlevel; force set to 0 then.
|
||||
if (!gotError && (errorlevel == undefined || (typeof errorlevel.trim == "function" && errorlevel.trim().length == 0) || isNaN(errorlevel)))
|
||||
errorlevel = 0;
|
||||
|
||||
serial.printerr(`errorlevel: ${errorlevel}`);
|
||||
|
||||
_G.shellProgramTitles.pop();
|
||||
sendLcdMsg(_G.shellProgramTitles[_G.shellProgramTitles.length - 1]);
|
||||
//serial.println(_G.shellProgramTitles);
|
||||
|
||||
return errorlevel;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Object.freeze(shell);
|
||||
_G.shell = shell;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
if (exec_args[1] !== undefined) {
|
||||
// only meaningful switches would be either /c or /k anyway
|
||||
var firstSwitch = exec_args[1].toLowerCase();
|
||||
|
||||
// command /c <commands>
|
||||
// ^[0] ^[1] ^[2]
|
||||
if ("/c" == firstSwitch) {
|
||||
if ("" == exec_args[2]) return 0; // no commands were given, just exit successfully
|
||||
return shell.execute(exec_args[2]);
|
||||
}
|
||||
else if ("/k" == firstSwitch) {
|
||||
if ("" == exec_args[2]) return 0; // no commands were given, just exit successfully
|
||||
shell.execute(exec_args[2]);
|
||||
goInteractive = true;
|
||||
}
|
||||
else if ("/fancy" == firstSwitch) {
|
||||
graphics.setBackground(2,3,4);
|
||||
goFancy = true;
|
||||
goInteractive = true;
|
||||
}
|
||||
else {
|
||||
printerrln("잘못된 스위치: "+exec_args[1]);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
goInteractive = true;
|
||||
}
|
||||
|
||||
let cmdExit = false;
|
||||
if (goInteractive) {
|
||||
con.curs_set(1);
|
||||
greet();
|
||||
|
||||
let cmdHistory = []; // zeroth element is the oldest
|
||||
let cmdHistoryScroll = 0; // 0 for outside-of-buffer, 1 for most recent
|
||||
while (!cmdExit) {
|
||||
con.curs_set(1);
|
||||
print_prompt_text();
|
||||
|
||||
var cmdbuf = "";
|
||||
|
||||
while (true) {
|
||||
let key = con.getch();
|
||||
|
||||
// printable chars
|
||||
if (key >= 32 && key <= 126) {
|
||||
var s = String.fromCharCode(key);
|
||||
cmdbuf += s;
|
||||
print(s);
|
||||
}
|
||||
// backspace
|
||||
else if (key === con.KEY_BACKSPACE && cmdbuf.length > 0) {
|
||||
cmdbuf = cmdbuf.substring(0, cmdbuf.length - 1);
|
||||
print(String.fromCharCode(key));
|
||||
}
|
||||
// enter
|
||||
else if (key === 10 || key === con.KEY_RETURN) {
|
||||
println();
|
||||
|
||||
shell.execute(cmdbuf);
|
||||
|
||||
if (cmdbuf.trim().length > 0)
|
||||
cmdHistory.push(cmdbuf);
|
||||
|
||||
cmdHistoryScroll = 0;
|
||||
con.curs_set(1);
|
||||
|
||||
break;
|
||||
}
|
||||
// up arrow
|
||||
else if (key === con.KEY_UP && cmdHistory.length > 0 && cmdHistoryScroll < cmdHistory.length) {
|
||||
cmdHistoryScroll += 1;
|
||||
|
||||
// back the cursor in order to type new cmd
|
||||
var x = 0;
|
||||
for (x = 0; x < cmdbuf.length; x++) print(String.fromCharCode(8));
|
||||
cmdbuf = cmdHistory[cmdHistory.length - cmdHistoryScroll];
|
||||
// re-type the new command
|
||||
print(cmdbuf);
|
||||
|
||||
}
|
||||
// down arrow
|
||||
else if (key === con.KEY_DOWN) {
|
||||
if (cmdHistoryScroll > 0) {
|
||||
// back the cursor in order to type new cmd
|
||||
var x = 0;
|
||||
for (x = 0; x < cmdbuf.length; x++) print(String.fromCharCode(8));
|
||||
cmdbuf = cmdHistory[cmdHistory.length - cmdHistoryScroll];
|
||||
// re-type the new command
|
||||
print(cmdbuf);
|
||||
|
||||
cmdHistoryScroll -= 1;
|
||||
}
|
||||
else {
|
||||
// back the cursor in order to type new cmd
|
||||
var x = 0;
|
||||
for (x = 0; x < cmdbuf.length; x++) print(String.fromCharCode(8));
|
||||
cmdbuf = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -1,288 +0,0 @@
|
||||
graphics.setBackground(2,1,3);
|
||||
graphics.resetPalette();
|
||||
|
||||
function captureUserInput() {
|
||||
sys.poke(-40, 1);
|
||||
}
|
||||
|
||||
function getKeyPushed(keyOrder) {
|
||||
return sys.peek(-41 - keyOrder);
|
||||
}
|
||||
|
||||
let _fsh = {};
|
||||
_fsh.titlebarTex = new GL.Texture(2, 14, base64.atob("/u/+/v3+/f39/f39/f39/f39/P39/Pz8/Pv7+w=="));
|
||||
_fsh.scrdim = con.getmaxyx();
|
||||
_fsh.scrwidth = _fsh.scrdim[1];
|
||||
_fsh.scrheight = _fsh.scrdim[0];
|
||||
_fsh.brandName = "f\xb3Sh";
|
||||
_fsh.brandLogoTexSmall = new GL.Texture(24, 14, gzip.decomp(base64.atob(
|
||||
"H4sIAAAAAAAAAPv/Hy/4Qbz458+fIeILQQBIwoSh6qECuMVBukCmIJkDVQ+RQNgLE0MX/w+1lyhxqIUwTLJ/sQMAcIXsbVABAAA="
|
||||
)));
|
||||
_fsh.scrlayout = ["com.fsh.clock","com.fsh.calendar","com.fsh.todo_list", "com.fsh.quick_access"];
|
||||
|
||||
_fsh.drawWallpaper = function() {
|
||||
let wp = files.open("A:/tvdos/wall.bytes")
|
||||
// filesystem.open("A", "/tvdos/wall.bytes", "R")
|
||||
let b = sys.malloc(250880)
|
||||
// dma.comToRam(0, 0, b, 250880)
|
||||
wp.pread(b, 250880, 0)
|
||||
dma.ramToFrame(b, 0, 250880)
|
||||
sys.free(b)
|
||||
};
|
||||
|
||||
_fsh.drawTitlebar = function(titletext) {
|
||||
GL.drawTexPattern(_fsh.titlebarTex, 0, 0, 560, 14);
|
||||
if (titletext === undefined || titletext.length == 0) {
|
||||
con.move(1,1);
|
||||
print(" ".repeat(_fsh.scrwidth));
|
||||
GL.drawTexImageOver(_fsh.brandLogoTexSmall, 268, 0);
|
||||
}
|
||||
else {
|
||||
con.color_pair(240, 255);
|
||||
GL.drawTexPattern(_fsh.titlebarTex, 268, 0, 24, 14);
|
||||
con.move(1, 1 + (_fsh.scrwidth - titletext.length) / 2);
|
||||
print(titletext);
|
||||
}
|
||||
con.color_pair(254, 255);
|
||||
};
|
||||
|
||||
|
||||
_fsh.Widget = function(id, w, h) {
|
||||
this.identifier = id;
|
||||
this.width = w;
|
||||
this.height = h;
|
||||
|
||||
if (!this.identifier) {
|
||||
this.identifier = "";
|
||||
}
|
||||
|
||||
//this.update = function() {};
|
||||
/**
|
||||
* Params charXoff and charYoff are ZERO-BASED!
|
||||
*/
|
||||
this.draw = function(charXoff, charYoff) {};
|
||||
}
|
||||
|
||||
_fsh.widgets = {}
|
||||
_fsh.registerNewWidget = function(widget) {
|
||||
_fsh.widgets[widget.identifier] = widget;
|
||||
}
|
||||
|
||||
let clockWidget = new _fsh.Widget("com.fsh.clock", _fsh.scrwidth - 8, 7*2);
|
||||
clockWidget.numberSheet = new GL.SpriteSheet(19, 22, new GL.Texture(190, 22, gzip.decomp(base64.atob(
|
||||
"H4sIAAAAAAAAAMWVW3LEMAgE739aHcFJJV5ZMD2I9ToVfcl4GBr80HF8r/FaR1ozMuIyoUu87lEXI0al5qVR5AebSwchSaNE6Nyo1Nw5HXF3SfPT4Bshl"+
|
||||
"EycA8RD96mLlHbuhTgOrfLnUDZspafbSQWk56WEGvQEtWaWwgb8iz7a8AOXhsraO/q9Qw2/GnXovfVN+q2wM/p/oddn2cjF239GX3y11+SWCtc6FTHC1v"+
|
||||
"TVPkDPWWn0w+DDz93UX9v9mF5KIsQ6OdN2KJoB4ui1bXXr0AMp0YfiQo//4XhpK8555dsNehAqVS5uhb5iHn3Kko769J59KmLBe/TSR7hcsd+hr+HnrwR"+
|
||||
"9uvRF9+D3MP14gN7lqx+8OuNT+uqt3NFX3SN9fTbeeHNq+C29pRWzX5+Rcm7SZyjOKJ/2hkSPqul4xN279DrSYvCrNu2NI7ZMp1ouBxK3KBVVnEeAUWbK"+
|
||||
"MUDn5DPsPxmUqHZQjGpy2hergM3EVBAAAA=="
|
||||
))));
|
||||
|
||||
clockWidget.clockColon = new GL.Texture(4, 3, base64.atob("7+/v7+/v7+/v7+/v"));
|
||||
clockWidget.monthNames = ["Spring", "Summer", "Autumn", "Winter"];
|
||||
clockWidget.dayNames = ["Mondag ", "Tysdag ", "Midtveke", "Torsdag ", "Fredag ", "Laurdag ", "Sundag ", "Verddag "];
|
||||
clockWidget.draw = function(charXoff, charYoff) {
|
||||
con.color_pair(254, 255);
|
||||
let xoff = charXoff * 7;
|
||||
let yoff = charYoff * 14 + 3;
|
||||
let timeInMinutes = ((sys.currentTimeInMills() / 60000)|0);
|
||||
let mins = timeInMinutes % 60;
|
||||
let hours = ((timeInMinutes / 60)|0) % 24;
|
||||
let ordinalDay = ((timeInMinutes / (60*24))|0) % 120;
|
||||
let visualDay = (ordinalDay % 30) + 1;
|
||||
let months = ((timeInMinutes / (60*24*30))|0) % 4;
|
||||
let dayName = ordinalDay % 7; // 0 for Mondag
|
||||
if (ordinalDay == 119) dayName = 7; // Verddag
|
||||
let years = ((timeInMinutes / (60*24*30*120))|0) + 125;
|
||||
// draw timepiece
|
||||
GL.drawSprite(clockWidget.numberSheet, (hours / 10)|0, 0, xoff, yoff, 1);
|
||||
GL.drawSprite(clockWidget.numberSheet, hours % 10, 0, xoff + 24, yoff, 1);
|
||||
GL.drawTexImage(clockWidget.clockColon, xoff + 48, yoff + 5, 1);
|
||||
GL.drawTexImage(clockWidget.clockColon, xoff + 48, yoff + 14, 1);
|
||||
GL.drawSprite(clockWidget.numberSheet, (mins / 10)|0, 0, xoff + 57, yoff, 1);
|
||||
GL.drawSprite(clockWidget.numberSheet, mins % 10, 0, xoff + 81, yoff, 1);
|
||||
// print month and date
|
||||
con.move(1 + charYoff, 17 + charXoff);
|
||||
print(clockWidget.monthNames[months]+" "+visualDay);
|
||||
// print year and dayname
|
||||
con.move(2 + charYoff, 17 + charXoff);
|
||||
print("\xE7"+years+" "+clockWidget.dayNames[dayName]);
|
||||
};
|
||||
|
||||
|
||||
let calendarWidget = new _fsh.Widget("com.fsh.calendar", (_fsh.scrwidth - 8) / 2, 7*6)
|
||||
calendarWidget.dayLabels = [
|
||||
" 1 2 3 4 5 6 7 \xFA\xFA",
|
||||
" 8 9 10 11 12 13 14 \xFA\xFA",
|
||||
"15 16 17 18 19 20 21 \xFA\xFA",
|
||||
"22 23 24 25 26 27 28 \xFA\xFA",
|
||||
"29 30 1 2 3 4 5 \xFA\xFA",
|
||||
" 6 7 8 9 10 11 12 \xFA\xFA",
|
||||
"13 14 15 16 17 18 19 \xFA\xFA",
|
||||
"20 21 22 23 24 25 26 \xFA\xFA",
|
||||
"27 28 29 30 1 2 3 \xFA\xFA",
|
||||
" 4 5 6 7 8 9 10 \xFA\xFA",
|
||||
"11 12 13 14 15 16 17 \xFA\xFA",
|
||||
"18 19 20 21 22 23 24 \xFA\xFA",
|
||||
"25 26 27 28 29 30 1 \xFA\xFA",
|
||||
" 2 3 4 5 6 7 8 \xFA\xFA",
|
||||
" 9 10 11 12 13 14 15 \xFA\xFA",
|
||||
"16 17 18 19 20 21 22 \xFA\xFA",
|
||||
"23 24 25 26 27 28 29 30"
|
||||
]
|
||||
calendarWidget.seasonCols = [229,39,215,239,253]
|
||||
calendarWidget.draw = function(charXoff, charYoff) {
|
||||
con.color_pair(254, 255)
|
||||
let xoff = charXoff * 7
|
||||
let yoff = charYoff * 14 + 3
|
||||
|
||||
let timeInMinutes = ((sys.currentTimeInMills() / 60000)|0)
|
||||
let ordinalDay = ((timeInMinutes / (60*24))|0) % 120
|
||||
let offset = (119 == ordinalDay) ? 16 : (ordinalDay / 7)|0
|
||||
|
||||
|
||||
con.move(charYoff, charXoff)
|
||||
print("Mo Ty Mi To Fr La Su Ve")
|
||||
|
||||
for (let i = -3; i <= 3; i++) {
|
||||
let lineOff = (offset + i + 17) % 17 // adding 17 to prevent mod-ing on negative number
|
||||
let line = calendarWidget.dayLabels[lineOff]
|
||||
let textCol = 0
|
||||
|
||||
con.move(charYoff + 4 + i, charXoff)
|
||||
|
||||
for (let x = 0; x <= 23; x++) {
|
||||
let paintingDayOrd = lineOff*7 + ((x/3)|0)
|
||||
if (x >= 21 && lineOff != 16) textCol = calendarWidget.seasonCols[4]
|
||||
else textCol = calendarWidget.seasonCols[(paintingDayOrd / 30)|0]
|
||||
|
||||
// special colour for spaces between numbers
|
||||
if (x % 3 == 2) con.color_pair(255,255)
|
||||
// mark today
|
||||
else if (paintingDayOrd == ordinalDay && x < 21 || paintingDayOrd == 119 && ordinalDay == 119) con.color_pair(0,textCol)
|
||||
// paint normal day number with seasonal colour
|
||||
else con.color_pair(textCol,255)
|
||||
|
||||
con.addch(line.charCodeAt(x))
|
||||
con.curs_right()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let todoWidget = new _fsh.Widget("com.fsh.todo_list", (_fsh.scrwidth - 8) / 2, 7*10)
|
||||
todoWidget.todoList = [["Hello, world!", true]]
|
||||
todoWidget.draw = function(charXoff, charYoff) {
|
||||
con.color_pair(254, 255)
|
||||
let xoff = charXoff * 7
|
||||
let yoff = charYoff * 14 + 3
|
||||
|
||||
con.move(charYoff, charXoff)
|
||||
print("========== TODO ==========")
|
||||
|
||||
for (let i = 0; i <= 12; i++) {
|
||||
let list = todoWidget.todoList[i] || ["Click to add", null]
|
||||
|
||||
if (list[1] === null) con.color_pair(249, 255)
|
||||
else con.color_pair(254, 255)
|
||||
|
||||
con.move(charYoff + i + 2, charXoff)
|
||||
con.addch((list[1] === null) ? 43 : (list[1]) ? 0x9F : 0x9E)
|
||||
|
||||
if (i > todoWidget.todoList.length) {
|
||||
for (let k = 0; k < 24; k++) {
|
||||
con.mvaddch(charYoff + i + 2, charXoff + 2 + k, 95)
|
||||
}
|
||||
}
|
||||
else {
|
||||
con.move(charYoff + i + 2, charXoff + 2)
|
||||
print(`${list[0]}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let quickAccessWidget = new _fsh.Widget("com.fsh.quick_access", (_fsh.scrwidth - 8) / 2, 7*20)
|
||||
quickAccessWidget.entries = [
|
||||
["Files", "/tvdos/bin/explorer.js"],
|
||||
["Editor", "/tvdos/bin/edit.js"],
|
||||
["BASIC", "/tbas/basic.js"],
|
||||
["DOS Shell", "/tvdos/bin/command.js /fancy"]
|
||||
]
|
||||
quickAccessWidget.draw = function(charXoff, charYoff) {
|
||||
con.color_pair(254, 255)
|
||||
let xoff = charXoff * 7
|
||||
let yoff = charYoff * 14 + 3
|
||||
|
||||
con.move(charYoff, charXoff)
|
||||
print("====== QUICK ACCESS ======")
|
||||
|
||||
for (let i = 0; i <= 21; i++) {
|
||||
let list = quickAccessWidget.entries[i] || ["Click to add", null]
|
||||
|
||||
if (list[1] === null) con.color_pair(249, 255)
|
||||
else con.color_pair(254, 255)
|
||||
|
||||
con.move(charYoff + i + 2, charXoff)
|
||||
con.addch((list[1] === null) ? 0xF9 : (list[1]) ? 7 : 0x7F)
|
||||
|
||||
if (i > quickAccessWidget.entries.length) {
|
||||
for (let k = 0; k < 24; k++) {
|
||||
con.mvaddch(charYoff + i + 2, charXoff + 2 + k, 95)
|
||||
}
|
||||
}
|
||||
else {
|
||||
con.move(charYoff + i + 2, charXoff + 2)
|
||||
print(`${list[0]}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// change graphics mode and check if it's supported
|
||||
graphics.setGraphicsMode(3)
|
||||
if (graphics.getGraphicsMode() == 0) {
|
||||
printerrln("Insufficient VRAM")
|
||||
return 1
|
||||
}
|
||||
|
||||
// register widgets
|
||||
_fsh.registerNewWidget(clockWidget)
|
||||
_fsh.registerNewWidget(calendarWidget)
|
||||
_fsh.registerNewWidget(todoWidget)
|
||||
_fsh.registerNewWidget(quickAccessWidget)
|
||||
|
||||
// screen init
|
||||
con.color_pair(254, 255)
|
||||
con.clear()
|
||||
con.curs_set(0)
|
||||
graphics.clearPixels(255)
|
||||
graphics.clearPixels2(255)
|
||||
graphics.setFramebufferScroll(0,0)
|
||||
_fsh.drawWallpaper()
|
||||
_fsh.drawTitlebar()
|
||||
|
||||
|
||||
// TEST
|
||||
con.move(2,1);
|
||||
print("Hit backspace to exit")
|
||||
|
||||
// TODO update for events: key down (updates some widgets), timer (updates clock and calendar widgets)
|
||||
while (true) {
|
||||
captureUserInput();
|
||||
if (getKeyPushed(0) == 67) break;
|
||||
|
||||
_fsh.widgets["com.fsh.clock"].draw(25, 3);
|
||||
_fsh.widgets["com.fsh.calendar"].draw(12, 8);
|
||||
_fsh.widgets["com.fsh.todo_list"].draw(10, 17);
|
||||
_fsh.widgets["com.fsh.quick_access"].draw(47, 8);
|
||||
|
||||
sys.spin();sys.spin()
|
||||
}
|
||||
|
||||
con.move(3,1);
|
||||
con.color_pair(201,255);
|
||||
print("cya!");
|
||||
|
||||
let konsht = 3412341241;
|
||||
println(konsht);
|
||||
|
||||
let pppp = graphics.getCursorYX();
|
||||
println(pppp.toString());
|
||||
BIN
assets/disk0/tvdos/bin/gfont.gz
LFS
BIN
assets/disk0/tvdos/bin/gfont.gz
LFS
Binary file not shown.
@@ -1,43 +0,0 @@
|
||||
var w = 560;
|
||||
var h = 448;
|
||||
var hwoff = 1048576;
|
||||
|
||||
function inthash(x) {
|
||||
x = ((x >> 16) ^ x) * 0x45d9f3b;
|
||||
x = ((x >> 16) ^ x) * 0x45d9f3b;
|
||||
x = (x >> 16) ^ x;
|
||||
return x;
|
||||
}
|
||||
|
||||
var rng = Math.floor(Math.random() * 2147483647) + 1;
|
||||
|
||||
while (!con.hitterminate()) {
|
||||
|
||||
var tstart = sys.nanoTime();
|
||||
|
||||
for (var y = 0; y < 360; y++) {
|
||||
for (var x = 0; x < w; x++) {
|
||||
var palnum = 20 * Math.floor(y / 30) + Math.floor(x / 28);
|
||||
sys.poke(-(y * w + x + 1) - hwoff, inthash(palnum + rng));
|
||||
}
|
||||
}
|
||||
|
||||
for (var y = 360; y < h; y++) {
|
||||
for (var x = 0; x < w; x++) {
|
||||
var palnum = 240 + Math.floor(x / 35);
|
||||
sys.poke(-(y * w + x + 1) - hwoff, palnum);
|
||||
}
|
||||
}
|
||||
|
||||
/*for (var k = 0; k < 2560; k++) {
|
||||
sys.poke(-(253952 + k + 1) - hwoff, -2); // transparent
|
||||
sys.poke(-(253952 + 2560 + k + 1) - hwoff, -1); // white
|
||||
/*sys.poke(-(253952 + 2560*2 + k + 1) - hwoff, Math.round(Math.random() * 255));*/
|
||||
//}*/
|
||||
|
||||
rng = inthash(rng);
|
||||
|
||||
var tend = sys.nanoTime();
|
||||
|
||||
println("Apparent FPS: " + (1000000000 / (tend - tstart)));
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
const SCRW = 560
|
||||
const SCRH = 448
|
||||
const CHARW = 7
|
||||
const CHARH = 16
|
||||
const ROWS = 28
|
||||
const COLS = 80
|
||||
|
||||
const TITLEBAR_COL = 254
|
||||
const TOOLBAR_COL = 64
|
||||
const TOOLBAR_COL2 = 69
|
||||
|
||||
let isFileThere = filesystem.open("A", "/tvdos/bin/gfont.gz", "R")
|
||||
if (isFileThere != 0) {
|
||||
printerrln("Main Font file not found")
|
||||
return isFileThere
|
||||
}
|
||||
|
||||
|
||||
const fontMainTexBytes = gzip.decomp(filesystem.readAll("A"))
|
||||
const fontMainTex = new GL.Texture(CHARW*16, CHARH*16, fontMainTexBytes)
|
||||
const fontMain = new GL.SpriteSheet(CHARW, CHARH, fontMainTex)
|
||||
|
||||
function drawInit() {
|
||||
con.reset_graphics();con.curs_set(0);con.clear();
|
||||
graphics.clearPixels(255);graphics.resetPalette();graphics.setFramebufferScroll(0,0);
|
||||
for(let i=0;i<SCRH;i++)graphics.setLineOffset(i,0)
|
||||
}
|
||||
|
||||
function drawUI(scrollY) {
|
||||
for (let y = 0; y < CHARH; y++) for (let x = 0; x < SCRW; x++) graphics.plotPixel(x, (y+scrollY) % SCRH, TITLEBAR_COL)
|
||||
for (let y = 416; y < 432; y++) for (let x = 0; x < SCRW; x++) graphics.plotPixel(x, (y+scrollY) % SCRH, TOOLBAR_COL)
|
||||
for (let y = 432; y < 448; y++) for (let x = 0; x < SCRW; x++) graphics.plotPixel(x, (y+scrollY) % SCRH, TOOLBAR_COL2)
|
||||
}
|
||||
|
||||
/*
|
||||
* @param codepoint unicode code point
|
||||
* @param col x-position of the character in the terminal, ONE-BASED INDEX
|
||||
* @param row y-position of the character in the terminal, ONE-BASED INDEX
|
||||
* @param bgcol background colour of the character
|
||||
* @param fgcol foreground colour of the character
|
||||
* @param scrollY `graphics.getFramebufferScroll()[1]`
|
||||
*/
|
||||
function paintGlyph(codepoint, col, row, bgcol, fgcol, scrollY) {
|
||||
let sheet = fontMain
|
||||
let xi = codepoint % 16
|
||||
let yi = codepoint / 16
|
||||
|
||||
GL.drawSprite(sheet, xi, yi, CHARW*(col - 1), (CHARH*(row|0) + scrollY) % SCRH, fgcol, bgcol)
|
||||
}
|
||||
|
||||
|
||||
drawInit()
|
||||
drawUI(graphics.getFramebufferScroll()[1])
|
||||
|
||||
let ttyFore = 252
|
||||
let ttyBack = 255
|
||||
let curs = 0
|
||||
|
||||
print = function(str) {
|
||||
if ((typeof str === 'string' || str instanceof String) && str.length > 0) {
|
||||
let scrollY = graphics.getFramebufferScroll()[1]
|
||||
let cp = unicode.utf8toCodepoints(str)
|
||||
for (let i = 0; i < cp.length; i++) {
|
||||
let c = cp[i]
|
||||
|
||||
if (10 == c || 13 == c) {
|
||||
curs = ((curs / COLS)|0) * COLS + COLS
|
||||
}
|
||||
else {
|
||||
paintGlyph(c, 1+(curs % COLS), 1+(curs / COLS), ttyBack, ttyFore, scrollY)
|
||||
curs += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO basically port the GraphicsAdapter.kt into here
|
||||
|
||||
Reference in New Issue
Block a user