From 9dcb9681f609cec514f455e1a608320bda3f979b Mon Sep 17 00:00:00 2001 From: Intubun <41478036+Intubun@users.noreply.github.com> Date: Mon, 4 May 2026 13:31:11 +0200 Subject: [PATCH] Wire up headless / WASM mode at module boundaries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Glue between the null display driver and the rest of Magic so that running with -d null does not require any process-level resources (signals, timers, stdin, an X display, or a Tcl interpreter). * utils/signals.c — gate setitimer, fcntl-based file watches, kill and the legacy sigsetmask/sigaction setup behind #ifdef __EMSCRIPTEN__. Every signals path becomes a no-op in WASM. Also fixes DBWriteBackup() being called with one argument when its real prototype takes three. * windows/windDisp.c — WindUpdate() returns immediately when GrDisplayStatus == DISPLAY_SUSPEND. This is the runtime counterpart to the null driver's DISPLAY_SUSPEND state. * extflat/EFargs.c — EFArgs() with a missing input name no longer jumps to "usage:" in headless WASM (which would call MainExit and kill the process); it sets *err_result and returns NULL so the caller can decide what to do. Native MAGIC_WRAPPER and native non-MAGIC_WRAPPER builds keep their original behavior. * dbwind/DBWcommands.c — registers exttosim / ext2sim / exttospice / ext2spice in non-MAGIC_WRAPPER builds. Without this, WASM users could not invoke these commands at all (they were previously inside an #ifdef MAGIC_WRAPPER block). The C implementations (CmdExtToSim / CmdExtToSpice) are linked unconditionally outside modular builds. * textio/txCommands.c, textio/textio.h — TxDispatchString(), a new library-style command entry point that parses a single string, dispatches it through WindSendCommand and returns a status code. This is what magic_wasm_run_command() calls from JavaScript. --- dbwind/DBWcommands.c | 25 +++++++++++++++++++++++-- extflat/EFargs.c | 6 ++++++ textio/textio.h | 1 + textio/txCommands.c | 41 +++++++++++++++++++++++++++++++++++++++++ utils/signals.c | 38 ++++++++++++++++++++++++++++++++++++-- windows/windDisp.c | 4 ++++ 6 files changed, 111 insertions(+), 4 deletions(-) diff --git a/dbwind/DBWcommands.c b/dbwind/DBWcommands.c index e26004cf..01f9ddcc 100644 --- a/dbwind/DBWcommands.c +++ b/dbwind/DBWcommands.c @@ -149,6 +149,9 @@ extern void CmdAutoExtToSpice(); #else extern void CmdExtToSpice(); #endif +#else /* !MAGIC_WRAPPER */ +extern void CmdExtToSim(); +extern void CmdExtToSpice(); #endif /* @@ -568,8 +571,26 @@ DBWInitCommands() "ext2spice [args] convert extracted file(s) to a SPICE format file;" " type\n\t\t\t\"ext2spice help\" for information on options", CmdExtToSpice, FALSE); -#endif /* EXT2SPICE_AUTO */ -#endif /* MAGIC_WRAPPER */ +#endif /* EXT2SPICE_AUTO */ +#else /* !MAGIC_WRAPPER */ + /* In non-Tcl builds (e.g. WASM), register the C implementations directly */ + WindAddCommand(DBWclientID, + "exttosim [args] convert extracted file(s) to a sim format file;" + " type\n\t\t\t\"exttosim help\" for information on options", + CmdExtToSim, FALSE); + WindAddCommand(DBWclientID, + "ext2sim [args] convert extracted file(s) to a sim format file;" + " type\n\t\t\t\"ext2sim help\" for information on options", + CmdExtToSim, FALSE); + WindAddCommand(DBWclientID, + "exttospice [args] convert extracted file(s) to a SPICE format file;" + " type\n\t\t\t\"exttospice help\" for information on options", + CmdExtToSpice, FALSE); + WindAddCommand(DBWclientID, + "ext2spice [args] convert extracted file(s) to a SPICE format file;" + " type\n\t\t\t\"ext2spice help\" for information on options", + CmdExtToSpice, FALSE); +#endif /* MAGIC_WRAPPER */ #ifdef USE_READLINE diff --git a/extflat/EFargs.c b/extflat/EFargs.c index e3620e05..bcdb6c66 100644 --- a/extflat/EFargs.c +++ b/extflat/EFargs.c @@ -307,6 +307,12 @@ EFArgs(argc, argv, err_result, argsProc, cdata) if (inname == NULL) #ifdef MAGIC_WRAPPER return NULL; +#elif defined(EMSCRIPTEN) + { + /* Headless WASM: signal error via err_result, no goto usage path */ + if (err_result != NULL) *err_result = TRUE; + return NULL; + } #else goto usage; #endif diff --git a/textio/textio.h b/textio/textio.h index a7a3a856..61be7dc8 100644 --- a/textio/textio.h +++ b/textio/textio.h @@ -96,6 +96,7 @@ extern char TxInterruptChar; /* The current interrupt character */ /* command procedures */ extern void TxDispatch(FILE *f); +extern int TxDispatchString(const char *str, bool quiet); /* C99 compat */ extern void TxMore(const char *mesg); diff --git a/textio/txCommands.c b/textio/txCommands.c index 1d52b31e..57b1f103 100644 --- a/textio/txCommands.c +++ b/textio/txCommands.c @@ -1751,6 +1751,47 @@ done: DQFree(&inputCommands); } +int +TxDispatchString( + const char *str, + bool quiet) +{ + int result = 0; + DQueue inputCommands; + + DQInit(&inputCommands, 4); + TxParseString_internal(str, &inputCommands, (TxInputEvent *) NULL); + + while (!DQIsEmpty(&inputCommands)) + { + TxCommand *cmd; + + cmd = (TxCommand *) DQPopFront(&inputCommands); + + if (txHaveCurrentPoint) + { + cmd->tx_p = txCurrentPoint; + cmd->tx_wid = txCurrentWindowID; + txHaveCurrentPoint = FALSE; + } + + result = WindSendCommand((MagWindow *) NULL, cmd, quiet); + TxFreeCommand(cmd); + TxCommandNumber++; + + if (result != 0) + break; + } + + WindUpdate(); + + while (!DQIsEmpty(&inputCommands)) + TxFreeCommand((TxCommand *) DQPopFront(&inputCommands)); + DQFree(&inputCommands); + + return result; +} + #endif /* !MAGIC_WRAPPER */ /* diff --git a/utils/signals.c b/utils/signals.c index 74e80aef..772c7078 100644 --- a/utils/signals.c +++ b/utils/signals.c @@ -72,7 +72,7 @@ static char rcsid[] __attribute__ ((unused)) = "$Header: /usr/cvsroot/magic-8.0/ #endif /* specially imported */ -extern bool DBWriteBackup(); +/* DBWriteBackup declared in database/database.h */ /* macs support BSD4.2 signals, so turn off the SYSV flag for this module */ #ifdef __APPLE__ @@ -123,6 +123,10 @@ static int sigNumDisables = 0; void SigSetTimer(int secs) { +#ifdef __EMSCRIPTEN__ + (void)secs; + return; +#else struct itimerval subsecond; /* one-quarter second interval */ /* @@ -139,6 +143,7 @@ SigSetTimer(int secs) subsecond.it_value.tv_usec = (secs == 0) ? 250000 : 0; setitimer(ITIMER_REAL, &subsecond, NULL); +#endif } /*---------------------------*/ @@ -148,6 +153,9 @@ SigSetTimer(int secs) void SigRemoveTimer() { +#ifdef __EMSCRIPTEN__ + return; +#else struct itimerval zero; /* zero time to stop the timer */ /* fprintf(stderr, "Timer stop\n"); fflush(stderr); */ @@ -158,6 +166,7 @@ SigRemoveTimer() zero.it_interval.tv_usec = 0; setitimer(ITIMER_REAL, &zero, NULL); +#endif } sigRetVal @@ -256,11 +265,16 @@ bool SigCheckProcess(pid) int pid; { +#ifdef __EMSCRIPTEN__ + (void)pid; + return FALSE; +#else int result; result = kill((pid_t)pid, SIGCONT); if (result == 0) return TRUE; else return FALSE; +#endif } /*--------------------------------------------------------- @@ -334,6 +348,11 @@ SigWatchFile(filenum, filename) * calls (such as windows: /dev/winXX). */ { +#ifdef __EMSCRIPTEN__ + (void)filenum; + (void)filename; + return; +#else int flags; bool iswindow; @@ -383,6 +402,7 @@ SigWatchFile(filenum, filename) # endif #endif } +#endif } @@ -409,6 +429,11 @@ SigUnWatchFile(filenum, filename) * calls (such as windows: /dev/winXX). */ { +#ifdef __EMSCRIPTEN__ + (void)filenum; + (void)filename; + return; +#else int flags; flags = fcntl(filenum, F_GETFL, 0); @@ -429,6 +454,7 @@ SigUnWatchFile(filenum, filename) perror("(Magic) SigWatchFile3"); # endif #endif +#endif } @@ -475,7 +501,7 @@ sigOnInterrupt(int signo) sigRetVal sigOnTerm(int signo) { - DBWriteBackup(NULL); + DBWriteBackup(NULL, FALSE, FALSE); exit (1); } @@ -614,6 +640,13 @@ void SigInit(batchmode) int batchmode; { +#ifdef __EMSCRIPTEN__ + SigInterruptOnSigIO = (batchmode) ? -1 : 0; + SigInterruptPending = FALSE; + SigIOReady = FALSE; + SigGotSigWinch = FALSE; + return; +#else /* fprintf(stderr, "Establishing signal handlers.\n"); fflush(stderr); */ if (batchmode) @@ -669,6 +702,7 @@ SigInit(batchmode) #if !defined(SYSV) && !defined(CYGWIN) && !defined(EMSCRIPTEN) sigsetmask(0); #endif +#endif } void diff --git a/windows/windDisp.c b/windows/windDisp.c index 498b8398..02f38de7 100644 --- a/windows/windDisp.c +++ b/windows/windDisp.c @@ -795,6 +795,10 @@ WindUpdate() SigSetTimer(0); #endif + /* For headless/no-display mode: skip if display is suspended */ + if (GrDisplayStatus == DISPLAY_SUSPEND) + return; + TTMaskSetOnlyType(&windTileMask, TT_ERROR_P); /* Make a scan through each of the windows, in order from top