diff --git a/CLAUDE.md b/CLAUDE.md index b6dac8e..9c8e94f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -445,8 +445,9 @@ Implemented entirely in JS — **no tsvm_core changes**. ### Architecture -- **Dispatcher**: `assets/disk0/tvdos/sbin/vtmgr.js`. Launched as the boot shell - from `AUTOEXEC.BAT` (replaces the old `fsh` / `command -fancy` tail). Owns the +- **Dispatcher**: `assets/disk0/tvdos/sbin/vtmgr.js`. Launched directly by the + `TVDOS.SYS` boot block (only when `!_TVDOS_IS_VT_PANE`); when it exits (Alt-0) + the boot block runs `AUTOEXEC.BAT` as the bare fallback shell. Owns the physical keyboard and screen. Each VT runs in its own GraalVM context/thread via the existing `parallel.spawnNewContext` / `attachProgram` / `launch` API (see `VMJSR223Delegate.kt` `class Parallel`). VT 1 spawns at boot; VT 2-6 are @@ -462,15 +463,22 @@ Implemented entirely in JS — **no tsvm_core changes**. - **Compositor** (30 Hz): blits the active VT's text plane to the physical GPU text area via `sys.memcpy`, and pushes that VT's cursor-visibility into the GPU blink bit (MMIO attribute byte 6, addressed at `-1 - (131072*gpuSlot + 6)`). -- **Per-pane bootstrap**: each pane re-evals `TVDOS.SYS` (with - `_TVDOS_SKIP_AUTOEXEC` + `_TVDOS_IS_VT_PANE` set, and a `_BIOS` stub captured - live from the main context) then launches `command -fancy`, all in ONE direct - `eval` so the shell launcher shares scope with `_TVDOS`/`files`/`execApp`. - The environment (`_TVDOS.variables`: PATH/INCLPATH/HELPPATH/KEYBOARD, fully - `$PATH`-expanded) is snapshotted from the main context at vtmgr start and - replayed into every pane (env-copy, NOT per-pane AUTOEXEC — AUTOEXEC launches - the GUI shell `fsh` which must not run inside a pane). The snapshot is a - boot-time baseline; later `set` in one pane does not propagate to others. +- **Boot config split (`commandrc` + `AUTOEXEC.BAT`)**: environment setup and + app-launch are split into two files so panes can replay one without the other. + `\commandrc` holds the `set` commands (PATH/INCLPATH/HELPPATH/KEYBOARD) and is + run by the `TVDOS.SYS` boot block in **every** context (boot and pane) — it has + no `.BAT` extension, so the boot block runs it line-by-line (`set` mutates the + shared `_TVDOS.variables`, so the effect persists). `\AUTOEXEC.BAT` is the + **per-console launch** script (Korean IME `tvdos/i18n/korean`, then + `command -fancy`); it is run once per console — by each pane's bootstrap, and + by the boot block as the post-vtmgr fallback. No env snapshot/replay anymore; + each pane gets PATH/KEYBOARD/etc. natively from `commandrc`, and Korean IME + (a per-context `unicode.uniprint` handler) now registers in every pane. +- **Per-pane bootstrap**: each pane re-evals `TVDOS.SYS` (with `_TVDOS_IS_VT_PANE` + set — which makes the boot block run `commandrc` but skip the vtmgr/AUTOEXEC + launch — and a `_BIOS` stub captured live from the main context) then runs + `command -c \AUTOEXEC.BAT`, all in ONE direct `eval` so the launcher shares + scope with `_TVDOS`/`files`/`execApp`. ### Output/input shimming (in the pane bootstrap) @@ -520,10 +528,11 @@ arithmetic (no regression outside vtmgr). Applied so far in - New: `assets/disk0/tvdos/sbin/vtmgr.js` (dispatcher + per-pane bootstrap) - `assets/disk0/tvdos/bin/command.js`: `chvt` builtin, `[N]` prompt prefix for VT 2-6, `shell.stdio.out` → `__VT_OUT` delegation -- `assets/disk0/tvdos/TVDOS.SYS`: boot block skips AUTOEXEC when - `_TVDOS_SKIP_AUTOEXEC` is set (so pane re-init doesn't recurse) -- `assets/disk0/AUTOEXEC.BAT`: boots into `tvdos/sbin/vtmgr`, with - `command -fancy` as a fallback once vtmgr exits +- `assets/disk0/tvdos/TVDOS.SYS`: boot block runs `\commandrc` (env) in every + context, then — only when `!_TVDOS_IS_VT_PANE` — launches `tvdos/sbin/vtmgr` + and, on its exit, `\AUTOEXEC.BAT` as the fallback shell +- `assets/disk0/commandrc`: env-only `set` commands (PATH/INCLPATH/HELPPATH/KEYBOARD) +- `assets/disk0/AUTOEXEC.BAT`: per-console launch (Korean IME + `command -fancy`) - `assets/disk0/tvdos/bin/taut.js`, `assets/disk0/hopper/include/aa.mjs`: `vaddr` VT-aware direct-VRAM addressing diff --git a/assets/disk0/AUTOEXEC.BAT b/assets/disk0/AUTOEXEC.BAT index f084b57..c75dc71 100644 --- a/assets/disk0/AUTOEXEC.BAT +++ b/assets/disk0/AUTOEXEC.BAT @@ -1,20 +1,11 @@ -echo "Starting TVDOS..." - -rem put set-xxx commands here: -set PATH=\tvdos\installer;\tvdos\tuidev;\tbas;\hopper\bin;$PATH -set INCLPATH=\hopper\include;$INCLPATH -set HELPPATH=\hopper\help;$HELPPATH -set KEYBOARD=us_colemak - -rem load Korean font / IME (font upload is global hardware) +rem AUTOEXEC.BAT -- per-console launch script. Run once for every console: +rem each virtual-console pane runs it (via vtmgr's bootstrap), and the boot +rem shell runs it as the fallback once vtmgr exits (Alt-0). Environment setup +rem (`set` commands) lives in \commandrc, which TVDOS.SYS runs before this. +rem +rem Korean IME registers a per-CONTEXT handler (unicode.uniprint), so it must +rem run per-console here rather than once at boot. tvdos/i18n/korean -rem Boot into virtual consoles. vtmgr owns the keyboard and screen, and spawns -rem a `command -fancy` shell per VT (Alt-1..6 / chvt to switch, Alt-0 to exit). -rem It snapshots the environment set above and replays it into every pane. -rem NOTE: `fsh` is a graphical shell and must not run inside a VT pane; launch -rem it directly (not via vtmgr) if you want it. (Old boot line: fsh) -tvdos/sbin/vtmgr - -rem Fallback shell once vtmgr exits (Alt-0), so the console is never left bare. +rem The interactive shell for this console. command -fancy diff --git a/assets/disk0/commandrc b/assets/disk0/commandrc new file mode 100644 index 0000000..17903c8 --- /dev/null +++ b/assets/disk0/commandrc @@ -0,0 +1,9 @@ +rem commandrc -- environment setup, run by TVDOS.SYS in EVERY context +rem (the boot shell AND every virtual-console pane). Put `set` commands and +rem other env-only configuration here. Do NOT launch apps from this file: +rem app launches belong in AUTOEXEC.BAT (run per-console by vtmgr). + +set PATH=\tvdos\installer;\tvdos\tuidev;\tbas;\hopper\bin;$PATH +set INCLPATH=\hopper\include;$INCLPATH +set HELPPATH=\hopper\help;$HELPPATH +set KEYBOARD=us_colemak diff --git a/assets/disk0/tvdos/TVDOS.SYS b/assets/disk0/tvdos/TVDOS.SYS index 024ee36..aa17fcb 100644 --- a/assets/disk0/tvdos/TVDOS.SYS +++ b/assets/disk0/tvdos/TVDOS.SYS @@ -1471,17 +1471,40 @@ try { serial.println("Warning: Could not load HSDPA driver: " + e.message) } -// Boot script. When vtmgr re-evaluates TVDOS.SYS inside a per-VT pane -// context, the pane already has a SKIP flag set so we don't recursively -// kick off AUTOEXEC.BAT (which would itself invoke command -fancy and -// nest a shell underneath vtmgr). -if (typeof _TVDOS_SKIP_AUTOEXEC === "undefined" || !_TVDOS_SKIP_AUTOEXEC) { - serial.println(`TVDOS.SYS initialised on VM ${sys.getVmId()}, running boot script...`); +// Boot script. The work is split across two files: +// \commandrc -- environment (`set` commands); run in EVERY context. +// \AUTOEXEC.BAT -- per-console launch (IME + interactive shell). +// vtmgr re-evaluates TVDOS.SYS inside each per-VT pane; a pane sets +// _TVDOS_IS_VT_PANE so it only replays the environment here and leaves the +// AUTOEXEC launch to vtmgr's pane bootstrap (which avoids recursively +// spawning vtmgr inside a pane). +{ + let cmdsrc = files.open("A:/tvdos/bin/command.js").sread() + let runBatch = (path) => eval(`var _BAT=function(exec_args){${cmdsrc}\n};_BAT`)(["", "-c", path]) - let cmdfile = files.open("A:/tvdos/bin/command.js") - eval(`var _AUTOEXEC=function(exec_args){${cmdfile.sread()}\n};` + - `_AUTOEXEC`)(["", "-c", "\\AUTOEXEC.BAT"]) -} -else { - serial.println(`TVDOS.SYS re-initialised in VT pane on VM ${sys.getVmId()}`); + // Environment first, boot and pane alike. Gives every pane the same + // PATH / KEYBOARD / etc. natively, with no env-snapshot replay needed. + // \commandrc has no .BAT extension (so command.js's batch-file path, + // which keys off the extension, won't pick it up); run it line-by-line. + // `set` mutates the shared _TVDOS.variables, so the effect persists across + // the per-line shell invocations. Skip blanks and `rem` comments. + let rcFile = files.open("A:/commandrc") + if (rcFile.exists) { + rcFile.sread().split('\n').forEach((line) => { + let t = line.trim() + if (t.length > 0 && !/^rem(\s|$)/i.test(t)) runBatch(line) + }) + } + + if (typeof _TVDOS_IS_VT_PANE === "undefined" || !_TVDOS_IS_VT_PANE) { + serial.println(`TVDOS.SYS initialised on VM ${sys.getVmId()}, running boot script...`); + // Boot console: hand the screen to the virtual-console multiplexer. + // When it exits (Alt-0), fall through to AUTOEXEC so the console is + // never left bare. + runBatch("tvdos/sbin/vtmgr") + runBatch("\\AUTOEXEC.BAT") + } + else { + serial.println(`TVDOS.SYS re-initialised in VT pane on VM ${sys.getVmId()}`); + } } diff --git a/assets/disk0/tvdos/sbin/vtmgr.js b/assets/disk0/tvdos/sbin/vtmgr.js index 07c7f7d..6d1440c 100644 --- a/assets/disk0/tvdos/sbin/vtmgr.js +++ b/assets/disk0/tvdos/sbin/vtmgr.js @@ -72,25 +72,23 @@ const TVDOS_SYS_SRC = files.open("A:/tvdos/TVDOS.SYS").sread() // _BIOS is visible) and re-declare it in every pane bootstrap. const BIOS_FIRST_BOOTABLE_PORT = JSON.stringify(_BIOS.FIRST_BOOTABLE_PORT) -// Snapshot the live environment from the main context. vtmgr runs after -// AUTOEXEC.BAT, so _TVDOS.variables already holds the fully expanded PATH, -// INCLPATH, HELPPATH, KEYBOARD, etc. Each pane is a fresh context whose -// TVDOS.SYS only sets the bare defaults, so we replay this snapshot over the -// pane's defaults — giving panes the same path/variable resolution as the -// boot shell without re-running AUTOEXEC (which would relaunch the GUI shell). -const ENV_JSON = JSON.stringify(_TVDOS.variables) +// Environment no longer needs snapshotting/replaying: each pane re-evaluates +// TVDOS.SYS, whose boot block runs \commandrc in every context, so the pane +// gets the same PATH / KEYBOARD / etc. natively. The pane then runs +// \AUTOEXEC.BAT (the per-console launch script: IME + interactive shell). function makePaneBootstrap(vtNum) { const TP_BASE = vtTextPlaneAddr(vtNum) const VT_BLK = vtBlockAddr(vtNum) - // Shell-launcher code runs after TVDOS.SYS in the SAME eval scope, so - // `files`, `eval`, `_TVDOS` etc. resolve via lexical closure. Apply the - // captured environment before launching the shell. + // Launcher code runs after TVDOS.SYS in the SAME eval scope, so `files`, + // `eval`, `_TVDOS` etc. resolve via lexical closure. TVDOS.SYS's boot + // block already ran \commandrc (env) and skipped its own AUTOEXEC because + // the pane sets _TVDOS_IS_VT_PANE; here we run \AUTOEXEC.BAT to launch the + // per-console shell. const SHELL_START = ";\n" - + "Object.assign(_TVDOS.variables, " + ENV_JSON + ");\n" + "var _cmdfileSrc = files.open('A:/tvdos/bin/command.js').sread();\n" - + "eval('var _VTSHELL=function(exec_args){' + _cmdfileSrc + '\\n};_VTSHELL')(['', '-fancy']);\n" + + "eval('var _VTSHELL=function(exec_args){' + _cmdfileSrc + '\\n};_VTSHELL')(['', '-c', '\\\\AUTOEXEC.BAT']);\n" const combined = TVDOS_SYS_SRC + SHELL_START @@ -407,10 +405,9 @@ con.poll_keys = function() { return [0,0,0,0,0,0,0,0] } // ── TVDOS.SYS init flags + BIOS stub ─────────────────────────────────────── globalThis._TVDOS_IS_VT_PANE = true -globalThis._TVDOS_SKIP_AUTOEXEC = true globalThis._BIOS = { FIRST_BOOTABLE_PORT: ${BIOS_FIRST_BOOTABLE_PORT} } -// ── load TVDOS.SYS and start command -fancy in one direct-eval call ───── +// ── load TVDOS.SYS and run AUTOEXEC.BAT (the per-console shell) in one direct-eval ───── // Strict-mode direct eval is scope-isolated, so TVDOS.SYS's \`const _TVDOS\` // only survives within the eval scope. The shell launcher must run inside // the same eval to access it (via lexical closure into nested evals).