more vt stuffs

This commit is contained in:
minjaesong
2025-07-29 16:07:19 +09:00
parent a4cf087bfa
commit e32c8dd0e5
4 changed files with 383 additions and 173 deletions

View File

@@ -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...

View File

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

View File

@@ -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<Int, OutputStream>()
private val vtInputStream = mutableMapOf<Int, InputStream>()
private val vtOutputStreams = mutableMapOf<Int, OutputStream>()
private val vtInputStreams = mutableMapOf<Int, InputStream>()
private val vtTerminals = mutableMapOf<Int, VTTerminalAdapter>()
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<Int, Int> {
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) {}
}

View File

@@ -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())
}
}