/*1* tkMain.c --2*3* This file contains a generic main program for Tk-based applications.4* It can be used as-is for many applications, just by supplying a5* different appInitProc procedure for each specific application.6* Or, it can be used as a template for creating new main programs7* for Tk applications.8*9* Copyright (c) 1990-1994 The Regents of the University of California.10* Copyright (c) 1994-1996 Sun Microsystems, Inc.11*12* See the file "license.terms" for information on usage and redistribution13* of this file, and for a DISCLAIMER OF ALL WARRANTIES.14*15* SCCS: @(#) tkMain.c 1.150 96/09/05 18:42:2516*/1718#include <tcl.h>19#include <tk.h>20#include <ctype.h>2122#if !_PACKAGE_ast23# include <stdio.h>24# include <string.h>25# ifdef NO_STDLIB_H26# include "../compat/stdlib.h"27# else28# include <stdlib.h>29# endif3031/*32* Declarations for various library procedures and variables (don't want33* to include tkInt.h or tkPort.h here, because people might copy this34* file out of the Tk source directory to make their own modified versions).35* Note: don't declare "exit" here even though a declaration is really36* needed, because it will conflict with a declaration elsewhere on37* some systems.38*/3940extern int isatty _ANSI_ARGS_((int fd));41extern int read _ANSI_ARGS_((int fd, char *buf, size_t size));42extern char * strrchr _ANSI_ARGS_((CONST char *string, int c));4344#endif4546/*47* Global variables used by the main program:48*/4950static Tcl_Interp *interp; /* Interpreter for this application. */51static Tcl_DString command; /* Used to assemble lines of terminal input52* into Tcl commands. */53static Tcl_DString line; /* Used to read the next line from the54* terminal input. */55static int tty; /* Non-zero means standard input is a56* terminal-like device. Zero means it's57* a file. */5859/*60* Forward declarations for procedures defined later in this file.61*/6263static void Prompt _ANSI_ARGS_((Tcl_Interp *interp, int partial));64static void StdinProc _ANSI_ARGS_((ClientData clientData,65int mask));6667/*68*----------------------------------------------------------------------69*70* Tk_Main --71*72* Main program for Wish and most other Tk-based applications.73*74* Results:75* None. This procedure never returns (it exits the process when76* it's done.77*78* Side effects:79* This procedure initializes the Tk world and then starts80* interpreting commands; almost anything could happen, depending81* on the script being interpreted.82*83*----------------------------------------------------------------------84*/8586void87Tk_Main(argc, argv, appInitProc)88int argc; /* Number of arguments. */89char **argv; /* Array of argument strings. */90Tcl_AppInitProc *appInitProc; /* Application-specific initialization91* procedure to call after most92* initialization but before starting93* to execute commands. */94{95char *args, *fileName;96char buf[20];97int code;98size_t length;99Tcl_Channel inChannel, outChannel, errChannel;100101Tcl_FindExecutable(argv[0]);102interp = Tcl_CreateInterp();103#ifdef TCL_MEM_DEBUG104Tcl_InitMemory(interp);105#endif106107/*108* Parse command-line arguments. A leading "-file" argument is109* ignored (a historical relic from the distant past). If the110* next argument doesn't start with a "-" then strip it off and111* use it as the name of a script file to process.112*/113114fileName = NULL;115if (argc > 1) {116length = strlen(argv[1]);117if ((length >= 2) && (strncmp(argv[1], "-file", length) == 0)) {118argc--;119argv++;120}121}122if ((argc > 1) && (argv[1][0] != '-')) {123fileName = argv[1];124argc--;125argv++;126}127128/*129* Make command-line arguments available in the Tcl variables "argc"130* and "argv".131*/132133args = Tcl_Merge(argc-1, argv+1);134Tcl_SetVar(interp, "argv", args, TCL_GLOBAL_ONLY);135ckfree(args);136sprintf(buf, "%d", argc-1);137Tcl_SetVar(interp, "argc", buf, TCL_GLOBAL_ONLY);138Tcl_SetVar(interp, "argv0", (fileName != NULL) ? fileName : argv[0],139TCL_GLOBAL_ONLY);140141/*142* Set the "tcl_interactive" variable.143*/144145/*146* For now, under Windows, we assume we are not running as a console mode147* app, so we need to use the GUI console. In order to enable this, we148* always claim to be running on a tty. This probably isn't the right149* way to do it.150*/151152#ifdef WIN_TCL153tty = 1;154#else155tty = isatty(0);156#endif157Tcl_SetVar(interp, "tcl_interactive",158((fileName == NULL) && tty) ? "1" : "0", TCL_GLOBAL_ONLY);159160/*161* Invoke application-specific initialization.162*/163164if ((*appInitProc)(interp) != TCL_OK) {165errChannel = Tcl_GetStdChannel(TCL_STDERR);166if (errChannel) {167Tcl_Write(errChannel,168"application-specific initialization failed: ", -1);169Tcl_Write(errChannel, interp->result, -1);170Tcl_Write(errChannel, "\n", 1);171}172}173174/*175* Invoke the script specified on the command line, if any.176*/177178if (fileName != NULL) {179code = Tcl_EvalFile(interp, fileName);180if (code != TCL_OK) {181goto error;182}183tty = 0;184} else {185186/*187* Evaluate the .rc file, if one has been specified.188*/189190Tcl_SourceRCFile(interp);191192/*193* Establish a channel handler for stdin.194*/195196inChannel = Tcl_GetStdChannel(TCL_STDIN);197if (inChannel) {198Tcl_CreateChannelHandler(inChannel, TCL_READABLE, StdinProc,199(ClientData) inChannel);200}201if (tty) {202Prompt(interp, 0);203}204}205206outChannel = Tcl_GetStdChannel(TCL_STDOUT);207if (outChannel) {208Tcl_Flush(outChannel);209}210Tcl_DStringInit(&command);211Tcl_DStringInit(&line);212Tcl_ResetResult(interp);213214/*215* Loop infinitely, waiting for commands to execute. When there216* are no windows left, Tk_MainLoop returns and we exit.217*/218219Tk_MainLoop();220Tcl_DeleteInterp(interp);221Tcl_Exit(0);222223error:224/*225* The following statement guarantees that the errorInfo226* variable is set properly.227*/228229Tcl_AddErrorInfo(interp, "");230errChannel = Tcl_GetStdChannel(TCL_STDERR);231if (errChannel) {232Tcl_Write(errChannel, Tcl_GetVar(interp, "errorInfo", TCL_GLOBAL_ONLY),233-1);234Tcl_Write(errChannel, "\n", 1);235}236Tcl_DeleteInterp(interp);237Tcl_Exit(1);238}239240/*241*----------------------------------------------------------------------242*243* StdinProc --244*245* This procedure is invoked by the event dispatcher whenever246* standard input becomes readable. It grabs the next line of247* input characters, adds them to a command being assembled, and248* executes the command if it's complete.249*250* Results:251* None.252*253* Side effects:254* Could be almost arbitrary, depending on the command that's255* typed.256*257*----------------------------------------------------------------------258*/259260/* ARGSUSED */261static void262StdinProc(clientData, mask)263ClientData clientData; /* Not used. */264int mask; /* Not used. */265{266static int gotPartial = 0;267char *cmd;268int code, count;269Tcl_Channel chan = (Tcl_Channel) clientData;270271count = Tcl_Gets(chan, &line);272273if (count < 0) {274if (!gotPartial) {275if (tty) {276Tcl_Exit(0);277} else {278Tcl_DeleteChannelHandler(chan, StdinProc, (ClientData) chan);279}280return;281} else {282count = 0;283}284}285286(void) Tcl_DStringAppend(&command, Tcl_DStringValue(&line), -1);287cmd = Tcl_DStringAppend(&command, "\n", -1);288Tcl_DStringFree(&line);289290if (!Tcl_CommandComplete(cmd)) {291gotPartial = 1;292goto prompt;293}294gotPartial = 0;295296/*297* Disable the stdin channel handler while evaluating the command;298* otherwise if the command re-enters the event loop we might299* process commands from stdin before the current command is300* finished. Among other things, this will trash the text of the301* command being evaluated.302*/303304Tcl_CreateChannelHandler(chan, 0, StdinProc, (ClientData) chan);305code = Tcl_RecordAndEval(interp, cmd, TCL_EVAL_GLOBAL);306Tcl_CreateChannelHandler(chan, TCL_READABLE, StdinProc,307(ClientData) chan);308Tcl_DStringFree(&command);309if (*interp->result != 0) {310if ((code != TCL_OK) || (tty)) {311/*312* The statement below used to call "printf", but that resulted313* in core dumps under Solaris 2.3 if the result was very long.314*315* NOTE: This probably will not work under Windows either.316*/317318puts(interp->result);319}320}321322/*323* Output a prompt.324*/325326prompt:327if (tty) {328Prompt(interp, gotPartial);329}330Tcl_ResetResult(interp);331}332333/*334*----------------------------------------------------------------------335*336* Prompt --337*338* Issue a prompt on standard output, or invoke a script339* to issue the prompt.340*341* Results:342* None.343*344* Side effects:345* A prompt gets output, and a Tcl script may be evaluated346* in interp.347*348*----------------------------------------------------------------------349*/350351static void352Prompt(interp, partial)353Tcl_Interp *interp; /* Interpreter to use for prompting. */354int partial; /* Non-zero means there already355* exists a partial command, so use356* the secondary prompt. */357{358char *promptCmd;359int code;360Tcl_Channel outChannel, errChannel;361362errChannel = Tcl_GetChannel(interp, "stderr", NULL);363364promptCmd = Tcl_GetVar(interp,365partial ? "tcl_prompt2" : "tcl_prompt1", TCL_GLOBAL_ONLY);366if (promptCmd == NULL) {367defaultPrompt:368if (!partial) {369370/*371* We must check that outChannel is a real channel - it372* is possible that someone has transferred stdout out of373* this interpreter with "interp transfer".374*/375376outChannel = Tcl_GetChannel(interp, "stdout", NULL);377if (outChannel != (Tcl_Channel) NULL) {378Tcl_Write(outChannel, "% ", 2);379}380}381} else {382code = Tcl_Eval(interp, promptCmd);383if (code != TCL_OK) {384Tcl_AddErrorInfo(interp,385"\n (script that generates prompt)");386/*387* We must check that errChannel is a real channel - it388* is possible that someone has transferred stderr out of389* this interpreter with "interp transfer".390*/391392errChannel = Tcl_GetChannel(interp, "stderr", NULL);393if (errChannel != (Tcl_Channel) NULL) {394Tcl_Write(errChannel, interp->result, -1);395Tcl_Write(errChannel, "\n", 1);396}397goto defaultPrompt;398}399}400outChannel = Tcl_GetChannel(interp, "stdout", NULL);401if (outChannel != (Tcl_Channel) NULL) {402Tcl_Flush(outChannel);403}404}405406407