Files
tsvm/assets/disk0/tvdos/TVDOS.SYS
2025-10-03 23:28:23 +09:00

1542 lines
51 KiB
Plaintext

let _fromCharCode = b => String.fromCharCode((b >= 0) ? b : 256 + b)
function btostr(bytes) {
let s = ""
for (let i = 0; i < bytes.length; i++) {
s += _fromCharCode(bytes[i])
}
return s
}
// define exceptions
function InterruptedException(m) {
this.message = m;
this.stack = (new Error()).stack;
};
InterruptedException.prototype = Object.create(Error.prototype);
InterruptedException.prototype.name = 'InterruptedException';
InterruptedException.prototype.constructor = InterruptedException;
function IllegalAccessException(m) {
this.message = m;
this.stack = (new Error()).stack;
};
IllegalAccessException.prototype = Object.create(Error.prototype);
IllegalAccessException.prototype.name = 'IllegalAccessException';
IllegalAccessException.prototype.constructor = IllegalAccessException;
class SIG {
constructor(name, number) {
this.name = "SIG" + name;
this.number = number|0;
}
}
const SIGTERM = new SIG("TERM",15);
const SIGSEGV = new SIG("SEGV",11);
class PmemFSdir {
constructor(targetpath) {
if (targetpath === undefined) {
this.data = "" // the values is not used by anything :p
return
}
if (typeof targetpath === 'string' || targetpath instanceof String) {
this.target = targetpath
}
else {
throw Error("Invalid type for directory")
}
}
}
class PmemFSfile {
constructor(bytes) {
if (bytes === undefined) {
this.data = ""
return
}
// string representation (preferable)
if (typeof bytes === 'string' || bytes instanceof String) {
this.data = bytes
}
// Javascript array OR JVM byte[]
else if (Array.isArray(bytes) || bytes.toString().startsWith("[B")) {
this.bdata = bytes[i]
}
else {
throw Error("Invalid type for directory")
}
}
dataAsString() {
if (this.data !== undefined) return this.data
this.data = ""
for (let i = 0; i < bytes.length; i++) {
this.data += String.fromCharCode(bytes[i])
}
return this.data
}
dataAsBytes() {
if (this.bdata !== undefined) return this.bdata
this.bdata = new Int8Array(this.data.length)
for (let i = 0; i < this.data.length; i++) {
let p = this.data.charCodeAt(i)
this.bdata[i] = (p > 127) ? p - 255 : p
}
return this.bdata
}
}
function generateRandomHashStr(len) {
let cs = 'qwfpgarstdzxcvbjluyhneiokmQWFPGARSTDZXCVBJLUYHNEIOKM';
let s = '';
for (let i = 0; i < len; i++) {
s += cs[(Math.random()*cs.length)|0];
}
return s;
}
// define TVDOS
const _TVDOS = {};
_TVDOS.VERSION = "1.0";
_TVDOS.DRIVES = {}; // Object where key-value pair is <drive-letter> : [serial-port, drive-number]
_TVDOS.DRIVEFS = {}; // filesystem driver for the drive letter
_TVDOS.DRIVEINFO = {};
// actually figure out the drive letter association
// Drive A is always the device we're currently on
_TVDOS.DRIVES["A"] = _BIOS.FIRST_BOOTABLE_PORT
_TVDOS.DRIVEFS["A"] = "SERIAL"
_TVDOS.DRIVEINFO["A"] = {
name: com.sendMessageGetBytes(_BIOS.FIRST_BOOTABLE_PORT[0], "DEVNAM\x17").init(),
type: com.sendMessageGetBytes(_BIOS.FIRST_BOOTABLE_PORT[0], "DEVTYP\x17").substring(0,4)
}
_TVDOS.TMPFS = {
".": new PmemFSdir("TMP/"),
"..": new PmemFSdir("TMP/"),
};
// probe filesystem devices
let devnameToDriver = {"PRNT":"LP","STOR":"SERIAL","COMM":"COM","HTTP":"NET","TMP":"PMEMFS"}
let currentDriveLetter = ["A","B","C","D"]
for (let portNo = 1; portNo < 4; portNo++) {
if (com.areYouThere(portNo)) {
com.sendMessage(portNo, "DEVRST\x17")
sys.spin()
let devname = com.sendMessageGetBytes(portNo, "DEVTYP\x17").substring(0,4)
let driver = devnameToDriver[devname]
// serial.println(`port: ${portNo}, devname: ${devname} ${com.sendMessageGetBytes(portNo, "DEVNAM\x17")}`)
if (driver) {
let dlet = currentDriveLetter[portNo]
_TVDOS.DRIVEFS[dlet] = driver
_TVDOS.DRIVES[dlet] = [portNo, 1]
_TVDOS.DRIVEINFO[dlet] = {
name: com.sendMessageGetBytes(portNo, "DEVNAM\x17").init(),
type: devname
}
}
}
}
_TVDOS.DRV = {}
_TVDOS.getPath = function() {
return [''].concat(_TVDOS.variables.PATH.split(';'));
};
// initial values
_TVDOS.variables = {
DOSDIR: "\\tvdos",
LANG: "EN",
KEYBOARD: "us_qwerty",
PATH: "\\tvdos\\bin;\\home",
PATHEXT: ".com;.bat;.app;.js",
HELPPATH: "\\tvdos\\help",
OS_NAME: "TSVM Disk Operating System",
OS_VERSION: _TVDOS.VERSION
};
Object.freeze(_TVDOS);
///////////////////////////////////////////////////////////////////////////////
_TVDOS.DRV.FS = {}
class TVDOSFileDescriptor {
constructor(path0, driverID) {
if (path0.startsWith("$")) {
let path1 = path0.substring(3)
let slashPos = path1.indexOf("/")
let devName = path1.substring(0, (slashPos < 0) ? path1.length : slashPos)
if (!files.reservedNames.includes(devName)) {
throw Error(`${devName} is not a valid device file`)
}
this._driveLetter = undefined
this._path = path0
this._driverID = `DEV${devName}`
this._driver = _TVDOS.DRV.FS[`DEV${devName}`] // can't just put `driverID` here
}
else {
let p = path0.replaceAll("/", "\\")
this._driveLetter = p[0]
p = p.substring(2) // detaches A:
// remove trailing slashes
while (p.endsWith("\\")) {
p = p.substring(0, p.length - 1)
}
// remove initial slashes...
while (p.startsWith("\\")) {
p = p.substring(1)
}
// then add our own
p = "\\" + p
this._path = p
if (driverID == undefined) driverID = "UNDEFINED"
this._driverID = driverID
this._driver = _TVDOS.DRV.FS[driverID]
// serial.println(`TVDOSFileDescriptor input path: ${path0}, p = ${p}, driveLetter = ${this._driveLetter}, path = ${this._path}, driver = ${driverID}`)
}
}
get size() {
return this.driver.getFileLen(this)
}
get path() {
return this._path
}
get driverID() {
return this._driverID
}
get driver() {
return this._driver
}
get driveLetter() {
return this._driveLetter
}
get fullPath() {
return `${this._driveLetter}:${this._path}`
}
/** reads the file bytewise and puts it to the specified memory address
* @param count optional -- how many bytes to read
* @param offset optional -- how many bytes to skip initially
*/
pread(ptr, count, offset) {
this.driver.pread(this, ptr, count, offset)
}
/** @return bytewise (actually SIGNED Int8) contents of the file in JS array */
bread() {
return this.driver.bread(this)
}
/** @return textwise contents of the file in JS string */
sread() {
return this.driver.sread(this)
}
/** writes the bytes stored in the memory[ptr .. ptr+count-1] to file[offset .. offset+count-1]
* - @param offset is optional
*/
pwrite(ptr, count, offset) {
this.driver.pwrite(this, ptr, count, offset)
}
/** @param bytes bytewise contents to write, in JS array */
bwrite(bytes) {
this.driver.bwrite(this, bytes)
}
/** @param string stringwise contents to write, in JS array */
swrite(string) {
this.driver.swrite(this, string)
}
/**
* @param ptr where the byes are (offsets from the base ptr can be coded as `baseptr + offset`)
* @param count how many bytes to append
*/
pappend(ptr, count) {
this.driver.pappend(this, ptr, count)
}
bappend(bytes) {
this.driver.bappend(this, bytes)
}
sappend(string) {
this.driver.sappend(this, string)
}
flush() {
this.driver.flush(this)
}
close() {
this.driver.close(this)
}
get isDirectory() {
return this.driver.isDirectory(this)
}
get name() {
return this.path.split("\\").last()
}
get extension() {
let fname = this.name
let dotpos = fname.lastIndexOf('.')
if (dotpos < 0) return ''
else return fname.substring(dotpos+1)
}
get parentPath() {
// return this.path.split("\\").init().join("\\")
let li = this.path.lastIndexOf("\\")
return this.path.substring(0, li)
}
list() {
return (!this.isDirectory) ? undefined : this.driver.listFiles(this)
}
/** When the file does not exist, mkfile() will be called; if you want to make a directory, use mkdir() */
touch() {
return this.driver.touch(this)
}
/** Creates the directory named by this abstract pathname, including any necessary but nonexistent parent directories */
mkDir() {
return this.driver.mkDir(this)
}
mkFile() {
return this.driver.mkFile(this)
}
remove() {
return this.driver.remove(this)
}
get exists() {
return this.driver.exists(this)
}
}
///////////////////////////////////////////////////////////////////////////////
_TVDOS.DRV.FS.SERIAL = {}
// TODO \dev\null zero full random lp fb0..fb3 mem pmem0..pmem7 com1..com4
_TVDOS.DRV.FS.SERIAL._toPorts = (driveLetter) => {
if (driveLetter.toUpperCase === undefined) {
throw Error("'"+driveLetter+"' (type: "+typeof driveLetter+") is not a valid drive letter")
}
var port = _TVDOS.DRIVES[driveLetter.toUpperCase()]
if (port === undefined) {
throw Error("Drive letter '" + driveLetter.toUpperCase() + "' does not exist")
}
return port
}
_TVDOS.DRV.FS.SERIAL._openr = (fd) => {
let port = _TVDOS.DRV.FS.SERIAL._toPorts(fd.driveLetter)
_TVDOS.DRV.FS.SERIAL._flush(port[0]);_TVDOS.DRV.FS.SERIAL._close(port[0])
com.sendMessage(port[0], "OPENR"+'"'+fd.path+'",'+port[1])
return com.getStatusCode(port[0])
}
_TVDOS.DRV.FS.SERIAL._openw = (fd) => {
let port = _TVDOS.DRV.FS.SERIAL._toPorts(fd.driveLetter)
_TVDOS.DRV.FS.SERIAL._flush(port[0]);_TVDOS.DRV.FS.SERIAL._close(port[0])
com.sendMessage(port[0], "OPENW"+'"'+fd.path+'",'+port[1])
return com.getStatusCode(port[0])
}
_TVDOS.DRV.FS.SERIAL._opena = (fd) => {
let port = _TVDOS.DRV.FS.SERIAL._toPorts(fd.driveLetter)
_TVDOS.DRV.FS.SERIAL._flush(port[0]);_TVDOS.DRV.FS.SERIAL._close(port[0])
com.sendMessage(port[0], "OPENA"+'"'+fd.path+'",'+port[1])
return com.getStatusCode(port[0])
}
_TVDOS.DRV.FS.SERIAL._close = (portNo) => {
com.sendMessage(portNo, "CLOSE")
}
_TVDOS.DRV.FS.SERIAL._flush = (portNo) => {
com.sendMessage(portNo, "FLUSH")
}
_TVDOS.DRV.FS.SERIAL.close = (fd) => {
let portNo = _TVDOS.DRV.FS.SERIAL._toPorts(fd.driveLetter)
com.sendMessage(portNo[0], "CLOSE")
}
_TVDOS.DRV.FS.SERIAL.flush = (fd) => {
let portNo = _TVDOS.DRV.FS.SERIAL._toPorts(fd.driveLetter)
com.sendMessage(portNo[0], "FLUSH")
}
_TVDOS.DRV.FS.SERIAL.getFileLen = (fd) => {
if (129 == _TVDOS.DRV.FS.SERIAL._openr(fd)) throw Error(`No such file: ${fd.fullPath}`)
let port = _TVDOS.DRV.FS.SERIAL._toPorts(fd.driveLetter)
com.sendMessage(port[0], "GETLEN")
var response = com.getStatusCode(port[0])
if (135 == response) {
throw Error("File not opened")
}
if (response < 0 || response >= 128) {
throw Error("Reading a file failed with "+response)
}
return Number(com.pullMessage(port[0]))
}
_TVDOS.DRV.FS.SERIAL.pread = (fd, ptr, count, offset) => {
if (129 == _TVDOS.DRV.FS.SERIAL._openr(fd)) throw Error(`No such file: ${fd.fullPath}`)
let port = _TVDOS.DRV.FS.SERIAL._toPorts(fd.driveLetter)
com.sendMessage(port[0], "READ")
let response = com.getStatusCode(port[0])
if (135 == response) {
throw Error("File not opened")
}
if (response < 0 || response >= 128) {
throw Error("Reading a file failed with "+response)
}
dma.comToRam(port[0], offset || 0, ptr, count || 0)
}
_TVDOS.DRV.FS.SERIAL.sread = (fd) => {
if (129 == _TVDOS.DRV.FS.SERIAL._openr(fd)) throw Error(`No such file: ${fd.fullPath}`)
let port = _TVDOS.DRV.FS.SERIAL._toPorts(fd.driveLetter)
com.sendMessage(port[0], "READ")
let response = com.getStatusCode(port[0])
if (135 == response) {
throw Error("File not opened")
}
if (response < 0 || response >= 128) {
throw Error("Reading a file failed with "+response)
}
return com.pullMessage(port[0])
}
_TVDOS.DRV.FS.SERIAL.bread = (fd) => {
let str = _TVDOS.DRV.FS.SERIAL.sread(fd)
let bytes = new Int8Array(str.length)
for (let i = 0; i < str.length; i++) {
// let p = str.charCodeAt(i)
// bytes[i] = (p > 127) ? p - 255 : p
bytes[i] = str.charCodeAt(i)
}
return bytes
}
_TVDOS.DRV.FS.SERIAL.pwrite = (fd, ptr, count, offset) => {
if (offset) throw Error(`Write offset not supported on Serial device such as disk drives`)
let rrrr = _TVDOS.DRV.FS.SERIAL._openw(fd); if (rrrr != 0) throw Error("Writing a file failed with "+rrrr)
let port = _TVDOS.DRV.FS.SERIAL._toPorts(fd.driveLetter)
dma.ramToCom(ptr, port[0], count)
}
_TVDOS.DRV.FS.SERIAL.swrite = (fd, string) => {
let rrrr = _TVDOS.DRV.FS.SERIAL._openw(fd); if (rrrr != 0) throw Error("Writing a file failed with "+rrrr)
let port = _TVDOS.DRV.FS.SERIAL._toPorts(fd.driveLetter)
com.sendMessage(port[0], "WRITE"+string.length)
let response = com.getStatusCode(port[0])
if (135 == response) {
throw Error("File not opened")
}
if (response < 0 || response >= 128) {
throw Error("Writing a file failed with "+response)
}
com.sendMessage(port[0], string)
_TVDOS.DRV.FS.SERIAL._flush(port[0]);_TVDOS.DRV.FS.SERIAL._close(port[0])
}
_TVDOS.DRV.FS.SERIAL.bwrite = (fd, bytes) => { // pwrite replaces DMA.ramToCom
let string = btostr(bytes) // no spreading: has length limit
_TVDOS.DRV.FS.SERIAL.swrite(fd, string)
}
_TVDOS.DRV.FS.SERIAL.pappend = (fd, ptr, count) => {
let rrrr = _TVDOS.DRV.FS.SERIAL._opena(fd); if (rrrr != 0) throw Error("Appending to a file failed with "+rrrr)
let port = _TVDOS.DRV.FS.SERIAL._toPorts(fd.driveLetter)
dma.ramToCom(ptr, port[0], count)
}
_TVDOS.DRV.FS.SERIAL.bappend = (fd, bytes) => { // pwrite replaces DMA.ramToCom
let string = btostr(bytes) // no spreading: has length limit
_TVDOS.DRV.FS.SERIAL.sappend(fd, string)
}
_TVDOS.DRV.FS.SERIAL.sappend = (fd, string) => {
let rrrr = _TVDOS.DRV.FS.SERIAL._opena(fd); if (rrrr != 0) throw Error("Appending to a file failed with "+rrrr)
let port = _TVDOS.DRV.FS.SERIAL._toPorts(fd.driveLetter)
com.sendMessage(port[0], "WRITE"+string.length)
let response = com.getStatusCode(port[0])
if (135 == response) {
throw Error("File not opened")
}
if (response < 0 || response >= 128) {
throw Error("Appending to a file failed with "+response)
}
com.sendMessage(port[0], string)
_TVDOS.DRV.FS.SERIAL._flush(port[0]);_TVDOS.DRV.FS.SERIAL._close(port[0])
}
_TVDOS.DRV.FS.SERIAL.isDirectory = (fd) => {
if (129 == _TVDOS.DRV.FS.SERIAL._openr(fd)) return false // file not exists
let port = _TVDOS.DRV.FS.SERIAL._toPorts(fd.driveLetter)
com.sendMessage(port[0], "LISTFILES")
let response = com.getStatusCode(port[0])
return (response === 0)
}
_TVDOS.DRV.FS.SERIAL.listFiles = (fd) => {
if (129 == _TVDOS.DRV.FS.SERIAL._openr(fd)) throw Error(`No such file: ${fd.fullPath}`)
let port = _TVDOS.DRV.FS.SERIAL._toPorts(fd.driveLetter)
com.sendMessage(port[0], "LISTFILES")
let response = com.getStatusCode(port[0])
if (response !== 0) return undefined
let rawStr = com.pullMessage(port[0]) // {\x11 | \x12} <name> [ \x1E {\x11 | \x12} <name> ] \x17
let parentPath = fd.fullPath
return rawStr.substring(0, rawStr.length).split('\x1E').map(it => new TVDOSFileDescriptor(`${parentPath}/${it.substring(1)}`, "SERIAL"))
}
_TVDOS.DRV.FS.SERIAL.touch = (fd) => {
let rrrr = _TVDOS.DRV.FS.SERIAL._openw(fd); if (rrrr != 0) throw Error("Touching a file failed with "+rrrr)
let port = _TVDOS.DRV.FS.SERIAL._toPorts(fd.driveLetter)
com.sendMessage(port[0], "TOUCH")
let response = com.getStatusCode(port[0])
return (response === 0)
}
_TVDOS.DRV.FS.SERIAL.mkDir = (fd) => {
let rrrr = _TVDOS.DRV.FS.SERIAL._openw(fd); if (rrrr != 0) throw Error("Mkdir a file failed with "+rrrr)
let port = _TVDOS.DRV.FS.SERIAL._toPorts(fd.driveLetter)
com.sendMessage(port[0], "MKDIR")
let response = com.getStatusCode(port[0])
if (response < 0 || response >= 128) {
var status = com.getDeviceStatus(port[0])
throw Error(`Creating a directory failed with (${response}): ${status.message}\n`)
}
return (response === 0) // possible status codes: 0 (success), 1 (fail)
}
_TVDOS.DRV.FS.SERIAL.mkFile = (fd) => {
let rrrr = _TVDOS.DRV.FS.SERIAL._openw(fd); if (rrrr != 0) throw Error("Mkfile failed with "+rrrr)
let port = _TVDOS.DRV.FS.SERIAL._toPorts(fd.driveLetter)
com.sendMessage(port[0], "MKFILE")
let response = com.getStatusCode(port[0])
return (response === 0)
}
_TVDOS.DRV.FS.SERIAL.remove = (fd) => {
let rrrr = _TVDOS.DRV.FS.SERIAL._openw(fd); if (rrrr != 0) throw Error("Writing a file failed with "+rrrr)
let port = _TVDOS.DRV.FS.SERIAL._toPorts(fd.driveLetter)
com.sendMessage(port[0], "DELETE")
let response = com.getStatusCode(port[0])
return response
}
_TVDOS.DRV.FS.SERIAL.exists = (fd) => {
return (0 == _TVDOS.DRV.FS.SERIAL._openr(fd))
}
Object.freeze(_TVDOS.DRV.FS.SERIAL)
///////////////////////////////////////////////////////////////////////////////
_TVDOS.DRV.FS.DEVRND = {}
_TVDOS.DRV.FS.DEVRND._mkrndbytes = (length) => {
let bytes = new Int8Array(length)
let timeNow = sys.currentTimeInMills()
let state = 12345678910 + timeNow
for (let i = 0; i < length; i++) {
let x = state
x ^= x << 13
x ^= x >> 7
x ^= x << 17
state = x
bytes[i] = state & 255
}
return bytes
}
_TVDOS.DRV.FS.DEVRND.pread = (fd, ptr, count, offset) => {
let bytes = _TVDOS.DRV.FS.DEVRND._mkrndbytes(count + offset)
for (let i = 0; i < count; i++) {
sys.poke(ptr + i, bytes[offset + i])
}
}
_TVDOS.DRV.FS.DEVRND.pwrite = (fd, ptr, count, offset) => {}
_TVDOS.DRV.FS.DEVRND.bwrite = (fd, bytes) => {}
_TVDOS.DRV.FS.DEVRND.swrite = (fd, string) => {}
_TVDOS.DRV.FS.DEVRND.flush = () => {}
_TVDOS.DRV.FS.DEVRND.close = () => {}
_TVDOS.DRV.FS.DEVRND.isDirectory = () => false
_TVDOS.DRV.FS.DEVRND.listFiles = () => undefined
_TVDOS.DRV.FS.DEVRND.touch = () => {}
_TVDOS.DRV.FS.DEVRND.mkDir = () => {}
_TVDOS.DRV.FS.DEVRND.mkFile = () => {}
_TVDOS.DRV.FS.DEVRND.remove = () => {}
_TVDOS.DRV.FS.DEVRND.exists = () => true
Object.freeze(_TVDOS.DRV.FS.DEVRND)
///////////////////////////////////////////////////////////////////////////////
_TVDOS.DRV.FS.UNDEFINED = {}
_TVDOS.DRV.FS.UNDEFINED.pread = (fd, ptr, count, offset) => { throw Error("File does not exist") }
_TVDOS.DRV.FS.UNDEFINED.bread = (fd) => { throw Error("File does not exist") }
_TVDOS.DRV.FS.UNDEFINED.sread = (fd) => { throw Error("File does not exist") }
_TVDOS.DRV.FS.UNDEFINED.pwrite = (fd, ptr, count, offset) => { throw Error("File does not exist") }
_TVDOS.DRV.FS.UNDEFINED.bwrite = (fd, bytes) => { throw Error("File does not exist") }
_TVDOS.DRV.FS.UNDEFINED.swrite = (fd, string) => { throw Error("File does not exist") }
_TVDOS.DRV.FS.UNDEFINED.pappend = (fd, ptr, count, offset) => { throw Error("File does not exist") }
_TVDOS.DRV.FS.UNDEFINED.bappend = (fd, bytes) => { throw Error("File does not exist") }
_TVDOS.DRV.FS.UNDEFINED.sappend = (fd, string) => { throw Error("File does not exist") }
_TVDOS.DRV.FS.UNDEFINED.flush = () => { throw Error("File does not exist") }
_TVDOS.DRV.FS.UNDEFINED.close = () => { throw Error("File does not exist") }
_TVDOS.DRV.FS.UNDEFINED.isDirectory = () => false
_TVDOS.DRV.FS.UNDEFINED.listFiles = () => { throw Error("File does not exist") }
_TVDOS.DRV.FS.UNDEFINED.touch = () => { throw Error("File does not exist") }
_TVDOS.DRV.FS.UNDEFINED.mkDir = () => { throw Error("File does not exist") }
_TVDOS.DRV.FS.UNDEFINED.mkFile = () => { throw Error("File does not exist") }
_TVDOS.DRV.FS.UNDEFINED.remove = () => { throw Error("File does not exist") }
_TVDOS.DRV.FS.UNDEFINED.exists = () => false
Object.freeze(_TVDOS.DRV.FS.UNDEFINED)
///////////////////////////////////////////////////////////////////////////////
_TVDOS.DRV.FS.DEVNUL = {}
_TVDOS.DRV.FS.DEVNUL.pread = (fd, ptr, count, offset) => {
for (let i = 0; i < count; i++) {
sys.poke(ptr + i, -1)
}
}
_TVDOS.DRV.FS.DEVNUL.bread = (fd) => { return [] }
_TVDOS.DRV.FS.DEVNUL.sread = (fd) => { return "" }
_TVDOS.DRV.FS.DEVNUL.pwrite = (fd, ptr, count, offset) => {}
_TVDOS.DRV.FS.DEVNUL.bwrite = (fd, bytes) => {}
_TVDOS.DRV.FS.DEVNUL.swrite = (fd, string) => {}
_TVDOS.DRV.FS.DEVNUL.flush = () => {}
_TVDOS.DRV.FS.DEVNUL.close = () => {}
_TVDOS.DRV.FS.DEVNUL.isDirectory = () => false
_TVDOS.DRV.FS.DEVNUL.listFiles = () => undefined
_TVDOS.DRV.FS.DEVNUL.touch = () => {}
_TVDOS.DRV.FS.DEVNUL.mkDir = () => {}
_TVDOS.DRV.FS.DEVNUL.mkFile = () => {}
_TVDOS.DRV.FS.DEVNUL.remove = () => {}
_TVDOS.DRV.FS.DEVNUL.exists = () => true
Object.freeze(_TVDOS.DRV.FS.DEVNUL)
///////////////////////////////////////////////////////////////////////////////
_TVDOS.DRV.FS.DEVZERO = {}
_TVDOS.DRV.FS.DEVZERO.pread = (fd, ptr, count, offset) => {
for (let i = 0; i < count; i++) {
sys.poke(ptr + i, 0)
}
}
_TVDOS.DRV.FS.DEVZERO.pwrite = (fd, ptr, count, offset) => {}
_TVDOS.DRV.FS.DEVZERO.bwrite = (fd, bytes) => {}
_TVDOS.DRV.FS.DEVZERO.swrite = (fd, string) => {}
_TVDOS.DRV.FS.DEVZERO.flush = () => {}
_TVDOS.DRV.FS.DEVZERO.close = () => {}
_TVDOS.DRV.FS.DEVZERO.isDirectory = () => false
_TVDOS.DRV.FS.DEVZERO.listFiles = () => undefined
_TVDOS.DRV.FS.DEVZERO.touch = () => {}
_TVDOS.DRV.FS.DEVZERO.mkDir = () => {}
_TVDOS.DRV.FS.DEVZERO.mkFile = () => {}
_TVDOS.DRV.FS.DEVZERO.remove = () => {}
_TVDOS.DRV.FS.DEVZERO.exists = () => true
Object.freeze(_TVDOS.DRV.FS.DEVZERO)
///////////////////////////////////////////////////////////////////////////////
_TVDOS.DRV.FS.DEVCON = {}
_TVDOS.DRV.FS.DEVCON.pread = (fd, ptr, count, offset) => {
let mem = graphics.getGpuMemBase()
let consize = Math.min(count, grapihcs.getTermDimension().sum())
for (let i = 0; i < consize; i++) {
sys.poke(ptr + i, sys.peek(mem - 5122 - i))
}
}
_TVDOS.DRV.FS.DEVCON.bread = (fd) => {
let mem = graphics.getGpuMemBase()
let consize = Math.min(count, grapihcs.getTermDimension().sum())
let r = new Int8Array(consize)
for (let i = 0; i < consize; i++) {
// let p = sys.peek(mem - 5122 - i)
// r[i] = (p > 127) ? p - 255 : p
r[i] = sys.peek(mem - 5122 - i)
}
return r
}
_TVDOS.DRV.FS.DEVCON.sread = (fd) => {
let mem = graphics.getGpuMemBase()
let consize = Math.min(count, grapihcs.getTermDimension().sum())
let r = ''
for (let i = 0; i < consize; i++) {
r += String.fromCharCode(sys.peek(mem - 5122 - i))
}
return r
}
_TVDOS.DRV.FS.DEVCON.pwrite = (fd, ptr, count, offset) => {}
_TVDOS.DRV.FS.DEVCON.bwrite = (fd, bytes) => {
let string = btostr(bytes) // no spreading: has length limit
_TVDOS.DRV.FS.DEVZERO.swrite(fd, string)
}
_TVDOS.DRV.FS.DEVCON.swrite = (fd, string) => {
print(string)
}
_TVDOS.DRV.FS.DEVCON.flush = () => {}
_TVDOS.DRV.FS.DEVCON.close = () => {}
_TVDOS.DRV.FS.DEVCON.isDirectory = () => false
_TVDOS.DRV.FS.DEVCON.listFiles = () => undefined
_TVDOS.DRV.FS.DEVCON.touch = () => {}
_TVDOS.DRV.FS.DEVCON.mkDir = () => {}
_TVDOS.DRV.FS.DEVCON.mkFile = () => {}
_TVDOS.DRV.FS.DEVCON.remove = () => {}
_TVDOS.DRV.FS.DEVCON.exists = () => true
Object.freeze(_TVDOS.DRV.FS.DEVCON)
///////////////////////////////////////////////////////////////////////////////
let mkvtcontext = (index, VMEM) => {
return { // VT0 is reserved for symlink to physical terminal
isActive: (index === 0 || index === "0"),
buffer: (index === 0 || index === "0") ? undefined : VMEM
// TODO: getCursorYX, setCursorYX, setCursorX, setCursorY, getTextForeBack, setTextFore, setTextBack
// all read/write form the VMEM
}
}
// byte-to-byte copy of the 512 KB of VRAM
function mkdevvt(index) {
// VT0 is reserved for symlink to the physical terminal
if (isNaN(index)) return false // check for non-numerics and undefined
if (index !== index || index <= 0) return false // check for NaN, 0 and negative values
// check if the device already exists
if (_TVDOS.DRV.FS['DEVVT'+index] != undefined) return false
let VDEV = {}
let VMEM = new Int8Array(512 * 1024)
VDEV.pread = (fd, ptr, count, offset) => {
for (let i = 0; i < (count || 0); i++) {
sys.poke(ptr + i, VMEM[i + (offset || 0)])
}
}
VDEV.bread = (fd) => {
return VMEM.slice()
}
VDEV.sread = (fd) => {
let r = ''
for (let i = 0; i < VMEM.length; i++) {
let c = VMEM[i]
r += String.fromCharCode((c < 0) ? 256 + c : c)
}
return r
}
VDEV.pwrite = (fd, ptr, count, offset) => {
for (let i = 0; i < (count || 0); i++) {
VMEM[i + (offset || 0)] = sys.peek(ptr + i)
}
}
VDEV.bwrite = (fd, bytes) => {
if (bytes.length == VMEM.length && bytes instanceof Int8Array) {
VMEM = bytes.slice()
}
else {
for (let i = 0; i < bytes.length; i++) {
VMEM[i] = bytes[i]
}
}
}
VDEV.swrite = (fd, string) => {
for (let i = 0; i < string.length; i++) {
VMEM[i] = string.charCodeAt(i)
}
}
VDEV.flush = () => {}
VDEV.close = () => {}
VDEV.isDirectory = () => false
VDEV.listFiles = () => undefined
VDEV.touch = () => {}
VDEV.mkDir = () => {}
VDEV.mkFile = () => {}
VDEV.remove = () => {}
VDEV.exists = () => true
_TVDOS.VT_CONTEXTS[index] = mkvtcontext(index, VMEM)
_TVDOS.DRV.FS['DEVVT'+index] = VDEV
Object.freeze(_TVDOS.DRV.FS['DEVVT'+index])
return true
}
///////////////////////////////////////////////////////////////////////////////
// physical video terminal
_TVDOS.DRV.FS.DEVPT = {}
_TVDOS.DRV.FS.DEVPT.pread = (fd, ptr, count, offset) => {
let mem = graphics.getGpuMemBase()
for (let i = 0; i < (count || 0); i++) {
sys.poke(ptr + i, sys.peek(mem - i))
}
}
_TVDOS.DRV.FS.DEVPT.bread = (fd) => {
let mem = graphics.getGpuMemBase()
let r = new Int8Array(512 * 1024)
for (let i = 0; i < r.length; i++) {
r[i] = sys.peek(mem - i)
}
}
_TVDOS.DRV.FS.DEVPT.sread = (fd) => {
let mem = graphics.getGpuMemBase()
let r = ''
for (let i = 0; i < 512 * 1024; i++) {
let c = sys.peek(mem - i)
r[i] = String.fromCharCode((c < 0) ? 256 + c : c)
}
}
_TVDOS.DRV.FS.DEVPT.pwrite = (fd, ptr, count, offset) => {
let mem = graphics.getGpuMemBase()
for (let i = 0; i < (count || 0); i++) {
sys.poke(mem - i, sys.peek(ptr + i))
}
}
_TVDOS.DRV.FS.DEVPT.bwrite = (fd, bytes) => {
let mem = graphics.getGpuMemBase()
for (let i = 0; i < bytes.length; i++) {
sys.poke(mem - i, bytes[i])
}
}
_TVDOS.DRV.FS.DEVPT.swrite = (fd, string) => {
let mem = graphics.getGpuMemBase()
for (let i = 0; i < string.length; i++) {
sys.poke(mem - i, string.charCodeAt(i))
}
}
_TVDOS.DRV.FS.DEVPT.flush = () => {}
_TVDOS.DRV.FS.DEVPT.close = () => {}
_TVDOS.DRV.FS.DEVPT.isDirectory = () => false
_TVDOS.DRV.FS.DEVPT.listFiles = () => undefined
_TVDOS.DRV.FS.DEVPT.touch = () => {}
_TVDOS.DRV.FS.DEVPT.mkDir = () => {}
_TVDOS.DRV.FS.DEVPT.mkFile = () => {}
_TVDOS.DRV.FS.DEVPT.remove = () => {}
_TVDOS.DRV.FS.DEVPT.exists = () => true
mkvtcontext(0)
Object.freeze(_TVDOS.DRV.FS.DEVPT)
///////////////////////////////////////////////////////////////////////////////
_TVDOS.DRV.FS.DEVFBIPF = {}
_TVDOS.DRV.FS.DEVFBIPF.pwrite = (fd, infilePtr, count, _2) => {
let decodefun = ([graphics.decodeIpf1, graphics.decodeIpf2])[sys.peek(infilePtr + 13)]
let width = sys.peek(infilePtr+8) | (sys.peek(infilePtr+9) << 8)
let height = sys.peek(infilePtr+10) | (sys.peek(infilePtr+11) << 8)
let hasAlpha = (sys.peek(infilePtr+12) != 0)
let ipfType = sys.peek(infilePtr+13)
let imgLen = sys.peek(infilePtr+24) | (sys.peek(infilePtr+25) << 8) | (sys.peek(infilePtr+26) << 16) | (sys.peek(infilePtr+27) << 24)
let ipfbuf = sys.malloc(imgLen)
gzip.decompFromTo(infilePtr + 28, count - 28, ipfbuf) // should return FBUF_SIZE
graphics.setGraphicsMode(4)
decodefun(ipfbuf, -1048577, -1310721, width, height, hasAlpha)
sys.free(ipfbuf)
}
_TVDOS.DRV.FS.DEVFBIPF.bwrite = (fd, bytes) => {
// TODO pread the file
let fp = sys.malloc(bytes.length)
bytes.forEach((it,i) => {
sys.poke(fp + i, it)
})
_TVDOS.DRV.FS.DEVFBIPF.pwrite(fd, fp)
sys.free(fp)
}
// _TVDOS.DRV.FS.DEVFBIPF will be write-only: no way to select IPF1/2 and/or transparency
_TVDOS.DRV.FS.DEVFBIPF.flush = () => {}
_TVDOS.DRV.FS.DEVFBIPF.close = () => {}
_TVDOS.DRV.FS.DEVFBIPF.isDirectory = () => false
_TVDOS.DRV.FS.DEVFBIPF.listFiles = () => undefined
_TVDOS.DRV.FS.DEVFBIPF.touch = () => {}
_TVDOS.DRV.FS.DEVFBIPF.mkDir = () => {}
_TVDOS.DRV.FS.DEVFBIPF.mkFile = () => {}
_TVDOS.DRV.FS.DEVFBIPF.remove = () => {}
_TVDOS.DRV.FS.DEVFBIPF.exists = () => true
Object.freeze(_TVDOS.DRV.FS.DEVFBIPF)
///////////////////////////////////////////////////////////////////////////////
_TVDOS.DRV.FS.DEVTMP = {}
_TVDOS.DRV.FS.DEVTMP.sread = (fd) => {
if (_TVDOS.TMPFS[fd.path] === undefined) throw Error(`No such file: ${fd.fullPath}`)
return _TVDOS.TMPFS[fd.path].dataAsString()
}
_TVDOS.DRV.FS.DEVTMP.bread = (fd) => {
if (_TVDOS.TMPFS[fd.path] === undefined) throw Error(`No such file: ${fd.fullPath}`)
return _TVDOS.TMPFS[fd.path].dataAsBytes()
}
_TVDOS.DRV.FS.DEVTMP.pread = (fd, ptr, count, offset) => {
if (_TVDOS.TMPFS[fd.path] === undefined) throw Error(`No such file: ${fd.fullPath}`)
let str = _TVDOS.TMPFS[fd.path].dataAsString()
for (let i = 0; i < count - (offset || 0); i++) {
sys.poke(ptr + i, String.charCodeAt(i + (offset || 0)))
}
}
_TVDOS.DRV.FS.DEVTMP.swrite = (fd, str) => {
_TVDOS.TMPFS[fd.path] = new PmemFSfile(str)
}
_TVDOS.DRV.FS.DEVTMP.bwrite = (fd, bytes) => {
_TVDOS.TMPFS[fd.path] = new PmemFSfile(bytes)
}
_TVDOS.DRV.FS.DEVTMP.pwrite = (fd, ptr, count, offset) => {
let appendstr = ""
for (let i = 0; i < count; i++) {
appendstr += String.fromCharCode(sys.peek(ptr + i))
}
let oldstr = (_TVDOS.TMPFS[fd.path] === undefined) ? "" : _TVDOS.TMPFS[fd.path].dataAsString().substring(0, offset || 0)
_TVDOS.TMPFS[fd.path] = new PmemFSfile(oldstr + appendstr)
}
_TVDOS.DRV.FS.DEVTMP.flush = (fd) => {}
_TVDOS.DRV.FS.DEVTMP.close = (fd) => {}
_TVDOS.DRV.FS.DEVTMP.isDirectory = (fd) => (_TVDOS.TMPFS[fd.path] != undefined && _TVDOS.TMPFS[fd.path] instanceof PmemFSdir)
_TVDOS.DRV.FS.DEVTMP.listFiles = (fd) => {
Object.keys(_TVDOS.TMPFS).filter(it => it.startsWith(fd.path)).map(it => new TVDOSFileDescriptor(`$:/TMP/${it}`))
}
_TVDOS.DRV.FS.DEVTMP.touch = (fd) => {}
_TVDOS.DRV.FS.DEVTMP.mkDir = (fd) => {
_TVDOS.TMPFS[fd.path] = new PmemFSdir(fd.path)
return true
}
_TVDOS.DRV.FS.DEVTMP.mkFile = (fd) => {
_TVDOS.TMPFS[fd.path] = new PmemFSfile("")
return true
}
_TVDOS.DRV.FS.DEVTMP.remove = (fd) => {
let path = _TVDOS.TMPFS[fd.path]
delete _TVDOS.TMPFS[fd.path]
// delete all the keys starting with _TVDOS.TMPFS[fd.path]
if (_TVDOS.DRV.FS.DEVTMP.isDirectory(fd)) {
Object.keys(_TVDOS.TMPFS).filter(it=>it.startsWith(path)).forEach(childs=>{
delete _TVDOS.TMPFS[childs]
})
}
return true
}
_TVDOS.DRV.FS.DEVTMP.exists = (fd) => (_TVDOS.TMPFS[fd.path] !== undefined)
Object.freeze(_TVDOS.DRV.FS.DEVTMP)
///////////////////////////////////////////////////////////////////////////////
_TVDOS.DRV.FS.NET = {}
_TVDOS.DRV.FS.NET.sread = (fd) => {
let port = _TVDOS.DRV.FS.SERIAL._toPorts(fd.driveLetter)
let url = fd.path.substring(fd.path.indexOf("\\")+1).replaceAll("\\","/")
// serial.println("NETFILE GET " + url)
com.sendMessage(port[0], "GET " + url)
com.waitUntilReady(port[0])
if (com.getStatusCode(port[0]) == 0)
return com.pullMessage(port[0])
else
return null
}
_TVDOS.DRV.FS.NET.flush = () => {}
_TVDOS.DRV.FS.NET.close = () => {}
_TVDOS.DRV.FS.NET.isDirectory = () => false
_TVDOS.DRV.FS.NET.listFiles = () => undefined
_TVDOS.DRV.FS.NET.touch = () => {}
_TVDOS.DRV.FS.NET.mkDir = () => {}
_TVDOS.DRV.FS.NET.mkFile = () => {}
_TVDOS.DRV.FS.NET.remove = () => {}
_TVDOS.DRV.FS.NET.exists = (fd) => {
let port = _TVDOS.DRV.FS.SERIAL._toPorts(fd.driveLetter)
let url = fd.path.substring(fd.path.indexOf("\\")+1).replaceAll("\\","/")
com.sendMessage(port[0], "GET " + url)
com.waitUntilReady(port[0])
return (0 == com.getStatusCode(port[0]))
}
///////////////////////////////////////////////////////////////////////////////
// Legacy Serial filesystem, !!pending for removal!!
/*const filesystem = {};
filesystem._toPorts = (driveLetter) => {
if (driveLetter.toUpperCase === undefined) {
throw Error("'"+driveLetter+"' (type: "+typeof driveLetter+") is not a valid drive letter");
}
var port = _TVDOS.DRIVES[driveLetter.toUpperCase()];
if (port === undefined) {
throw Error("Drive letter '" + driveLetter.toUpperCase() + "' does not exist");
}
return port
};
filesystem._close = (portNo) => {
com.sendMessage(portNo, "CLOSE")
}
filesystem._flush = (portNo) => {
com.sendMessage(portNo, "FLUSH")
}
// @return disk status code (0 for successful operation)
// throws if:
// - java.lang.NullPointerException if path is null
// - Error if operation mode is not "R", "W" nor "A"
filesystem.open = (driveLetter, path, operationMode) => {
var port = filesystem._toPorts(driveLetter);
filesystem._flush(port[0]); filesystem._close(port[0]);
var mode = operationMode.toUpperCase();
if (mode != "R" && mode != "W" && mode != "A") {
throw Error("Unknown file opening mode: " + mode);
}
com.sendMessage(port[0], "OPEN"+mode+'"'+path+'",'+port[1]);
return com.getStatusCode(port[0]);
};
filesystem.getFileLen = (driveLetter) => {
var port = filesystem._toPorts(driveLetter);
com.sendMessage(port[0], "GETLEN");
var response = com.getStatusCode(port[0]);
if (135 == response) {
throw Error("File not opened");
}
if (response < 0 || response >= 128) {
throw Error("Reading a file failed with "+response);
}
return Number(com.pullMessage(port[0]));
};
// @return the entire contents of the file in String
filesystem.readAll = (driveLetter) => {
var port = filesystem._toPorts(driveLetter);
com.sendMessage(port[0], "READ");
var response = com.getStatusCode(port[0]);
if (135 == response) {
throw Error("File not opened");
}
if (response < 0 || response >= 128) {
throw Error("Reading a file failed with "+response);
}
return com.pullMessage(port[0]);
};
filesystem.readAllBytes = (driveLetter) => {
var str = filesystem.readAll(driveLetter);
var bytes = new Uint8Array(str.length);
for (let i = 0; i < str.length; i++) {
bytes[i] = str.charCodeAt(i);
}
return bytes;
};
filesystem.write = (driveLetter, string) => {
var port = filesystem._toPorts(driveLetter);
com.sendMessage(port[0], "WRITE"+string.length);
var response = com.getStatusCode(port[0]);
if (135 == response) {
throw Error("File not opened");
}
if (response < 0 || response >= 128) {
throw Error("Writing a file failed with "+response);
}
com.sendMessage(port[0], string);
filesystem._flush(port[0]); filesystem._close(port[0]);
};
filesystem.writeBytes = (driveLetter, bytes) => {
var string = btostr(bytes); // no spreading: has length limit
filesystem.write(driveLetter, string);
};
filesystem.isDirectory = (driveLetter) => {
var port = filesystem._toPorts(driveLetter);
com.sendMessage(port[0], "LISTFILES");
var response = com.getStatusCode(port[0]);
return (response === 0);
};
filesystem.mkDir = (driveLetter) => {
var port = filesystem._toPorts(driveLetter);
com.sendMessage(port[0], "MKDIR");
var response = com.getStatusCode(port[0]);
if (response < 0 || response >= 128) {
var status = com.getDeviceStatus(port[0]);
throw Error("Creating a directory failed with ("+response+"): "+status.message+"\n");
}
return (response === 0); // possible status codes: 0 (success), 1 (fail)
};
filesystem.touch = (driveLetter) => {
var port = filesystem._toPorts(driveLetter);
com.sendMessage(port[0], "TOUCH");
var response = com.getStatusCode(port[0]);
return (response === 0);
};
filesystem.mkFile = (driveLetter) => {
var port = filesystem._toPorts(driveLetter);
com.sendMessage(port[0], "MKFILE");
var response = com.getStatusCode(port[0]);
return (response === 0);
};
filesystem.remove = (driveLetter) => {
var port = filesystem._toPorts(driveLetter);
com.sendMessage(port[0], "DELETE");
var response = com.getStatusCode(port[0]);
return (response === 0);
};
Object.freeze(filesystem);*/
///////////////////////////////////////////////////////////////////////////////
const files = {}
files.reservedNames = ["AUX", // unused
"COM1","COM2","COM3","COM4", // serial ports
"CON", // the tty
"FB1","FB2", // raw framebuffer, 256 colours, typ. 560x448
"FBIPF", // framebuffer that accepts IPF-formatted bytes, reduced colours, typ. 560x448
"HFB1","HFB2","HFB3","HFB4", // half-dimension framebuffers, 256 colours, typ. 280x224
"LPT1","LPT2","LPT3","LPT4", // reserved for "parallel ports"; unused
"MEM", // /dev/mem
"NUL", // /dev/null
"PMEM0","PMEM1","PMEM2","PMEM3","PMEM4","PMEM5","PMEM6","PMEM7", // /dev/mem for peripherals
"PRN", // a printer
"RND", // /dev/urandom
"TMP", // tmp file that resides in the Program Memory
"XFB", // raw framebuffer, 4096 colours, typ. 560x448. Memory layout follows the gpu's (0..250779: RG-plane, 250880..262143: gap, 262144..513023: BA-plane)
"ZERO"] // /dev/zero
/** This function only creates a file descriptor; will not actually interact with the drives yet. */
files.open = (fullPath) => {
if (fullPath == undefined) throw Error("path is undefined")
if (files.reservedNames.includes(fullPath.toUpperCase())) {
return new TVDOSFileDescriptor(fullPath.toUpperCase())
}
if (fullPath[2] != '/' && fullPath[2] != '\\') throw Error("Expected full path with drive letter, got " + fullPath)
let driveLetter = fullPath[0].toUpperCase()
let driver = _TVDOS.DRIVEFS[driveLetter]
// special device files ($:\NUL, $:\CON, etc)
if ("$" == driveLetter) {
return new TVDOSFileDescriptor(fullPath.toUpperCase())
}
return new TVDOSFileDescriptor(fullPath, driver)
}
Object.freeze(files)
///////////////////////////////////////////////////////////////////////////////
const input = {};
const inputwork = {};
inputwork.keymap = [];
input.changeKeyLayout = function(name) {
let f = files.open(`A:/tvdos/${name.toLowerCase()}.key`)
let res0 = f.sread().trim()
if (res0.length == 0 && inputwork.keymap.length == 0) throw new Error(`I/O Error on ${f.fullPath}`)
try {
inputwork.keymap = eval(res0)
}
catch (e) {
printerrln(e)
return -1
}
}
// load initial key layout
input.changeKeyLayout(_TVDOS.variables.KEYBOARD || "us_qwerty");
// states to run the keyboard input
inputwork.stroboTime = 4294967296*0;
inputwork.stroboDelays = [0,250000000,0,25000000,0]; // 250ms, 25ms
inputwork.stroboStatus = 0; // 0: first key, 1: waiting for initial delay, 2: repeating key, 3: waiting for repeat delay
inputwork.oldKeys = [];
inputwork.oldMouse = [];
inputwork.repeatCount = 0;
//inputwork.keyChanged = false;
/**
* callback: takes one argument of object which
* [ <eventname>, args ]
* where:
* "key_down", <key symbol string>, <repeat count>, keycode0, keycode1 .. keycode7
* "key_change", <key symbol string (what went up)>, 0, keycode0, keycode1 .. keycode7 (remaining keys that are held down)
* "mouse_down", pos-x, pos-y, 1 // yes there's only one mouse button :p
* "mouse_up", pos-x, pos-y, 0
* "mouse_move", pos-x, pos-y, <button down?>, oldpos-x, oldpos-y
*/
input.withEvent = function(callback) {
// TODO mouse event
function arrayEq(a,b) {
for (let i = 0; i < a.length; ++i) {
if (a[i] !== b[i]) return false;
}
return true;
}
function arrayDiff(a,b) {
return a.filter(x => !b.includes(x));
}
function keysToStr(keys) {
//let shiftin = keys.includes(59) || keys.includes(60);
let headkey = keys.head();
//return (inputwork.keymap[headkey] == undefined) ? undefined : (shiftin) ? (inputwork.keymap[headkey][1] || inputwork.keymap[headkey][0]) : inputwork.keymap[headkey][0];
return (inputwork.keymap[headkey] == undefined) ? undefined : inputwork.keymap[headkey];
}
sys.poke(-40, 255);
let keys = [sys.peek(-41),sys.peek(-42),sys.peek(-43),sys.peek(-44),sys.peek(-45),sys.peek(-46),sys.peek(-47),sys.peek(-48)];
let mouse = [sys.peek(-33) | (sys.peek(-34) << 8), sys.peek(-35) | (sys.peek(-36) << 8), sys.peek(-37)];
let keyChanged = !arrayEq(keys, inputwork.oldKeys)
let keyDiff = arrayDiff(keys, inputwork.oldKeys)
// FIXME does not work with QMK's "LT(1,KC_ENT)" or something similar
//if (keys.sum() > 0) serial.println(`! captured=${keys.join()}`)
if (inputwork.stroboStatus % 2 == 0 && keys[0] != 0) {
inputwork.stroboStatus += 1;
inputwork.stroboTime = sys.nanoTime();
inputwork.repeatCount += 1;
let shiftin = keys.includes(59) || keys.includes(60);
let keysym0 = keysToStr(keys);
let newKeysym0 = keysToStr(keyDiff);
let keysym = (keysym0 === undefined) ? undefined : (shiftin && keysym0[1]) ? keysym0[1] : keysym0[0];
let newKeysym = (newKeysym0 === undefined) ? undefined : (shiftin && newKeysym0[1]) ? newKeysym0[1] : newKeysym0[0];
//serial.println(`keys=${keys.join()}; oldkeys=${inputwork.oldKeys}; keyDiff=${keyDiff}`)
//serial.println(`keysym=${keysym}; newkeysym=${newKeysym}`)
if (!keyChanged) {
callback(["key_down", keysym, inputwork.repeatCount].concat(keys));
}
else if (newKeysym !== undefined) {
callback(["key_down", newKeysym, inputwork.repeatCount].concat(keys));
}
inputwork.oldKeys = keys; // don't put this outside of if-cascade
}
else if (keyChanged || keys[0] == 0) {
inputwork.stroboStatus = 0;
inputwork.repeatCount = 0;
if (keys[0] == 0) {
inputwork.keyChanged = false;
sys.sleep(25)
}
if (keyChanged) {
//callback(["key_change", keysToStr(arrayDiff(keys, inputwork.oldKeys)), inputwork.repeatCount].concat(keys));
//serial.println(`$ oldkeys=${inputwork.oldKeys}; newkeys=${keys}`)
//serial.println(`$ keydiff=${arrayDiff(keys, inputwork.oldKeys)}; newkeysym=${keysToStr(arrayDiff(keys, inputwork.oldKeys))}`)
//inputwork.keyChanged = keysToStr(arrayDiff(keys, inputwork.oldKeys));
}
}
else if (inputwork.stroboStatus % 2 == 1 && sys.nanoTime() - inputwork.stroboTime < inputwork.stroboDelays[inputwork.stroboStatus]) {
sys.sleep(25);
}
else {
inputwork.stroboStatus += 1;
if (inputwork.stroboStatus >= 4) {
inputwork.stroboStatus = 2;
}
}
inputwork.oldMouse = mouse;
};
Object.freeze(input);
///////////////////////////////////////////////////////////////////////////////
const unicode = {};
unicode.utf8toCodepoints = function(utf8text) {
let UTF8_ACCEPT = 0
let UTF8D = [
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8,
0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12,
12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12,
12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12,
12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12,
12,36,12,12,12,12,12,12,12,12,12,12,
]
let state = UTF8_ACCEPT
let codep = 0
let codepoints = []
for (let i=0; i < utf8text.length; i++) {
let byte = utf8text.charCodeAt(i)
let type = UTF8D[byte]
codep = (state != UTF8_ACCEPT) ?
(byte & 0x3f) | (codep << 6) : (0xff >> type) & (byte)
state = UTF8D[256 + state + type]
if (state == UTF8_ACCEPT)
codepoints.push(codep)
}
return codepoints
}
// array of array: [predicate_for_coderange: (Codepoint) -> Boolean , printing_function: (Codepoint) -> Unit_that_does_screen_drawing]
// Usage: unicode.uniprint.push[(c) => 0xAC00 <= c && c <= 0xD7A3, printHalfRowHangul]
unicode.uniprint = [];
unicode.uniprint.push([c => (0 <= c && c <= 255), c => { sys.print(String.fromCodePoint(c)) }]);
// @return [predicate, printing function]
unicode.getUniprint = (c) => {
for (let k = 0; k < unicode.uniprint.length; k++) {
if (unicode.uniprint[k][0](c))
return unicode.uniprint[k]
}}
unicode.print = (str) => {
if ((typeof str === 'string' || str instanceof String) && str.length > 0) {
let cp = unicode.utf8toCodepoints(str)
cp.forEach(c => {
let q = unicode.getUniprint(c)
if (q == undefined || !q[0](c)) {
con.addch(4)
con.curs_right()
}
else {
q[1](c)
}
})
}
else {
sys.print(str)
}
}
unicode.println = (str) => {
unicode.print(str+'\n\n')
}
Object.freeze(unicode);
///////////////////////////////////////////////////////////////////////////////
// patch con to use VTs
/*con.move = function(y, x) {
let activeVT = _TVDOS.ACTIVE_VT || 0 // 0 is physical terminal
let vt = _TVDOS.VT_CONTEXTS[activeVT]
vt.setCursorYX(y|0, x|0)
};
con.getyx = function() {
let activeVT = _TVDOS.ACTIVE_VT || 0 // 0 is physical terminal
let vt = _TVDOS.VT_CONTEXTS[activeVT]
return vt.getCursorYX()
};*/
///////////////////////////////////////////////////////////////////////////////
let checkTerm = `if (sys.peek(-49)&1) throw new InterruptedException();`
let injectIntChk = (s, n) => {
// primitive way of injecting a code; will replace a JS string that matches the regex...
let k = s
.replace(/while *\([^\n]+\) *{/, "$& "+n+"();")
.replace(/for *\([^\n]+\) *{/, "$& "+n+"();")
.replace(/do *{/, "$& "+n+"();");
//serial.println(k);
return k;
}
var _G = {} // The Glorious Global Table
const moduleCache = {}
// install other stuffs
// js source-based impl
var require = (absdir) => {
let moduleFile = files.open(absdir)
if (!moduleFile.exists) throw Error("No such file: " + absdir)
let moduleScript = moduleFile.sread()
var intchkFunName = `tvdosSIGTERM_${generateRandomHashStr(16)}`;
return eval(`var ${intchkFunName} = function(){ ${checkTerm} };let exports = {}; ${injectIntChk(moduleScript, intchkFunName)}; Object.freeze(exports)`)
}
// COCC-based impl helper functions
let loadImgToMem = (str) => {
let intent = str.charCodeAt(4) // 2 for executable, 3 for shared
let addrToLoad = (str.charCodeAt(5) << 16) | (str.charCodeAt(6) << 8) | (str.charCodeAt(7))
const imageSize = (str.charCodeAt(9) << 16) | (str.charCodeAt(10) << 8) | (str.charCodeAt(11))
if (addrToLoad == 0)
addrToLoad = sys.malloc(imageSize + 4)
else
sys.forceAlloc(addrToLoad, imageSize + 4)
dma.strToRam(str, addrToLoad, 0, str.length)
// write magic 0xA5 to the beginning of the image area
sys.poke(addrToLoad, 0xA5)
return addrToLoad
}
let requireFromMemory = (ptr) => {
if (moduleCache[ptr]) return moduleCache[ptr]
const codeStr = sys.toObjectCode(ptr)
// expose args through a wrapper -- just like calling a syscall
const wrapper = new Function("require", "module", "exports", "args", `
${codeStr}
if (typeof module.exports === 'function') {
return module.exports(...args)
}
return module.exports
`)
const moduleRunner = (...args) => {
const exports = {}
const module = { exports }
return wrapper((id) => requireFromMemory(id), module, exports, args);
}
moduleCache[moduleId] = moduleRunner
return moduleRunner
}
// COCC-based impl
/*var require = (absdir) => {
let moduleFile = files.open(absdir)
if (!moduleFile.exists) throw Error("No such file: " + absdir)
let moduleScript = moduleFile.sread()
let moduleName = absdir.split("\\").last().substringBeforeLast(".")
// load the "string" into memory
let ptr = loadImgToMem(moduleScript)
let intent = sys.peek(ptr + 4)
// if it's a shared library, put it into the global table
if (3 == intent) {
// create the table if it's not there
if (!_G.SO)
_G.SO = {}
_G.SO[moduleName] = ptr
}
requireFromMemory(ptr)
}*/
var GL = require("A:/tvdos/include/gl.mjs")
// @param cmdsrc JS source code
// @param args arguments for the program, must be Array, and args[0] is always the name of the program, e.g.
// for command line 'echo foo bar', args[0] must be 'echo'
// @return status returned by the program
var execApp = (cmdsrc, args, appname) => {
appname = (appname) ? `_${appname}` : "_appStub"
var intchkFunName = `tvdosSIGTERM_${generateRandomHashStr(16)}`;
var execAppPrg = eval(
`var ${intchkFunName} = function(){ ${checkTerm} };` +
`var ${appname}=function(exec_args){${injectIntChk(cmdsrc, intchkFunName)}\n};` +
`${appname}`); // making 'exec_args' a app-level global
execAppPrg(args);
}
///////////////////////////////////////////////////////////////////////////////
// Load HSDPA driver
try {
let hsdpadrvFile = files.open("A:/tvdos/HSDPADRV.SYS")
if (hsdpadrvFile.exists) {
eval(hsdpadrvFile.sread())
}
} catch (e) {
serial.println("Warning: Could not load HSDPA driver: " + e.message)
}
// Boot script
serial.println(`TVDOS.SYS initialised on VM ${sys.getVmId()}, running boot script...`);
let cmdfile = files.open("A:/tvdos/bin/command.js")
eval(`var _AUTOEXEC=function(exec_args){${cmdfile.sread()}\n};` +
`_AUTOEXEC`)(["", "-c", "\\AUTOEXEC.BAT"])