4 * This file implements a Tcl console for systems that may not
5 * otherwise have access to a console. It uses the Text widget
6 * and provides special access via a console command.
8 * Copyright (c) 1995-1996 Sun Microsystems, Inc.
10 * See the file "license.terms" for information on usage and redistribution
11 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
22 * A data structure of the following type holds information for each console
23 * which a handler (i.e. a Tcl command) has been defined for a particular
27 typedef struct ConsoleInfo {
28 Tcl_Interp *consoleInterp; /* Interpreter for the console. */
29 Tcl_Interp *interp; /* Interpreter to send console commands. */
32 typedef struct ThreadSpecificData {
33 Tcl_Interp *gStdoutInterp;
35 static Tcl_ThreadDataKey dataKey;
36 static int consoleInitialized = 0;
39 * The Mutex below is used to lock access to the consoleIntialized flag
42 TCL_DECLARE_MUTEX(consoleMutex)
45 * Forward declarations for procedures defined later in this file:
47 * The first three will be used in the tk app shells...
50 void TkConsolePrint _ANSI_ARGS_((Tcl_Interp *interp,
51 int devId, char *buffer, long size));
53 static int ConsoleCmd _ANSI_ARGS_((ClientData clientData,
54 Tcl_Interp *interp, int argc, char **argv));
55 static void ConsoleDeleteProc _ANSI_ARGS_((ClientData clientData));
56 static void ConsoleEventProc _ANSI_ARGS_((ClientData clientData,
58 static int InterpreterCmd _ANSI_ARGS_((ClientData clientData,
59 Tcl_Interp *interp, int argc, char **argv));
61 static int ConsoleInput _ANSI_ARGS_((ClientData instanceData,
62 char *buf, int toRead, int *errorCode));
63 static int ConsoleOutput _ANSI_ARGS_((ClientData instanceData,
64 char *buf, int toWrite, int *errorCode));
65 static int ConsoleClose _ANSI_ARGS_((ClientData instanceData,
67 static void ConsoleWatch _ANSI_ARGS_((ClientData instanceData,
69 static int ConsoleHandle _ANSI_ARGS_((ClientData instanceData,
70 int direction, ClientData *handlePtr));
73 * This structure describes the channel type structure for file based IO:
76 static Tcl_ChannelType consoleChannelType = {
77 "console", /* Type name. */
78 NULL, /* Always non-blocking.*/
79 ConsoleClose, /* Close proc. */
80 ConsoleInput, /* Input proc. */
81 ConsoleOutput, /* Output proc. */
82 NULL, /* Seek proc. */
83 NULL, /* Set option proc. */
84 NULL, /* Get option proc. */
85 ConsoleWatch, /* Watch for events on console. */
86 ConsoleHandle, /* Get a handle from the device. */
95 *----------------------------------------------------------------------
97 * ShouldUseConsoleChannel
99 * Check to see if console window should be used for a given
106 * Creates the console channel and installs it as the standard
109 *----------------------------------------------------------------------
111 static int ShouldUseConsoleChannel(type)
114 DWORD handleId; /* Standard handle to retrieve. */
124 handleId = STD_INPUT_HANDLE;
129 handleId = STD_OUTPUT_HANDLE;
134 handleId = STD_ERROR_HANDLE;
143 handle = GetStdHandle(handleId);
146 * Note that we need to check for 0 because Windows will return 0 if this
147 * is not a console mode application, even though this is not a valid
151 if ((handle == INVALID_HANDLE_VALUE) || (handle == 0)) {
154 fileType = GetFileType(handle);
157 * If the file is a character device, we need to try to figure out
158 * whether it is a serial port, a console, or something else. We
159 * test for the console case first because this is more common.
162 if (fileType == FILE_TYPE_CHAR) {
163 dcb.DCBlength = sizeof( DCB ) ;
164 if (!GetConsoleMode(handle, &consoleParams) &&
165 !GetCommState(handle, &dcb)) {
167 * Don't use a CHAR type channel for stdio, otherwise Tk
168 * runs into trouble with the MS DevStudio debugger.
173 } else if (fileType == FILE_TYPE_UNKNOWN) {
175 } else if (Tcl_GetStdChannel(type) == NULL) {
183 * Mac should always use a console channel, Unix should if it's trying to
186 #define ShouldUseConsoleChannel(chan) (1)
190 *----------------------------------------------------------------------
192 * Tk_InitConsoleChannels --
194 * Create the console channels and install them as the standard
195 * channels. All I/O will be discarded until TkConsoleInit is
196 * called to attach the console to a text widget.
202 * Creates the console channel and installs it as the standard
205 *----------------------------------------------------------------------
209 Tk_InitConsoleChannels(interp)
212 Tcl_Channel consoleChannel;
215 * Ensure that we are getting the matching version of Tcl. This is
216 * really only an issue when Tk is loaded dynamically.
219 if (Tcl_InitStubs(interp, TCL_VERSION, 1) == NULL) {
223 Tcl_MutexLock(&consoleMutex);
224 if (!consoleInitialized) {
226 consoleInitialized = 1;
229 * check for STDIN, otherwise create it
231 * Don't do this check on the Mac, because it is hard to prevent
232 * callbacks from the SIOUX layer from opening stdout & stdin, but
233 * we don't want to use the SIOUX console. Since the console is not
234 * actually created till something is written to the channel, it is
235 * okay to just ignore it here.
237 * This is still a bit of a hack, however, and should be cleaned up
238 * when we have a better abstraction for the console.
241 if (ShouldUseConsoleChannel(TCL_STDIN)) {
242 consoleChannel = Tcl_CreateChannel(&consoleChannelType, "console0",
243 (ClientData) TCL_STDIN, TCL_READABLE);
244 if (consoleChannel != NULL) {
245 Tcl_SetChannelOption(NULL, consoleChannel,
246 "-translation", "lf");
247 Tcl_SetChannelOption(NULL, consoleChannel,
248 "-buffering", "none");
249 Tcl_SetChannelOption(NULL, consoleChannel,
250 "-encoding", "utf-8");
252 Tcl_SetStdChannel(consoleChannel, TCL_STDIN);
256 * check for STDOUT, otherwise create it
259 if (ShouldUseConsoleChannel(TCL_STDOUT)) {
260 consoleChannel = Tcl_CreateChannel(&consoleChannelType, "console1",
261 (ClientData) TCL_STDOUT, TCL_WRITABLE);
262 if (consoleChannel != NULL) {
263 Tcl_SetChannelOption(NULL, consoleChannel,
264 "-translation", "lf");
265 Tcl_SetChannelOption(NULL, consoleChannel,
266 "-buffering", "none");
267 Tcl_SetChannelOption(NULL, consoleChannel,
268 "-encoding", "utf-8");
270 Tcl_SetStdChannel(consoleChannel, TCL_STDOUT);
274 * check for STDERR, otherwise create it
277 if (ShouldUseConsoleChannel(TCL_STDERR)) {
278 consoleChannel = Tcl_CreateChannel(&consoleChannelType, "console2",
279 (ClientData) TCL_STDERR, TCL_WRITABLE);
280 if (consoleChannel != NULL) {
281 Tcl_SetChannelOption(NULL, consoleChannel,
282 "-translation", "lf");
283 Tcl_SetChannelOption(NULL, consoleChannel,
284 "-buffering", "none");
285 Tcl_SetChannelOption(NULL, consoleChannel,
286 "-encoding", "utf-8");
288 Tcl_SetStdChannel(consoleChannel, TCL_STDERR);
291 Tcl_MutexUnlock(&consoleMutex);
295 *----------------------------------------------------------------------
297 * Tk_CreateConsoleWindow --
299 * Initialize the console. This code actually creates a new
300 * application and associated interpreter. This effectivly hides
301 * the implementation from the main application.
307 * A new console it created.
309 *----------------------------------------------------------------------
313 Tk_CreateConsoleWindow(interp)
314 Tcl_Interp *interp; /* Interpreter to use for prompting. */
316 Tcl_Interp *consoleInterp;
318 Tk_Window mainWindow = Tk_MainWindow(interp);
319 ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
320 Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
322 static char initCmd[] = "source -rsrc {Console}";
324 static char initCmd[] = "source $tk_library/console.tcl";
327 consoleInterp = Tcl_CreateInterp();
328 if (consoleInterp == NULL) {
333 * Initialized Tcl and Tk.
336 if (Tcl_Init(consoleInterp) != TCL_OK) {
339 if (Tk_Init(consoleInterp) != TCL_OK) {
342 tsdPtr->gStdoutInterp = interp;
345 * Add console commands to the interp
347 info = (ConsoleInfo *) ckalloc(sizeof(ConsoleInfo));
348 info->interp = interp;
349 info->consoleInterp = consoleInterp;
350 Tcl_CreateCommand(interp, "console", ConsoleCmd, (ClientData) info,
351 (Tcl_CmdDeleteProc *) ConsoleDeleteProc);
352 Tcl_CreateCommand(consoleInterp, "consoleinterp", InterpreterCmd,
353 (ClientData) info, (Tcl_CmdDeleteProc *) NULL);
355 Tk_CreateEventHandler(mainWindow, StructureNotifyMask, ConsoleEventProc,
358 Tcl_Preserve((ClientData) consoleInterp);
359 if (Tcl_Eval(consoleInterp, initCmd) == TCL_ERROR) {
360 /* goto error; -- no problem for now... */
361 printf("Eval error: %s", consoleInterp->result);
363 Tcl_Release((ClientData) consoleInterp);
367 if (consoleInterp != NULL) {
368 Tcl_DeleteInterp(consoleInterp);
374 *----------------------------------------------------------------------
378 * Writes the given output on the IO channel. Returns count of how
379 * many characters were actually written, and an error indication.
382 * A count of how many characters were written is returned and an
383 * error indication is returned in an output argument.
386 * Writes output on the actual channel.
388 *----------------------------------------------------------------------
392 ConsoleOutput(instanceData, buf, toWrite, errorCode)
393 ClientData instanceData; /* Indicates which device to use. */
394 char *buf; /* The data buffer. */
395 int toWrite; /* How many bytes to write? */
396 int *errorCode; /* Where to store error code. */
398 ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
399 Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
404 if (tsdPtr->gStdoutInterp != NULL) {
405 TkConsolePrint(tsdPtr->gStdoutInterp, (int) instanceData, buf,
413 *----------------------------------------------------------------------
417 * Read input from the console. Not currently implemented.
420 * Always returns EOF.
425 *----------------------------------------------------------------------
430 ConsoleInput(instanceData, buf, bufSize, errorCode)
431 ClientData instanceData; /* Unused. */
432 char *buf; /* Where to store data read. */
433 int bufSize; /* How much space is available
435 int *errorCode; /* Where to store error code. */
437 return 0; /* Always return EOF. */
441 *----------------------------------------------------------------------
445 * Closes the IO channel.
448 * Always returns 0 (success).
451 * Frees the dummy file associated with the channel.
453 *----------------------------------------------------------------------
458 ConsoleClose(instanceData, interp)
459 ClientData instanceData; /* Unused. */
460 Tcl_Interp *interp; /* Unused. */
466 *----------------------------------------------------------------------
470 * Called by the notifier to set up the console device so that
471 * events will be noticed. Since there are no events on the
472 * console, this routine just returns without doing anything.
480 *----------------------------------------------------------------------
485 ConsoleWatch(instanceData, mask)
486 ClientData instanceData; /* Device ID for the channel. */
487 int mask; /* OR-ed combination of
488 * TCL_READABLE, TCL_WRITABLE and
489 * TCL_EXCEPTION, for the events
490 * we are interested in. */
495 *----------------------------------------------------------------------
499 * Invoked by the generic IO layer to get a handle from a channel.
500 * Because console channels are not devices, this function always
504 * Always returns TCL_ERROR.
509 *----------------------------------------------------------------------
514 ConsoleHandle(instanceData, direction, handlePtr)
515 ClientData instanceData; /* Device ID for the channel. */
516 int direction; /* TCL_READABLE or TCL_WRITABLE to indicate
517 * which direction of the channel is being
519 ClientData *handlePtr; /* Where to store handle */
525 *----------------------------------------------------------------------
529 * The console command implements a Tcl interface to the various console
538 *----------------------------------------------------------------------
542 ConsoleCmd(clientData, interp, argc, argv)
543 ClientData clientData; /* Not used. */
544 Tcl_Interp *interp; /* Current interpreter. */
545 int argc; /* Number of arguments. */
546 char **argv; /* Argument strings. */
548 ConsoleInfo *info = (ConsoleInfo *) clientData;
552 Tcl_Interp *consoleInterp;
556 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
557 " option ?arg arg ...?\"", (char *) NULL);
562 length = strlen(argv[1]);
564 consoleInterp = info->consoleInterp;
565 Tcl_Preserve((ClientData) consoleInterp);
566 Tcl_DStringInit(&dString);
568 if ((c == 't') && (strncmp(argv[1], "title", length)) == 0) {
569 Tcl_DStringAppend(&dString, "wm title . ", -1);
571 Tcl_DStringAppendElement(&dString, argv[2]);
573 Tcl_Eval(consoleInterp, Tcl_DStringValue(&dString));
574 } else if ((c == 'h') && (strncmp(argv[1], "hide", length)) == 0) {
575 Tcl_DStringAppend(&dString, "wm withdraw . ", -1);
576 Tcl_Eval(consoleInterp, Tcl_DStringValue(&dString));
577 } else if ((c == 's') && (strncmp(argv[1], "show", length)) == 0) {
578 Tcl_DStringAppend(&dString, "wm deiconify . ", -1);
579 Tcl_Eval(consoleInterp, Tcl_DStringValue(&dString));
580 } else if ((c == 'e') && (strncmp(argv[1], "eval", length)) == 0) {
582 result = Tcl_Eval(consoleInterp, argv[2]);
583 Tcl_AppendResult(interp, Tcl_GetStringResult(consoleInterp),
586 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
587 " eval command\"", (char *) NULL);
591 Tcl_AppendResult(interp, "bad option \"", argv[1],
592 "\": should be hide, show, or title",
596 Tcl_DStringFree(&dString);
597 Tcl_Release((ClientData) consoleInterp);
602 *----------------------------------------------------------------------
606 * This command allows the console interp to communicate with the
615 *----------------------------------------------------------------------
619 InterpreterCmd(clientData, interp, argc, argv)
620 ClientData clientData; /* Not used. */
621 Tcl_Interp *interp; /* Current interpreter. */
622 int argc; /* Number of arguments. */
623 char **argv; /* Argument strings. */
625 ConsoleInfo *info = (ConsoleInfo *) clientData;
629 Tcl_Interp *otherInterp;
632 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
633 " option ?arg arg ...?\"", (char *) NULL);
638 length = strlen(argv[1]);
639 otherInterp = info->interp;
640 Tcl_Preserve((ClientData) otherInterp);
641 if ((c == 'e') && (strncmp(argv[1], "eval", length)) == 0) {
642 result = Tcl_GlobalEval(otherInterp, argv[2]);
643 Tcl_AppendResult(interp, otherInterp->result, (char *) NULL);
644 } else if ((c == 'r') && (strncmp(argv[1], "record", length)) == 0) {
645 Tcl_RecordAndEval(otherInterp, argv[2], TCL_EVAL_GLOBAL);
647 Tcl_ResetResult(interp);
648 Tcl_AppendResult(interp, otherInterp->result, (char *) NULL);
650 Tcl_AppendResult(interp, "bad option \"", argv[1],
651 "\": should be eval or record",
655 Tcl_Release((ClientData) otherInterp);
660 *----------------------------------------------------------------------
662 * ConsoleDeleteProc --
664 * If the console command is deleted we destroy the console window
665 * and all associated data structures.
671 * A new console it created.
673 *----------------------------------------------------------------------
677 ConsoleDeleteProc(clientData)
678 ClientData clientData;
680 ConsoleInfo *info = (ConsoleInfo *) clientData;
682 Tcl_DeleteInterp(info->consoleInterp);
683 info->consoleInterp = NULL;
687 *----------------------------------------------------------------------
689 * ConsoleEventProc --
691 * This event procedure is registered on the main window of the
692 * slave interpreter. If the user or a running script causes the
693 * main window to be destroyed, then we need to inform the console
694 * interpreter by invoking "tkConsoleExit".
700 * Invokes the "tkConsoleExit" procedure in the console interp.
702 *----------------------------------------------------------------------
706 ConsoleEventProc(clientData, eventPtr)
707 ClientData clientData;
710 ConsoleInfo *info = (ConsoleInfo *) clientData;
711 Tcl_Interp *consoleInterp;
714 if (eventPtr->type == DestroyNotify) {
716 Tcl_DStringInit(&dString);
718 consoleInterp = info->consoleInterp;
721 * It is possible that the console interpreter itself has
722 * already been deleted. In that case the consoleInterp
723 * field will be set to NULL. If the interpreter is already
724 * gone, we do not have to do any work here.
727 if (consoleInterp == (Tcl_Interp *) NULL) {
730 Tcl_Preserve((ClientData) consoleInterp);
731 Tcl_DStringAppend(&dString, "tkConsoleExit", -1);
732 Tcl_Eval(consoleInterp, Tcl_DStringValue(&dString));
733 Tcl_DStringFree(&dString);
734 Tcl_Release((ClientData) consoleInterp);
739 *----------------------------------------------------------------------
743 * Prints to the give text to the console. Given the main interp
744 * this functions find the appropiate console interp and forwards
745 * the text to be added to that console.
753 *----------------------------------------------------------------------
757 TkConsolePrint(interp, devId, buffer, size)
758 Tcl_Interp *interp; /* Main interpreter. */
759 int devId; /* TCL_STDOUT for stdout, TCL_STDERR for
761 char *buffer; /* Text buffer. */
762 long size; /* Size of text buffer. */
764 Tcl_DString command, output;
768 Tcl_Interp *consoleInterp;
771 if (interp == NULL) {
775 if (devId == TCL_STDERR) {
776 cmd = "tkConsoleOutput stderr ";
778 cmd = "tkConsoleOutput stdout ";
781 result = Tcl_GetCommandInfo(interp, "console", &cmdInfo);
785 info = (ConsoleInfo *) cmdInfo.clientData;
787 Tcl_DStringInit(&output);
788 Tcl_DStringAppend(&output, buffer, size);
790 Tcl_DStringInit(&command);
791 Tcl_DStringAppend(&command, cmd, (int) strlen(cmd));
792 Tcl_DStringAppendElement(&command, output.string);
794 consoleInterp = info->consoleInterp;
795 Tcl_Preserve((ClientData) consoleInterp);
796 Tcl_Eval(consoleInterp, command.string);
797 Tcl_Release((ClientData) consoleInterp);
799 Tcl_DStringFree(&command);
800 Tcl_DStringFree(&output);