From 61524b3685562ee1959e95ee48e54d23e7fd272e Mon Sep 17 00:00:00 2001 From: minjaesong Date: Sun, 17 May 2026 00:12:18 +0900 Subject: [PATCH] TVDOS: userconfigpath and zfmrc --- assets/disk0/AUTOEXEC.BAT | 1 + assets/disk0/tvdos/TVDOS.SYS | 3 +- assets/disk0/tvdos/bin/command.js | 27 ++++++++++++++-- assets/disk0/tvdos/bin/playucf.js | 4 +-- assets/disk0/tvdos/bin/taut.js | 10 +++--- assets/disk0/tvdos/bin/zfm.js | 54 +++++++++++++++++++++++++++++-- 6 files changed, 86 insertions(+), 13 deletions(-) diff --git a/assets/disk0/AUTOEXEC.BAT b/assets/disk0/AUTOEXEC.BAT index 103d9bb..029e333 100644 --- a/assets/disk0/AUTOEXEC.BAT +++ b/assets/disk0/AUTOEXEC.BAT @@ -3,6 +3,7 @@ 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 this line specifies which shell to be presented after the boot precess: diff --git a/assets/disk0/tvdos/TVDOS.SYS b/assets/disk0/tvdos/TVDOS.SYS index 11c4e18..226a86c 100644 --- a/assets/disk0/tvdos/TVDOS.SYS +++ b/assets/disk0/tvdos/TVDOS.SYS @@ -151,7 +151,8 @@ _TVDOS.variables = { PATHEXT: ".com;.bat;.app;.js;.alias", HELPPATH: "\\tvdos\\help", OS_NAME: "TSVM Disk Operating System", - OS_VERSION: _TVDOS.VERSION + OS_VERSION: _TVDOS.VERSION, + USERCONFIGPATH: "\\home\\config", }; Object.freeze(_TVDOS); diff --git a/assets/disk0/tvdos/bin/command.js b/assets/disk0/tvdos/bin/command.js index 11b1457..d9e735a 100644 --- a/assets/disk0/tvdos/bin/command.js +++ b/assets/disk0/tvdos/bin/command.js @@ -823,17 +823,26 @@ shell.execute = function(line, nameOverride) { // parse alias // $0: all arguments // $1..9: specific arguments + // Tokens that contain whitespace or shell metacharacters must be re-quoted + // before re-execution, otherwise the re-parse splits them on spaces. + var quoteAliasArg = function(s) { + if (s === undefined || s === null) return "" + s = ''+s + if (s.length === 0) return "" + if (/[\s"|><&]/.test(s)) return '"' + s.replaceAll('"', '^"') + '"' + return s + } var lines = programCode.split('\n').filter(function(it) { return it.length > 0 }) // this return is not shell's return! lines.forEach(function(line) { var newLine = line // replace $1..$9 - for (let j = 1; j < 9; j++) { - newLine = newLine.replaceAll('$'+j, tokens[j]) + for (let j = 1; j <= 9; j++) { + newLine = newLine.replaceAll('$'+j, quoteAliasArg(tokens[j])) } // replace $0 - newLine = newLine.replaceAll('$0', tokens.slice(1).join(' ')) + newLine = newLine.replaceAll('$0', tokens.slice(1).map(quoteAliasArg).join(' ')) shell.execute(newLine, cmd) }) @@ -955,6 +964,18 @@ _G.shell = shell /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// ensure USERCONFIGPATH directory exists +try { + let userConfigPath = `${CURRENT_DRIVE}:${_TVDOS.variables.USERCONFIGPATH}` + let userConfigDir = files.open(userConfigPath) + if (!userConfigDir.exists) { + debugprintln(`command.js > creating USERCONFIGPATH at ${userConfigPath}`) + userConfigDir.mkDir() + } +} catch (e) { + debugprintln("command.js > USERCONFIGPATH creation failed: " + e.message) +} + if (exec_args[1] !== undefined) { // only meaningful switches would be either -c or -k anyway var firstSwitch = exec_args[1].toLowerCase() diff --git a/assets/disk0/tvdos/bin/playucf.js b/assets/disk0/tvdos/bin/playucf.js index 21dc313..a886c16 100644 --- a/assets/disk0/tvdos/bin/playucf.js +++ b/assets/disk0/tvdos/bin/playucf.js @@ -307,7 +307,7 @@ for (let i = 0; i < cueElements.length; i++) { // Execute the player with modified environment exec_args[1] = targetPath if (playerFile) { - let playerPath = `A:\\tvdos\\bin\\${playerFile}.js` + let playerPath = `A:${_TVDOS.variables.DOSDIR}/bin/${playerFile}.js` if (files.open(playerPath).exists) { eval(files.readText(playerPath)) } else { @@ -334,7 +334,7 @@ for (let i = 0; i < cueElements.length; i++) { } // Execute the appropriate player - let playerPath = `A:\\tvdos\\bin\\${playerFile}.js` + let playerPath = `A:${_TVDOS.variables.DOSDIR}/bin/${playerFile}.js` if (!files.open(playerPath).exists) { serial.println(`Warning: Player script not found: ${playerPath}`) continue diff --git a/assets/disk0/tvdos/bin/taut.js b/assets/disk0/tvdos/bin/taut.js index b73a545..3008540 100644 --- a/assets/disk0/tvdos/bin/taut.js +++ b/assets/disk0/tvdos/bin/taut.js @@ -1745,18 +1745,18 @@ if (fullPathObj === undefined) { return 1 } -const logofile = files.open("A:/tvdos/bin/tauthdr.r8") +const logofile = files.open("A:"+_TVDOS.variables.DOSDIR+"/bin/tauthdr.r8") const logoBytes = logofile.bread(); logofile.close() const logoTexture = new gl.Texture(92, 14, logoBytes) -const buttonfile = files.open("A:/tvdos/bin/tautbtn.r8") +const buttonfile = files.open("A:"+_TVDOS.variables.DOSDIR+"/bin/tautbtn.r8") const buttonBytes = buttonfile.bread(); buttonfile.close() const buttonTexture = new gl.Texture(2, 28, buttonBytes) -//const buttonNullfile = files.open("A:/tvdos/bin/tautbtn0.r8") +//const buttonNullfile = files.open("A:"+_TVDOS.variables.DOSDIR+"/bin/tautbtn0.r8") //const buttonNullBytes = buttonNullfile.bread(); buttonNullfile.close() //const buttonNullTexture = new gl.Texture(35, 28, buttonNullBytes) -font.setLowRom("A:/tvdos/bin/tautfont_low.chr") -font.setHighRom("A:/tvdos/bin/tautfont_high.chr") +font.setLowRom("A:"+_TVDOS.variables.DOSDIR+"/bin/tautfont_low.chr") +font.setHighRom("A:"+_TVDOS.variables.DOSDIR+"/bin/tautfont_high.chr") const songsMeta = loadTaudSongList(fullPathObj.full) let currentSongIndex = 0 let projectSongCursor = 0 diff --git a/assets/disk0/tvdos/bin/zfm.js b/assets/disk0/tvdos/bin/zfm.js index 2188486..6b7298a 100644 --- a/assets/disk0/tvdos/bin/zfm.js +++ b/assets/disk0/tvdos/bin/zfm.js @@ -69,6 +69,55 @@ const EXEC_FUNS = { "taud": (f) => _G.shell.execute(`microtone "${f}"`), } +function makeExecFun(template) { + return (f) => _G.shell.execute(template.replaceAll("{0}", `"${f}"`)) +} + +function loadZfmrc() { + try { + let zfmrcPath = `A:${_TVDOS.variables.USERCONFIGPATH}\\zfmrc` + let zfmrcFile = files.open(zfmrcPath) + if (!zfmrcFile.exists) return + + let content = zfmrcFile.sread() + let lines = content.split(/\r?\n/) + let currentSection = null + + for (let i = 0; i < lines.length; i++) { + let line = lines[i].trim() + if (line.length === 0 || line.startsWith("#") || line.startsWith(";")) continue + + if (line.startsWith("[") && line.endsWith("]")) { + currentSection = line.substring(1, line.length - 1).toUpperCase() + continue + } + + if (currentSection === "EXEC_FUNS") { + let commaIdx = line.indexOf(",") + if (commaIdx < 0) continue + let ext = line.substring(0, commaIdx).trim().toLowerCase() + let template = line.substring(commaIdx + 1).trim() + if (ext.length === 0 || template.length === 0) continue + EXEC_FUNS[ext] = makeExecFun(template) + } + else if (currentSection === "COL_HL_EXT") { + let commaIdx = line.indexOf(",") + if (commaIdx < 0) continue + let ext = line.substring(0, commaIdx).trim().toLowerCase() + let colStr = line.substring(commaIdx + 1).trim() + if (ext.length === 0 || colStr.length === 0) continue + let col = parseInt(colStr, 10) + if (isNaN(col)) continue + COL_HL_EXT[ext] = col + } + } + } catch (e) { + serial.println("zfm: failed to load zfmrc: " + e.message) + } +} + +loadZfmrc() + let windowMode = 0 // 0 == left, 1 == right let windowFocus = [0] // is a stack; 0: files window, 1: palette window, 2: popup window @@ -82,6 +131,7 @@ let cursor = [0, 0] // absolute position! function bytesToReadable(i) { return ''+ ( + (i > 999999999999) ? (((i / 10000000000)|0)/100 + "T") : (i > 999999999) ? (((i / 10000000)|0)/100 + "G") : (i > 999999) ? (((i / 10000)|0)/100 + "M") : (i > 9999) ? (((i / 100)|0)/10 + "K") : @@ -677,11 +727,11 @@ while (!exit) { let keysym = event[1] let keyJustHit = (1 == event[2]) - if (keyJustHit && event[3] != keys.ENTER) { // release the latch right away if the key is not Return + if (keyJustHit && event[3] != keys.ENTER && keysym != "q") { // release the latch right away if the key is neither Return nor 'q' firstRunLatch = false } - if (keyJustHit && firstRunLatch) { // filter out the initial ENTER key as they would cause unwanted behaviours + if (keyJustHit && firstRunLatch) { // filter out the initial ENTER/'q' key as they would cause unwanted behaviours firstRunLatch = false } else {