/*
* tkMain.c (CTk) --
*
* This file contains a generic main program for Tk-based applications.
* It can be used as-is for many applications, just by supplying a
* different appInitProc procedure for each specific application.
* Or, it can be used as a template for creating new main programs
* for Tk applications.
*
* Copyright (c) 1990-1994 The Regents of the University of California.
* Copyright (c) 1994-1995 Sun Microsystems, Inc.
* Copyright (c) 1994-1995 Cleveland Clinic Foundation
*
* See the file "license.terms" for information on usage and redistribution
* of this file, and for a DISCLAIMER OF ALL WARRANTIES.
*
* @(#) $Id: ctk.shar,v 1.50 1996/01/15 14:47:16 andrewm Exp andrewm $
*/
#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <tcl.h>
#include "tk.h"
#ifdef NO_STDLIB_H
# include "compat/stdlib.h"
#else
# include <stdlib.h>
#endif
/*
* Declarations for various library procedures and variables (don't want
* to include tkInt.h or tkPort.h here, because people might copy this
* file out of the Tk source directory to make their own modified versions).
* Note: don't declare "exit" here even though a declaration is really
* needed, because it will conflict with a declaration elsewhere on
* some systems.
*/
extern int isatty _ANSI_ARGS_((int fd));
extern int read _ANSI_ARGS_((int fd, char *buf, size_t size));
extern char * strrchr _ANSI_ARGS_((CONST char *string, int c));
/*
* Global variables used by the main program:
*/
static Tk_Window mainWindow; /* The main window for the application. If
* NULL then the application no longer
* exists. */
static Tcl_Interp *interp; /* Interpreter for this application. */
static Tcl_DString command; /* Used to assemble lines of terminal input
* into Tcl commands. */
static Tcl_DString line; /* Used to read the next line from the
* terminal input. */
static int tty; /* Non-zero means standard input is a
* terminal-like device. Zero means it's
* a file. */
static char errorExitCmd[] = "exit 1";
/*
* Command-line options:
*/
static char *fileName = NULL;
static char *name = NULL;
static char *display = NULL;
static char *geometry = NULL;
static int rest = 0;
static Tk_ArgvInfo argTable[] = {
{"-display", TK_ARGV_STRING, (char *) NULL, (char *) &display,
"Display to use"},
{"-geometry", TK_ARGV_STRING, (char *) NULL, (char *) &geometry,
"Initial geometry for window"},
{"-name", TK_ARGV_STRING, (char *) NULL, (char *) &name,
"Name to use for application"},
{"--", TK_ARGV_REST, (char *) 1, (char *) &rest,
"Pass all remaining arguments through to script"},
{(char *) NULL, TK_ARGV_END, (char *) NULL, (char *) NULL,
(char *) NULL}
};
/*
* Forward declarations for procedures defined later in this file:
*/
static void Prompt _ANSI_ARGS_((Tcl_Interp *interp, int partial));
static void StdinProc _ANSI_ARGS_((ClientData clientData,
int mask));
/*
*----------------------------------------------------------------------
*
* Tk_Main --
*
* Main program for Wish and most other Tk-based applications.
*
* Results:
* None. This procedure never returns (it exits the process when
* it's done.
*
* Side effects:
* This procedure initializes the Tk world and then starts
* interpreting commands; almost anything could happen, depending
* on the script being interpreted.
*
*----------------------------------------------------------------------
*/
void
Tk_Main(argc, argv, appInitProc)
int argc; /* Number of arguments. */
char **argv; /* Array of argument strings. */
Tcl_AppInitProc *appInitProc; /* Application-specific initialization
* procedure to call after most
* initialization but befort starting
* to execute commands. */
{
char *args, *p, *msg, *argv0, *class;
char buf[20];
int code;
size_t length;
Tcl_Channel inChannel, outChannel, errChannel, chan;
interp = Tcl_CreateInterp();
#ifdef TCL_MEM_DEBUG
Tcl_InitMemory(interp);
#endif
/*
* Parse command-line arguments. A leading "-file" argument is
* ignored (a historical relic from the distant past). If the
* next argument doesn't start with a "-" then strip it off and
* use it as the name of a script file to process. Also check
* for other standard arguments, such as "-geometry", anywhere
* in the argument list.
*/
argv0 = argv[0];
if (argc > 1) {
length = strlen(argv[1]);
if ((length >= 2) && (strncmp(argv[1], "-file", length) == 0)) {
argc--;
argv++;
}
}
if ((argc > 1) && (argv[1][0] != '-')) {
fileName = argv[1];
argc--;
argv++;
}
if (Tk_ParseArgv(interp, (Tk_Window) NULL, &argc, argv, argTable, 0)
!= TCL_OK) {
fprintf(stderr, "%s\n", interp->result);
exit(1);
}
if (name == NULL) {
if (fileName != NULL) {
p = fileName;
} else {
p = argv[0];
}
name = strrchr(p, '/');
if (name != NULL) {
name++;
} else {
name = p;
}
}
/*
* Make command-line arguments available in the Tcl variables "argc"
* and "argv".
*/
args = Tcl_Merge(argc-1, argv+1);
Tcl_SetVar(interp, "argv", args, TCL_GLOBAL_ONLY);
ckfree(args);
sprintf(buf, "%d", argc-1);
Tcl_SetVar(interp, "argc", buf, TCL_GLOBAL_ONLY);
Tcl_SetVar(interp, "argv0", (fileName != NULL) ? fileName : argv0,
TCL_GLOBAL_ONLY);
/*
* If a display was specified, put it into the CTK_DISPLAY
* environment variable so that it will be available for
* any sub-processes created by us.
*/
if (display != NULL) {
Tcl_SetVar2(interp, "env", "CTK_DISPLAY", display, TCL_GLOBAL_ONLY);
}
/*
* Initialize the Tk application. If a -name option was provided,
* use it; otherwise, if a file name was provided, use the last
* element of its path as the name of the application; otherwise
* use the last element of the program name. For the application's
* class, capitalize the first letter of the name.
*/
if (name == NULL) {
p = (fileName != NULL) ? fileName : argv0;
name = strrchr(p, '/');
if (name != NULL) {
name++;
} else {
name = p;
}
}
class = (char *) ckalloc((unsigned) (strlen(name) + 1));
strcpy(class, name);
class[0] = toupper((unsigned char) class[0]);
mainWindow = Tk_CreateMainWindow(interp, display, name, class);
ckfree(class);
if (mainWindow == NULL) {
fprintf(stderr, "%s\n", interp->result);
exit(1);
}
/*
* Set the "tcl_interactive" variable.
*/
tty = isatty(0);
Tcl_SetVar(interp, "tcl_interactive",
((fileName == NULL) && tty) ? "1" : "0", TCL_GLOBAL_ONLY);
/*
* Set the geometry of the main window, if requested. Put the
* requested geometry into the "geometry" variable.
*/
if (geometry != NULL) {
Tcl_SetVar(interp, "geometry", geometry, TCL_GLOBAL_ONLY);
code = Tcl_VarEval(interp, "wm geometry . ", geometry, (char *) NULL);
if (code != TCL_OK) {
fprintf(stderr, "%s\n", interp->result);
}
}
/*
* Invoke application-specific initialization.
*/
if ((*appInitProc)(interp) != TCL_OK) {
errChannel = Tcl_GetStdChannel(TCL_STDERR);
if (errChannel) {
Tcl_Write(errChannel,
"application-specific initialization failed: ", -1);
Tcl_Write(errChannel, interp->result, -1);
Tcl_Write(errChannel, "\n", 1);
}
goto error;
}
/*
* Invoke the script specified on the command line, if any.
*/
if (fileName != NULL) {
code = Tcl_EvalFile(interp, fileName);
if (code != TCL_OK) {
goto error;
}
tty = 0;
} else {
/*
* Commands will come from standard input, so set up an event
* handler for standard input. Evaluate the .rc file, if one
* has been specified, set up an event handler for standard
* input, and print a prompt if the input device is a terminal.
*/
fileName = Tcl_GetVar(interp, "tcl_rcFileName", TCL_GLOBAL_ONLY);
if (fileName != NULL) {
Tcl_DString buffer;
char *fullName;
fullName = Tcl_TranslateFileName(interp, fileName, &buffer);
if (fullName == NULL) {
errChannel = Tcl_GetStdChannel(TCL_STDERR);
if (errChannel) {
Tcl_Write(errChannel, interp->result, -1);
Tcl_Write(errChannel, "\n", 1);
}
} else {
/*
* NOTE: The following relies on O_RDONLY==0.
*/
chan = Tcl_OpenFileChannel(interp, fullName, "r", 0);
if (chan != (Tcl_Channel) NULL) {
Tcl_Close(NULL, chan);
if (Tcl_EvalFile(interp, fullName) != TCL_OK) {
errChannel = Tcl_GetStdChannel(TCL_STDERR);
if (errChannel) {
Tcl_Write(errChannel, interp->result, -1);
Tcl_Write(errChannel, "\n", 1);
}
}
}
}
Tcl_DStringFree(&buffer);
}
if (tty &&
!Tcl_GetVar2(interp, "env", "CTK_DISPLAY", TCL_GLOBAL_ONLY)) {
/*
* Input is a terminal, and display was never set. Instead
* of reading command from stdin, pop-up a command dialog
* (since we are probably displaying to stdin/stdout).
*/
if (Tcl_Eval(interp, "ctkDialog") != TCL_OK) {
goto error;
}
} else {
Tcl_CreateFileHandler(0, TCL_READABLE, StdinProc, (ClientData) 0);
inChannel = Tcl_GetStdChannel(TCL_STDIN);
if (inChannel) {
Tcl_CreateChannelHandler(inChannel, TCL_READABLE, StdinProc,
(ClientData) inChannel);
}
if (tty) {
Prompt(interp, 0);
}
}
}
outChannel = Tcl_GetStdChannel(TCL_STDOUT);
if (outChannel) {
Tcl_Flush(outChannel);
}
Tcl_DStringInit(&command);
Tcl_DStringInit(&line);
Tcl_ResetResult(interp);
/*
* Loop infinitely, waiting for commands to execute. When there
* are no windows left, Tk_MainLoop returns and we exit.
*/
Tk_MainLoop();
/*
* Don't exit directly, but rather invoke the Tcl "exit" command.
* This gives the application the opportunity to redefine "exit"
* to do additional cleanup.
*/
Tcl_Eval(interp, "exit");
exit(1);
error:
/*
* The following statement guarantees that the errorInfo
* variable is set properly.
*/
Tcl_AddErrorInfo(interp, "");
errChannel = Tcl_GetStdChannel(TCL_STDERR);
if (errChannel) {
Tcl_Write(errChannel, Tcl_GetVar(interp, "errorInfo", TCL_GLOBAL_ONLY),
-1);
Tcl_Write(errChannel, "\n", 1);
}
Tcl_DeleteInterp(interp);
Tcl_Exit(1);
}
/*
*----------------------------------------------------------------------
*
* StdinProc --
*
* This procedure is invoked by the event dispatcher whenever
* standard input becomes readable. It grabs the next line of
* input characters, adds them to a command being assembled, and
* executes the command if it's complete.
*
* Results:
* None.
*
* Side effects:
* Could be almost arbitrary, depending on the command that's
* typed.
*
*----------------------------------------------------------------------
*/
/* ARGSUSED */
static void
StdinProc(clientData, mask)
ClientData clientData; /* Not used. */
int mask; /* Not used. */
{
static int gotPartial = 0;
char *cmd;
int code, count;
Tcl_Channel chan = (Tcl_Channel) clientData;
count = Tcl_Gets(chan, &line);
if (count < 0) {
if (!gotPartial) {
if (tty) {
Tcl_Exit(0);
} else {
Tcl_DeleteChannelHandler(chan, StdinProc, (ClientData) chan);
}
return;
} else {
count = 0;
}
}
(void) Tcl_DStringAppend(&command, Tcl_DStringValue(&line), -1);
cmd = Tcl_DStringAppend(&command, "\n", -1);
Tcl_DStringFree(&line);
if (!Tcl_CommandComplete(cmd)) {
gotPartial = 1;
goto prompt;
}
gotPartial = 0;
/*
* Disable the stdin channel handler while evaluating the command;
* otherwise if the command re-enters the event loop we might
* process commands from stdin before the current command is
* finished. Among other things, this will trash the text of the
* command being evaluated.
*/
Tcl_CreateFileHandler(chan, 0, StdinProc, (ClientData) chan);
code = Tcl_RecordAndEval(interp, cmd, TCL_EVAL_GLOBAL);
Tcl_CreateChannelHandler(chan, TCL_READABLE, StdinProc, (ClientData) chan);
Tcl_DStringFree(&command);
if (*interp->result != 0) {
if ((code != TCL_OK) || (tty)) {
/*
* The statement below used to call "printf", but that resulted
* in core dumps under Solaris 2.3 if the result was very long.
*
* NOTE: This probably will not work under Windows either.
*/
puts(interp->result);
}
}
/*
* Output a prompt.
*/
prompt:
if (tty) {
Prompt(interp, gotPartial);
}
Tcl_ResetResult(interp);
}
/*
*----------------------------------------------------------------------
*
* Prompt --
*
* Issue a prompt on standard output, or invoke a script
* to issue the prompt.
*
* Results:
* None.
*
* Side effects:
* A prompt gets output, and a Tcl script may be evaluated
* in interp.
*
*----------------------------------------------------------------------
*/
static void
Prompt(interp, partial)
Tcl_Interp *interp; /* Interpreter to use for prompting. */
int partial; /* Non-zero means there already
* exists a partial command, so use
* the secondary prompt. */
{
char *promptCmd;
int code;
promptCmd = Tcl_GetVar(interp,
partial ? "tcl_prompt2" : "tcl_prompt1", TCL_GLOBAL_ONLY);
if (promptCmd == NULL) {
defaultPrompt:
if (!partial) {
fputs("% ", stdout);
}
} else {
code = Tcl_Eval(interp, promptCmd);
if (code != TCL_OK) {
Tcl_AddErrorInfo(interp,
"\n (script that generates prompt)");
fprintf(stderr, "%s\n", interp->result);
goto defaultPrompt;
}
}
fflush(stdout);
}