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 static int ConsoleCmd _ANSI_ARGS_((ClientData clientData,
51 Tcl_Interp *interp, int argc, CONST char **argv));
52 static void ConsoleDeleteProc _ANSI_ARGS_((ClientData clientData));
53 static void ConsoleEventProc _ANSI_ARGS_((ClientData clientData,
55 static int InterpreterCmd _ANSI_ARGS_((ClientData clientData,
56 Tcl_Interp *interp, int argc, CONST char **argv));
58 static int ConsoleInput _ANSI_ARGS_((ClientData instanceData,
59 char *buf, int toRead, int *errorCode));
60 static int ConsoleOutput _ANSI_ARGS_((ClientData instanceData,
61 CONST char *buf, int toWrite, int *errorCode));
62 static int ConsoleClose _ANSI_ARGS_((ClientData instanceData,
64 static void ConsoleWatch _ANSI_ARGS_((ClientData instanceData,
66 static int ConsoleHandle _ANSI_ARGS_((ClientData instanceData,
67 int direction, ClientData *handlePtr));
70 * This structure describes the channel type structure for file based IO:
73 static Tcl_ChannelType consoleChannelType = {
74 "console", /* Type name. */
75 NULL, /* Always non-blocking.*/
76 ConsoleClose, /* Close proc. */
77 ConsoleInput, /* Input proc. */
78 ConsoleOutput, /* Output proc. */
79 NULL, /* Seek proc. */
80 NULL, /* Set option proc. */
81 NULL, /* Get option proc. */
82 ConsoleWatch, /* Watch for events on console. */
83 ConsoleHandle, /* Get a handle from the device. */
92 *----------------------------------------------------------------------
94 * ShouldUseConsoleChannel
96 * Check to see if console window should be used for a given
103 * Creates the console channel and installs it as the standard
106 *----------------------------------------------------------------------
108 static int ShouldUseConsoleChannel(type)
111 DWORD handleId; /* Standard handle to retrieve. */
121 handleId = STD_INPUT_HANDLE;
126 handleId = STD_OUTPUT_HANDLE;
131 handleId = STD_ERROR_HANDLE;
140 handle = GetStdHandle(handleId);
143 * Note that we need to check for 0 because Windows will return 0 if this
144 * is not a console mode application, even though this is not a valid
148 if ((handle == INVALID_HANDLE_VALUE) || (handle == 0)) {
153 * Win2K BUG: GetStdHandle(STD_OUTPUT_HANDLE) can return what appears
154 * to be a valid handle. See TclpGetDefaultStdChannel() for this change
155 * implemented. We didn't change it here because GetFileType() [below]
156 * will catch this with FILE_TYPE_UNKNOWN and appropriately return a
157 * value of 1, anyways.
160 * DWORD dummyWritten;
162 * if ((type == TCL_STDOUT)
163 * && !WriteFile(handle, dummyBuff, 0, &dummyWritten, NULL)) {
168 fileType = GetFileType(handle);
171 * If the file is a character device, we need to try to figure out
172 * whether it is a serial port, a console, or something else. We
173 * test for the console case first because this is more common.
176 if (fileType == FILE_TYPE_CHAR) {
177 dcb.DCBlength = sizeof( DCB ) ;
178 if (!GetConsoleMode(handle, &consoleParams) &&
179 !GetCommState(handle, &dcb)) {
181 * Don't use a CHAR type channel for stdio, otherwise Tk
182 * runs into trouble with the MS DevStudio debugger.
187 } else if (fileType == FILE_TYPE_UNKNOWN) {
189 } else if (Tcl_GetStdChannel(type) == NULL) {
197 * Mac should always use a console channel, Unix should if it's trying to
200 #define ShouldUseConsoleChannel(chan) (1)
204 *----------------------------------------------------------------------
206 * Tk_InitConsoleChannels --
208 * Create the console channels and install them as the standard
209 * channels. All I/O will be discarded until TkConsoleInit is
210 * called to attach the console to a text widget.
216 * Creates the console channel and installs it as the standard
219 *----------------------------------------------------------------------
223 Tk_InitConsoleChannels(interp)
226 Tcl_Channel consoleChannel;
229 * Ensure that we are getting the matching version of Tcl. This is
230 * really only an issue when Tk is loaded dynamically.
233 if (Tcl_InitStubs(interp, TCL_VERSION, 1) == NULL) {
237 Tcl_MutexLock(&consoleMutex);
238 if (!consoleInitialized) {
240 consoleInitialized = 1;
243 * check for STDIN, otherwise create it
245 * Don't do this check on the Mac, because it is hard to prevent
246 * callbacks from the SIOUX layer from opening stdout & stdin, but
247 * we don't want to use the SIOUX console. Since the console is not
248 * actually created till something is written to the channel, it is
249 * okay to just ignore it here.
251 * This is still a bit of a hack, however, and should be cleaned up
252 * when we have a better abstraction for the console.
255 if (ShouldUseConsoleChannel(TCL_STDIN)) {
256 consoleChannel = Tcl_CreateChannel(&consoleChannelType, "console0",
257 (ClientData) TCL_STDIN, TCL_READABLE);
258 if (consoleChannel != NULL) {
259 Tcl_SetChannelOption(NULL, consoleChannel,
260 "-translation", "lf");
261 Tcl_SetChannelOption(NULL, consoleChannel,
262 "-buffering", "none");
263 Tcl_SetChannelOption(NULL, consoleChannel,
264 "-encoding", "utf-8");
266 Tcl_SetStdChannel(consoleChannel, TCL_STDIN);
270 * check for STDOUT, otherwise create it
273 if (ShouldUseConsoleChannel(TCL_STDOUT)) {
274 consoleChannel = Tcl_CreateChannel(&consoleChannelType, "console1",
275 (ClientData) TCL_STDOUT, TCL_WRITABLE);
276 if (consoleChannel != NULL) {
277 Tcl_SetChannelOption(NULL, consoleChannel,
278 "-translation", "lf");
279 Tcl_SetChannelOption(NULL, consoleChannel,
280 "-buffering", "none");
281 Tcl_SetChannelOption(NULL, consoleChannel,
282 "-encoding", "utf-8");
284 Tcl_SetStdChannel(consoleChannel, TCL_STDOUT);
288 * check for STDERR, otherwise create it
291 if (ShouldUseConsoleChannel(TCL_STDERR)) {
292 consoleChannel = Tcl_CreateChannel(&consoleChannelType, "console2",
293 (ClientData) TCL_STDERR, TCL_WRITABLE);
294 if (consoleChannel != NULL) {
295 Tcl_SetChannelOption(NULL, consoleChannel,
296 "-translation", "lf");
297 Tcl_SetChannelOption(NULL, consoleChannel,
298 "-buffering", "none");
299 Tcl_SetChannelOption(NULL, consoleChannel,
300 "-encoding", "utf-8");
302 Tcl_SetStdChannel(consoleChannel, TCL_STDERR);
305 Tcl_MutexUnlock(&consoleMutex);
309 *----------------------------------------------------------------------
311 * Tk_CreateConsoleWindow --
313 * Initialize the console. This code actually creates a new
314 * application and associated interpreter. This effectivly hides
315 * the implementation from the main application.
321 * A new console it created.
323 *----------------------------------------------------------------------
327 Tk_CreateConsoleWindow(interp)
328 Tcl_Interp *interp; /* Interpreter to use for prompting. */
330 Tcl_Interp *consoleInterp;
332 Tk_Window mainWindow = Tk_MainWindow(interp);
333 ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
334 Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
336 static char initCmd[] = "if {[catch {source $tk_library:console.tcl}]} {source -rsrc console}";
338 static char initCmd[] = "source $tk_library/console.tcl";
341 consoleInterp = Tcl_CreateInterp();
342 if (consoleInterp == NULL) {
347 * Initialized Tcl and Tk.
350 if (Tcl_Init(consoleInterp) != TCL_OK) {
353 if (Tk_Init(consoleInterp) != TCL_OK) {
356 tsdPtr->gStdoutInterp = interp;
359 * Add console commands to the interp
361 info = (ConsoleInfo *) ckalloc(sizeof(ConsoleInfo));
362 info->interp = interp;
363 info->consoleInterp = consoleInterp;
364 Tcl_CreateCommand(interp, "console", ConsoleCmd, (ClientData) info,
365 (Tcl_CmdDeleteProc *) ConsoleDeleteProc);
366 Tcl_CreateCommand(consoleInterp, "consoleinterp", InterpreterCmd,
367 (ClientData) info, (Tcl_CmdDeleteProc *) NULL);
369 Tk_CreateEventHandler(mainWindow, StructureNotifyMask, ConsoleEventProc,
372 Tcl_Preserve((ClientData) consoleInterp);
373 if (Tcl_Eval(consoleInterp, initCmd) == TCL_ERROR) {
374 /* goto error; -- no problem for now... */
375 printf("Eval error: %s", consoleInterp->result);
377 Tcl_Release((ClientData) consoleInterp);
381 if (consoleInterp != NULL) {
382 Tcl_DeleteInterp(consoleInterp);
388 *----------------------------------------------------------------------
392 * Writes the given output on the IO channel. Returns count of how
393 * many characters were actually written, and an error indication.
396 * A count of how many characters were written is returned and an
397 * error indication is returned in an output argument.
400 * Writes output on the actual channel.
402 *----------------------------------------------------------------------
406 ConsoleOutput(instanceData, buf, toWrite, errorCode)
407 ClientData instanceData; /* Indicates which device to use. */
408 CONST char *buf; /* The data buffer. */
409 int toWrite; /* How many bytes to write? */
410 int *errorCode; /* Where to store error code. */
412 ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
413 Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
418 if (tsdPtr->gStdoutInterp != NULL) {
419 TkConsolePrint(tsdPtr->gStdoutInterp, (int) instanceData, buf,
427 *----------------------------------------------------------------------
431 * Read input from the console. Not currently implemented.
434 * Always returns EOF.
439 *----------------------------------------------------------------------
444 ConsoleInput(instanceData, buf, bufSize, errorCode)
445 ClientData instanceData; /* Unused. */
446 char *buf; /* Where to store data read. */
447 int bufSize; /* How much space is available
449 int *errorCode; /* Where to store error code. */
451 return 0; /* Always return EOF. */
455 *----------------------------------------------------------------------
459 * Closes the IO channel.
462 * Always returns 0 (success).
465 * Frees the dummy file associated with the channel.
467 *----------------------------------------------------------------------
472 ConsoleClose(instanceData, interp)
473 ClientData instanceData; /* Unused. */
474 Tcl_Interp *interp; /* Unused. */
476 ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
477 Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
478 tsdPtr->gStdoutInterp = NULL;
483 *----------------------------------------------------------------------
487 * Called by the notifier to set up the console device so that
488 * events will be noticed. Since there are no events on the
489 * console, this routine just returns without doing anything.
497 *----------------------------------------------------------------------
502 ConsoleWatch(instanceData, mask)
503 ClientData instanceData; /* Device ID for the channel. */
504 int mask; /* OR-ed combination of
505 * TCL_READABLE, TCL_WRITABLE and
506 * TCL_EXCEPTION, for the events
507 * we are interested in. */
512 *----------------------------------------------------------------------
516 * Invoked by the generic IO layer to get a handle from a channel.
517 * Because console channels are not devices, this function always
521 * Always returns TCL_ERROR.
526 *----------------------------------------------------------------------
531 ConsoleHandle(instanceData, direction, handlePtr)
532 ClientData instanceData; /* Device ID for the channel. */
533 int direction; /* TCL_READABLE or TCL_WRITABLE to indicate
534 * which direction of the channel is being
536 ClientData *handlePtr; /* Where to store handle */
542 *----------------------------------------------------------------------
546 * The console command implements a Tcl interface to the various console
555 *----------------------------------------------------------------------
559 ConsoleCmd(clientData, interp, argc, argv)
560 ClientData clientData; /* Not used. */
561 Tcl_Interp *interp; /* Current interpreter. */
562 int argc; /* Number of arguments. */
563 CONST char **argv; /* Argument strings. */
565 ConsoleInfo *info = (ConsoleInfo *) clientData;
569 Tcl_Interp *consoleInterp;
573 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
574 " option ?arg arg ...?\"", (char *) NULL);
579 length = strlen(argv[1]);
581 consoleInterp = info->consoleInterp;
582 Tcl_Preserve((ClientData) consoleInterp);
583 Tcl_DStringInit(&dString);
585 if ((c == 't') && (strncmp(argv[1], "title", length)) == 0) {
586 Tcl_DStringAppend(&dString, "wm title . ", -1);
588 Tcl_DStringAppendElement(&dString, argv[2]);
590 Tcl_Eval(consoleInterp, Tcl_DStringValue(&dString));
591 } else if ((c == 'h') && (strncmp(argv[1], "hide", length)) == 0) {
592 Tcl_DStringAppend(&dString, "wm withdraw . ", -1);
593 Tcl_Eval(consoleInterp, Tcl_DStringValue(&dString));
594 } else if ((c == 's') && (strncmp(argv[1], "show", length)) == 0) {
595 Tcl_DStringAppend(&dString, "wm deiconify . ", -1);
596 Tcl_Eval(consoleInterp, Tcl_DStringValue(&dString));
597 } else if ((c == 'e') && (strncmp(argv[1], "eval", length)) == 0) {
599 result = Tcl_Eval(consoleInterp, argv[2]);
600 Tcl_AppendResult(interp, Tcl_GetStringResult(consoleInterp),
603 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
604 " eval command\"", (char *) NULL);
608 Tcl_AppendResult(interp, "bad option \"", argv[1],
609 "\": should be hide, show, or title",
613 Tcl_DStringFree(&dString);
614 Tcl_Release((ClientData) consoleInterp);
619 *----------------------------------------------------------------------
623 * This command allows the console interp to communicate with the
632 *----------------------------------------------------------------------
636 InterpreterCmd(clientData, interp, argc, argv)
637 ClientData clientData; /* Not used. */
638 Tcl_Interp *interp; /* Current interpreter. */
639 int argc; /* Number of arguments. */
640 CONST char **argv; /* Argument strings. */
642 ConsoleInfo *info = (ConsoleInfo *) clientData;
646 Tcl_Interp *otherInterp;
649 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
650 " option ?arg arg ...?\"", (char *) NULL);
655 length = strlen(argv[1]);
656 otherInterp = info->interp;
657 Tcl_Preserve((ClientData) otherInterp);
658 if ((c == 'e') && (strncmp(argv[1], "eval", length)) == 0) {
659 result = Tcl_GlobalEval(otherInterp, argv[2]);
660 Tcl_AppendResult(interp, otherInterp->result, (char *) NULL);
661 } else if ((c == 'r') && (strncmp(argv[1], "record", length)) == 0) {
662 Tcl_RecordAndEval(otherInterp, argv[2], TCL_EVAL_GLOBAL);
664 Tcl_ResetResult(interp);
665 Tcl_AppendResult(interp, otherInterp->result, (char *) NULL);
667 Tcl_AppendResult(interp, "bad option \"", argv[1],
668 "\": should be eval or record",
672 Tcl_Release((ClientData) otherInterp);
677 *----------------------------------------------------------------------
679 * ConsoleDeleteProc --
681 * If the console command is deleted we destroy the console window
682 * and all associated data structures.
688 * A new console it created.
690 *----------------------------------------------------------------------
694 ConsoleDeleteProc(clientData)
695 ClientData clientData;
697 ConsoleInfo *info = (ConsoleInfo *) clientData;
699 Tcl_DeleteInterp(info->consoleInterp);
700 info->consoleInterp = NULL;
704 *----------------------------------------------------------------------
706 * ConsoleEventProc --
708 * This event procedure is registered on the main window of the
709 * slave interpreter. If the user or a running script causes the
710 * main window to be destroyed, then we need to inform the console
711 * interpreter by invoking "::tk::ConsoleExit".
717 * Invokes the "::tk::ConsoleExit" procedure in the console interp.
719 *----------------------------------------------------------------------
723 ConsoleEventProc(clientData, eventPtr)
724 ClientData clientData;
727 ConsoleInfo *info = (ConsoleInfo *) clientData;
728 Tcl_Interp *consoleInterp;
731 if (eventPtr->type == DestroyNotify) {
733 Tcl_DStringInit(&dString);
735 consoleInterp = info->consoleInterp;
738 * It is possible that the console interpreter itself has
739 * already been deleted. In that case the consoleInterp
740 * field will be set to NULL. If the interpreter is already
741 * gone, we do not have to do any work here.
744 if (consoleInterp == (Tcl_Interp *) NULL) {
747 Tcl_Preserve((ClientData) consoleInterp);
748 Tcl_DStringAppend(&dString, "::tk::ConsoleExit", -1);
749 Tcl_Eval(consoleInterp, Tcl_DStringValue(&dString));
750 Tcl_DStringFree(&dString);
751 Tcl_Release((ClientData) consoleInterp);
756 *----------------------------------------------------------------------
760 * Prints to the give text to the console. Given the main interp
761 * this functions find the appropiate console interp and forwards
762 * the text to be added to that console.
770 *----------------------------------------------------------------------
774 TkConsolePrint(interp, devId, buffer, size)
775 Tcl_Interp *interp; /* Main interpreter. */
776 int devId; /* TCL_STDOUT for stdout, TCL_STDERR for
778 CONST char *buffer; /* Text buffer. */
779 long size; /* Size of text buffer. */
781 Tcl_DString command, output;
785 Tcl_Interp *consoleInterp;
788 if (interp == NULL) {
792 if (devId == TCL_STDERR) {
793 cmd = "::tk::ConsoleOutput stderr ";
795 cmd = "::tk::ConsoleOutput stdout ";
798 result = Tcl_GetCommandInfo(interp, "console", &cmdInfo);
802 info = (ConsoleInfo *) cmdInfo.clientData;
804 Tcl_DStringInit(&output);
805 Tcl_DStringAppend(&output, buffer, size);
807 Tcl_DStringInit(&command);
808 Tcl_DStringAppend(&command, cmd, (int) strlen(cmd));
809 Tcl_DStringAppendElement(&command, output.string);
811 consoleInterp = info->consoleInterp;
812 Tcl_Preserve((ClientData) consoleInterp);
813 Tcl_Eval(consoleInterp, command.string);
814 Tcl_Release((ClientData) consoleInterp);
816 Tcl_DStringFree(&command);
817 Tcl_DStringFree(&output);