sl@0: /* sl@0: * tclWinConsole.c -- sl@0: * sl@0: * This file implements the Windows-specific console functions, sl@0: * and the "console" channel driver. sl@0: * sl@0: * Copyright (c) 1999 by Scriptics Corp. sl@0: * sl@0: * See the file "license.terms" for information on usage and redistribution sl@0: * of this file, and for a DISCLAIMER OF ALL WARRANTIES. sl@0: * sl@0: * RCS: @(#) $Id: tclWinConsole.c,v 1.11.2.3 2006/03/28 21:02:37 hobbs Exp $ sl@0: */ sl@0: sl@0: #include "tclWinInt.h" sl@0: sl@0: #include sl@0: #include sl@0: #include sl@0: sl@0: /* sl@0: * The following variable is used to tell whether this module has been sl@0: * initialized. sl@0: */ sl@0: sl@0: static int initialized = 0; sl@0: sl@0: /* sl@0: * The consoleMutex locks around access to the initialized variable, and it is sl@0: * used to protect background threads from being terminated while they are sl@0: * using APIs that hold locks. sl@0: */ sl@0: sl@0: TCL_DECLARE_MUTEX(consoleMutex) sl@0: sl@0: /* sl@0: * Bit masks used in the flags field of the ConsoleInfo structure below. sl@0: */ sl@0: sl@0: #define CONSOLE_PENDING (1<<0) /* Message is pending in the queue. */ sl@0: #define CONSOLE_ASYNC (1<<1) /* Channel is non-blocking. */ sl@0: sl@0: /* sl@0: * Bit masks used in the sharedFlags field of the ConsoleInfo structure below. sl@0: */ sl@0: sl@0: #define CONSOLE_EOF (1<<2) /* Console has reached EOF. */ sl@0: #define CONSOLE_BUFFERED (1<<3) /* data was read into a buffer by the reader sl@0: thread */ sl@0: sl@0: #define CONSOLE_BUFFER_SIZE (8*1024) sl@0: /* sl@0: * This structure describes per-instance data for a console based channel. sl@0: */ sl@0: sl@0: typedef struct ConsoleInfo { sl@0: HANDLE handle; sl@0: int type; sl@0: struct ConsoleInfo *nextPtr;/* Pointer to next registered console. */ sl@0: Tcl_Channel channel; /* Pointer to channel structure. */ sl@0: int validMask; /* OR'ed combination of TCL_READABLE, sl@0: * TCL_WRITABLE, or TCL_EXCEPTION: indicates sl@0: * which operations are valid on the file. */ sl@0: int watchMask; /* OR'ed combination of TCL_READABLE, sl@0: * TCL_WRITABLE, or TCL_EXCEPTION: indicates sl@0: * which events should be reported. */ sl@0: int flags; /* State flags, see above for a list. */ sl@0: Tcl_ThreadId threadId; /* Thread to which events should be reported. sl@0: * This value is used by the reader/writer sl@0: * threads. */ sl@0: HANDLE writeThread; /* Handle to writer thread. */ sl@0: HANDLE readThread; /* Handle to reader thread. */ sl@0: HANDLE writable; /* Manual-reset event to signal when the sl@0: * writer thread has finished waiting for sl@0: * the current buffer to be written. */ sl@0: HANDLE readable; /* Manual-reset event to signal when the sl@0: * reader thread has finished waiting for sl@0: * input. */ sl@0: HANDLE startWriter; /* Auto-reset event used by the main thread to sl@0: * signal when the writer thread should attempt sl@0: * to write to the console. */ sl@0: HANDLE stopWriter; /* Auto-reset event used by the main thread to sl@0: * signal when the writer thread should exit. sl@0: */ sl@0: HANDLE startReader; /* Auto-reset event used by the main thread to sl@0: * signal when the reader thread should attempt sl@0: * to read from the console. */ sl@0: HANDLE stopReader; /* Auto-reset event used by the main thread to sl@0: * signal when the reader thread should exit. sl@0: */ sl@0: DWORD writeError; /* An error caused by the last background sl@0: * write. Set to 0 if no error has been sl@0: * detected. This word is shared with the sl@0: * writer thread so access must be sl@0: * synchronized with the writable object. sl@0: */ sl@0: char *writeBuf; /* Current background output buffer. sl@0: * Access is synchronized with the writable sl@0: * object. */ sl@0: int writeBufLen; /* Size of write buffer. Access is sl@0: * synchronized with the writable sl@0: * object. */ sl@0: int toWrite; /* Current amount to be written. Access is sl@0: * synchronized with the writable object. */ sl@0: int readFlags; /* Flags that are shared with the reader sl@0: * thread. Access is synchronized with the sl@0: * readable object. */ sl@0: int bytesRead; /* number of bytes in the buffer */ sl@0: int offset; /* number of bytes read out of the buffer */ sl@0: char buffer[CONSOLE_BUFFER_SIZE]; sl@0: /* Data consumed by reader thread. */ sl@0: } ConsoleInfo; sl@0: sl@0: typedef struct ThreadSpecificData { sl@0: /* sl@0: * The following pointer refers to the head of the list of consoles sl@0: * that are being watched for file events. sl@0: */ sl@0: sl@0: ConsoleInfo *firstConsolePtr; sl@0: } ThreadSpecificData; sl@0: sl@0: static Tcl_ThreadDataKey dataKey; sl@0: sl@0: /* sl@0: * The following structure is what is added to the Tcl event queue when sl@0: * console events are generated. sl@0: */ sl@0: sl@0: typedef struct ConsoleEvent { sl@0: Tcl_Event header; /* Information that is standard for sl@0: * all events. */ sl@0: ConsoleInfo *infoPtr; /* Pointer to console info structure. Note sl@0: * that we still have to verify that the sl@0: * console exists before dereferencing this sl@0: * pointer. */ sl@0: } ConsoleEvent; sl@0: sl@0: /* sl@0: * Declarations for functions used only in this file. sl@0: */ sl@0: sl@0: static int ConsoleBlockModeProc(ClientData instanceData, int mode); sl@0: static void ConsoleCheckProc(ClientData clientData, int flags); sl@0: static int ConsoleCloseProc(ClientData instanceData, sl@0: Tcl_Interp *interp); sl@0: static int ConsoleEventProc(Tcl_Event *evPtr, int flags); sl@0: static void ConsoleExitHandler(ClientData clientData); sl@0: static int ConsoleGetHandleProc(ClientData instanceData, sl@0: int direction, ClientData *handlePtr); sl@0: static void ConsoleInit(void); sl@0: static int ConsoleInputProc(ClientData instanceData, char *buf, sl@0: int toRead, int *errorCode); sl@0: static int ConsoleOutputProc(ClientData instanceData, sl@0: CONST char *buf, int toWrite, int *errorCode); sl@0: static DWORD WINAPI ConsoleReaderThread(LPVOID arg); sl@0: static void ConsoleSetupProc(ClientData clientData, int flags); sl@0: static void ConsoleWatchProc(ClientData instanceData, int mask); sl@0: static DWORD WINAPI ConsoleWriterThread(LPVOID arg); sl@0: static void ProcExitHandler(ClientData clientData); sl@0: static int WaitForRead(ConsoleInfo *infoPtr, int blocking); sl@0: sl@0: static void ConsoleThreadActionProc _ANSI_ARGS_ (( sl@0: ClientData instanceData, int action)); sl@0: sl@0: /* sl@0: * This structure describes the channel type structure for command console sl@0: * based IO. sl@0: */ sl@0: sl@0: static Tcl_ChannelType consoleChannelType = { sl@0: "console", /* Type name. */ sl@0: TCL_CHANNEL_VERSION_4, /* v4 channel */ sl@0: ConsoleCloseProc, /* Close proc. */ sl@0: ConsoleInputProc, /* Input proc. */ sl@0: ConsoleOutputProc, /* Output proc. */ sl@0: NULL, /* Seek proc. */ sl@0: NULL, /* Set option proc. */ sl@0: NULL, /* Get option proc. */ sl@0: ConsoleWatchProc, /* Set up notifier to watch the channel. */ sl@0: ConsoleGetHandleProc, /* Get an OS handle from channel. */ sl@0: NULL, /* close2proc. */ sl@0: ConsoleBlockModeProc, /* Set blocking or non-blocking mode.*/ sl@0: NULL, /* flush proc. */ sl@0: NULL, /* handler proc. */ sl@0: NULL, /* wide seek proc */ sl@0: ConsoleThreadActionProc, /* thread action proc */ sl@0: }; sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * ConsoleInit -- sl@0: * sl@0: * This function initializes the static variables for this file. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * Creates a new event source. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static void sl@0: ConsoleInit() sl@0: { sl@0: ThreadSpecificData *tsdPtr; sl@0: sl@0: /* sl@0: * Check the initialized flag first, then check again in the mutex. sl@0: * This is a speed enhancement. sl@0: */ sl@0: sl@0: if (!initialized) { sl@0: Tcl_MutexLock(&consoleMutex); sl@0: if (!initialized) { sl@0: initialized = 1; sl@0: Tcl_CreateExitHandler(ProcExitHandler, NULL); sl@0: } sl@0: Tcl_MutexUnlock(&consoleMutex); sl@0: } sl@0: sl@0: tsdPtr = (ThreadSpecificData *)TclThreadDataKeyGet(&dataKey); sl@0: if (tsdPtr == NULL) { sl@0: tsdPtr = TCL_TSD_INIT(&dataKey); sl@0: tsdPtr->firstConsolePtr = NULL; sl@0: Tcl_CreateEventSource(ConsoleSetupProc, ConsoleCheckProc, NULL); sl@0: Tcl_CreateThreadExitHandler(ConsoleExitHandler, NULL); sl@0: } sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * ConsoleExitHandler -- sl@0: * sl@0: * This function is called to cleanup the console module before sl@0: * Tcl is unloaded. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * Removes the console event source. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static void sl@0: ConsoleExitHandler( sl@0: ClientData clientData) /* Old window proc */ sl@0: { sl@0: Tcl_DeleteEventSource(ConsoleSetupProc, ConsoleCheckProc, NULL); sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * ProcExitHandler -- sl@0: * sl@0: * This function is called to cleanup the process list before sl@0: * Tcl is unloaded. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * Resets the process list. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static void sl@0: ProcExitHandler( sl@0: ClientData clientData) /* Old window proc */ sl@0: { sl@0: Tcl_MutexLock(&consoleMutex); sl@0: initialized = 0; sl@0: Tcl_MutexUnlock(&consoleMutex); sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * ConsoleSetupProc -- sl@0: * sl@0: * This procedure is invoked before Tcl_DoOneEvent blocks waiting sl@0: * for an event. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * Adjusts the block time if needed. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: void sl@0: ConsoleSetupProc( sl@0: ClientData data, /* Not used. */ sl@0: int flags) /* Event flags as passed to Tcl_DoOneEvent. */ sl@0: { sl@0: ConsoleInfo *infoPtr; sl@0: Tcl_Time blockTime = { 0, 0 }; sl@0: int block = 1; sl@0: ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); sl@0: sl@0: if (!(flags & TCL_FILE_EVENTS)) { sl@0: return; sl@0: } sl@0: sl@0: /* sl@0: * Look to see if any events are already pending. If they are, poll. sl@0: */ sl@0: sl@0: for (infoPtr = tsdPtr->firstConsolePtr; infoPtr != NULL; sl@0: infoPtr = infoPtr->nextPtr) { sl@0: if (infoPtr->watchMask & TCL_WRITABLE) { sl@0: if (WaitForSingleObject(infoPtr->writable, 0) != WAIT_TIMEOUT) { sl@0: block = 0; sl@0: } sl@0: } sl@0: if (infoPtr->watchMask & TCL_READABLE) { sl@0: if (WaitForRead(infoPtr, 0) >= 0) { sl@0: block = 0; sl@0: } sl@0: } sl@0: } sl@0: if (!block) { sl@0: Tcl_SetMaxBlockTime(&blockTime); sl@0: } sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * ConsoleCheckProc -- sl@0: * sl@0: * This procedure is called by Tcl_DoOneEvent to check the console sl@0: * event source for events. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * May queue an event. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static void sl@0: ConsoleCheckProc( sl@0: ClientData data, /* Not used. */ sl@0: int flags) /* Event flags as passed to Tcl_DoOneEvent. */ sl@0: { sl@0: ConsoleInfo *infoPtr; sl@0: ConsoleEvent *evPtr; sl@0: int needEvent; sl@0: ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); sl@0: sl@0: if (!(flags & TCL_FILE_EVENTS)) { sl@0: return; sl@0: } sl@0: sl@0: /* sl@0: * Queue events for any ready consoles that don't already have events sl@0: * queued. sl@0: */ sl@0: sl@0: for (infoPtr = tsdPtr->firstConsolePtr; infoPtr != NULL; sl@0: infoPtr = infoPtr->nextPtr) { sl@0: if (infoPtr->flags & CONSOLE_PENDING) { sl@0: continue; sl@0: } sl@0: sl@0: /* sl@0: * Queue an event if the console is signaled for reading or writing. sl@0: */ sl@0: sl@0: needEvent = 0; sl@0: if (infoPtr->watchMask & TCL_WRITABLE) { sl@0: if (WaitForSingleObject(infoPtr->writable, 0) != WAIT_TIMEOUT) { sl@0: needEvent = 1; sl@0: } sl@0: } sl@0: sl@0: if (infoPtr->watchMask & TCL_READABLE) { sl@0: if (WaitForRead(infoPtr, 0) >= 0) { sl@0: needEvent = 1; sl@0: } sl@0: } sl@0: sl@0: if (needEvent) { sl@0: infoPtr->flags |= CONSOLE_PENDING; sl@0: evPtr = (ConsoleEvent *) ckalloc(sizeof(ConsoleEvent)); sl@0: evPtr->header.proc = ConsoleEventProc; sl@0: evPtr->infoPtr = infoPtr; sl@0: Tcl_QueueEvent((Tcl_Event *) evPtr, TCL_QUEUE_TAIL); sl@0: } sl@0: } sl@0: } sl@0: sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * ConsoleBlockModeProc -- sl@0: * sl@0: * Set blocking or non-blocking mode on channel. sl@0: * sl@0: * Results: sl@0: * 0 if successful, errno when failed. sl@0: * sl@0: * Side effects: sl@0: * Sets the device into blocking or non-blocking mode. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static int sl@0: ConsoleBlockModeProc( sl@0: ClientData instanceData, /* Instance data for channel. */ sl@0: int mode) /* TCL_MODE_BLOCKING or sl@0: * TCL_MODE_NONBLOCKING. */ sl@0: { sl@0: ConsoleInfo *infoPtr = (ConsoleInfo *) instanceData; sl@0: sl@0: /* sl@0: * Consoles on Windows can not be switched between blocking and nonblocking, sl@0: * hence we have to emulate the behavior. This is done in the input sl@0: * function by checking against a bit in the state. We set or unset the sl@0: * bit here to cause the input function to emulate the correct behavior. sl@0: */ sl@0: sl@0: if (mode == TCL_MODE_NONBLOCKING) { sl@0: infoPtr->flags |= CONSOLE_ASYNC; sl@0: } else { sl@0: infoPtr->flags &= ~(CONSOLE_ASYNC); sl@0: } sl@0: return 0; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * ConsoleCloseProc -- sl@0: * sl@0: * Closes a console based IO channel. sl@0: * sl@0: * Results: sl@0: * 0 on success, errno otherwise. sl@0: * sl@0: * Side effects: sl@0: * Closes the physical channel. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static int sl@0: ConsoleCloseProc( sl@0: ClientData instanceData, /* Pointer to ConsoleInfo structure. */ sl@0: Tcl_Interp *interp) /* For error reporting. */ sl@0: { sl@0: ConsoleInfo *consolePtr = (ConsoleInfo *) instanceData; sl@0: int errorCode; sl@0: ConsoleInfo *infoPtr, **nextPtrPtr; sl@0: ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); sl@0: DWORD exitCode; sl@0: sl@0: errorCode = 0; sl@0: sl@0: /* sl@0: * Clean up the background thread if necessary. Note that this sl@0: * must be done before we can close the file, since the sl@0: * thread may be blocking trying to read from the console. sl@0: */ sl@0: sl@0: if (consolePtr->readThread) { sl@0: sl@0: /* sl@0: * The thread may already have closed on it's own. Check it's sl@0: * exit code. sl@0: */ sl@0: sl@0: GetExitCodeThread(consolePtr->readThread, &exitCode); sl@0: sl@0: if (exitCode == STILL_ACTIVE) { sl@0: sl@0: /* sl@0: * Set the stop event so that if the reader thread is blocked sl@0: * in ConsoleReaderThread on WaitForMultipleEvents, it will exit sl@0: * cleanly. sl@0: */ sl@0: sl@0: SetEvent(consolePtr->stopReader); sl@0: sl@0: /* sl@0: * Wait at most 20 milliseconds for the reader thread to close. sl@0: */ sl@0: sl@0: if (WaitForSingleObject(consolePtr->readThread, 20) sl@0: == WAIT_TIMEOUT) { sl@0: /* sl@0: * Forcibly terminate the background thread as a last sl@0: * resort. Note that we need to guard against sl@0: * terminating the thread while it is in the middle of sl@0: * Tcl_ThreadAlert because it won't be able to release sl@0: * the notifier lock. sl@0: */ sl@0: sl@0: Tcl_MutexLock(&consoleMutex); sl@0: sl@0: /* BUG: this leaks memory. */ sl@0: TerminateThread(consolePtr->readThread, 0); sl@0: Tcl_MutexUnlock(&consoleMutex); sl@0: } sl@0: } sl@0: sl@0: CloseHandle(consolePtr->readThread); sl@0: CloseHandle(consolePtr->readable); sl@0: CloseHandle(consolePtr->startReader); sl@0: CloseHandle(consolePtr->stopReader); sl@0: consolePtr->readThread = NULL; sl@0: } sl@0: consolePtr->validMask &= ~TCL_READABLE; sl@0: sl@0: /* sl@0: * Wait for the writer thread to finish the current buffer, then sl@0: * terminate the thread and close the handles. If the channel is sl@0: * nonblocking, there should be no pending write operations. sl@0: */ sl@0: sl@0: if (consolePtr->writeThread) { sl@0: if (consolePtr->toWrite) { sl@0: /* sl@0: * We only need to wait if there is something to write. sl@0: * This may prevent infinite wait on exit. [python bug 216289] sl@0: */ sl@0: WaitForSingleObject(consolePtr->writable, INFINITE); sl@0: } sl@0: sl@0: /* sl@0: * The thread may already have closed on it's own. Check it's sl@0: * exit code. sl@0: */ sl@0: sl@0: GetExitCodeThread(consolePtr->writeThread, &exitCode); sl@0: sl@0: if (exitCode == STILL_ACTIVE) { sl@0: /* sl@0: * Set the stop event so that if the reader thread is blocked sl@0: * in ConsoleWriterThread on WaitForMultipleEvents, it will sl@0: * exit cleanly. sl@0: */ sl@0: sl@0: SetEvent(consolePtr->stopWriter); sl@0: sl@0: /* sl@0: * Wait at most 20 milliseconds for the writer thread to close. sl@0: */ sl@0: sl@0: if (WaitForSingleObject(consolePtr->writeThread, 20) sl@0: == WAIT_TIMEOUT) { sl@0: /* sl@0: * Forcibly terminate the background thread as a last sl@0: * resort. Note that we need to guard against sl@0: * terminating the thread while it is in the middle of sl@0: * Tcl_ThreadAlert because it won't be able to release sl@0: * the notifier lock. sl@0: */ sl@0: sl@0: Tcl_MutexLock(&consoleMutex); sl@0: sl@0: /* BUG: this leaks memory. */ sl@0: TerminateThread(consolePtr->writeThread, 0); sl@0: Tcl_MutexUnlock(&consoleMutex); sl@0: } sl@0: } sl@0: sl@0: CloseHandle(consolePtr->writeThread); sl@0: CloseHandle(consolePtr->writable); sl@0: CloseHandle(consolePtr->startWriter); sl@0: CloseHandle(consolePtr->stopWriter); sl@0: consolePtr->writeThread = NULL; sl@0: } sl@0: consolePtr->validMask &= ~TCL_WRITABLE; sl@0: sl@0: sl@0: /* sl@0: * Don't close the Win32 handle if the handle is a standard channel sl@0: * during the thread exit process. Otherwise, one thread may kill sl@0: * the stdio of another. sl@0: */ sl@0: sl@0: if (!TclInThreadExit() sl@0: || ((GetStdHandle(STD_INPUT_HANDLE) != consolePtr->handle) sl@0: && (GetStdHandle(STD_OUTPUT_HANDLE) != consolePtr->handle) sl@0: && (GetStdHandle(STD_ERROR_HANDLE) != consolePtr->handle))) { sl@0: if (CloseHandle(consolePtr->handle) == FALSE) { sl@0: TclWinConvertError(GetLastError()); sl@0: errorCode = errno; sl@0: } sl@0: } sl@0: sl@0: consolePtr->watchMask &= consolePtr->validMask; sl@0: sl@0: /* sl@0: * Remove the file from the list of watched files. sl@0: */ sl@0: sl@0: for (nextPtrPtr = &(tsdPtr->firstConsolePtr), infoPtr = *nextPtrPtr; sl@0: infoPtr != NULL; sl@0: nextPtrPtr = &infoPtr->nextPtr, infoPtr = *nextPtrPtr) { sl@0: if (infoPtr == (ConsoleInfo *)consolePtr) { sl@0: *nextPtrPtr = infoPtr->nextPtr; sl@0: break; sl@0: } sl@0: } sl@0: if (consolePtr->writeBuf != NULL) { sl@0: ckfree(consolePtr->writeBuf); sl@0: consolePtr->writeBuf = 0; sl@0: } sl@0: ckfree((char*) consolePtr); sl@0: sl@0: return errorCode; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * ConsoleInputProc -- sl@0: * sl@0: * Reads input from the IO channel into the buffer given. Returns sl@0: * count of how many bytes were actually read, and an error indication. sl@0: * sl@0: * Results: sl@0: * A count of how many bytes were read is returned and an error sl@0: * indication is returned in an output argument. sl@0: * sl@0: * Side effects: sl@0: * Reads input from the actual channel. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static int sl@0: ConsoleInputProc( sl@0: ClientData instanceData, /* Console state. */ sl@0: char *buf, /* Where to store data read. */ sl@0: int bufSize, /* How much space is available sl@0: * in the buffer? */ sl@0: int *errorCode) /* Where to store error code. */ sl@0: { sl@0: ConsoleInfo *infoPtr = (ConsoleInfo *) instanceData; sl@0: DWORD count, bytesRead = 0; sl@0: int result; sl@0: sl@0: *errorCode = 0; sl@0: sl@0: /* sl@0: * Synchronize with the reader thread. sl@0: */ sl@0: sl@0: result = WaitForRead(infoPtr, (infoPtr->flags & CONSOLE_ASYNC) ? 0 : 1); sl@0: sl@0: /* sl@0: * If an error occurred, return immediately. sl@0: */ sl@0: sl@0: if (result == -1) { sl@0: *errorCode = errno; sl@0: return -1; sl@0: } sl@0: sl@0: if (infoPtr->readFlags & CONSOLE_BUFFERED) { sl@0: /* sl@0: * Data is stored in the buffer. sl@0: */ sl@0: sl@0: if (bufSize < (infoPtr->bytesRead - infoPtr->offset)) { sl@0: memcpy(buf, &infoPtr->buffer[infoPtr->offset], (size_t) bufSize); sl@0: bytesRead = bufSize; sl@0: infoPtr->offset += bufSize; sl@0: } else { sl@0: memcpy(buf, &infoPtr->buffer[infoPtr->offset], (size_t) bufSize); sl@0: bytesRead = infoPtr->bytesRead - infoPtr->offset; sl@0: sl@0: /* sl@0: * Reset the buffer sl@0: */ sl@0: sl@0: infoPtr->readFlags &= ~CONSOLE_BUFFERED; sl@0: infoPtr->offset = 0; sl@0: } sl@0: sl@0: return bytesRead; sl@0: } sl@0: sl@0: /* sl@0: * Attempt to read bufSize bytes. The read will return immediately sl@0: * if there is any data available. Otherwise it will block until sl@0: * at least one byte is available or an EOF occurs. sl@0: */ sl@0: sl@0: if (ReadConsole(infoPtr->handle, (LPVOID) buf, (DWORD) bufSize, &count, sl@0: (LPOVERLAPPED) NULL) == TRUE) { sl@0: buf[count] = '\0'; sl@0: return count; sl@0: } sl@0: sl@0: return -1; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * ConsoleOutputProc -- sl@0: * sl@0: * Writes the given output on the IO channel. Returns count of how sl@0: * many characters were actually written, and an error indication. sl@0: * sl@0: * Results: sl@0: * A count of how many characters were written is returned and an sl@0: * error indication is returned in an output argument. sl@0: * sl@0: * Side effects: sl@0: * Writes output on the actual channel. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static int sl@0: ConsoleOutputProc( sl@0: ClientData instanceData, /* Console state. */ sl@0: CONST char *buf, /* The data buffer. */ sl@0: int toWrite, /* How many bytes to write? */ sl@0: int *errorCode) /* Where to store error code. */ sl@0: { sl@0: ConsoleInfo *infoPtr = (ConsoleInfo *) instanceData; sl@0: DWORD bytesWritten, timeout; sl@0: sl@0: *errorCode = 0; sl@0: timeout = (infoPtr->flags & CONSOLE_ASYNC) ? 0 : INFINITE; sl@0: if (WaitForSingleObject(infoPtr->writable, timeout) == WAIT_TIMEOUT) { sl@0: /* sl@0: * The writer thread is blocked waiting for a write to complete sl@0: * and the channel is in non-blocking mode. sl@0: */ sl@0: sl@0: errno = EAGAIN; sl@0: goto error; sl@0: } sl@0: sl@0: /* sl@0: * Check for a background error on the last write. sl@0: */ sl@0: sl@0: if (infoPtr->writeError) { sl@0: TclWinConvertError(infoPtr->writeError); sl@0: infoPtr->writeError = 0; sl@0: goto error; sl@0: } sl@0: sl@0: if (infoPtr->flags & CONSOLE_ASYNC) { sl@0: /* sl@0: * The console is non-blocking, so copy the data into the output sl@0: * buffer and restart the writer thread. sl@0: */ sl@0: sl@0: if (toWrite > infoPtr->writeBufLen) { sl@0: /* sl@0: * Reallocate the buffer to be large enough to hold the data. sl@0: */ sl@0: sl@0: if (infoPtr->writeBuf) { sl@0: ckfree(infoPtr->writeBuf); sl@0: } sl@0: infoPtr->writeBufLen = toWrite; sl@0: infoPtr->writeBuf = ckalloc((unsigned int) toWrite); sl@0: } sl@0: memcpy(infoPtr->writeBuf, buf, (size_t) toWrite); sl@0: infoPtr->toWrite = toWrite; sl@0: ResetEvent(infoPtr->writable); sl@0: SetEvent(infoPtr->startWriter); sl@0: bytesWritten = toWrite; sl@0: } else { sl@0: /* sl@0: * In the blocking case, just try to write the buffer directly. sl@0: * This avoids an unnecessary copy. sl@0: */ sl@0: sl@0: if (WriteFile(infoPtr->handle, (LPVOID) buf, (DWORD) toWrite, sl@0: &bytesWritten, (LPOVERLAPPED) NULL) == FALSE) { sl@0: TclWinConvertError(GetLastError()); sl@0: goto error; sl@0: } sl@0: } sl@0: return bytesWritten; sl@0: sl@0: error: sl@0: *errorCode = errno; sl@0: return -1; sl@0: sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * ConsoleEventProc -- sl@0: * sl@0: * This function is invoked by Tcl_ServiceEvent when a file event sl@0: * reaches the front of the event queue. This procedure invokes sl@0: * Tcl_NotifyChannel on the console. sl@0: * sl@0: * Results: sl@0: * Returns 1 if the event was handled, meaning it should be removed sl@0: * from the queue. Returns 0 if the event was not handled, meaning sl@0: * it should stay on the queue. The only time the event isn't sl@0: * handled is if the TCL_FILE_EVENTS flag bit isn't set. sl@0: * sl@0: * Side effects: sl@0: * Whatever the notifier callback does. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static int sl@0: ConsoleEventProc( sl@0: Tcl_Event *evPtr, /* Event to service. */ sl@0: int flags) /* Flags that indicate what events to sl@0: * handle, such as TCL_FILE_EVENTS. */ sl@0: { sl@0: ConsoleEvent *consoleEvPtr = (ConsoleEvent *)evPtr; sl@0: ConsoleInfo *infoPtr; sl@0: int mask; sl@0: ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); sl@0: sl@0: if (!(flags & TCL_FILE_EVENTS)) { sl@0: return 0; sl@0: } sl@0: sl@0: /* sl@0: * Search through the list of watched consoles for the one whose handle sl@0: * matches the event. We do this rather than simply dereferencing sl@0: * the handle in the event so that consoles can be deleted while the sl@0: * event is in the queue. sl@0: */ sl@0: sl@0: for (infoPtr = tsdPtr->firstConsolePtr; infoPtr != NULL; sl@0: infoPtr = infoPtr->nextPtr) { sl@0: if (consoleEvPtr->infoPtr == infoPtr) { sl@0: infoPtr->flags &= ~(CONSOLE_PENDING); sl@0: break; sl@0: } sl@0: } sl@0: sl@0: /* sl@0: * Remove stale events. sl@0: */ sl@0: sl@0: if (!infoPtr) { sl@0: return 1; sl@0: } sl@0: sl@0: /* sl@0: * Check to see if the console is readable. Note sl@0: * that we can't tell if a console is writable, so we always report it sl@0: * as being writable unless we have detected EOF. sl@0: */ sl@0: sl@0: mask = 0; sl@0: if (infoPtr->watchMask & TCL_WRITABLE) { sl@0: if (WaitForSingleObject(infoPtr->writable, 0) != WAIT_TIMEOUT) { sl@0: mask = TCL_WRITABLE; sl@0: } sl@0: } sl@0: sl@0: if (infoPtr->watchMask & TCL_READABLE) { sl@0: if (WaitForRead(infoPtr, 0) >= 0) { sl@0: if (infoPtr->readFlags & CONSOLE_EOF) { sl@0: mask = TCL_READABLE; sl@0: } else { sl@0: mask |= TCL_READABLE; sl@0: } sl@0: } sl@0: } sl@0: sl@0: /* sl@0: * Inform the channel of the events. sl@0: */ sl@0: sl@0: Tcl_NotifyChannel(infoPtr->channel, infoPtr->watchMask & mask); sl@0: return 1; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * ConsoleWatchProc -- sl@0: * sl@0: * Called by the notifier to set up to watch for events on this sl@0: * channel. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * None. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static void sl@0: ConsoleWatchProc( sl@0: ClientData instanceData, /* Console state. */ sl@0: int mask) /* What events to watch for, OR-ed sl@0: * combination of TCL_READABLE, sl@0: * TCL_WRITABLE and TCL_EXCEPTION. */ sl@0: { sl@0: ConsoleInfo **nextPtrPtr, *ptr; sl@0: ConsoleInfo *infoPtr = (ConsoleInfo *) instanceData; sl@0: int oldMask = infoPtr->watchMask; sl@0: ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); sl@0: sl@0: /* sl@0: * Since most of the work is handled by the background threads, sl@0: * we just need to update the watchMask and then force the notifier sl@0: * to poll once. sl@0: */ sl@0: sl@0: infoPtr->watchMask = mask & infoPtr->validMask; sl@0: if (infoPtr->watchMask) { sl@0: Tcl_Time blockTime = { 0, 0 }; sl@0: if (!oldMask) { sl@0: infoPtr->nextPtr = tsdPtr->firstConsolePtr; sl@0: tsdPtr->firstConsolePtr = infoPtr; sl@0: } sl@0: Tcl_SetMaxBlockTime(&blockTime); sl@0: } else { sl@0: if (oldMask) { sl@0: /* sl@0: * Remove the console from the list of watched consoles. sl@0: */ sl@0: sl@0: for (nextPtrPtr = &(tsdPtr->firstConsolePtr), ptr = *nextPtrPtr; sl@0: ptr != NULL; sl@0: nextPtrPtr = &ptr->nextPtr, ptr = *nextPtrPtr) { sl@0: if (infoPtr == ptr) { sl@0: *nextPtrPtr = ptr->nextPtr; sl@0: break; sl@0: } sl@0: } sl@0: } sl@0: } sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * ConsoleGetHandleProc -- sl@0: * sl@0: * Called from Tcl_GetChannelHandle to retrieve OS handles from sl@0: * inside a command consoleline based channel. sl@0: * sl@0: * Results: sl@0: * Returns TCL_OK with the fd in handlePtr, or TCL_ERROR if sl@0: * there is no handle for the specified direction. sl@0: * sl@0: * Side effects: sl@0: * None. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static int sl@0: ConsoleGetHandleProc( sl@0: ClientData instanceData, /* The console state. */ sl@0: int direction, /* TCL_READABLE or TCL_WRITABLE */ sl@0: ClientData *handlePtr) /* Where to store the handle. */ sl@0: { sl@0: ConsoleInfo *infoPtr = (ConsoleInfo *) instanceData; sl@0: sl@0: *handlePtr = (ClientData) infoPtr->handle; sl@0: return TCL_OK; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * WaitForRead -- sl@0: * sl@0: * Wait until some data is available, the console is at sl@0: * EOF or the reader thread is blocked waiting for data (if the sl@0: * channel is in non-blocking mode). sl@0: * sl@0: * Results: sl@0: * Returns 1 if console is readable. Returns 0 if there is no data sl@0: * on the console, but there is buffered data. Returns -1 if an sl@0: * error occurred. If an error occurred, the threads may not sl@0: * be synchronized. sl@0: * sl@0: * Side effects: sl@0: * Updates the shared state flags. If no error occurred, sl@0: * the reader thread is blocked waiting for a signal from the sl@0: * main thread. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static int sl@0: WaitForRead( sl@0: ConsoleInfo *infoPtr, /* Console state. */ sl@0: int blocking) /* Indicates whether call should be sl@0: * blocking or not. */ sl@0: { sl@0: DWORD timeout, count; sl@0: HANDLE *handle = infoPtr->handle; sl@0: INPUT_RECORD input; sl@0: sl@0: while (1) { sl@0: /* sl@0: * Synchronize with the reader thread. sl@0: */ sl@0: sl@0: timeout = blocking ? INFINITE : 0; sl@0: if (WaitForSingleObject(infoPtr->readable, timeout) == WAIT_TIMEOUT) { sl@0: /* sl@0: * The reader thread is blocked waiting for data and the channel sl@0: * is in non-blocking mode. sl@0: */ sl@0: errno = EAGAIN; sl@0: return -1; sl@0: } sl@0: sl@0: /* sl@0: * At this point, the two threads are synchronized, so it is safe sl@0: * to access shared state. sl@0: */ sl@0: sl@0: /* sl@0: * If the console has hit EOF, it is always readable. sl@0: */ sl@0: sl@0: if (infoPtr->readFlags & CONSOLE_EOF) { sl@0: return 1; sl@0: } sl@0: sl@0: if (PeekConsoleInput(handle, &input, 1, &count) == FALSE) { sl@0: /* sl@0: * Check to see if the peek failed because of EOF. sl@0: */ sl@0: sl@0: TclWinConvertError(GetLastError()); sl@0: sl@0: if (errno == EOF) { sl@0: infoPtr->readFlags |= CONSOLE_EOF; sl@0: return 1; sl@0: } sl@0: sl@0: /* sl@0: * Ignore errors if there is data in the buffer. sl@0: */ sl@0: sl@0: if (infoPtr->readFlags & CONSOLE_BUFFERED) { sl@0: return 0; sl@0: } else { sl@0: return -1; sl@0: } sl@0: } sl@0: sl@0: /* sl@0: * If there is data in the buffer, the console must be sl@0: * readable (since it is a line-oriented device). sl@0: */ sl@0: sl@0: if (infoPtr->readFlags & CONSOLE_BUFFERED) { sl@0: return 1; sl@0: } sl@0: sl@0: sl@0: /* sl@0: * There wasn't any data available, so reset the thread and sl@0: * try again. sl@0: */ sl@0: sl@0: ResetEvent(infoPtr->readable); sl@0: SetEvent(infoPtr->startReader); sl@0: } sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * ConsoleReaderThread -- sl@0: * sl@0: * This function runs in a separate thread and waits for input sl@0: * to become available on a console. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * Signals the main thread when input become available. May sl@0: * cause the main thread to wake up by posting a message. May sl@0: * one line from the console for each wait operation. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static DWORD WINAPI sl@0: ConsoleReaderThread(LPVOID arg) sl@0: { sl@0: ConsoleInfo *infoPtr = (ConsoleInfo *)arg; sl@0: HANDLE *handle = infoPtr->handle; sl@0: DWORD count, waitResult; sl@0: HANDLE wEvents[2]; sl@0: sl@0: /* The first event takes precedence. */ sl@0: wEvents[0] = infoPtr->stopReader; sl@0: wEvents[1] = infoPtr->startReader; sl@0: sl@0: for (;;) { sl@0: /* sl@0: * Wait for the main thread to signal before attempting to wait. sl@0: */ sl@0: sl@0: waitResult = WaitForMultipleObjects(2, wEvents, FALSE, INFINITE); sl@0: sl@0: if (waitResult != (WAIT_OBJECT_0 + 1)) { sl@0: /* sl@0: * The start event was not signaled. It must be the stop event sl@0: * or an error, so exit this thread. sl@0: */ sl@0: sl@0: break; sl@0: } sl@0: sl@0: count = 0; sl@0: sl@0: /* sl@0: * Look for data on the console, but first ignore any events sl@0: * that are not KEY_EVENTs sl@0: */ sl@0: if (ReadConsoleA(handle, infoPtr->buffer, CONSOLE_BUFFER_SIZE, sl@0: (LPDWORD) &infoPtr->bytesRead, NULL) != FALSE) { sl@0: /* sl@0: * Data was stored in the buffer. sl@0: */ sl@0: sl@0: infoPtr->readFlags |= CONSOLE_BUFFERED; sl@0: } else { sl@0: DWORD err; sl@0: err = GetLastError(); sl@0: sl@0: if (err == EOF) { sl@0: infoPtr->readFlags = CONSOLE_EOF; sl@0: } sl@0: } sl@0: sl@0: /* sl@0: * Signal the main thread by signalling the readable event and sl@0: * then waking up the notifier thread. sl@0: */ sl@0: sl@0: SetEvent(infoPtr->readable); sl@0: sl@0: /* sl@0: * Alert the foreground thread. Note that we need to treat this like sl@0: * a critical section so the foreground thread does not terminate sl@0: * this thread while we are holding a mutex in the notifier code. sl@0: */ sl@0: sl@0: Tcl_MutexLock(&consoleMutex); sl@0: if (infoPtr->threadId != NULL) { sl@0: /* TIP #218. When in flight ignore the event, no one will receive it anyway */ sl@0: Tcl_ThreadAlert(infoPtr->threadId); sl@0: } sl@0: Tcl_MutexUnlock(&consoleMutex); sl@0: } sl@0: sl@0: return 0; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * ConsoleWriterThread -- sl@0: * sl@0: * This function runs in a separate thread and writes data sl@0: * onto a console. sl@0: * sl@0: * Results: sl@0: * Always returns 0. sl@0: * sl@0: * Side effects: sl@0: * Signals the main thread when an output operation is completed. sl@0: * May cause the main thread to wake up by posting a message. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static DWORD WINAPI sl@0: ConsoleWriterThread(LPVOID arg) sl@0: { sl@0: sl@0: ConsoleInfo *infoPtr = (ConsoleInfo *)arg; sl@0: HANDLE *handle = infoPtr->handle; sl@0: DWORD count, toWrite, waitResult; sl@0: char *buf; sl@0: HANDLE wEvents[2]; sl@0: sl@0: /* The first event takes precedence. */ sl@0: wEvents[0] = infoPtr->stopWriter; sl@0: wEvents[1] = infoPtr->startWriter; sl@0: sl@0: for (;;) { sl@0: /* sl@0: * Wait for the main thread to signal before attempting to write. sl@0: */ sl@0: sl@0: waitResult = WaitForMultipleObjects(2, wEvents, FALSE, INFINITE); sl@0: sl@0: if (waitResult != (WAIT_OBJECT_0 + 1)) { sl@0: /* sl@0: * The start event was not signaled. It must be the stop event sl@0: * or an error, so exit this thread. sl@0: */ sl@0: sl@0: break; sl@0: } sl@0: sl@0: buf = infoPtr->writeBuf; sl@0: toWrite = infoPtr->toWrite; sl@0: sl@0: /* sl@0: * Loop until all of the bytes are written or an error occurs. sl@0: */ sl@0: sl@0: while (toWrite > 0) { sl@0: if (WriteConsoleA(handle, buf, toWrite, &count, NULL) == FALSE) { sl@0: infoPtr->writeError = GetLastError(); sl@0: break; sl@0: } else { sl@0: toWrite -= count; sl@0: buf += count; sl@0: } sl@0: } sl@0: sl@0: /* sl@0: * Signal the main thread by signalling the writable event and sl@0: * then waking up the notifier thread. sl@0: */ sl@0: sl@0: SetEvent(infoPtr->writable); sl@0: sl@0: /* sl@0: * Alert the foreground thread. Note that we need to treat this like sl@0: * a critical section so the foreground thread does not terminate sl@0: * this thread while we are holding a mutex in the notifier code. sl@0: */ sl@0: sl@0: Tcl_MutexLock(&consoleMutex); sl@0: if (infoPtr->threadId != NULL) { sl@0: /* TIP #218. When in flight ignore the event, no one will receive it anyway */ sl@0: Tcl_ThreadAlert(infoPtr->threadId); sl@0: } sl@0: Tcl_MutexUnlock(&consoleMutex); sl@0: } sl@0: sl@0: return 0; sl@0: } sl@0: sl@0: sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * TclWinOpenConsoleChannel -- sl@0: * sl@0: * Constructs a Console channel for the specified standard OS handle. sl@0: * This is a helper function to break up the construction of sl@0: * channels into File, Console, or Serial. sl@0: * sl@0: * Results: sl@0: * Returns the new channel, or NULL. sl@0: * sl@0: * Side effects: sl@0: * May open the channel sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: Tcl_Channel sl@0: TclWinOpenConsoleChannel(handle, channelName, permissions) sl@0: HANDLE handle; sl@0: char *channelName; sl@0: int permissions; sl@0: { sl@0: char encoding[4 + TCL_INTEGER_SPACE]; sl@0: ConsoleInfo *infoPtr; sl@0: DWORD id, modes; sl@0: sl@0: ConsoleInit(); sl@0: sl@0: /* sl@0: * See if a channel with this handle already exists. sl@0: */ sl@0: sl@0: infoPtr = (ConsoleInfo *) ckalloc((unsigned) sizeof(ConsoleInfo)); sl@0: memset(infoPtr, 0, sizeof(ConsoleInfo)); sl@0: sl@0: infoPtr->validMask = permissions; sl@0: infoPtr->handle = handle; sl@0: infoPtr->channel = (Tcl_Channel) NULL; sl@0: sl@0: wsprintfA(encoding, "cp%d", GetConsoleCP()); sl@0: sl@0: infoPtr->threadId = Tcl_GetCurrentThread(); sl@0: sl@0: /* sl@0: * Use the pointer for the name of the result channel. sl@0: * This keeps the channel names unique, since some may share sl@0: * handles (stdin/stdout/stderr for instance). sl@0: */ sl@0: sl@0: wsprintfA(channelName, "file%lx", (int) infoPtr); sl@0: sl@0: infoPtr->channel = Tcl_CreateChannel(&consoleChannelType, channelName, sl@0: (ClientData) infoPtr, permissions); sl@0: sl@0: if (permissions & TCL_READABLE) { sl@0: /* sl@0: * Make sure the console input buffer is ready for only character sl@0: * input notifications and the buffer is set for line buffering. sl@0: * IOW, we only want to catch when complete lines are ready for sl@0: * reading. sl@0: */ sl@0: GetConsoleMode(infoPtr->handle, &modes); sl@0: modes &= ~(ENABLE_WINDOW_INPUT | ENABLE_MOUSE_INPUT); sl@0: modes |= ENABLE_LINE_INPUT; sl@0: SetConsoleMode(infoPtr->handle, modes); sl@0: sl@0: infoPtr->readable = CreateEvent(NULL, TRUE, TRUE, NULL); sl@0: infoPtr->startReader = CreateEvent(NULL, FALSE, FALSE, NULL); sl@0: infoPtr->stopReader = CreateEvent(NULL, FALSE, FALSE, NULL); sl@0: infoPtr->readThread = CreateThread(NULL, 256, ConsoleReaderThread, sl@0: infoPtr, 0, &id); sl@0: SetThreadPriority(infoPtr->readThread, THREAD_PRIORITY_HIGHEST); sl@0: } sl@0: sl@0: if (permissions & TCL_WRITABLE) { sl@0: infoPtr->writable = CreateEvent(NULL, TRUE, TRUE, NULL); sl@0: infoPtr->startWriter = CreateEvent(NULL, FALSE, FALSE, NULL); sl@0: infoPtr->stopWriter = CreateEvent(NULL, FALSE, FALSE, NULL); sl@0: infoPtr->writeThread = CreateThread(NULL, 256, ConsoleWriterThread, sl@0: infoPtr, 0, &id); sl@0: SetThreadPriority(infoPtr->writeThread, THREAD_PRIORITY_HIGHEST); sl@0: } sl@0: sl@0: /* sl@0: * Files have default translation of AUTO and ^Z eof char, which sl@0: * means that a ^Z will be accepted as EOF when reading. sl@0: */ sl@0: sl@0: Tcl_SetChannelOption(NULL, infoPtr->channel, "-translation", "auto"); sl@0: Tcl_SetChannelOption(NULL, infoPtr->channel, "-eofchar", "\032 {}"); sl@0: Tcl_SetChannelOption(NULL, infoPtr->channel, "-encoding", encoding); sl@0: sl@0: return infoPtr->channel; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * ConsoleThreadActionProc -- sl@0: * sl@0: * Insert or remove any thread local refs to this channel. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * Changes thread local list of valid channels. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static void sl@0: ConsoleThreadActionProc (instanceData, action) sl@0: ClientData instanceData; sl@0: int action; sl@0: { sl@0: ConsoleInfo *infoPtr = (ConsoleInfo *) instanceData; sl@0: sl@0: /* We do not access firstConsolePtr in the thread structures. This is sl@0: * not for all serials managed by the thread, but only those we are sl@0: * watching. Removal of the filevent handlers before transfer thus sl@0: * takes care of this structure. sl@0: */ sl@0: sl@0: Tcl_MutexLock(&consoleMutex); sl@0: if (action == TCL_CHANNEL_THREAD_INSERT) { sl@0: /* We can't copy the thread information from the channel when sl@0: * the channel is created. At this time the channel back sl@0: * pointer has not been set yet. However in that case the sl@0: * threadId has already been set by TclpCreateCommandChannel sl@0: * itself, so the structure is still good. sl@0: */ sl@0: sl@0: ConsoleInit (); sl@0: if (infoPtr->channel != NULL) { sl@0: infoPtr->threadId = Tcl_GetChannelThread (infoPtr->channel); sl@0: } sl@0: } else { sl@0: infoPtr->threadId = NULL; sl@0: } sl@0: Tcl_MutexUnlock(&consoleMutex); sl@0: }