2017-04-25 14:41:48 +02:00
|
|
|
/*----------------------------------------------------------------------*/
|
|
|
|
|
/* tclmagic.c --- Creates the interpreter-wrapped version of magic. */
|
|
|
|
|
/* */
|
|
|
|
|
/* Written by Tim Edwards August 2002 */
|
|
|
|
|
/* */
|
|
|
|
|
/* Note that this file is tied to Tcl. The original version (from */
|
|
|
|
|
/* around April 2002) relied on SWIG, the only differences being */
|
|
|
|
|
/* as few %{ ... %} boundaries and the replacement of the */
|
|
|
|
|
/* Tclmagic_Init function header with "%init %{", and call the */
|
|
|
|
|
/* file "tclmagic.i". However, the rest of the associated wrapper */
|
|
|
|
|
/* code got so dependent on Tcl commands that there is no longer any */
|
|
|
|
|
/* point in using SWIG. */
|
|
|
|
|
/* */
|
|
|
|
|
/* When using SWIG, the Makefile requires: */
|
|
|
|
|
/* */
|
|
|
|
|
/* tclmagic.c: tclmagic.i */
|
|
|
|
|
/* swig -tcl8 -o tclmagic.c tclmagic.i */
|
|
|
|
|
/* */
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
|
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <stdarg.h>
|
|
|
|
|
#include <unistd.h>
|
|
|
|
|
#include <signal.h>
|
|
|
|
|
#include <string.h>
|
|
|
|
|
#include <errno.h>
|
2025-02-24 09:39:27 +01:00
|
|
|
#ifdef HAVE_SYS_RESOURCE_H
|
|
|
|
|
#include <sys/resource.h>
|
|
|
|
|
#endif
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
#include "tcltk/tclmagic.h"
|
2020-05-23 23:13:14 +02:00
|
|
|
#include "utils/main.h"
|
|
|
|
|
#include "utils/magic.h"
|
2017-04-25 14:41:48 +02:00
|
|
|
#include "utils/geometry.h"
|
2020-05-23 23:13:14 +02:00
|
|
|
#include "tiles/tile.h"
|
|
|
|
|
#include "utils/hash.h"
|
2017-04-25 14:41:48 +02:00
|
|
|
#include "utils/dqueue.h"
|
|
|
|
|
#include "database/database.h"
|
|
|
|
|
#include "windows/windows.h"
|
|
|
|
|
#include "commands/commands.h"
|
|
|
|
|
#include "utils/utils.h"
|
|
|
|
|
#include "textio/textio.h"
|
|
|
|
|
#include "textio/txcommands.h"
|
|
|
|
|
#include "utils/signals.h"
|
|
|
|
|
#include "graphics/graphics.h"
|
2020-05-23 23:13:14 +02:00
|
|
|
#include "utils/malloc.h"
|
2017-04-25 14:41:48 +02:00
|
|
|
#include "dbwind/dbwind.h"
|
|
|
|
|
|
|
|
|
|
/*
|
2026-07-23 15:55:20 +02:00
|
|
|
* MagicVersion / MagicRevision / MagicCompileTime are defined once in
|
|
|
|
|
* utils/buildinfo.c and declared in utils/magic_buildinfo.h (included via
|
|
|
|
|
* utils/magic.h). They no longer need a MAGIC_WRAPPER-guarded definition here.
|
2017-04-25 14:41:48 +02:00
|
|
|
*/
|
|
|
|
|
|
2024-10-21 10:19:15 +02:00
|
|
|
#if TCL_MAJOR_VERSION < 9
|
|
|
|
|
const char *Tclmagic_InitStubsVersion = "8.5";
|
|
|
|
|
#else
|
|
|
|
|
/* Major version changed API (as you'd expect for a major version upgrade)
|
|
|
|
|
* which is compiled into the resulting binary.
|
|
|
|
|
* No possibility of dual version support.
|
|
|
|
|
*/
|
|
|
|
|
const char *Tclmagic_InitStubsVersion = "9.0";
|
|
|
|
|
#endif
|
|
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
Tcl_Interp *magicinterp;
|
|
|
|
|
Tcl_Interp *consoleinterp;
|
|
|
|
|
|
|
|
|
|
HashTable txTclTagTable;
|
|
|
|
|
|
|
|
|
|
Tcl_ChannelType inChannel;
|
|
|
|
|
|
|
|
|
|
/* Forward declarations */
|
|
|
|
|
|
|
|
|
|
int TerminalInputProc(ClientData, char *, int, int *);
|
|
|
|
|
void TxFlushErr();
|
|
|
|
|
void TxFlushOut();
|
|
|
|
|
void RegisterTkCommands();
|
|
|
|
|
|
|
|
|
|
/*--------------------------------------------------------------*/
|
|
|
|
|
/* Verify if a command has a tag callback. */
|
|
|
|
|
/*--------------------------------------------------------------*/
|
|
|
|
|
|
|
|
|
|
int
|
|
|
|
|
TagVerify(keyword)
|
|
|
|
|
char *keyword;
|
|
|
|
|
{
|
|
|
|
|
char *croot, *postcmd;
|
|
|
|
|
HashEntry *entry;
|
|
|
|
|
|
|
|
|
|
/* Skip over namespace qualifier, if any */
|
|
|
|
|
|
|
|
|
|
croot = keyword;
|
|
|
|
|
if (!strncmp(croot, "::", 2)) croot += 2;
|
|
|
|
|
if (!strncmp(croot, "magic::", 7)) croot += 7;
|
|
|
|
|
|
|
|
|
|
entry = HashLookOnly(&txTclTagTable, croot);
|
|
|
|
|
postcmd = (entry) ? (char *)HashGetValue(entry) : NULL;
|
|
|
|
|
return (postcmd) ? TRUE : FALSE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*--------------------------------------------------------------*/
|
|
|
|
|
/* Find any tags associated with a command and execute them. */
|
|
|
|
|
/*--------------------------------------------------------------*/
|
|
|
|
|
|
2023-09-12 17:12:00 +02:00
|
|
|
int
|
2017-04-25 14:41:48 +02:00
|
|
|
TagCallback(interp, tkpath, argc, argv)
|
|
|
|
|
Tcl_Interp *interp;
|
|
|
|
|
char *tkpath;
|
|
|
|
|
int argc; /* original command's number of arguments */
|
|
|
|
|
char *argv[]; /* original command's argument list */
|
|
|
|
|
{
|
|
|
|
|
int argidx, result = TCL_OK;
|
|
|
|
|
char *postcmd, *substcmd, *newcmd, *sptr, *sres;
|
|
|
|
|
char *croot;
|
|
|
|
|
HashEntry *entry;
|
2024-10-21 10:04:38 +02:00
|
|
|
#if TCL_MAJOR_VERSION < 9
|
2017-04-25 14:41:48 +02:00
|
|
|
Tcl_SavedResult state;
|
2024-10-21 10:04:38 +02:00
|
|
|
#else
|
|
|
|
|
Tcl_InterpState state;
|
|
|
|
|
#endif
|
2017-04-25 14:41:48 +02:00
|
|
|
bool reset = FALSE;
|
|
|
|
|
int cmdnum;
|
|
|
|
|
|
|
|
|
|
/* No command, no action */
|
|
|
|
|
|
|
|
|
|
if (argc == 0) return TCL_OK;
|
|
|
|
|
|
|
|
|
|
/* Skip over namespace qualifier, if any */
|
|
|
|
|
|
|
|
|
|
croot = argv[0];
|
|
|
|
|
if (!strncmp(croot, "::", 2)) croot += 2;
|
|
|
|
|
if (!strncmp(croot, "magic::", 7)) croot += 7;
|
|
|
|
|
|
|
|
|
|
entry = HashLookOnly(&txTclTagTable, croot);
|
|
|
|
|
postcmd = (entry) ? (char *)HashGetValue(entry) : NULL;
|
|
|
|
|
|
|
|
|
|
if (postcmd)
|
|
|
|
|
{
|
|
|
|
|
/* The Tag callback should not increase the command number */
|
2020-05-23 23:13:14 +02:00
|
|
|
/* sequence, so save it now and restore it before returning. */
|
2017-04-25 14:41:48 +02:00
|
|
|
cmdnum = TxCommandNumber;
|
|
|
|
|
|
|
|
|
|
substcmd = (char *)mallocMagic(strlen(postcmd) + 1);
|
|
|
|
|
strcpy(substcmd, postcmd);
|
|
|
|
|
sptr = substcmd;
|
|
|
|
|
|
|
|
|
|
/*--------------------------------------------------------------*/
|
|
|
|
|
/* Parse "postcmd" for Tk-substitution escapes */
|
|
|
|
|
/* Allowed escapes are: */
|
|
|
|
|
/* %W substitute the tk path of the layout window */
|
|
|
|
|
/* %r substitute the previous Tcl result string */
|
|
|
|
|
/* %R substitute the previous Tcl result string and */
|
|
|
|
|
/* reset the Tcl result. */
|
|
|
|
|
/* %[0-5] substitute the argument to the original command */
|
|
|
|
|
/* %% substitute a single percent character */
|
|
|
|
|
/* %* (all others) no action: print as-is. */
|
|
|
|
|
/*--------------------------------------------------------------*/
|
|
|
|
|
|
|
|
|
|
while ((sptr = strchr(sptr, '%')) != NULL)
|
|
|
|
|
{
|
|
|
|
|
switch (*(sptr + 1))
|
|
|
|
|
{
|
|
|
|
|
case 'W':
|
|
|
|
|
|
|
|
|
|
/* In the case of the %W escape, first we see if a Tk */
|
|
|
|
|
/* path has been passed in the argument. If not, get */
|
|
|
|
|
/* the window path if there is only one window. */
|
|
|
|
|
/* Otherwise, the window is unknown so we substitute */
|
2020-05-23 23:13:14 +02:00
|
|
|
/* a null list "{}". */
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
if (tkpath == NULL)
|
|
|
|
|
{
|
|
|
|
|
MagWindow *w = NULL;
|
|
|
|
|
windCheckOnlyWindow(&w, DBWclientID);
|
|
|
|
|
if (w != NULL && !(w->w_flags & WIND_OFFSCREEN))
|
|
|
|
|
{
|
Add TCL-embedded WASM build variant alongside the existing non-TCL build
Bump VERSION to 8.3.645.
magic.wasm can now be built as two variants packaged in the same npm
release: notcl/ (legacy, magic's own parser) and tcl/ (intubun/tcl 9.x
statically linked, commands evaluated by Tcl_EvalEx). The TCL fork is
pinned via npm/tcl.ref and cloned/built by magic itself — the tcl/
checkout is treated as read-only and built out-of-source into
magic/build-tcl-wasm/.
Configure layer:
- New usingTk variable decoupled from usingTcl in scripts/configure.in
+ scripts/configure, so --with-tcl --without-tk is finally a valid
combination. Native Linux Tcl+Tk builds keep their previous behaviour
(both flags default to enabled).
- When usingTk is empty, configure passes -DMAGIC_NO_TK so the small
number of remaining Tk callsites in tcltk/tclmagic.{h,c} compile out,
and TKCOMMON_SRCS / USE_TK_STUBS are omitted from the link.
WASM build orchestration:
- toolchains/emscripten/build-tcl-wasm.sh builds libtcl9.x.a + libtclstub.a
+ tclConfig.sh out-of-source from a pristine intubun/tcl checkout.
- npm/build.sh grew a --variant=<tcl|notcl|both> flag and writes its
outputs into npm/tcl/ and npm/notcl/. It also clones intubun/tcl with
autocrlf=false at the SHA pinned by npm/tcl.ref.
- magic/Makefile (WASM block only): magicWasm.o is now compiled with
DFLAGS_NOSTUB so Tcl_CreateInterp resolves to libtcl9.x directly
before tclStubsPtr is set. magic.js link pulls in LIB_SPECS_NOSTUB
and -ltclstub. After rules.mak include, magic: is a phony alias for
magic.js so the generic ${MODULE} recipe doesn't fight it.
- toolchains/emscripten/defs.mak: add -sUSE_ZLIB=1 (libtcl9 references
zlib), replace -sSTACK_SIZE=N with -Wl,-z,stack-size=N (emcc >=5
rejects the setting form).
- magic/magicWasm.c bootstraps the embedded interp under MAGIC_WRAPPER
(Tcl_CreateInterp -> Tcl_Init -> Tclmagic_Init) and routes
run_command through Tcl_EvalEx.
- magic/magicTop.c: gate MagicVersion/Revision/CompileTime on
!MAGIC_WRAPPER so they don't collide with the copies in
tcltk/tclmagic.c when both objects land in the same wasm binary.
npm package:
- Subpath exports: ".", "./tcl", "./notcl". Default import keeps the
pre-existing non-TCL behaviour for backward compatibility.
- examples/smoke-tcl.mjs exercises the TCL variant.
CI:
- main-wasm.yml clones intubun/tcl at the pinned ref, builds both
variants via npm/build.sh --variant=both, runs the existing notcl
test suite and the new TCL smoke test, and publishes only on a
v<x.y.z>... git tag. Tag name (minus the leading v) becomes the
npm version.
2026-05-17 21:41:03 +02:00
|
|
|
#ifndef MAGIC_NO_TK
|
2017-04-25 14:41:48 +02:00
|
|
|
Tk_Window tkwind = (Tk_Window) w->w_grdata;
|
|
|
|
|
if (tkwind != NULL) tkpath = Tk_PathName(tkwind);
|
Add TCL-embedded WASM build variant alongside the existing non-TCL build
Bump VERSION to 8.3.645.
magic.wasm can now be built as two variants packaged in the same npm
release: notcl/ (legacy, magic's own parser) and tcl/ (intubun/tcl 9.x
statically linked, commands evaluated by Tcl_EvalEx). The TCL fork is
pinned via npm/tcl.ref and cloned/built by magic itself — the tcl/
checkout is treated as read-only and built out-of-source into
magic/build-tcl-wasm/.
Configure layer:
- New usingTk variable decoupled from usingTcl in scripts/configure.in
+ scripts/configure, so --with-tcl --without-tk is finally a valid
combination. Native Linux Tcl+Tk builds keep their previous behaviour
(both flags default to enabled).
- When usingTk is empty, configure passes -DMAGIC_NO_TK so the small
number of remaining Tk callsites in tcltk/tclmagic.{h,c} compile out,
and TKCOMMON_SRCS / USE_TK_STUBS are omitted from the link.
WASM build orchestration:
- toolchains/emscripten/build-tcl-wasm.sh builds libtcl9.x.a + libtclstub.a
+ tclConfig.sh out-of-source from a pristine intubun/tcl checkout.
- npm/build.sh grew a --variant=<tcl|notcl|both> flag and writes its
outputs into npm/tcl/ and npm/notcl/. It also clones intubun/tcl with
autocrlf=false at the SHA pinned by npm/tcl.ref.
- magic/Makefile (WASM block only): magicWasm.o is now compiled with
DFLAGS_NOSTUB so Tcl_CreateInterp resolves to libtcl9.x directly
before tclStubsPtr is set. magic.js link pulls in LIB_SPECS_NOSTUB
and -ltclstub. After rules.mak include, magic: is a phony alias for
magic.js so the generic ${MODULE} recipe doesn't fight it.
- toolchains/emscripten/defs.mak: add -sUSE_ZLIB=1 (libtcl9 references
zlib), replace -sSTACK_SIZE=N with -Wl,-z,stack-size=N (emcc >=5
rejects the setting form).
- magic/magicWasm.c bootstraps the embedded interp under MAGIC_WRAPPER
(Tcl_CreateInterp -> Tcl_Init -> Tclmagic_Init) and routes
run_command through Tcl_EvalEx.
- magic/magicTop.c: gate MagicVersion/Revision/CompileTime on
!MAGIC_WRAPPER so they don't collide with the copies in
tcltk/tclmagic.c when both objects land in the same wasm binary.
npm package:
- Subpath exports: ".", "./tcl", "./notcl". Default import keeps the
pre-existing non-TCL behaviour for backward compatibility.
- examples/smoke-tcl.mjs exercises the TCL variant.
CI:
- main-wasm.yml clones intubun/tcl at the pinned ref, builds both
variants via npm/build.sh --variant=both, runs the existing notcl
test suite and the new TCL smoke test, and publishes only on a
v<x.y.z>... git tag. Tag name (minus the leading v) becomes the
npm version.
2026-05-17 21:41:03 +02:00
|
|
|
#endif
|
2017-04-25 14:41:48 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (tkpath == NULL)
|
|
|
|
|
newcmd = (char *)mallocMagic(strlen(substcmd) + 2);
|
|
|
|
|
else
|
|
|
|
|
newcmd = (char *)mallocMagic(strlen(substcmd) + strlen(tkpath));
|
|
|
|
|
|
|
|
|
|
strcpy(newcmd, substcmd);
|
|
|
|
|
|
|
|
|
|
if (tkpath == NULL)
|
|
|
|
|
strcpy(newcmd + (int)(sptr - substcmd), "{}");
|
|
|
|
|
else
|
|
|
|
|
strcpy(newcmd + (int)(sptr - substcmd), tkpath);
|
|
|
|
|
|
|
|
|
|
strcat(newcmd, sptr + 2);
|
|
|
|
|
freeMagic(substcmd);
|
|
|
|
|
substcmd = newcmd;
|
|
|
|
|
sptr = substcmd;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 'R':
|
|
|
|
|
reset = TRUE;
|
|
|
|
|
case 'r':
|
|
|
|
|
sres = (char *)Tcl_GetStringResult(magicinterp);
|
|
|
|
|
newcmd = (char *)mallocMagic(strlen(substcmd)
|
|
|
|
|
+ strlen(sres) + 1);
|
|
|
|
|
strcpy(newcmd, substcmd);
|
|
|
|
|
sprintf(newcmd + (int)(sptr - substcmd), "\"%s\"", sres);
|
|
|
|
|
strcat(newcmd, sptr + 2);
|
|
|
|
|
freeMagic(substcmd);
|
|
|
|
|
substcmd = newcmd;
|
|
|
|
|
sptr = substcmd;
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case '0': case '1': case '2': case '3': case '4': case '5':
|
|
|
|
|
argidx = (int)(*(sptr + 1) - '0');
|
|
|
|
|
if ((argidx >= 0) && (argidx < argc))
|
|
|
|
|
{
|
|
|
|
|
newcmd = (char *)mallocMagic(strlen(substcmd)
|
2025-10-08 23:11:27 +02:00
|
|
|
+ strlen(argv[argidx]) + 1);
|
2017-04-25 14:41:48 +02:00
|
|
|
strcpy(newcmd, substcmd);
|
|
|
|
|
strcpy(newcmd + (int)(sptr - substcmd), argv[argidx]);
|
|
|
|
|
strcat(newcmd, sptr + 2);
|
|
|
|
|
freeMagic(substcmd);
|
|
|
|
|
substcmd = newcmd;
|
|
|
|
|
sptr = substcmd;
|
|
|
|
|
}
|
|
|
|
|
else if (argidx >= argc)
|
|
|
|
|
{
|
2025-10-08 23:11:27 +02:00
|
|
|
/* Note that the assumption is that a specific
|
|
|
|
|
* command option is expected. Therefore if there
|
|
|
|
|
* are fewer options given to the command, a
|
|
|
|
|
* placeholder should be added. Use an empty
|
|
|
|
|
* brace {} for this.
|
|
|
|
|
*/
|
|
|
|
|
newcmd = (char *)mallocMagic(strlen(substcmd) + 3);
|
2017-04-25 14:41:48 +02:00
|
|
|
strcpy(newcmd, substcmd);
|
2025-10-08 23:11:27 +02:00
|
|
|
strcpy(newcmd + (int)(sptr - substcmd), "{}");
|
|
|
|
|
strcat(newcmd, sptr + 2);
|
2017-04-25 14:41:48 +02:00
|
|
|
freeMagic(substcmd);
|
|
|
|
|
substcmd = newcmd;
|
|
|
|
|
sptr = substcmd;
|
|
|
|
|
}
|
|
|
|
|
else sptr++;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case '%':
|
|
|
|
|
newcmd = (char *)mallocMagic(strlen(substcmd) + 1);
|
|
|
|
|
strcpy(newcmd, substcmd);
|
|
|
|
|
strcpy(newcmd + (int)(sptr - substcmd), sptr + 1);
|
|
|
|
|
freeMagic(substcmd);
|
|
|
|
|
substcmd = newcmd;
|
|
|
|
|
sptr = substcmd;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* fprintf(stderr, "Substituted tag callback is \"%s\"\n", substcmd); */
|
|
|
|
|
/* fflush(stderr); */
|
|
|
|
|
|
2024-10-21 10:04:38 +02:00
|
|
|
#if TCL_MAJOR_VERSION < 9
|
2017-04-25 14:41:48 +02:00
|
|
|
Tcl_SaveResult(interp, &state);
|
2024-10-21 10:04:38 +02:00
|
|
|
#else
|
|
|
|
|
state = Tcl_SaveInterpState(interp, TCL_OK);
|
|
|
|
|
#endif
|
2017-04-25 14:41:48 +02:00
|
|
|
result = Tcl_EvalEx(interp, substcmd, -1, 0);
|
|
|
|
|
if ((result == TCL_OK) && (reset == FALSE))
|
2024-10-21 10:04:38 +02:00
|
|
|
{
|
|
|
|
|
#if TCL_MAJOR_VERSION < 9
|
2017-04-25 14:41:48 +02:00
|
|
|
Tcl_RestoreResult(interp, &state);
|
2024-10-21 10:04:38 +02:00
|
|
|
#else
|
|
|
|
|
Tcl_RestoreInterpState(interp, state);
|
|
|
|
|
#endif
|
|
|
|
|
}
|
2017-04-25 14:41:48 +02:00
|
|
|
else
|
2024-10-21 10:04:38 +02:00
|
|
|
{
|
|
|
|
|
#if TCL_MAJOR_VERSION < 9
|
2017-04-25 14:41:48 +02:00
|
|
|
Tcl_DiscardResult(&state);
|
2024-10-21 10:04:38 +02:00
|
|
|
#else
|
|
|
|
|
Tcl_DiscardInterpState(state);
|
|
|
|
|
#endif
|
|
|
|
|
}
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
freeMagic(substcmd);
|
|
|
|
|
TxCommandNumber = cmdnum; /* restore original value */
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*--------------------------------------------------------------*/
|
|
|
|
|
/* Add a command tag callback */
|
|
|
|
|
/*--------------------------------------------------------------*/
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
AddCommandTag(ClientData clientData,
|
|
|
|
|
Tcl_Interp *interp, int argc, char *argv[])
|
|
|
|
|
{
|
|
|
|
|
HashEntry *entry;
|
|
|
|
|
char *hstring;
|
2025-10-08 23:11:27 +02:00
|
|
|
int argstart = 1, idx;
|
|
|
|
|
bool doadd = FALSE;
|
|
|
|
|
Tcl_Obj *objv1;
|
|
|
|
|
|
|
|
|
|
static char *tagtypes[] =
|
|
|
|
|
{
|
|
|
|
|
"add", "replace", NULL
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
typedef enum
|
|
|
|
|
{
|
|
|
|
|
IDX_ADD, IDX_REPLACE
|
|
|
|
|
} tagOption;
|
|
|
|
|
|
|
|
|
|
if (argc == 4)
|
|
|
|
|
{
|
|
|
|
|
/* For four arguments, the 2nd must be "add" or "replace" */
|
|
|
|
|
objv1 = Tcl_NewStringObj(argv[1], strlen(argv[1]));
|
|
|
|
|
if (Tcl_GetIndexFromObj(interp, objv1, (const char **)tagtypes,
|
|
|
|
|
"tag options", 0, &idx) == TCL_OK)
|
|
|
|
|
{
|
|
|
|
|
if (idx == IDX_ADD)
|
|
|
|
|
doadd = TRUE;
|
|
|
|
|
else if (idx == IDX_REPLACE)
|
|
|
|
|
doadd = FALSE;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
return TCL_ERROR;
|
|
|
|
|
|
|
|
|
|
argstart++;
|
|
|
|
|
argc--;
|
|
|
|
|
}
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
if (argc != 2 && argc != 3)
|
|
|
|
|
return TCL_ERROR;
|
|
|
|
|
|
2025-10-08 23:11:27 +02:00
|
|
|
entry = HashFind(&txTclTagTable, argv[argstart]);
|
2020-05-23 23:13:14 +02:00
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
if (entry == NULL) return TCL_ERROR;
|
|
|
|
|
|
|
|
|
|
hstring = (char *)HashGetValue(entry);
|
|
|
|
|
|
|
|
|
|
if (argc == 2)
|
|
|
|
|
{
|
|
|
|
|
Tcl_SetResult(magicinterp, hstring, NULL);
|
|
|
|
|
return TCL_OK;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-08 23:11:27 +02:00
|
|
|
/* If there is no existing tag then "tag add" is just "tag replace" */
|
|
|
|
|
if (doadd && (hstring == NULL)) doadd = FALSE;
|
2017-04-25 14:41:48 +02:00
|
|
|
|
2025-10-08 23:11:27 +02:00
|
|
|
if (doadd) /* add to existing contents */
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
2025-10-08 23:11:27 +02:00
|
|
|
if (strlen(argv[argstart + 1]) > 0) /* Only handle non-empty strings */
|
|
|
|
|
{
|
|
|
|
|
char *newstring = mallocMagic(strlen(hstring)
|
|
|
|
|
+ strlen(argv[argstart + 1]) + 4);
|
|
|
|
|
sprintf(newstring, "%s ; %s", hstring, argv[argstart + 1]);
|
|
|
|
|
HashSetValue(entry, newstring);
|
|
|
|
|
freeMagic(hstring);
|
|
|
|
|
}
|
2017-04-25 14:41:48 +02:00
|
|
|
}
|
2025-10-08 23:11:27 +02:00
|
|
|
else /* replace */
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
2025-10-08 23:11:27 +02:00
|
|
|
if (hstring != NULL) freeMagic(hstring);
|
|
|
|
|
|
|
|
|
|
if (strlen(argv[argstart + 1]) == 0)
|
|
|
|
|
{
|
|
|
|
|
HashSetValue(entry, NULL);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
hstring = StrDup((char **)NULL, argv[argstart + 1]);
|
|
|
|
|
HashSetValue(entry, hstring);
|
|
|
|
|
}
|
2017-04-25 14:41:48 +02:00
|
|
|
}
|
|
|
|
|
return TCL_OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*--------------------------------------------------------------*/
|
|
|
|
|
/* Dispatch a command from Tcl */
|
|
|
|
|
/* See TxTclDispatch() in textio/txCommands.c */
|
|
|
|
|
/*--------------------------------------------------------------*/
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
_tcl_dispatch(ClientData clientData,
|
|
|
|
|
Tcl_Interp *interp, int argc, char *argv[])
|
|
|
|
|
{
|
2020-05-23 23:13:14 +02:00
|
|
|
int wval;
|
2017-04-25 14:41:48 +02:00
|
|
|
int result, idx;
|
|
|
|
|
Tcl_Obj *objv0;
|
|
|
|
|
char *argv0, *tkwind;
|
|
|
|
|
|
|
|
|
|
/* Check command (argv[0]) against known conflicting */
|
|
|
|
|
/* command names. If the command is potentially a */
|
|
|
|
|
/* Tcl/Tk command, try it as such, first. If a Tcl */
|
|
|
|
|
/* error is returned, then try it as a magic */
|
|
|
|
|
/* command. Note that the other way (try the magic */
|
|
|
|
|
/* command first) would necessitate setting Tcl */
|
|
|
|
|
/* results for every magic command. Too much work. */
|
|
|
|
|
|
|
|
|
|
static char *conflicts[] =
|
|
|
|
|
{
|
|
|
|
|
"clockwise", "flush", "load", "label", "array", "grid", NULL
|
|
|
|
|
};
|
|
|
|
|
static char *resolutions[] =
|
|
|
|
|
{
|
|
|
|
|
"orig_clock", "tcl_flush", "tcl_load", "tcl_label", "tcl_array",
|
|
|
|
|
"tcl_grid", NULL
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
typedef enum
|
|
|
|
|
{
|
|
|
|
|
IDX_CLOCK, IDX_FLUSH, IDX_LOAD, IDX_LABEL, IDX_ARRAY,
|
|
|
|
|
IDX_GRID
|
|
|
|
|
} conflictCommand;
|
|
|
|
|
|
|
|
|
|
/* Skip any "::" namespace prefix before parsing */
|
|
|
|
|
argv0 = argv[0];
|
|
|
|
|
if (!strncmp(argv0, "::", 2)) argv0 += 2;
|
|
|
|
|
|
|
|
|
|
objv0 = Tcl_NewStringObj(argv0, strlen(argv0));
|
2025-02-26 14:41:13 +01:00
|
|
|
Tcl_IncrRefCount(objv0); /* this seems needed to ensure it is freed across Tcl_EvalObjv() */
|
2024-10-21 10:04:37 +02:00
|
|
|
if (Tcl_GetIndexFromObj(interp, objv0, (const char **)conflicts,
|
2017-04-25 14:41:48 +02:00
|
|
|
"overloaded command", 0, &idx) == TCL_OK)
|
|
|
|
|
{
|
|
|
|
|
int i;
|
|
|
|
|
Tcl_Obj **objv = (Tcl_Obj **)Tcl_Alloc(argc * sizeof(Tcl_Obj *));
|
2020-05-23 23:13:14 +02:00
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
/* Create a Tcl_Obj array suitable for calling Tcl_EvalObjv. */
|
|
|
|
|
/* The first argument is changed from the magic command name to */
|
|
|
|
|
/* "tcl" + the command name. This assumes that all conflicting */
|
|
|
|
|
/* command names have been so renamed in the startup script! */
|
|
|
|
|
|
|
|
|
|
objv[0] = Tcl_NewStringObj(resolutions[idx], strlen(resolutions[idx]));
|
|
|
|
|
Tcl_IncrRefCount(objv[0]);
|
|
|
|
|
|
|
|
|
|
for (i = 1; i < argc; i++)
|
|
|
|
|
{
|
|
|
|
|
objv[i] = Tcl_NewStringObj(argv[i], strlen(argv[i]));
|
|
|
|
|
Tcl_IncrRefCount(objv[i]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
result = Tcl_EvalObjv(interp, argc, objv, 0);
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < argc; i++)
|
|
|
|
|
Tcl_DecrRefCount(objv[i]);
|
|
|
|
|
Tcl_Free((char *)objv);
|
|
|
|
|
|
|
|
|
|
if (result == TCL_OK)
|
2025-02-26 14:41:13 +01:00
|
|
|
{
|
|
|
|
|
Tcl_DecrRefCount(objv0);
|
2017-04-25 14:41:48 +02:00
|
|
|
return result;
|
2025-02-26 14:41:13 +01:00
|
|
|
}
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
/* The rule is to execute Magic commands for any Tcl command */
|
|
|
|
|
/* with the same name that returns an error. However, this */
|
|
|
|
|
/* rule hangs magic when the "load" command is used on a shared */
|
|
|
|
|
/* object file that fails to load properly. So if the filename */
|
2022-11-02 22:12:46 +01:00
|
|
|
/* has an extension which is not ".mag" or ".gz", we will */
|
|
|
|
|
/* return the error. */
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
/* Updated 1/20/2015: Need to check for a '.' AFTER the last */
|
|
|
|
|
/* slash, so as to avoid problems with ./, ../, etc. */
|
|
|
|
|
|
|
|
|
|
if (idx == IDX_LOAD)
|
|
|
|
|
{
|
|
|
|
|
char *dotptr, *slashptr;
|
|
|
|
|
if (argc >= 2)
|
|
|
|
|
{
|
|
|
|
|
slashptr = strrchr(argv[1], '/');
|
|
|
|
|
if (slashptr == NULL)
|
|
|
|
|
slashptr = argv[1];
|
|
|
|
|
else
|
|
|
|
|
slashptr++;
|
|
|
|
|
|
|
|
|
|
if ((dotptr = strrchr(slashptr, '.')) != NULL)
|
2025-02-26 14:41:13 +01:00
|
|
|
{
|
2022-11-02 22:12:46 +01:00
|
|
|
if (strcmp(dotptr + 1, "mag") && strcmp(dotptr + 1, "gz"))
|
2025-02-26 14:41:13 +01:00
|
|
|
{
|
|
|
|
|
Tcl_DecrRefCount(objv0);
|
2017-04-25 14:41:48 +02:00
|
|
|
return result;
|
2025-02-26 14:41:13 +01:00
|
|
|
}
|
|
|
|
|
}
|
2017-04-25 14:41:48 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-02-26 14:41:13 +01:00
|
|
|
Tcl_DecrRefCount(objv0);
|
2017-04-25 14:41:48 +02:00
|
|
|
Tcl_ResetResult(interp);
|
|
|
|
|
|
|
|
|
|
if (TxInputRedirect == TX_INPUT_REDIRECTED)
|
|
|
|
|
TxInputRedirect = TX_INPUT_PENDING_RESET;
|
|
|
|
|
|
|
|
|
|
wval = TxTclDispatch(clientData, argc, argv, TRUE);
|
|
|
|
|
|
|
|
|
|
if (TxInputRedirect == TX_INPUT_PENDING_RESET)
|
|
|
|
|
TxInputRedirect = TX_INPUT_NORMAL;
|
|
|
|
|
|
|
|
|
|
/* If the command did not pass through _tk_dispatch, but the command was */
|
|
|
|
|
/* entered by key redirection from a window, then TxInputRedirect will be */
|
|
|
|
|
/* set to TX_INPUT_PROCESSING and the window ID will have been set by */
|
|
|
|
|
/* TxSetPoint(). Do our level best to find the Tk window name. */
|
|
|
|
|
|
|
|
|
|
if (TxInputRedirect == TX_INPUT_PROCESSING)
|
|
|
|
|
{
|
|
|
|
|
if (GrWindowNamePtr)
|
|
|
|
|
{
|
|
|
|
|
MagWindow *mw = WindSearchWid(TxGetPoint(NULL));
|
|
|
|
|
if (mw != NULL)
|
|
|
|
|
tkwind = (*GrWindowNamePtr)(mw);
|
|
|
|
|
else
|
|
|
|
|
tkwind = NULL;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
tkwind = NULL;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
tkwind = NULL;
|
|
|
|
|
|
|
|
|
|
// Pass back an error if TxTclDispatch failed
|
|
|
|
|
if (wval != 0) return TCL_ERROR;
|
|
|
|
|
|
|
|
|
|
return TagCallback(interp, tkwind, argc, argv);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*--------------------------------------------------------------*/
|
|
|
|
|
/* Dispatch a window-related command. The first argument is */
|
|
|
|
|
/* the window to which the command should be directed, so we */
|
|
|
|
|
/* determine which window this is, set "TxCurCommand" values */
|
|
|
|
|
/* to point to the window, then dispatch the command. */
|
|
|
|
|
/*--------------------------------------------------------------*/
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
_tk_dispatch(ClientData clientData,
|
|
|
|
|
Tcl_Interp *interp, int argc, char *argv[])
|
|
|
|
|
{
|
|
|
|
|
int id;
|
|
|
|
|
char *tkpath;
|
|
|
|
|
char *arg0;
|
|
|
|
|
Point txp;
|
|
|
|
|
|
2024-10-04 20:59:55 +02:00
|
|
|
arg0 = argv[0];
|
|
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
if (GrWindowIdPtr)
|
|
|
|
|
{
|
|
|
|
|
/* Key macros set the point from the graphics module code but */
|
|
|
|
|
/* set up the command to be dispatched via _tk_dispatch(). */
|
|
|
|
|
/* Therefore it is necessary to check if a point position */
|
|
|
|
|
/* has already been set for this command. If not, then the */
|
|
|
|
|
/* command was probably called from the command entry window, */
|
|
|
|
|
/* so we choose an arbitrary point which is somewhere in the */
|
|
|
|
|
/* window, so that command functions have a point of reference. */
|
|
|
|
|
|
|
|
|
|
id = (*GrWindowIdPtr)(argv[0]);
|
|
|
|
|
|
|
|
|
|
if (TxGetPoint(&txp) != id)
|
|
|
|
|
{
|
|
|
|
|
/* This is a point in the window, inside the */
|
|
|
|
|
/* scrollbars if they are managed by magic. */
|
|
|
|
|
|
|
|
|
|
txp.p_x = 20;
|
|
|
|
|
txp.p_y = 20;
|
|
|
|
|
}
|
|
|
|
|
TxSetPoint(txp.p_x, txp.p_y, id);
|
|
|
|
|
argc--;
|
|
|
|
|
argv++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TxTclDispatch(clientData, argc, argv, FALSE);
|
|
|
|
|
|
|
|
|
|
/* Get pathname of window and pass to TagCallback */
|
|
|
|
|
return TagCallback(interp, arg0, argc, argv);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*--------------------------------------------------------------*/
|
|
|
|
|
/* Set up a window to use commands via _tk_dispatch */
|
|
|
|
|
/*--------------------------------------------------------------*/
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
MakeWindowCommand(char *wname, MagWindow *mw)
|
|
|
|
|
{
|
|
|
|
|
char *tclcmdstr;
|
|
|
|
|
|
|
|
|
|
Tcl_CreateCommand(magicinterp, wname, (Tcl_CmdProc *)_tk_dispatch,
|
|
|
|
|
(ClientData)mw, (Tcl_CmdDeleteProc *) NULL);
|
|
|
|
|
|
|
|
|
|
/* Force the window manager to use magic's "close" command to close */
|
|
|
|
|
/* down a window. */
|
|
|
|
|
|
|
|
|
|
tclcmdstr = (char *)mallocMagic(52 + 2 * strlen(wname));
|
|
|
|
|
sprintf(tclcmdstr, "wm protocol %s WM_DELETE_WINDOW "
|
|
|
|
|
"{magic::closewindow %s}", wname, wname);
|
|
|
|
|
Tcl_EvalEx(magicinterp, tclcmdstr, -1, 0);
|
|
|
|
|
freeMagic(tclcmdstr);
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-24 09:39:27 +01:00
|
|
|
#ifdef HAVE_SETRLIMIT
|
|
|
|
|
static int
|
|
|
|
|
process_rlimit_nofile_ensure(rlim_t nofile)
|
|
|
|
|
{
|
|
|
|
|
struct rlimit rlim;
|
|
|
|
|
int err = getrlimit(RLIMIT_NOFILE, &rlim);
|
|
|
|
|
if (err < 0)
|
|
|
|
|
return err;
|
|
|
|
|
rlim_t rlim_cur = rlim.rlim_cur;
|
|
|
|
|
/* nofile != RLIM_INFINITY && rlim.rlim_max != RLIM_INFINITY */
|
|
|
|
|
if (nofile > rlim.rlim_max && nofile != rlim.rlim_max)
|
|
|
|
|
return -1;
|
|
|
|
|
if (rlim.rlim_cur < nofile || nofile == RLIM_INFINITY)
|
|
|
|
|
{
|
|
|
|
|
rlim.rlim_cur = nofile;
|
|
|
|
|
err = setrlimit(RLIMIT_NOFILE, &rlim);
|
|
|
|
|
}
|
|
|
|
|
if (err != 0)
|
|
|
|
|
TxPrintf("WARNING: process_rlimit_nofile_ensure(%lu) = %d (%d) [rlim_cur=%lu rlim_max=%lu]\n", nofile, err, errno, rlim_cur, rlim.rlim_max);
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
#endif /* HAVE_SETRLIMIT */
|
|
|
|
|
|
|
|
|
|
/* this function encapsulates the default policy on startup */
|
|
|
|
|
static int
|
|
|
|
|
process_rlimit_startup_check(void)
|
|
|
|
|
{
|
|
|
|
|
#ifdef HAVE_GETRLIMIT
|
|
|
|
|
#if TCL_MAJOR_VERSION < 9
|
|
|
|
|
/* TCL8 has select() support and no support for poll/epoll for the main event loop */
|
|
|
|
|
struct rlimit rlim;
|
|
|
|
|
int err = getrlimit(RLIMIT_NOFILE, &rlim);
|
|
|
|
|
if (err < 0)
|
|
|
|
|
return err;
|
|
|
|
|
if (rlim.rlim_cur > FD_SETSIZE)
|
|
|
|
|
{
|
|
|
|
|
TxPrintf("WARNING: RLIMIT_NOFILE is above %d and Tcl_Version<9 this may cause runtime issues [rlim_cur=%lu]\n", FD_SETSIZE, rlim.rlim_cur);
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
#else
|
|
|
|
|
#ifdef HAVE_SETRLIMIT
|
|
|
|
|
/* TCL9 has poll/epoll support for the main event loop,
|
|
|
|
|
* ifdef due to rlim_t type availbility
|
|
|
|
|
*/
|
|
|
|
|
return process_rlimit_nofile_ensure(4096);
|
|
|
|
|
#else
|
|
|
|
|
return -1;
|
|
|
|
|
#endif /* HAVE_SETRLIMIT */
|
|
|
|
|
#endif /* TCL_MAJOR_VERSION < 9 */
|
|
|
|
|
#else
|
|
|
|
|
return -1;
|
|
|
|
|
#endif /* HAVE_GETRLIMIT */
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-21 12:39:38 +02:00
|
|
|
/*--------------------------------------------------------------*/
|
|
|
|
|
/* Register magic:: commands with the Tcl interpreter. */
|
|
|
|
|
/* Called after Magic's C subsystems are fully */
|
|
|
|
|
/* initialized (i.e. after magicMainInit returns 0) */
|
|
|
|
|
/* so that WindNextClient / WindGetCommandTable return */
|
|
|
|
|
/* populated tables. */
|
|
|
|
|
/*--------------------------------------------------------------*/
|
2026-05-21 10:25:51 +02:00
|
|
|
|
|
|
|
|
void
|
|
|
|
|
TclmagicRegisterCommands(Tcl_Interp *interp)
|
|
|
|
|
{
|
|
|
|
|
WindClient client;
|
|
|
|
|
int n;
|
|
|
|
|
char keyword[100];
|
|
|
|
|
char *kwptr = keyword + 7;
|
|
|
|
|
const char * const *commandTable;
|
|
|
|
|
|
|
|
|
|
sprintf(keyword, "magic::");
|
|
|
|
|
client = (WindClient)NULL;
|
|
|
|
|
while ((client = WindNextClient(client)) != NULL)
|
|
|
|
|
{
|
|
|
|
|
commandTable = WindGetCommandTable(client);
|
|
|
|
|
for (n = 0; commandTable[n] != NULL; n++)
|
|
|
|
|
{
|
2026-05-21 14:28:09 +02:00
|
|
|
sscanf(commandTable[n], "%92s", kwptr);
|
2026-05-21 10:25:51 +02:00
|
|
|
Tcl_CreateCommand(interp, keyword, (Tcl_CmdProc *)_tcl_dispatch,
|
|
|
|
|
(ClientData)NULL, (Tcl_CmdDeleteProc *)NULL);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
/*------------------------------------------------------*/
|
|
|
|
|
/* Main startup procedure */
|
|
|
|
|
/*------------------------------------------------------*/
|
2020-05-23 23:13:14 +02:00
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
static int
|
|
|
|
|
_magic_initialize(ClientData clientData,
|
|
|
|
|
Tcl_Interp *interp, int argc, char *argv[])
|
|
|
|
|
{
|
|
|
|
|
WindClient client;
|
|
|
|
|
int n, i;
|
|
|
|
|
char keyword[100];
|
|
|
|
|
char *kwptr = keyword + 7;
|
2024-10-10 21:16:36 +02:00
|
|
|
const char * const *commandTable;
|
2017-04-25 14:41:48 +02:00
|
|
|
int result;
|
|
|
|
|
|
|
|
|
|
/* Is magic being executed in a slave interpreter? */
|
|
|
|
|
|
|
|
|
|
if ((consoleinterp = Tcl_GetMaster(interp)) == NULL)
|
|
|
|
|
consoleinterp = interp;
|
|
|
|
|
|
|
|
|
|
// Force tkcon to send output to terminal during initialization
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
RuntimeFlags |= (MAIN_TK_CONSOLE | MAIN_TK_PRINTF);
|
|
|
|
|
Tcl_Eval(consoleinterp, "rename ::puts ::unused_puts\n");
|
|
|
|
|
Tcl_Eval(consoleinterp, "rename ::tkcon_tcl_puts ::puts\n");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Did we start in the same interpreter as we initialized? */
|
|
|
|
|
if (magicinterp != interp)
|
|
|
|
|
{
|
|
|
|
|
TxError("Warning: Switching interpreters. Tcl-magic is not set up "
|
|
|
|
|
"to handle this.\n");
|
|
|
|
|
magicinterp = interp;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (mainInitBeforeArgs(argc, argv) != 0) goto magicfatal;
|
|
|
|
|
if (mainDoArgs(argc, argv) != 0) goto magicfatal;
|
|
|
|
|
|
|
|
|
|
// Redirect output back to the console
|
|
|
|
|
if (TxTkConsole)
|
|
|
|
|
{
|
|
|
|
|
RuntimeFlags &= ~MAIN_TK_PRINTF;
|
|
|
|
|
Tcl_Eval(consoleinterp, "rename ::puts ::tkcon_tcl_puts\n");
|
|
|
|
|
Tcl_Eval(consoleinterp, "rename ::unused_puts ::puts\n");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Identify version and revision */
|
|
|
|
|
|
|
|
|
|
TxPrintf("\nMagic %s revision %s - Compiled on %s.\n", MagicVersion,
|
|
|
|
|
MagicRevision, MagicCompileTime);
|
|
|
|
|
TxPrintf("Starting magic under Tcl interpreter\n");
|
|
|
|
|
if (TxTkConsole)
|
|
|
|
|
TxPrintf("Using Tk console window\n");
|
|
|
|
|
else
|
|
|
|
|
TxPrintf("Using the terminal as the console.\n");
|
|
|
|
|
TxFlushOut();
|
|
|
|
|
|
2025-02-24 09:39:27 +01:00
|
|
|
process_rlimit_startup_check();
|
|
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
if (mainInitAfterArgs() != 0) goto magicfatal;
|
|
|
|
|
|
|
|
|
|
/* Registration of commands is performed after calling the */
|
|
|
|
|
/* start function, not after initialization, as the command */
|
|
|
|
|
/* modularization requires magic initialization to get a */
|
|
|
|
|
/* valid DBWclientID, windClientID, etc. */
|
|
|
|
|
|
|
|
|
|
sprintf(keyword, "magic::");
|
|
|
|
|
|
|
|
|
|
/* Work through all the known clients, and register the */
|
|
|
|
|
/* commands of all of them. */
|
|
|
|
|
|
|
|
|
|
client = (WindClient)NULL;
|
|
|
|
|
while ((client = WindNextClient(client)) != NULL)
|
|
|
|
|
{
|
|
|
|
|
commandTable = WindGetCommandTable(client);
|
|
|
|
|
for (n = 0; commandTable[n] != NULL; n++)
|
|
|
|
|
{
|
|
|
|
|
sscanf(commandTable[n], "%s ", kwptr); /* get first word */
|
|
|
|
|
Tcl_CreateCommand(interp, keyword, (Tcl_CmdProc *)_tcl_dispatch,
|
|
|
|
|
(ClientData)NULL, (Tcl_CmdDeleteProc *) NULL);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Extra commands provided by the Tk graphics routines */
|
|
|
|
|
/* (See graphics/grTkCommon.c) */
|
|
|
|
|
/* (Unless "-dnull" option has been given) */
|
|
|
|
|
|
Add TCL-embedded WASM build variant alongside the existing non-TCL build
Bump VERSION to 8.3.645.
magic.wasm can now be built as two variants packaged in the same npm
release: notcl/ (legacy, magic's own parser) and tcl/ (intubun/tcl 9.x
statically linked, commands evaluated by Tcl_EvalEx). The TCL fork is
pinned via npm/tcl.ref and cloned/built by magic itself — the tcl/
checkout is treated as read-only and built out-of-source into
magic/build-tcl-wasm/.
Configure layer:
- New usingTk variable decoupled from usingTcl in scripts/configure.in
+ scripts/configure, so --with-tcl --without-tk is finally a valid
combination. Native Linux Tcl+Tk builds keep their previous behaviour
(both flags default to enabled).
- When usingTk is empty, configure passes -DMAGIC_NO_TK so the small
number of remaining Tk callsites in tcltk/tclmagic.{h,c} compile out,
and TKCOMMON_SRCS / USE_TK_STUBS are omitted from the link.
WASM build orchestration:
- toolchains/emscripten/build-tcl-wasm.sh builds libtcl9.x.a + libtclstub.a
+ tclConfig.sh out-of-source from a pristine intubun/tcl checkout.
- npm/build.sh grew a --variant=<tcl|notcl|both> flag and writes its
outputs into npm/tcl/ and npm/notcl/. It also clones intubun/tcl with
autocrlf=false at the SHA pinned by npm/tcl.ref.
- magic/Makefile (WASM block only): magicWasm.o is now compiled with
DFLAGS_NOSTUB so Tcl_CreateInterp resolves to libtcl9.x directly
before tclStubsPtr is set. magic.js link pulls in LIB_SPECS_NOSTUB
and -ltclstub. After rules.mak include, magic: is a phony alias for
magic.js so the generic ${MODULE} recipe doesn't fight it.
- toolchains/emscripten/defs.mak: add -sUSE_ZLIB=1 (libtcl9 references
zlib), replace -sSTACK_SIZE=N with -Wl,-z,stack-size=N (emcc >=5
rejects the setting form).
- magic/magicWasm.c bootstraps the embedded interp under MAGIC_WRAPPER
(Tcl_CreateInterp -> Tcl_Init -> Tclmagic_Init) and routes
run_command through Tcl_EvalEx.
- magic/magicTop.c: gate MagicVersion/Revision/CompileTime on
!MAGIC_WRAPPER so they don't collide with the copies in
tcltk/tclmagic.c when both objects land in the same wasm binary.
npm package:
- Subpath exports: ".", "./tcl", "./notcl". Default import keeps the
pre-existing non-TCL behaviour for backward compatibility.
- examples/smoke-tcl.mjs exercises the TCL variant.
CI:
- main-wasm.yml clones intubun/tcl at the pinned ref, builds both
variants via npm/build.sh --variant=both, runs the existing notcl
test suite and the new TCL smoke test, and publishes only on a
v<x.y.z>... git tag. Tag name (minus the leading v) becomes the
npm version.
2026-05-17 21:41:03 +02:00
|
|
|
#ifndef MAGIC_NO_TK
|
2017-04-25 14:41:48 +02:00
|
|
|
if (strcmp(MainDisplayType, "NULL"))
|
|
|
|
|
RegisterTkCommands(interp);
|
Add TCL-embedded WASM build variant alongside the existing non-TCL build
Bump VERSION to 8.3.645.
magic.wasm can now be built as two variants packaged in the same npm
release: notcl/ (legacy, magic's own parser) and tcl/ (intubun/tcl 9.x
statically linked, commands evaluated by Tcl_EvalEx). The TCL fork is
pinned via npm/tcl.ref and cloned/built by magic itself — the tcl/
checkout is treated as read-only and built out-of-source into
magic/build-tcl-wasm/.
Configure layer:
- New usingTk variable decoupled from usingTcl in scripts/configure.in
+ scripts/configure, so --with-tcl --without-tk is finally a valid
combination. Native Linux Tcl+Tk builds keep their previous behaviour
(both flags default to enabled).
- When usingTk is empty, configure passes -DMAGIC_NO_TK so the small
number of remaining Tk callsites in tcltk/tclmagic.{h,c} compile out,
and TKCOMMON_SRCS / USE_TK_STUBS are omitted from the link.
WASM build orchestration:
- toolchains/emscripten/build-tcl-wasm.sh builds libtcl9.x.a + libtclstub.a
+ tclConfig.sh out-of-source from a pristine intubun/tcl checkout.
- npm/build.sh grew a --variant=<tcl|notcl|both> flag and writes its
outputs into npm/tcl/ and npm/notcl/. It also clones intubun/tcl with
autocrlf=false at the SHA pinned by npm/tcl.ref.
- magic/Makefile (WASM block only): magicWasm.o is now compiled with
DFLAGS_NOSTUB so Tcl_CreateInterp resolves to libtcl9.x directly
before tclStubsPtr is set. magic.js link pulls in LIB_SPECS_NOSTUB
and -ltclstub. After rules.mak include, magic: is a phony alias for
magic.js so the generic ${MODULE} recipe doesn't fight it.
- toolchains/emscripten/defs.mak: add -sUSE_ZLIB=1 (libtcl9 references
zlib), replace -sSTACK_SIZE=N with -Wl,-z,stack-size=N (emcc >=5
rejects the setting form).
- magic/magicWasm.c bootstraps the embedded interp under MAGIC_WRAPPER
(Tcl_CreateInterp -> Tcl_Init -> Tclmagic_Init) and routes
run_command through Tcl_EvalEx.
- magic/magicTop.c: gate MagicVersion/Revision/CompileTime on
!MAGIC_WRAPPER so they don't collide with the copies in
tcltk/tclmagic.c when both objects land in the same wasm binary.
npm package:
- Subpath exports: ".", "./tcl", "./notcl". Default import keeps the
pre-existing non-TCL behaviour for backward compatibility.
- examples/smoke-tcl.mjs exercises the TCL variant.
CI:
- main-wasm.yml clones intubun/tcl at the pinned ref, builds both
variants via npm/build.sh --variant=both, runs the existing notcl
test suite and the new TCL smoke test, and publishes only on a
v<x.y.z>... git tag. Tag name (minus the leading v) becomes the
npm version.
2026-05-17 21:41:03 +02:00
|
|
|
#endif
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
/* Set up the console so that its menu option File->Exit */
|
|
|
|
|
/* calls magic's exit routine first. This should not be */
|
|
|
|
|
/* done in console.tcl, or else it puts the console in a */
|
|
|
|
|
/* state where it is difficult to exit, if magic doesn't */
|
|
|
|
|
/* start up correctly. */
|
|
|
|
|
|
|
|
|
|
if (TxTkConsole)
|
|
|
|
|
{
|
|
|
|
|
Tcl_Eval(consoleinterp, "rename ::exit ::quit\n");
|
|
|
|
|
Tcl_Eval(consoleinterp, "proc ::exit args {slave eval quit}\n");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return TCL_OK;
|
|
|
|
|
|
|
|
|
|
magicfatal:
|
2024-10-21 10:23:41 +02:00
|
|
|
TxResetTerminal(FALSE);
|
2017-04-25 14:41:48 +02:00
|
|
|
Tcl_SetResult(interp, "Magic initialization encountered a fatal error.", NULL);
|
|
|
|
|
return TCL_ERROR;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*--------------------------------------------------------------*/
|
|
|
|
|
|
|
|
|
|
typedef struct FileState {
|
|
|
|
|
Tcl_Channel channel;
|
|
|
|
|
int fd;
|
|
|
|
|
int validMask;
|
|
|
|
|
} FileState;
|
|
|
|
|
|
|
|
|
|
/*--------------------------------------------------------------*/
|
|
|
|
|
/* "Wizard" command for manipulating run-time flags. */
|
|
|
|
|
/*--------------------------------------------------------------*/
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
_magic_flags(ClientData clientData,
|
2024-10-21 10:03:27 +02:00
|
|
|
Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
|
|
|
|
int index, index2;
|
|
|
|
|
bool value;
|
|
|
|
|
static char *flagOptions[] = {"debug", "recover", "silent",
|
|
|
|
|
"window", "console", "printf", (char *)NULL};
|
|
|
|
|
static char *yesNo[] = {"off", "no", "false", "0", "on", "yes",
|
|
|
|
|
"true", "1", (char *)NULL};
|
|
|
|
|
|
|
|
|
|
if ((objc != 2) && (objc != 3)) {
|
2020-05-23 23:13:14 +02:00
|
|
|
Tcl_WrongNumArgs(interp, 1, objv, "flag ?value?");
|
2017-04-25 14:41:48 +02:00
|
|
|
return TCL_ERROR;
|
|
|
|
|
}
|
2024-10-21 10:04:37 +02:00
|
|
|
if (Tcl_GetIndexFromObj(interp, objv[1], (const char **)flagOptions,
|
2017-04-25 14:41:48 +02:00
|
|
|
"option", 0, &index) != TCL_OK) {
|
|
|
|
|
return TCL_ERROR;
|
|
|
|
|
}
|
|
|
|
|
if (objc == 2) {
|
|
|
|
|
switch (index) {
|
|
|
|
|
case 0:
|
|
|
|
|
value = (RuntimeFlags & MAIN_DEBUG) ? TRUE : FALSE;
|
|
|
|
|
break;
|
|
|
|
|
case 1:
|
|
|
|
|
value = (RuntimeFlags & MAIN_RECOVER) ? TRUE : FALSE;
|
|
|
|
|
break;
|
|
|
|
|
case 2:
|
|
|
|
|
value = (RuntimeFlags & MAIN_SILENT) ? TRUE : FALSE;
|
|
|
|
|
break;
|
|
|
|
|
case 3:
|
|
|
|
|
value = (RuntimeFlags & MAIN_MAKE_WINDOW) ? TRUE : FALSE;
|
|
|
|
|
break;
|
|
|
|
|
case 4:
|
|
|
|
|
value = (RuntimeFlags & MAIN_TK_CONSOLE) ? TRUE : FALSE;
|
|
|
|
|
break;
|
|
|
|
|
case 5:
|
|
|
|
|
value = (RuntimeFlags & MAIN_TK_PRINTF) ? TRUE : FALSE;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
Tcl_SetObjResult(interp, Tcl_NewBooleanObj(value));
|
|
|
|
|
}
|
|
|
|
|
else {
|
2024-10-21 10:04:37 +02:00
|
|
|
if (Tcl_GetIndexFromObj(interp, objv[2], (const char **)yesNo,
|
2017-04-25 14:41:48 +02:00
|
|
|
"value", 0, &index2) != TCL_OK)
|
|
|
|
|
return TCL_ERROR;
|
|
|
|
|
|
|
|
|
|
value = (index2 > 3) ? TRUE : FALSE;
|
|
|
|
|
switch (index) {
|
|
|
|
|
case 0:
|
|
|
|
|
if (value == TRUE)
|
|
|
|
|
RuntimeFlags |= MAIN_DEBUG;
|
|
|
|
|
else
|
|
|
|
|
RuntimeFlags &= ~MAIN_DEBUG;
|
|
|
|
|
break;
|
|
|
|
|
case 1:
|
|
|
|
|
if (value == TRUE)
|
|
|
|
|
RuntimeFlags |= MAIN_RECOVER;
|
|
|
|
|
else
|
|
|
|
|
RuntimeFlags &= ~MAIN_RECOVER;
|
|
|
|
|
break;
|
|
|
|
|
case 2:
|
|
|
|
|
if (value == TRUE)
|
|
|
|
|
RuntimeFlags |= MAIN_SILENT;
|
|
|
|
|
else
|
|
|
|
|
RuntimeFlags &= ~MAIN_SILENT;
|
|
|
|
|
break;
|
|
|
|
|
case 3:
|
|
|
|
|
if (value == TRUE)
|
|
|
|
|
RuntimeFlags |= MAIN_MAKE_WINDOW;
|
|
|
|
|
else
|
|
|
|
|
RuntimeFlags &= ~MAIN_MAKE_WINDOW;
|
|
|
|
|
break;
|
|
|
|
|
case 4:
|
|
|
|
|
if (value == TRUE)
|
|
|
|
|
RuntimeFlags |= MAIN_TK_CONSOLE;
|
|
|
|
|
else
|
|
|
|
|
RuntimeFlags &= ~MAIN_TK_CONSOLE;
|
|
|
|
|
break;
|
|
|
|
|
case 5:
|
|
|
|
|
if (value == TRUE)
|
|
|
|
|
RuntimeFlags |= MAIN_TK_PRINTF;
|
|
|
|
|
else
|
|
|
|
|
RuntimeFlags &= ~MAIN_TK_PRINTF;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return TCL_OK;
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-30 16:55:08 +02:00
|
|
|
/*--------------------------------------------------------------*/
|
|
|
|
|
/* Pre-initialization: Return the state of the graphics type */
|
|
|
|
|
/* so that certain steps can be taken when NULL graphics are */
|
|
|
|
|
/* specified. */
|
|
|
|
|
/*--------------------------------------------------------------*/
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
_magic_display(ClientData clientData,
|
|
|
|
|
Tcl_Interp *interp, int argc, char *argv[])
|
|
|
|
|
{
|
|
|
|
|
/* Set the result to the name of the graphics mode used. */
|
|
|
|
|
Tcl_SetResult(magicinterp, MainDisplayType, NULL);
|
|
|
|
|
return TCL_OK;
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
/*--------------------------------------------------------------*/
|
|
|
|
|
/* Post-initialization: read in the magic startup files and */
|
|
|
|
|
/* load any initial layout. Note that this is not done via */
|
|
|
|
|
/* script, but probably should be. */
|
|
|
|
|
/*--------------------------------------------------------------*/
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
_magic_startup(ClientData clientData,
|
|
|
|
|
Tcl_Interp *interp, int argc, char *argv[])
|
|
|
|
|
{
|
|
|
|
|
/* Execute contents of startup files and load any initial cell */
|
|
|
|
|
|
|
|
|
|
if (mainInitFinal() != 0)
|
|
|
|
|
{
|
|
|
|
|
/* We don't want mainInitFinal errors to return TCL_ERROR from */
|
|
|
|
|
/* magic::start; otherwise, the window won't come up. As long */
|
|
|
|
|
/* as we have successfully passed mainInitAfterArgs(), magic is */
|
|
|
|
|
/* fundamentally sound. */
|
|
|
|
|
|
|
|
|
|
Tcl_SetResult(interp,
|
|
|
|
|
"Magic encountered problems with the startup files.",
|
|
|
|
|
NULL);
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-21 10:23:41 +02:00
|
|
|
TxResetTerminal(FALSE);
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
if (TxTkConsole)
|
|
|
|
|
{
|
|
|
|
|
Tcl_EvalEx(consoleinterp, "tkcon set ::tkcon::OPT(showstatusbar) 1", -1, 0);
|
|
|
|
|
TxSetPrompt('%');
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
Tcl_Channel oldchannel;
|
|
|
|
|
Tcl_ChannelType *stdChannel;
|
|
|
|
|
FileState *fsPtr, *fsOrig;
|
|
|
|
|
|
|
|
|
|
/* Use the terminal. */
|
|
|
|
|
/* Replace the input proc for stdin with our own. */
|
|
|
|
|
|
|
|
|
|
oldchannel = Tcl_GetStdChannel(TCL_STDIN); // Get existing stdin
|
|
|
|
|
fsOrig = Tcl_GetChannelInstanceData(oldchannel);
|
|
|
|
|
|
tcltk: don't discard piped stdin buffered before the stdin-channel swap
magic replaces Tcl's stdin channel with its own (TerminalInputProc) during
startup (_magic_startup). The replacement builds a brand-new Tcl channel over
the same fd; the new channel starts with an empty buffer. If anything had
already read() stdin bytes out of the kernel into the *old* channel's buffer
before the swap -- e.g. a script/commands piped in fast enough that Tcl buffered
them during startup -- those bytes were silently dropped, and every command
after that point vanished. This was timing-dependent, so it surfaced only
intermittently ("piped input discarded sometimes").
Fix: before creating the replacement channel, drain any bytes still buffered in
the old channel (Tcl_InputBuffered / Tcl_ReadChars) and hand them to
TerminalInputProc via TxBuffer, which it already serves ahead of any fresh
read(). The swap is now lossless by construction rather than by luck. On the
normal path Tcl_InputBuffered() is 0, so the new branch is inert.
How to reproduce / observe the original loss:
Temporarily force the old channel to buffer input just before the swap, right
after `fsOrig = Tcl_GetChannelInstanceData(oldchannel);` in _magic_startup():
{ Tcl_Obj *l = Tcl_NewObj(); Tcl_IncrRefCount(l);
Tcl_GetsObj(oldchannel, l); /* consume 1 line, buffering the rest */
fprintf(stderr, "buffered=%d\n", (int)Tcl_InputBuffered(oldchannel));
Tcl_DecrRefCount(l); }
then pipe a burst of commands (more than one line):
printf 'puts a\nputs b\n...\nquit -noprompt\n' | ./run_magicnull.sh
Comment the drain out and every command after the first disappears; restore it
and they all survive. Measured with 218 bytes buffered: 0/20 commands survive
without the drain, 19/20 with it (the one line the probe consumed aside).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 01:39:50 +02:00
|
|
|
/* We are about to replace stdin with a brand-new channel over the */
|
|
|
|
|
/* same file descriptor. The new channel starts with an empty */
|
|
|
|
|
/* buffer, so any bytes the old channel had already read() out of the */
|
|
|
|
|
/* kernel into *its* buffer would be silently dropped by the swap -- */
|
|
|
|
|
/* e.g. commands piped in fast enough that Tcl buffered them during */
|
|
|
|
|
/* startup. Drain them here and hand them to TerminalInputProc via */
|
|
|
|
|
/* TxBuffer, which it serves ahead of any fresh read(), so no piped */
|
|
|
|
|
/* input is ever lost regardless of startup timing. */
|
|
|
|
|
{
|
|
|
|
|
int nqueued = Tcl_InputBuffered(oldchannel);
|
|
|
|
|
if (nqueued > 0)
|
|
|
|
|
{
|
|
|
|
|
Tcl_Obj *qobj = Tcl_NewObj();
|
|
|
|
|
Tcl_IncrRefCount(qobj);
|
|
|
|
|
if (Tcl_ReadChars(oldchannel, qobj, nqueued, 0) > 0)
|
|
|
|
|
{
|
|
|
|
|
#if TCL_MAJOR_VERSION < 9
|
|
|
|
|
int qlen;
|
|
|
|
|
#else
|
|
|
|
|
Tcl_Size qlen;
|
|
|
|
|
#endif
|
|
|
|
|
char *qstr = Tcl_GetStringFromObj(qobj, &qlen);
|
|
|
|
|
if (qlen > 0)
|
|
|
|
|
{
|
|
|
|
|
if (TxBuffer == NULL)
|
|
|
|
|
{
|
|
|
|
|
TxBuffer = Tcl_Alloc(qlen + 1);
|
|
|
|
|
memcpy(TxBuffer, qstr, qlen);
|
|
|
|
|
TxBuffer[qlen] = '\0';
|
|
|
|
|
}
|
|
|
|
|
else /* prepend the drained bytes ahead of TxBuffer */
|
|
|
|
|
{
|
|
|
|
|
size_t blen = strlen(TxBuffer);
|
|
|
|
|
char *merged = Tcl_Alloc(qlen + blen + 1);
|
|
|
|
|
memcpy(merged, qstr, qlen);
|
|
|
|
|
memcpy(merged + qlen, TxBuffer, blen);
|
|
|
|
|
merged[qlen + blen] = '\0';
|
|
|
|
|
Tcl_Free(TxBuffer);
|
|
|
|
|
TxBuffer = merged;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Tcl_DecrRefCount(qobj);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
/* Copy the structure from the old to the new channel */
|
|
|
|
|
stdChannel = (Tcl_ChannelType *)Tcl_GetChannelType(oldchannel);
|
|
|
|
|
memcpy(&inChannel, stdChannel, sizeof(Tcl_ChannelType));
|
|
|
|
|
inChannel.inputProc = TerminalInputProc;
|
|
|
|
|
|
|
|
|
|
fsPtr = (FileState *)Tcl_Alloc(sizeof(FileState));
|
|
|
|
|
fsPtr->validMask = fsOrig->validMask;
|
|
|
|
|
fsPtr->fd = fsOrig->fd;
|
|
|
|
|
fsPtr->channel = Tcl_CreateChannel(&inChannel, "stdin",
|
|
|
|
|
(ClientData)fsPtr, TCL_READABLE);
|
|
|
|
|
|
|
|
|
|
Tcl_SetStdChannel(fsPtr->channel, TCL_STDIN); // Apply new stdin
|
|
|
|
|
Tcl_RegisterChannel(NULL, fsPtr->channel);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return TCL_OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*--------------------------------------------------------------*/
|
|
|
|
|
/* Tk version of TxDialog */
|
|
|
|
|
/*--------------------------------------------------------------*/
|
|
|
|
|
|
|
|
|
|
int
|
|
|
|
|
TxDialog(prompt, responses, defresp)
|
2025-01-31 18:33:16 +01:00
|
|
|
const char *prompt;
|
|
|
|
|
const char * const *responses;
|
2017-04-25 14:41:48 +02:00
|
|
|
int defresp;
|
|
|
|
|
{
|
|
|
|
|
Tcl_Obj *objPtr;
|
|
|
|
|
int code, result, pos;
|
|
|
|
|
char *evalstr, *newstr;
|
|
|
|
|
|
|
|
|
|
/* Ensure that use of TxPrintString doesn't overwrite the */
|
|
|
|
|
/* value of prompt my making a copy of it. */
|
|
|
|
|
/* 5/11/05---use Tcl_escape() to do the duplication; this */
|
|
|
|
|
/* ensures that cell names with special characters like '$' */
|
|
|
|
|
/* will be handled properly. */
|
|
|
|
|
|
|
|
|
|
newstr = Tcl_escape(prompt);
|
|
|
|
|
/* newstr = StrDup((char **)NULL, prompt); */
|
|
|
|
|
evalstr = TxPrintString("tk_dialog .dialog \"Dialog\""
|
|
|
|
|
" \"%s\" {} %d ", newstr, defresp);
|
|
|
|
|
/* freeMagic(newstr); */
|
|
|
|
|
Tcl_Free(newstr); /* Tcl_escape() uses Tcl_Alloc() */
|
|
|
|
|
|
|
|
|
|
for (pos = 0; responses[pos] != 0; pos++)
|
|
|
|
|
{
|
|
|
|
|
newstr = StrDup((char **)NULL, evalstr);
|
|
|
|
|
evalstr = TxPrintString("%s \"%s\" ", newstr,
|
|
|
|
|
responses[pos]);
|
|
|
|
|
freeMagic(newstr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Tcl_EvalEx(magicinterp, evalstr, -1, 0);
|
|
|
|
|
objPtr = Tcl_GetObjResult(magicinterp);
|
2024-10-21 10:21:11 +02:00
|
|
|
/* tcl9 checked, this API is still (int) for &code */
|
2017-04-25 14:41:48 +02:00
|
|
|
result = Tcl_GetIntFromObj(magicinterp, objPtr, &code);
|
|
|
|
|
|
|
|
|
|
if (result == TCL_OK) return code;
|
|
|
|
|
else return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*--------------------------------------------------------------*/
|
|
|
|
|
/* TxUseMore and TxStopMore are dummy functions, although they */
|
|
|
|
|
/* could be used to set up a top-level window containing the */
|
|
|
|
|
/* result (redefine "puts" to write to the window). */
|
|
|
|
|
/*--------------------------------------------------------------*/
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
TxUseMore()
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*--------------------------------------------------------------*/
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
TxStopMore()
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*--------------------------------------------------------------*/
|
|
|
|
|
/* Set the prompt, if we are using the TkCon console */
|
|
|
|
|
/*--------------------------------------------------------------*/
|
|
|
|
|
|
|
|
|
|
extern char txPromptChar;
|
|
|
|
|
|
|
|
|
|
void
|
2025-01-31 18:33:16 +01:00
|
|
|
TxSetPrompt(
|
|
|
|
|
char ch)
|
2020-05-23 23:13:14 +02:00
|
|
|
{
|
2024-10-21 10:04:38 +02:00
|
|
|
#if TCL_MAJOR_VERSION < 9
|
2017-04-25 14:41:48 +02:00
|
|
|
Tcl_SavedResult state;
|
2024-10-21 10:04:38 +02:00
|
|
|
#else
|
|
|
|
|
Tcl_InterpState state;
|
|
|
|
|
#endif
|
2017-04-25 14:41:48 +02:00
|
|
|
char promptline[16];
|
|
|
|
|
|
|
|
|
|
if (TxTkConsole)
|
|
|
|
|
{
|
|
|
|
|
sprintf(promptline, "replaceprompt %c", ch);
|
2024-10-21 10:04:38 +02:00
|
|
|
#if TCL_MAJOR_VERSION < 9
|
2017-04-25 14:41:48 +02:00
|
|
|
Tcl_SaveResult(consoleinterp, &state);
|
2024-10-21 10:04:38 +02:00
|
|
|
#else
|
|
|
|
|
state = Tcl_SaveInterpState(consoleinterp, TCL_OK);
|
|
|
|
|
#endif
|
2017-04-25 14:41:48 +02:00
|
|
|
Tcl_EvalEx(consoleinterp, promptline, 15, 0);
|
2024-10-21 10:04:38 +02:00
|
|
|
#if TCL_MAJOR_VERSION < 9
|
2017-04-25 14:41:48 +02:00
|
|
|
Tcl_RestoreResult(consoleinterp, &state);
|
2024-10-21 10:04:38 +02:00
|
|
|
#else
|
|
|
|
|
Tcl_RestoreInterpState(consoleinterp, state);
|
|
|
|
|
#endif
|
2017-04-25 14:41:48 +02:00
|
|
|
}
|
2020-05-23 23:13:14 +02:00
|
|
|
}
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
/*--------------------------------------------------------------*/
|
|
|
|
|
/* Get a line from stdin (Tcl replacement for Tx function) */
|
|
|
|
|
/*--------------------------------------------------------------*/
|
|
|
|
|
|
|
|
|
|
char *
|
|
|
|
|
TxGetLinePfix(dest, maxChars, prefix)
|
|
|
|
|
char *dest;
|
|
|
|
|
int maxChars;
|
|
|
|
|
char *prefix;
|
|
|
|
|
{
|
|
|
|
|
Tcl_Obj *objPtr;
|
2024-10-21 10:07:16 +02:00
|
|
|
int charsStored;
|
|
|
|
|
#if TCL_MAJOR_VERSION < 9
|
|
|
|
|
int length;
|
|
|
|
|
#else
|
|
|
|
|
Tcl_Size length;
|
|
|
|
|
#endif
|
2017-04-25 14:41:48 +02:00
|
|
|
char *string;
|
|
|
|
|
|
|
|
|
|
if (TxTkConsole)
|
|
|
|
|
{
|
|
|
|
|
/* Use dialog function (must be defined in magic.tcl!) */
|
|
|
|
|
if (prefix != NULL)
|
|
|
|
|
{
|
|
|
|
|
string = Tcl_Alloc(20 + strlen(prefix));
|
|
|
|
|
sprintf(string, "magic::dialog \"\" \"%s\"\n", prefix);
|
|
|
|
|
Tcl_EvalEx(magicinterp, string, -1, 0);
|
|
|
|
|
Tcl_Free(string);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
Tcl_EvalEx(magicinterp, "magic::dialog", 13, 0);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (prefix != NULL)
|
|
|
|
|
{
|
|
|
|
|
TxPrintf("%s", prefix);
|
|
|
|
|
TxFlushOut();
|
|
|
|
|
}
|
|
|
|
|
Tcl_EvalEx(magicinterp, "gets stdin", 10, 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
objPtr = Tcl_GetObjResult(magicinterp);
|
|
|
|
|
string = Tcl_GetStringFromObj(objPtr, &length);
|
|
|
|
|
|
|
|
|
|
if (length > 0)
|
|
|
|
|
if (*(string + length - 1) == '\n')
|
|
|
|
|
length--;
|
|
|
|
|
|
|
|
|
|
if (length == 0)
|
|
|
|
|
return NULL;
|
|
|
|
|
else if (length >= maxChars)
|
|
|
|
|
length = (maxChars - 1);
|
|
|
|
|
|
|
|
|
|
strncpy(dest, string, length);
|
|
|
|
|
*(dest + length) = '\0';
|
|
|
|
|
return dest;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*--------------------------------------------------------------*/
|
|
|
|
|
/* Parse a file. This is a skeleton version of the TxDispatch */
|
|
|
|
|
/* routine in textio/txCommands.c */
|
|
|
|
|
/*--------------------------------------------------------------*/
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
TxDispatch(f)
|
|
|
|
|
FILE *f; /* Under Tcl, we never call this with NULL */
|
|
|
|
|
{
|
|
|
|
|
if (f == NULL)
|
|
|
|
|
{
|
|
|
|
|
TxError("Error: TxDispatch(NULL) was called\n");
|
|
|
|
|
}
|
|
|
|
|
while (!feof(f))
|
|
|
|
|
{
|
|
|
|
|
if (SigInterruptPending)
|
|
|
|
|
{
|
|
|
|
|
TxError("Read-in of file aborted.\n");
|
|
|
|
|
SigInterruptPending = FALSE;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
txGetFileCommand(f, NULL);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*--------------------------------------------------------------*/
|
|
|
|
|
/* Send a command line which was collected by magic's TxEvent */
|
|
|
|
|
/* handler to the interpreter's event queue. */
|
|
|
|
|
/*--------------------------------------------------------------*/
|
|
|
|
|
|
|
|
|
|
void
|
2025-01-31 18:39:24 +01:00
|
|
|
TxParseString(str)
|
2025-01-31 18:33:16 +01:00
|
|
|
const char *str;
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
2025-01-31 18:33:16 +01:00
|
|
|
const char *reply;
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
Tcl_EvalEx(magicinterp, str, -1, 0);
|
|
|
|
|
|
|
|
|
|
reply = (char *)Tcl_GetStringResult(magicinterp);
|
|
|
|
|
|
|
|
|
|
if (strlen(reply) > 0)
|
|
|
|
|
TxPrintf("%s: %s\n", str, reply);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*--------------------------------------------------------------*/
|
|
|
|
|
/* Replacement for TxFlush(): use Tcl interpreter */
|
|
|
|
|
/* If we just call "flush", _tcl_dispatch gets called, and */
|
|
|
|
|
/* bad things will happen. */
|
|
|
|
|
/*--------------------------------------------------------------*/
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
TxFlushErr()
|
|
|
|
|
{
|
2024-10-21 10:04:38 +02:00
|
|
|
#if TCL_MAJOR_VERSION < 9
|
2017-04-25 14:41:48 +02:00
|
|
|
Tcl_SavedResult state;
|
2024-10-21 10:04:38 +02:00
|
|
|
#else
|
|
|
|
|
Tcl_InterpState state;
|
|
|
|
|
#endif
|
2017-04-25 14:41:48 +02:00
|
|
|
|
2024-10-21 10:04:38 +02:00
|
|
|
#if TCL_MAJOR_VERSION < 9
|
2017-04-25 14:41:48 +02:00
|
|
|
Tcl_SaveResult(magicinterp, &state);
|
2024-10-21 10:04:38 +02:00
|
|
|
#else
|
|
|
|
|
state = Tcl_SaveInterpState(magicinterp, TCL_OK);
|
|
|
|
|
#endif
|
2017-04-25 14:41:48 +02:00
|
|
|
Tcl_EvalEx(magicinterp, "::tcl_flush stderr", 18, 0);
|
2024-10-21 10:04:38 +02:00
|
|
|
#if TCL_MAJOR_VERSION < 9
|
2017-04-25 14:41:48 +02:00
|
|
|
Tcl_RestoreResult(magicinterp, &state);
|
2024-10-21 10:04:38 +02:00
|
|
|
#else
|
|
|
|
|
Tcl_RestoreInterpState(magicinterp, state);
|
|
|
|
|
#endif
|
2017-04-25 14:41:48 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*--------------------------------------------------------------*/
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
TxFlushOut()
|
|
|
|
|
{
|
2024-10-21 10:04:38 +02:00
|
|
|
#if TCL_MAJOR_VERSION < 9
|
2017-04-25 14:41:48 +02:00
|
|
|
Tcl_SavedResult state;
|
2024-10-21 10:04:38 +02:00
|
|
|
#else
|
|
|
|
|
Tcl_InterpState state;
|
|
|
|
|
#endif
|
2017-04-25 14:41:48 +02:00
|
|
|
|
2024-10-21 10:04:38 +02:00
|
|
|
#if TCL_MAJOR_VERSION < 9
|
2017-04-25 14:41:48 +02:00
|
|
|
Tcl_SaveResult(magicinterp, &state);
|
2024-10-21 10:04:38 +02:00
|
|
|
#else
|
|
|
|
|
state = Tcl_SaveInterpState(magicinterp, TCL_OK);
|
|
|
|
|
#endif
|
2017-04-25 14:41:48 +02:00
|
|
|
Tcl_EvalEx(magicinterp, "::tcl_flush stdout", 18, 0);
|
2024-10-21 10:04:38 +02:00
|
|
|
#if TCL_MAJOR_VERSION < 9
|
2017-04-25 14:41:48 +02:00
|
|
|
Tcl_RestoreResult(magicinterp, &state);
|
2024-10-21 10:04:38 +02:00
|
|
|
#else
|
|
|
|
|
Tcl_RestoreInterpState(magicinterp, state);
|
|
|
|
|
#endif
|
2017-04-25 14:41:48 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*--------------------------------------------------------------*/
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
TxFlush()
|
|
|
|
|
{
|
|
|
|
|
TxFlushOut();
|
|
|
|
|
TxFlushErr();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*--------------------------------------------------------------*/
|
|
|
|
|
/* Tcl_printf() replaces vfprintf() for use by every Tx output */
|
|
|
|
|
/* function (namely, TxError() for stderr and TxPrintf() for */
|
|
|
|
|
/* stdout). It changes the result to a Tcl "puts" call, which */
|
|
|
|
|
/* can be changed inside Tcl, as, for example, by TkCon. */
|
|
|
|
|
/* */
|
|
|
|
|
/* 6/17/04---Routine extended to escape double-dollar-sign '$$' */
|
|
|
|
|
/* which is used by some tools when generating via cells. */
|
|
|
|
|
/* */
|
|
|
|
|
/* 12/23/16---Noted that using consoleinterp simply prevents */
|
|
|
|
|
/* the output from being redirected to another window such as */
|
|
|
|
|
/* the command entry window. Split off another bit TxTkOutput */
|
|
|
|
|
/* from TxTkConsole and set it to zero by default. The */
|
|
|
|
|
/* original behavior can be restored using the *flags wizard */
|
|
|
|
|
/* command (*flags printf true). */
|
2021-11-24 18:40:59 +01:00
|
|
|
/* */
|
|
|
|
|
/* 11/24/21---Routing extended to excape any dollar sign that */
|
|
|
|
|
/* does not represent a valid Tcl variable. */
|
2017-04-25 14:41:48 +02:00
|
|
|
/*--------------------------------------------------------------*/
|
|
|
|
|
|
|
|
|
|
int
|
2024-10-20 14:34:42 +02:00
|
|
|
Tcl_printf(FILE *f, const char *fmt, va_list args_in)
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
|
|
|
|
va_list args;
|
|
|
|
|
static char outstr[128] = "puts -nonewline std";
|
|
|
|
|
char *outptr, *bigstr = NULL, *finalstr = NULL;
|
|
|
|
|
int i, nchars, result, escapes = 0, limit;
|
|
|
|
|
Tcl_Interp *printinterp = (TxTkOutput) ? consoleinterp : magicinterp;
|
|
|
|
|
|
|
|
|
|
strcpy (outstr + 19, (f == stderr) ? "err \"" : "out \"");
|
|
|
|
|
|
|
|
|
|
va_copy(args, args_in);
|
|
|
|
|
outptr = outstr;
|
|
|
|
|
nchars = vsnprintf(outptr + 24, 102, fmt, args);
|
|
|
|
|
va_end(args);
|
|
|
|
|
|
|
|
|
|
if (nchars >= 102)
|
|
|
|
|
{
|
|
|
|
|
va_copy(args, args_in);
|
|
|
|
|
bigstr = Tcl_Alloc(nchars + 26);
|
|
|
|
|
strncpy(bigstr, outptr, 24);
|
|
|
|
|
outptr = bigstr;
|
|
|
|
|
vsnprintf(outptr + 24, nchars + 2, fmt, args);
|
|
|
|
|
va_end(args);
|
|
|
|
|
}
|
|
|
|
|
else if (nchars == -1) nchars = 126;
|
|
|
|
|
|
|
|
|
|
for (i = 24; *(outptr + i) != '\0'; i++)
|
|
|
|
|
{
|
|
|
|
|
if (*(outptr + i) == '\"' || *(outptr + i) == '[' ||
|
|
|
|
|
*(outptr + i) == ']' || *(outptr + i) == '\\')
|
|
|
|
|
escapes++;
|
2021-11-24 18:40:59 +01:00
|
|
|
else if (*(outptr + i) == '$')
|
2017-04-25 14:41:48 +02:00
|
|
|
escapes += 2;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (escapes > 0)
|
|
|
|
|
{
|
2020-05-23 23:13:14 +02:00
|
|
|
/* "+ 4" required to process "$$...$$"; haven't figured out why. */
|
2017-04-25 14:41:48 +02:00
|
|
|
finalstr = Tcl_Alloc(nchars + escapes + 26 + 4);
|
|
|
|
|
strncpy(finalstr, outptr, 24);
|
|
|
|
|
escapes = 0;
|
|
|
|
|
for (i = 24; *(outptr + i) != '\0'; i++)
|
|
|
|
|
{
|
|
|
|
|
if (*(outptr + i) == '\"' || *(outptr + i) == '[' ||
|
|
|
|
|
*(outptr + i) == ']' || *(outptr + i) == '\\')
|
|
|
|
|
{
|
|
|
|
|
*(finalstr + i + escapes) = '\\';
|
|
|
|
|
escapes++;
|
|
|
|
|
}
|
2021-11-24 18:40:59 +01:00
|
|
|
else if (*(outptr + i) == '$')
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
2021-11-24 18:40:59 +01:00
|
|
|
char *wsp;
|
|
|
|
|
|
|
|
|
|
/* Determine if what follows the '$' is a valid Tcl */
|
|
|
|
|
/* variable name. If not, then escape the '$'. */
|
|
|
|
|
|
|
|
|
|
wsp = strchr(outptr + i + 1, ' ');
|
|
|
|
|
if (wsp != NULL) *wsp = '\0';
|
|
|
|
|
if (Tcl_GetVar(printinterp, outptr + i + 1, 0) == NULL)
|
|
|
|
|
{
|
|
|
|
|
*(finalstr + i + escapes) = '\\';
|
|
|
|
|
escapes++;
|
|
|
|
|
}
|
|
|
|
|
if (wsp != NULL) *wsp = ' ';
|
2017-04-25 14:41:48 +02:00
|
|
|
}
|
|
|
|
|
*(finalstr + i + escapes) = *(outptr + i);
|
|
|
|
|
}
|
|
|
|
|
outptr = finalstr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
*(outptr + 24 + nchars + escapes) = '\"';
|
|
|
|
|
*(outptr + 25 + nchars + escapes) = '\0';
|
|
|
|
|
|
|
|
|
|
result = Tcl_EvalEx(printinterp, outptr, -1, 0);
|
|
|
|
|
|
|
|
|
|
if (bigstr != NULL) Tcl_Free(bigstr);
|
|
|
|
|
if (finalstr != NULL) Tcl_Free(finalstr);
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
2020-05-23 23:13:14 +02:00
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
/*--------------------------------------------------------------*/
|
|
|
|
|
/* Tcl_escape() takes a string as input and produces a string */
|
|
|
|
|
/* in which characters are escaped as necessary to make them */
|
|
|
|
|
/* printable from Tcl. The new string is allocated by */
|
|
|
|
|
/* Tcl_Alloc() which needs to be free'd with Tcl_Free(). */
|
|
|
|
|
/* */
|
|
|
|
|
/* 6/17/04---extended like Tcl_printf to escape double-dollar- */
|
|
|
|
|
/* sign ('$$') in names. */
|
2021-11-24 18:40:59 +01:00
|
|
|
/* 11/24/21---modified like Tcl_printf to escape any dollar */
|
|
|
|
|
/* sign that does not precede a valid Tcl variable name. */
|
2017-04-25 14:41:48 +02:00
|
|
|
/*--------------------------------------------------------------*/
|
|
|
|
|
|
|
|
|
|
char *
|
|
|
|
|
Tcl_escape(instring)
|
|
|
|
|
char *instring;
|
|
|
|
|
{
|
|
|
|
|
char *newstr;
|
|
|
|
|
int nchars = 0;
|
|
|
|
|
int escapes = 0;
|
|
|
|
|
int i;
|
2021-11-24 18:40:59 +01:00
|
|
|
Tcl_Interp *printinterp = (TxTkOutput) ? consoleinterp : magicinterp;
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
for (i = 0; *(instring + i) != '\0'; i++)
|
|
|
|
|
{
|
|
|
|
|
nchars++;
|
|
|
|
|
if (*(instring + i) == '\"' || *(instring + i) == '[' ||
|
2021-11-24 18:40:59 +01:00
|
|
|
*(instring + i) == ']' || *(instring + i) == '$')
|
2017-04-25 14:41:48 +02:00
|
|
|
escapes++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
newstr = Tcl_Alloc(nchars + escapes + 1);
|
|
|
|
|
escapes = 0;
|
|
|
|
|
for (i = 0; *(instring + i) != '\0'; i++)
|
|
|
|
|
{
|
|
|
|
|
if (*(instring + i) == '\"' || *(instring + i) == '[' ||
|
|
|
|
|
*(instring + i) == ']')
|
|
|
|
|
{
|
|
|
|
|
*(newstr + i + escapes) = '\\';
|
|
|
|
|
escapes++;
|
|
|
|
|
}
|
2021-11-24 18:40:59 +01:00
|
|
|
else if (*(instring + i) == '$')
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
2021-11-24 18:40:59 +01:00
|
|
|
char *wsp;
|
|
|
|
|
|
|
|
|
|
/* If what follows '$' is a valid Tcl variable, don't escape it */
|
|
|
|
|
wsp = strchr(instring + i + 1, ' ');
|
|
|
|
|
if (wsp != NULL) *wsp = '\0';
|
|
|
|
|
if (Tcl_GetVar(printinterp, instring + i + 1, 0) == NULL)
|
|
|
|
|
{
|
|
|
|
|
*(newstr + i + escapes) = '\\';
|
|
|
|
|
escapes++;
|
|
|
|
|
}
|
|
|
|
|
if (wsp != NULL) *wsp = ' ';
|
2017-04-25 14:41:48 +02:00
|
|
|
}
|
|
|
|
|
*(newstr + i + escapes) = *(instring + i);
|
|
|
|
|
}
|
|
|
|
|
*(newstr + i + escapes) = '\0';
|
|
|
|
|
return newstr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*--------------------------------------------------------------*/
|
|
|
|
|
|
|
|
|
|
int
|
|
|
|
|
TerminalInputProc(instanceData, buf, toRead, errorCodePtr)
|
|
|
|
|
ClientData instanceData;
|
|
|
|
|
char *buf;
|
|
|
|
|
int toRead;
|
|
|
|
|
int *errorCodePtr;
|
|
|
|
|
{
|
|
|
|
|
FileState *fsPtr = (FileState *)instanceData;
|
|
|
|
|
int bytesRead, i, tlen;
|
|
|
|
|
char *locbuf;
|
|
|
|
|
|
|
|
|
|
*errorCodePtr = 0;
|
|
|
|
|
|
|
|
|
|
TxInputRedirect = TX_INPUT_NORMAL;
|
|
|
|
|
if (TxBuffer != NULL) {
|
|
|
|
|
tlen = strlen(TxBuffer);
|
|
|
|
|
if (tlen < toRead) {
|
|
|
|
|
strcpy(buf, TxBuffer);
|
|
|
|
|
Tcl_Free(TxBuffer);
|
|
|
|
|
TxBuffer = NULL;
|
|
|
|
|
return tlen;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
strncpy(buf, TxBuffer, toRead);
|
|
|
|
|
locbuf = Tcl_Alloc(tlen - toRead + 1);
|
|
|
|
|
strcpy(locbuf, TxBuffer + toRead);
|
|
|
|
|
Tcl_Free(TxBuffer);
|
|
|
|
|
TxBuffer = locbuf;
|
|
|
|
|
return toRead;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
while (1) {
|
|
|
|
|
bytesRead = read(fsPtr->fd, buf, (size_t) toRead);
|
|
|
|
|
if (bytesRead > -1)
|
|
|
|
|
return bytesRead;
|
|
|
|
|
|
|
|
|
|
// Ignore interrupts, which may be generated by new
|
|
|
|
|
// terminal windows (added by Tim, 9/30/2014)
|
|
|
|
|
|
|
|
|
|
if (errno != EINTR) break;
|
|
|
|
|
}
|
|
|
|
|
*errorCodePtr = errno;
|
2020-05-23 23:13:14 +02:00
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*--------------------------------------------------------------*/
|
|
|
|
|
|
|
|
|
|
int
|
|
|
|
|
Tclmagic_Init(interp)
|
|
|
|
|
Tcl_Interp *interp;
|
|
|
|
|
{
|
2021-01-04 16:02:15 +01:00
|
|
|
const char *cadroot;
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
/* Sanity check! */
|
|
|
|
|
if (interp == NULL) return TCL_ERROR;
|
|
|
|
|
|
|
|
|
|
/* Remember the interpreter */
|
|
|
|
|
magicinterp = interp;
|
|
|
|
|
|
2024-10-21 10:19:15 +02:00
|
|
|
if (Tcl_InitStubs(interp, Tclmagic_InitStubsVersion, 0) == NULL) return TCL_ERROR;
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
/* Initialization and Startup commands */
|
|
|
|
|
Tcl_CreateCommand(interp, "magic::initialize", (Tcl_CmdProc *)_magic_initialize,
|
|
|
|
|
(ClientData)NULL, (Tcl_CmdDeleteProc *) NULL);
|
|
|
|
|
Tcl_CreateCommand(interp, "magic::startup", (Tcl_CmdProc *)_magic_startup,
|
|
|
|
|
(ClientData)NULL, (Tcl_CmdDeleteProc *) NULL);
|
2022-03-30 16:55:08 +02:00
|
|
|
Tcl_CreateCommand(interp, "magic::display", (Tcl_CmdProc *)_magic_display,
|
|
|
|
|
(ClientData)NULL, (Tcl_CmdDeleteProc *) NULL);
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
/* Initialize the command-tag callback feature */
|
|
|
|
|
|
|
|
|
|
HashInit(&txTclTagTable, 10, HT_STRINGKEYS);
|
|
|
|
|
Tcl_CreateCommand(interp, "magic::tag", (Tcl_CmdProc *)AddCommandTag,
|
|
|
|
|
(ClientData)NULL, (Tcl_CmdDeleteProc *) NULL);
|
|
|
|
|
|
|
|
|
|
/* Add "*flags" command for manipulating run-time flags */
|
|
|
|
|
Tcl_CreateObjCommand(interp, "magic::*flags", (Tcl_ObjCmdProc *)_magic_flags,
|
|
|
|
|
(ClientData)NULL, (Tcl_CmdDeleteProc *) NULL);
|
|
|
|
|
|
|
|
|
|
/* Add the magic TCL directory to the Tcl library search path */
|
|
|
|
|
|
|
|
|
|
Tcl_Eval(interp, "lappend auto_path " TCL_DIR );
|
|
|
|
|
|
2021-01-04 15:47:03 +01:00
|
|
|
/* Get $CAD_ROOT from a Tcl variable, if it exists, and if not, then */
|
|
|
|
|
/* set CAD_ROOT from the environment variable of the same name, if */
|
|
|
|
|
/* it exists, and finally fall back on the CAD_DIR set at compile */
|
|
|
|
|
/* time. */
|
2017-04-25 14:41:48 +02:00
|
|
|
|
2021-01-04 15:47:03 +01:00
|
|
|
cadroot = Tcl_GetVar(interp, "CAD_ROOT", TCL_GLOBAL_ONLY);
|
|
|
|
|
if (cadroot == NULL)
|
|
|
|
|
{
|
2021-01-04 16:02:15 +01:00
|
|
|
cadroot = (const char *)getenv("CAD_ROOT");
|
2021-01-04 15:47:03 +01:00
|
|
|
if (cadroot == NULL) cadroot = CAD_DIR;
|
|
|
|
|
Tcl_SetVar(interp, "CAD_ROOT", cadroot, TCL_GLOBAL_ONLY);
|
|
|
|
|
}
|
2017-04-25 14:41:48 +02:00
|
|
|
|
2026-07-23 15:55:20 +02:00
|
|
|
Tcl_PkgProvide(interp, "Tclmagic", MagicVersion);
|
2017-04-25 14:41:48 +02:00
|
|
|
return TCL_OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*--------------------------------------------------------------*/
|
|
|
|
|
/* Define a "safe init" function for those platforms that */
|
|
|
|
|
/* require it. */
|
|
|
|
|
/*--------------------------------------------------------------*/
|
|
|
|
|
|
|
|
|
|
int
|
|
|
|
|
Tclmagic_SafeInit(interp)
|
2020-05-23 23:13:14 +02:00
|
|
|
Tcl_Interp *interp;
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
|
|
|
|
return Tclmagic_Init(interp);
|
|
|
|
|
}
|
2026-06-23 16:08:31 +02:00
|
|
|
|
2026-06-25 15:23:09 +02:00
|
|
|
#ifndef MAGIC_NO_TK
|
|
|
|
|
|
2026-06-23 16:08:31 +02:00
|
|
|
/* Procedure to set up restricted Tk event processing to allow window
|
|
|
|
|
* exposure and updates to occur while deferring key and button press
|
|
|
|
|
* events. This allows window updates during potentially long-running
|
|
|
|
|
* processes like extraction or GDS writes but prevents commands from
|
|
|
|
|
* being executed while the process is still running.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
Tk_RestrictAction
|
|
|
|
|
RestrictInputProc(
|
|
|
|
|
ClientData clientData,
|
|
|
|
|
XEvent *eventPtr)
|
|
|
|
|
{
|
|
|
|
|
switch (eventPtr->type)
|
|
|
|
|
{
|
|
|
|
|
case KeyPress:
|
|
|
|
|
case KeyRelease:
|
|
|
|
|
case ButtonPress:
|
|
|
|
|
case ButtonRelease:
|
|
|
|
|
return TK_DEFER_EVENT;
|
|
|
|
|
|
|
|
|
|
case MotionNotify:
|
|
|
|
|
return TK_DISCARD_EVENT;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
return TK_PROCESS_EVENT;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-25 15:23:09 +02:00
|
|
|
#endif /* MAGIC_NO_TK */
|