diff --git a/assets/disk0/tvdos/TVDOS.SYS b/assets/disk0/tvdos/TVDOS.SYS index dd6255a..e050cd1 100644 --- a/assets/disk0/tvdos/TVDOS.SYS +++ b/assets/disk0/tvdos/TVDOS.SYS @@ -768,7 +768,7 @@ VDEV.pwrite = (fd, ptr, count, offset) => { VMEM[i + (offset || 0)] = sys.peek(ptr + i) } } -VDEV.bwrite = (fd, bytes) { +VDEV.bwrite = (fd, bytes) => { if (bytes.length == VMEM.length && bytes instanceof Int8Array) { VMEM = bytes.slice() } @@ -837,7 +837,7 @@ _TVDOS.DRV.FS.DEVPT.pwrite = (fd, ptr, count, offset) => { sys.poke(mem - i, sys.peek(ptr + i)) } } -_TVDOS.DRV.FS.DEVPT.bwrite = (fd, bytes) { +_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]) @@ -1386,6 +1386,24 @@ print = function(str) { 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... diff --git a/tsvm_core/src/net/torvald/tsvm/JS_INIT.js b/tsvm_core/src/net/torvald/tsvm/JS_INIT.js index bb67f80..0e68682 100644 --- a/tsvm_core/src/net/torvald/tsvm/JS_INIT.js +++ b/tsvm_core/src/net/torvald/tsvm/JS_INIT.js @@ -456,11 +456,7 @@ con.move = function(y, x) { //print("\x1B["+(y|0)+";"+(x|0)+"H"); // NOT using ANSI escape sequence as it conflicts with some multilingual drive which redefines PRINT function // and obviously this method is faster albeit less genuine :p - - let activeVT = _TVDOS.ACTIVE_VT || 0 // 0 is physical terminal - let vt = _TVDOS.VT_CONTEXTS[activeVT] - - vt.setCursorYX(y|0, x|0) + graphics.setCursorYX(y|0, x|0); }; con.addch = function(c) { graphics.putSymbol(c|0); @@ -478,10 +474,7 @@ con.getmaxyx = function() { return graphics.getTermDimension(); // [rows, cols] }; con.getyx = function() { - let activeVT = _TVDOS.ACTIVE_VT || 0 // 0 is physical terminal - let vt = _TVDOS.VT_CONTEXTS[activeVT] - - return vt.getCursorYX() + return graphics.getCursorYX(); }; con.curs_up = function() { let [y,x] = con.getyx(); @@ -551,7 +544,6 @@ con.poll_keys = function() { sys.poke(-40, 1); return [-41,-42,-43,-44,-45,-46,-47,-48].map(it => sys.peek(it)); }; -Object.freeze(con); // some utilities functions // TypedArray re-implementation diff --git a/tsvm_core/src/net/torvald/tsvm/VM.kt b/tsvm_core/src/net/torvald/tsvm/VM.kt index 048f041..2138cba 100644 --- a/tsvm_core/src/net/torvald/tsvm/VM.kt +++ b/tsvm_core/src/net/torvald/tsvm/VM.kt @@ -3,8 +3,10 @@ package net.torvald.tsvm import net.torvald.UnsafeHelper import net.torvald.UnsafePtr import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.toHex +import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.toUint import net.torvald.tsvm.peripheral.* import org.graalvm.polyglot.Context +import org.graalvm.polyglot.Value import java.io.InputStream import java.io.OutputStream import java.nio.charset.Charset @@ -83,8 +85,9 @@ class VM( // VT tracking of the VM private var currentVTIndex = 0 // default to physical terminal - private val vtOutputStream = mutableMapOf() - private val vtInputStream = mutableMapOf() + private val vtOutputStreams = mutableMapOf() + private val vtInputStreams = mutableMapOf() + private val vtTerminals = mutableMapOf() fun getCurrentVT(): Int { // try to read from JS context @@ -110,6 +113,196 @@ class VM( } } + private fun getVTTerminal(vtIndex: Int, buffer: Value): VTTerminalAdapter { + return vtTerminals.getOrPut(vtIndex) { + VTTerminalAdapter(vtIndex, buffer) + } + } + + fun getVTOutputStream(vtIndex: Int): OutputStream { + return vtOutputStreams.getOrPut(vtIndex) { + if (vtIndex == 0) { + // VT0 is physical terminal - use existing terminal + getPrintStream() + } else { + // Create virtual terminal output stream + createVTOutputStream(vtIndex) + } + } + } + + private fun createVTOutputStream(vtIndex: Int): OutputStream { + return object : OutputStream() { + override fun write(b: Int) { + // Write to virtual terminal buffer + writeToVTBuffer(vtIndex, byteArrayOf(b.toByte())) + } + + override fun write(b: ByteArray, off: Int, len: Int) { + // Write to virtual terminal buffer + writeToVTBuffer(vtIndex, b.sliceArray(off until off + len)) + } + } + } + + + + private val rawCursorPosLo = 252030L + private val rawCursorPosHi = rawCursorPosLo + 1 + private val ptrTxtFore = rawCursorPosHi + 1 + private val ptrTxtBack = ptrTxtFore + 2560 + private val ptrTxt = ptrTxtBack + 2560 + + private fun writeToVTBuffer(vtIndex: Int, textToWrite: ByteArray) { + try { + // Access the JavaScript VT device driver + val context = getCurrentJSContext() + + val VMEM: Value = context.eval("js", "return _TVDOS.VT_CONTEXTS[$vtIndex].buffer") + + + + val rawCursorPos = VMEM.getArrayElement(rawCursorPosLo).asByte().toUint() or + VMEM.getArrayElement(rawCursorPosHi).asByte().toUint().shl(8) // Cursor position in: (y*80 + x) + val printOff = rawCursorPos + + // mimic the behaviour of the GraphicsAdapter.getPrintStream.write(ByteArray) + this.isIdle.set(true) + Objects.checkFromIndexSize(printOff, textToWrite.size, textToWrite.size) + + for (i in 0 until textToWrite.size) { + vtWriteOut(VMEM, textToWrite[i]) + } + this.isIdle.set(false) + } catch (e: Exception) { + System.err.println("Failed to write to VT$vtIndex: ${e.message}") + } + } + + private fun vtWriteOut(VMEM: Value, char: Byte) { + val terminal = getVTTerminal(getCurrentVT(), VMEM) + terminal.writeOut(char) // calls GlassTty.writeOut() which handles everything + } + + private inner class VTTerminalAdapter(private val vtIndex: Int, private val VMEM: Value) : StandardTty(32, 80) { + + private fun toTtyTextOffset(x: Int, y: Int): Int { + return TEXT_COLS * y + x + } + + override fun putChar(x: Int, y: Int, text: Byte, foreColour: Byte, backColour: Byte) { + val textOff = toTtyTextOffset(x, y) + VMEM.setArrayElement(ptrTxtFore + textOff, foreColour) + VMEM.setArrayElement(ptrTxtBack + textOff, backColour) + VMEM.setArrayElement(ptrTxt + textOff, text) + } + + override fun eraseInDisp(arg: Int) { + when (arg) { + 2 -> { + val foreBits = ttyFore or ttyFore.shl(8) or ttyFore.shl(16) or ttyFore.shl(24) + val backBits = ttyBack or ttyBack.shl(8) or ttyBack.shl(16) or ttyBack.shl(24) + for (i in 0 until TEXT_COLS * TEXT_ROWS step 4) { + VMEM.setArrayElement(ptrTxtFore + 1, foreBits) + VMEM.setArrayElement(ptrTxtBack + 1, backBits) + VMEM.setArrayElement(ptrTxt + 1, 0) + } + VMEM.setArrayElement(rawCursorPosLo, 0) + VMEM.setArrayElement(rawCursorPosHi, 0) + } + else -> TODO() + } + } + + override fun eraseInLine(arg: Int) { + when (arg) { + else -> TODO() + } + } + + override fun scrollUp(arg: Int) { + TODO("Not yet implemented") + } + + override fun scrollDown(arg: Int) { + TODO("Not yet implemented") + } + + override fun getPrintStream(): OutputStream { + TODO("how???") + } + + override fun getErrorStream(): OutputStream { + TODO("how???") + } + + override fun getInputStream(): InputStream { + TODO("how???") + } + + override fun putKey(key: Int) { + // VT has no keyboard attached + } + + override fun takeKey(): Int { + // VT has no keyboard attached + return 0 + } + + override fun peek(addr: Long): Byte? { + if (addr < 0) return null + return VMEM.getArrayElement(addr % 524288).asByte() + } + + override fun poke(addr: Long, byte: Byte) { + if (addr < 0) return + VMEM.setArrayElement(addr % 524288, byte) + } + + override fun mmio_read(addr: Long): Byte? { + return null + } + + override fun mmio_write(addr: Long, byte: Byte) { + } + + override fun dispose() { + } + + override fun getVM(): VM { + return this@VM + } + + override fun getCursorPos(): Pair { + val rawPos = VMEM.getArrayElement(rawCursorPosLo).asByte().toUint() or + VMEM.getArrayElement(rawCursorPosHi).asByte().toUint().shl(8) + return (rawPos % 80) to (rawPos / 80) + } + + override fun setCursorPos(x: Int, y: Int) { + val rawPos = (y * 80 + x).coerceIn(0, 2559) + VMEM.setArrayElement(rawCursorPosLo, (rawPos and 0xFF).toByte()) + VMEM.setArrayElement(rawCursorPosHi, (rawPos shr 8).toByte()) + } + + override var rawCursorPos: Int + get() = TODO("Not yet implemented") + set(value) {} + override var blinkCursor: Boolean + get() = TODO("Not yet implemented") + set(value) {} + override var ttyFore: Int + get() = TODO("Not yet implemented") + set(value) {} + override var ttyBack: Int + get() = TODO("Not yet implemented") + set(value) {} + override var ttyRawMode: Boolean + get() = TODO("Not yet implemented") + set(value) {} + + + } diff --git a/tsvm_core/src/net/torvald/tsvm/peripheral/GraphicsAdapter.kt b/tsvm_core/src/net/torvald/tsvm/peripheral/GraphicsAdapter.kt index 53fb459..29a0fec 100644 --- a/tsvm_core/src/net/torvald/tsvm/peripheral/GraphicsAdapter.kt +++ b/tsvm_core/src/net/torvald/tsvm/peripheral/GraphicsAdapter.kt @@ -58,7 +58,7 @@ class ReferenceLikeLCD(assetsRoot: String, vm: VM) : GraphicsAdapter(assetsRoot, * NOTE: if TTY size is greater than 80*32, SEGFAULT will occur because text buffer is fixed in size */ open class GraphicsAdapter(private val assetsRoot: String, val vm: VM, val config: AdapterConfig, val sgr: SuperGraphicsAddonConfig = SuperGraphicsAddonConfig()) : - GlassTty(config.textRows, config.textCols) { + StandardTty(config.textRows, config.textCols) { override val typestring = VM.PERITYPE_GPU_AND_TERM @@ -690,6 +690,11 @@ open class GraphicsAdapter(private val assetsRoot: String, val vm: VM, val confi } + override fun resetTtyStatus() { + ttyFore = TTY_FORE_DEFAULT + ttyBack = TTY_BACK_DEFAULT + } + /** * @param mode 0-Low, 1-High */ @@ -705,86 +710,6 @@ open class GraphicsAdapter(private val assetsRoot: String, val vm: VM, val confi } - override fun resetTtyStatus() { - ttyFore = TTY_FORE_DEFAULT - ttyBack = TTY_BACK_DEFAULT - } - - override fun putChar(x: Int, y: Int, text: Byte, foreColour: Byte, backColour: Byte) { - val textOff = toTtyTextOffset(x, y) - textArea[memTextForeOffset + textOff] = foreColour - textArea[memTextBackOffset + textOff] = backColour - textArea[memTextOffset + textOff] = text - applyDelay() - } - - override fun emitChar(code: Int) { - val (x, y) = getCursorPos() - putChar(x, y, code.toByte()) - setCursorPos(x + 1, y) - } - override fun cursorUp(arg: Int) { - val (x, y) = getCursorPos() - setCursorPos(x, y - arg) - } - - override fun cursorDown(arg: Int) { - val (x, y) = getCursorPos() - val newy = y + arg - setCursorPos(x, if (newy >= TEXT_ROWS) TEXT_ROWS - 1 else newy) - } - - override fun cursorFwd(arg: Int) { - val (x, y) = getCursorPos() - setCursorPos(x + arg, y) - } - - override fun cursorBack(arg: Int) { - val (x, y) = getCursorPos() - setCursorPos(x - arg, y) - } - - override fun cursorNextLine(arg: Int) { - val (_, y) = getCursorPos() - val newy = y + arg - setCursorPos(0, if (newy >= TEXT_ROWS) TEXT_ROWS - 1 else newy) - if (newy >= TEXT_ROWS) { - scrollUp(newy - TEXT_ROWS + 1) - } - } - - override fun cursorPrevLine(arg: Int) { - val (_, y) = getCursorPos() - setCursorPos(0, y - arg) - } - - override fun cursorX(arg: Int) { - val (_, y) = getCursorPos() - setCursorPos(arg, y) - } - - override fun eraseInDisp(arg: Int) { - when (arg) { - 2 -> { - val foreBits = ttyFore or ttyFore.shl(8) or ttyFore.shl(16) or ttyFore.shl(24) - val backBits = ttyBack or ttyBack.shl(8) or ttyBack.shl(16) or ttyBack.shl(24) - for (i in 0 until TEXT_COLS * TEXT_ROWS step 4) { - textArea.setIntFree(memTextForeOffset + i, foreBits) - textArea.setIntFree(memTextBackOffset + i, backBits) - textArea.setIntFree(memTextOffset + i, 0) - } - textArea.setShortFree(memTextCursorPosOffset, 0) - } - else -> TODO() - } - } - - override fun eraseInLine(arg: Int) { - when (arg) { - else -> TODO() - } - } - /** New lines are added at the bottom */ override fun scrollUp(arg: Int) { val displacement = arg.toLong() * TEXT_COLS @@ -835,92 +760,34 @@ open class GraphicsAdapter(private val assetsRoot: String, val vm: VM, val confi } } - /* - Color table for default palette - - Black 240 - Red 211 - Green 61 - Yellow 230 - Blue 49 - Magenta 219 - Cyan 114 - White 254 - */ - private val sgrDefault8ColPal = intArrayOf(240,211,61,230,49,219,114,254) - - override fun sgrOneArg(arg: Int) { - - if (arg in 30..37) { - ttyFore = sgrDefault8ColPal[arg - 30] - } - else if (arg in 40..47) { - ttyBack = sgrDefault8ColPal[arg - 40] - } - else if (arg == 7) { - val t = ttyFore - ttyFore = ttyBack - ttyBack = t - } - else if (arg == 0) { - ttyFore = TTY_FORE_DEFAULT - ttyBack = TTY_BACK_DEFAULT - blinkCursor = true - } - } - - override fun sgrTwoArg(arg1: Int, arg2: Int) { - TODO("Not yet implemented") - } - - override fun sgrThreeArg(arg1: Int, arg2: Int, arg3: Int) { - if (arg1 == 38 && arg2 == 5) { - ttyFore = arg3 - } - else if (arg1 == 48 && arg2 == 5) { - ttyBack = arg3 - } - } - - override fun privateSeqH(arg: Int) { + override fun eraseInDisp(arg: Int) { when (arg) { - 25 -> blinkCursor = true + 2 -> { + val foreBits = ttyFore or ttyFore.shl(8) or ttyFore.shl(16) or ttyFore.shl(24) + val backBits = ttyBack or ttyBack.shl(8) or ttyBack.shl(16) or ttyBack.shl(24) + for (i in 0 until TEXT_COLS * TEXT_ROWS step 4) { + textArea.setIntFree(memTextForeOffset + i, foreBits) + textArea.setIntFree(memTextBackOffset + i, backBits) + textArea.setIntFree(memTextOffset + i, 0) + } + textArea.setShortFree(memTextCursorPosOffset, 0) + } + else -> TODO() } } - override fun privateSeqL(arg: Int) { + override fun eraseInLine(arg: Int) { when (arg) { - 25 -> blinkCursor = false + else -> TODO() } } - /** The values are one-based - * @param arg1 y-position (row) - * @param arg2 x-position (column) */ - override fun cursorXY(arg1: Int, arg2: Int) { - setCursorPos(arg2 - 1, arg1 - 1) - } - - override fun ringBell() { - - } - - override fun insertTab() { - val (x, y) = getCursorPos() - setCursorPos((x / TAB_SIZE + 1) * TAB_SIZE, y) - } - - override fun crlf() { - val (_, y) = getCursorPos() - val newy = y + 1 //+ halfrowMode.toInt() - setCursorPos(0, if (newy >= TEXT_ROWS) TEXT_ROWS - 1 else newy) - if (newy >= TEXT_ROWS) scrollUp(1) - } - - override fun backspace() { - val (x, y) = getCursorPos() - setCursorPos(x - 1, y) - putChar(x - 1, y, 0x20.toByte()) + override fun putChar(x: Int, y: Int, text: Byte, foreColour: Byte, backColour: Byte) { + val textOff = toTtyTextOffset(x, y) + textArea[memTextForeOffset + textOff] = foreColour + textArea[memTextBackOffset + textOff] = backColour + textArea[memTextOffset + textOff] = text + applyDelay() } @@ -2341,3 +2208,143 @@ internal fun SpriteBatch.inUse(action: () -> Unit) { action() this.end() } + +abstract class StandardTty(rows: Int, cols: Int) : GlassTty(rows, cols) { + + override fun resetTtyStatus() { + ttyFore = 253 + ttyBack = 255 + } + + override fun emitChar(code: Int) { + val (x, y) = getCursorPos() + putChar(x, y, code.toByte()) + setCursorPos(x + 1, y) + } + override fun cursorUp(arg: Int) { + val (x, y) = getCursorPos() + setCursorPos(x, y - arg) + } + + override fun cursorDown(arg: Int) { + val (x, y) = getCursorPos() + val newy = y + arg + setCursorPos(x, if (newy >= TEXT_ROWS) TEXT_ROWS - 1 else newy) + } + + override fun cursorFwd(arg: Int) { + val (x, y) = getCursorPos() + setCursorPos(x + arg, y) + } + + override fun cursorBack(arg: Int) { + val (x, y) = getCursorPos() + setCursorPos(x - arg, y) + } + + override fun cursorNextLine(arg: Int) { + val (_, y) = getCursorPos() + val newy = y + arg + setCursorPos(0, if (newy >= TEXT_ROWS) TEXT_ROWS - 1 else newy) + if (newy >= TEXT_ROWS) { + scrollUp(newy - TEXT_ROWS + 1) + } + } + + override fun cursorPrevLine(arg: Int) { + val (_, y) = getCursorPos() + setCursorPos(0, y - arg) + } + + override fun cursorX(arg: Int) { + val (_, y) = getCursorPos() + setCursorPos(arg, y) + } + + /* + Color table for default palette + + Black 240 + Red 211 + Green 61 + Yellow 230 + Blue 49 + Magenta 219 + Cyan 114 + White 254 + */ + private val sgrDefault8ColPal = intArrayOf(240,211,61,230,49,219,114,254) + + override fun sgrOneArg(arg: Int) { + if (arg in 30..37) { + ttyFore = sgrDefault8ColPal[arg - 30] + } + else if (arg in 40..47) { + ttyBack = sgrDefault8ColPal[arg - 40] + } + else if (arg == 7) { + val t = ttyFore + ttyFore = ttyBack + ttyBack = t + } + else if (arg == 0) { + ttyFore = 253 + ttyBack = 255 + blinkCursor = true + } + } + + override fun sgrTwoArg(arg1: Int, arg2: Int) { + TODO("Not yet implemented") + } + + override fun sgrThreeArg(arg1: Int, arg2: Int, arg3: Int) { + if (arg1 == 38 && arg2 == 5) { + ttyFore = arg3 + } + else if (arg1 == 48 && arg2 == 5) { + ttyBack = arg3 + } + } + + override fun privateSeqH(arg: Int) { + when (arg) { + 25 -> blinkCursor = true + } + } + + override fun privateSeqL(arg: Int) { + when (arg) { + 25 -> blinkCursor = false + } + } + + /** The values are one-based + * @param arg1 y-position (row) + * @param arg2 x-position (column) */ + override fun cursorXY(arg1: Int, arg2: Int) { + setCursorPos(arg2 - 1, arg1 - 1) + } + + override fun ringBell() { + + } + + override fun insertTab() { + val (x, y) = getCursorPos() + setCursorPos((x / 8 + 1) * 8, y) // tab size is 8 + } + + override fun crlf() { + val (_, y) = getCursorPos() + val newy = y + 1 //+ halfrowMode.toInt() + setCursorPos(0, if (newy >= TEXT_ROWS) TEXT_ROWS - 1 else newy) + if (newy >= TEXT_ROWS) scrollUp(1) + } + + override fun backspace() { + val (x, y) = getCursorPos() + setCursorPos(x - 1, y) + putChar(x - 1, y, 0x20.toByte()) + } +} \ No newline at end of file