Wire up headless / WASM mode at module boundaries

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.
This commit is contained in:
Intubun 2026-05-04 13:31:11 +02:00 committed by Enno Schnackenberg
parent b63f83ac0f
commit 9dcb9681f6
6 changed files with 111 additions and 4 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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