|
|
/*++
Copyright (c) 1993 Microsoft Corporation
Module Name:
spterm.c
Abstract:
Text setup support for terminals
Author:
Sean Selitrennikoff (v-seans) 25-May-1999
Revision History:
--*/
#include "spprecmp.h"
#include "ntddser.h"
#pragma hdrstop
#include <hdlsblk.h>
#include <hdlsterm.h>
#define MS_DSRCTSCD 0xB0 // Status bits for DSR, CTS and CD
BOOLEAN HeadlessTerminalConnected = FALSE; UCHAR Utf8ConversionBuffer[80*3+1]; PUCHAR TerminalBuffer = Utf8ConversionBuffer; WCHAR UnicodeScratchBuffer[80+1];
//
// Use these variables to decode incoming UTF8
// data streams.
//
WCHAR IncomingUnicodeValue; UCHAR IncomingUtf8ConversionBuffer[3];
//
// Determine if we will do UTF8 encoding before we send the string off
// to the headless terminal.
//
BOOLEAN SpTermDoUtf8 = FALSE;
//
// Reverse lookup table for the odd-ball unicode line drawing
// characters they use for JPN builds. If we detect one of these
// we need to convert it into real unicode before we UTF8
// encode it.
//
typedef struct _UNICODE_CROSS_REFERENCE {
//
// Whacky far east unicode value. For example, we
// might get 0x0006 for a double-horizontal line, which
// is just nonsense.
//
WCHAR FECode;
//
// The corresponding real unicode value.
//
WCHAR Unicode; } UNICODE_CROSS_REFERENCE;
UNICODE_CROSS_REFERENCE FEUnicodeToRealUnicodeValue[LineCharMax] = {
0x0001, 0x2554, // DoubleUpperLeft
0x0002, 0x2557, // DoubleUpperRight
0x0003, 0x255a, // DoubleLowerLeft
0x0004, 0x255d, // DoubleLowerRight
0x0006, 0x2550, // DoubleHorizontal
0x0005, 0x2551, // DoubleVertical
0x0001, 0x250c, // SingleUpperLeft
0x0002, 0x2510, // SingleUpperRight
0x0003, 0x2514, // SingleLowerLeft
0x0004, 0x2518, // SingleLowerRight
0x0006, 0x2500, // SingleHorizontal
0x0005, 0x2502, // SingleVertical
0x0019, 0x255f, // DoubleVerticalToSingleHorizontalRight,
0x0017, 0x2562 // DoubleVerticalToSingleHorizontalLeft
};
BOOLEAN SpTranslateUnicodeToUtf8( PCWSTR SourceBuffer, UCHAR *DestinationBuffer ) /*++
Routine Description:
translates a unicode buffer into a UTF8 version.
Arguments:
SourceBuffer - unicode buffer to be translated. DestinationBuffer - receives UTF8 version of same buffer.
Return Value:
TRUE - We successfully translated the Unicode value into its corresponding UTF8 encoding.
FALSE - The translation failed.
--*/
{ ULONG Count = 0; ULONG i = 0; WCHAR CurrentChar = 0; //
// convert into UTF8 for actual transmission
//
// UTF-8 encodes 2-byte Unicode characters as follows:
// If the first nine bits are zero (00000000 0xxxxxxx), encode it as one byte 0xxxxxxx
// If the first five bits are zero (00000yyy yyxxxxxx), encode it as two bytes 110yyyyy 10xxxxxx
// Otherwise (zzzzyyyy yyxxxxxx), encode it as three bytes 1110zzzz 10yyyyyy 10xxxxxx
//
DestinationBuffer[Count] = (UCHAR)'\0'; while (*SourceBuffer) {
CurrentChar = *SourceBuffer;
if( CurrentChar < 0x0020 ) { //
// See if we need to convert this FarEast whacky unicode value into a real
// unicode encoding.
//
for (i = 0; i < LineCharMax; i++) { if( FEUnicodeToRealUnicodeValue[i].FECode == CurrentChar ) { CurrentChar = FEUnicodeToRealUnicodeValue[i].Unicode; break; } } }
if( (CurrentChar & 0xFF80) == 0 ) { //
// if the top 9 bits are zero, then just
// encode as 1 byte. (ASCII passes through unchanged).
//
DestinationBuffer[Count++] = (UCHAR)(CurrentChar & 0x7F); } else if( (CurrentChar & 0xF800) == 0 ) { //
// if the top 5 bits are zero, then encode as 2 bytes
//
DestinationBuffer[Count++] = (UCHAR)((CurrentChar >> 6) & 0x1F) | 0xC0; DestinationBuffer[Count++] = (UCHAR)(CurrentChar & 0xBF) | 0x80; } else { //
// encode as 3 bytes
//
DestinationBuffer[Count++] = (UCHAR)((CurrentChar >> 12) & 0xF) | 0xE0; DestinationBuffer[Count++] = (UCHAR)((CurrentChar >> 6) & 0x3F) | 0x80; DestinationBuffer[Count++] = (UCHAR)(CurrentChar & 0xBF) | 0x80; } SourceBuffer += 1; }
DestinationBuffer[Count] = (UCHAR)'\0';
return(TRUE);
}
BOOLEAN SpTranslateUtf8ToUnicode( UCHAR IncomingByte, UCHAR *ExistingUtf8Buffer, WCHAR *DestinationUnicodeVal ) /*++
Routine Description:
Takes IncomingByte and concatenates it onto ExistingUtf8Buffer. Then attempts to decode the new contents of ExistingUtf8Buffer.
Arguments:
IncomingByte - New character to be appended onto ExistingUtf8Buffer.
ExistingUtf8Buffer - running buffer containing incomplete UTF8 encoded unicode value. When it gets full, we'll decode the value and return the corresponding Unicode value.
Note that if we *do* detect a completed UTF8 buffer and actually do a decode and return a Unicode value, then we will zero-fill the contents of ExistingUtf8Buffer.
DestinationUnicodeVal - receives Unicode version of the UTF8 buffer.
Note that if we do *not* detect a completed UTF8 buffer and thus can not return any data in DestinationUnicodeValue, then we will zero-fill the contents of DestinationUnicodeVal.
Return Value:
TRUE - We received a terminating character for our UTF8 buffer and will return a decoded Unicode value in DestinationUnicode.
FALSE - We haven't yet received a terminating character for our UTF8 buffer.
--*/
{ // ULONG Count = 0;
ULONG i = 0; BOOLEAN ReturnValue = FALSE;
//
// Insert our byte into ExistingUtf8Buffer.
//
i = 0; do { if( ExistingUtf8Buffer[i] == 0 ) { ExistingUtf8Buffer[i] = IncomingByte; break; }
i++; } while( i < 3 );
//
// If we didn't get to actually insert our IncomingByte,
// then someone sent us a fully-qualified UTF8 buffer.
// This means we're about to drop IncomingByte.
//
// Drop the zero-th byte, shift everything over by one
// and insert our new character.
//
// This implies that we should *never* need to zero out
// the contents of ExistingUtf8Buffer unless we detect
// a completed UTF8 packet. Otherwise, assume one of
// these cases:
// 1. We started listening mid-stream, so we caught the
// last half of a UTF8 packet. In this case, we'll
// end up shifting the contents of ExistingUtf8Buffer
// until we detect a proper UTF8 start byte in the zero-th
// position.
// 2. We got some garbage character, which would invalidate
// a UTF8 packet. By using the logic below, we would
// end up disregarding that packet and waiting for
// the next UTF8 packet to come in.
if( i >= 3 ) { ExistingUtf8Buffer[0] = ExistingUtf8Buffer[1]; ExistingUtf8Buffer[1] = ExistingUtf8Buffer[2]; ExistingUtf8Buffer[2] = IncomingByte; }
//
// Attempt to convert the UTF8 buffer
//
// UTF8 decodes to Unicode in the following fashion:
// If the high-order bit is 0 in the first byte:
// 0xxxxxxx yyyyyyyy zzzzzzzz decodes to a Unicode value of 00000000 0xxxxxxx
//
// If the high-order 3 bits in the first byte == 6:
// 110xxxxx 10yyyyyy zzzzzzzz decodes to a Unicode value of 00000xxx xxyyyyyy
//
// If the high-order 3 bits in the first byte == 7:
// 1110xxxx 10yyyyyy 10zzzzzz decodes to a Unicode value of xxxxyyyy yyzzzzzz
//
KdPrintEx((DPFLTR_SETUP_ID, DPFLTR_INFO_LEVEL, "SETUP: SpTranslateUtf8ToUnicode - About to decode the UTF8 buffer.\n" )); KdPrintEx((DPFLTR_SETUP_ID, DPFLTR_INFO_LEVEL, " UTF8[0]: 0x%02lx UTF8[1]: 0x%02lx UTF8[2]: 0x%02lx\n", ExistingUtf8Buffer[0], ExistingUtf8Buffer[1], ExistingUtf8Buffer[2] ));
if( (ExistingUtf8Buffer[0] & 0x80) == 0 ) {
KdPrintEx((DPFLTR_SETUP_ID, DPFLTR_INFO_LEVEL, "SETUP: SpTranslateUtf8ToUnicode - Case1\n" ));
//
// First case described above. Just return the first byte
// of our UTF8 buffer.
//
*DestinationUnicodeVal = (WCHAR)(ExistingUtf8Buffer[0]);
//
// We used 1 byte. Discard that byte and shift everything
// in our buffer over by 1.
//
ExistingUtf8Buffer[0] = ExistingUtf8Buffer[1]; ExistingUtf8Buffer[1] = ExistingUtf8Buffer[2]; ExistingUtf8Buffer[2] = 0;
ReturnValue = TRUE;
} else if( (ExistingUtf8Buffer[0] & 0xE0) == 0xC0 ) {
KdPrintEx((DPFLTR_SETUP_ID, DPFLTR_INFO_LEVEL, "SETUP: SpTranslateUtf8ToUnicode - 1st byte of UTF8 buffer says Case2\n" ));
//
// Second case described above. Decode the first 2 bytes of
// of our UTF8 buffer.
//
if( (ExistingUtf8Buffer[1] & 0xC0) == 0x80 ) {
KdPrintEx((DPFLTR_SETUP_ID, DPFLTR_INFO_LEVEL, "SETUP: SpTranslateUtf8ToUnicode - 2nd byte of UTF8 buffer says Case2.\n" ));
// upper byte: 00000xxx
*DestinationUnicodeVal = ((ExistingUtf8Buffer[0] >> 2) & 0x07); *DestinationUnicodeVal = *DestinationUnicodeVal << 8;
// high bits of lower byte: xx000000
*DestinationUnicodeVal |= ((ExistingUtf8Buffer[0] & 0x03) << 6);
// low bits of lower byte: 00yyyyyy
*DestinationUnicodeVal |= (ExistingUtf8Buffer[1] & 0x3F);
//
// We used 2 bytes. Discard those bytes and shift everything
// in our buffer over by 2.
//
ExistingUtf8Buffer[0] = ExistingUtf8Buffer[2]; ExistingUtf8Buffer[1] = 0; ExistingUtf8Buffer[2] = 0;
ReturnValue = TRUE;
} } else if( (ExistingUtf8Buffer[0] & 0xF0) == 0xE0 ) {
KdPrintEx((DPFLTR_SETUP_ID, DPFLTR_INFO_LEVEL, "SETUP: SpTranslateUtf8ToUnicode - 1st byte of UTF8 buffer says Case3\n" ));
//
// Third case described above. Decode the all 3 bytes of
// of our UTF8 buffer.
//
if( (ExistingUtf8Buffer[1] & 0xC0) == 0x80 ) {
KdPrintEx((DPFLTR_SETUP_ID, DPFLTR_INFO_LEVEL, "SETUP: SpTranslateUtf8ToUnicode - 2nd byte of UTF8 buffer says Case3\n" ));
if( (ExistingUtf8Buffer[2] & 0xC0) == 0x80 ) {
KdPrintEx((DPFLTR_SETUP_ID, DPFLTR_INFO_LEVEL, "SETUP: SpTranslateUtf8ToUnicode - 3rd byte of UTF8 buffer says Case3\n" ));
// upper byte: xxxx0000
*DestinationUnicodeVal = ((ExistingUtf8Buffer[0] << 4) & 0xF0);
// upper byte: 0000yyyy
*DestinationUnicodeVal |= ((ExistingUtf8Buffer[1] >> 2) & 0x0F);
*DestinationUnicodeVal = *DestinationUnicodeVal << 8;
// lower byte: yy000000
*DestinationUnicodeVal |= ((ExistingUtf8Buffer[1] << 6) & 0xC0);
// lower byte: 00zzzzzz
*DestinationUnicodeVal |= (ExistingUtf8Buffer[2] & 0x3F);
//
// We used all 3 bytes. Zero out the buffer.
//
ExistingUtf8Buffer[0] = 0; ExistingUtf8Buffer[1] = 0; ExistingUtf8Buffer[2] = 0;
ReturnValue = TRUE;
} } }
return ReturnValue; }
VOID SpTermInitialize( VOID )
/*++
Routine Description:
Attempts to connect to a VT100 attached to COM1
Arguments:
None.
Return Value:
None.
--*/
{ HEADLESS_CMD_ENABLE_TERMINAL Command; NTSTATUS Status;
Command.Enable = TRUE; Status = HeadlessDispatch(HeadlessCmdEnableTerminal, &Command, sizeof(HEADLESS_CMD_ENABLE_TERMINAL), NULL, NULL );
HeadlessTerminalConnected = NT_SUCCESS(Status); }
VOID SpTermDisplayStringOnTerminal( IN PWSTR String, IN UCHAR Attribute, IN ULONG X, // 0-based coordinates (character units)
IN ULONG Y )
/*++
Routine Description:
Write a string of characters to the terminal.
Arguments:
Character - supplies a string to be displayed at the given position.
Attribute - supplies the attributes for the characters in the string.
X,Y - specify the character-based (0-based) position of the output.
Return Value:
None.
--*/
{ PWSTR EscapeString;
//
// send <CSI>x;yH to move the cursor to the specified location
//
swprintf(UnicodeScratchBuffer, L"\033[%d;%dH", Y + 1, X + 1); SpTermSendStringToTerminal(UnicodeScratchBuffer, TRUE);
//
// convert any attributes to an escape string. EscapeString uses
// the TerminalBuffer global scratch buffer
//
EscapeString = SpTermAttributeToTerminalEscapeString(Attribute);
//
// transmit the escape string if we received one
//
if (EscapeString != NULL) { SpTermSendStringToTerminal(EscapeString, TRUE); }
//
// finally send the actual string contents to the terminal
//
SpTermSendStringToTerminal(String, FALSE); }
PWSTR SpTermAttributeToTerminalEscapeString( IN UCHAR Attribute )
/*++
Routine Description:
Convert a vga attribute byte to an escape sequence to send to the terminal.
Arguments:
Attribute - supplies the attribute.
Return Value:
A pointer to the escape sequence, or NULL if it could not be converted.
--*/
{ ULONG BgColor; ULONG FgColor; BOOLEAN Inverse;
BgColor = (Attribute & 0x70) >> 4; FgColor = Attribute & 0x07;
Inverse = !((BgColor == 0) || (BgColor == DEFAULT_BACKGROUND));
//
// Convert the colors.
//
switch (BgColor) { case ATT_BLUE: BgColor = 44; break; case ATT_GREEN: BgColor = 42; break; case ATT_CYAN: BgColor = 46; break; case ATT_RED: BgColor = 41; break; case ATT_MAGENTA: BgColor = 45; break; case ATT_YELLOW: BgColor = 43; break; case ATT_BLACK: BgColor = 40; break; case ATT_WHITE: BgColor = 47; break; } switch (FgColor) { case ATT_BLUE: FgColor = 34; break; case ATT_GREEN: FgColor = 32; break; case ATT_CYAN: FgColor = 36; break; case ATT_RED: FgColor = 31; break; case ATT_MAGENTA: FgColor = 35; break; case ATT_YELLOW: FgColor = 33; break; case ATT_BLACK: FgColor = 30; break; case ATT_WHITE: FgColor = 37; break; }
//
// <CSI>%1;%2;%3m is the escape to set a color
// where 1 = video mode
// 2 = foreground color
// 3 = background color
//
swprintf(UnicodeScratchBuffer, L"\033[%u;%u;%um", (Inverse ? 7 : 0), FgColor, BgColor );
return UnicodeScratchBuffer; }
VOID SpTermSendStringToTerminal( IN PWSTR String, IN BOOLEAN Raw )
/*++
Routine Description:
Write a character string to the terminal, translating some codes if desired.
Arguments:
String - NULL terminated string to write.
Raw - Send the string raw or not.
Return Value:
None.
--*/
{ ULONG i = 0; PWSTR LocalBuffer = UnicodeScratchBuffer;
ASSERT(FIELD_OFFSET(HEADLESS_CMD_PUT_STRING, String) == 0); // ASSERT if anyone changes this structure.
//
// Don't do anything if we aren't running headless.
//
if( !HeadlessTerminalConnected ) { return; }
if (Raw) {
if (SpTermDoUtf8) { SpTranslateUnicodeToUtf8( String, Utf8ConversionBuffer );
HeadlessDispatch( HeadlessCmdPutData, Utf8ConversionBuffer, strlen(Utf8ConversionBuffer), NULL, NULL ); } else { //
// Convert unicode string to oem, guarding against overflow.
//
RtlUnicodeToOemN( Utf8ConversionBuffer, sizeof(Utf8ConversionBuffer)-1, // guarantee room for nul
NULL, String, (wcslen(String)+1)*sizeof(WCHAR) );
Utf8ConversionBuffer[sizeof(Utf8ConversionBuffer)-1] = '\0';
HeadlessDispatch( HeadlessCmdPutString, Utf8ConversionBuffer, strlen(Utf8ConversionBuffer) + sizeof(UCHAR), NULL, NULL ); }
return; }
while (*String != L'\0') {
LocalBuffer[i++] = *String;
if (*String == L'\n') {
//
// Every \n becomes a \n\r sequence.
//
LocalBuffer[i++] = L'\r';
} else if (*String == 0x00DC) {
//
// The cursor becomes a space and then a backspace, this is to
// delete the old character and position the terminal cursor properly.
//
LocalBuffer[i-1] = 0x0020; LocalBuffer[i++] = 0x0008;
}
//
// we've got an entire line of text -- we need to transmit it now or
// we can end up scrolling the text and everything will look funny from
// this point forward.
//
if (i >= 70) {
LocalBuffer[i] = L'\0'; if (SpTermDoUtf8) { SpTranslateUnicodeToUtf8( LocalBuffer, Utf8ConversionBuffer );
HeadlessDispatch(HeadlessCmdPutData, Utf8ConversionBuffer, strlen(Utf8ConversionBuffer), NULL, NULL );
} else { //
// Convert unicode string to oem, guarding against overflow.
//
RtlUnicodeToOemN( Utf8ConversionBuffer, sizeof(Utf8ConversionBuffer)-1, // guarantee room for nul
NULL, LocalBuffer, (wcslen(LocalBuffer)+1)*sizeof(WCHAR) );
Utf8ConversionBuffer[sizeof(Utf8ConversionBuffer)-1] = '\0';
HeadlessDispatch(HeadlessCmdPutString, Utf8ConversionBuffer, strlen(Utf8ConversionBuffer) + sizeof(UCHAR), NULL, NULL );
}
i = 0; }
String++; }
LocalBuffer[i] = L'\0'; if (SpTermDoUtf8) { SpTranslateUnicodeToUtf8( LocalBuffer, Utf8ConversionBuffer );
HeadlessDispatch(HeadlessCmdPutData, Utf8ConversionBuffer, strlen(Utf8ConversionBuffer), NULL, NULL );
} else { //
// Convert unicode string to oem, guarding against overflow.
//
RtlUnicodeToOemN( Utf8ConversionBuffer, sizeof(Utf8ConversionBuffer)-1, // guarantee room for nul
NULL, LocalBuffer, (wcslen(LocalBuffer)+1)*sizeof(WCHAR) );
Utf8ConversionBuffer[sizeof(Utf8ConversionBuffer)-1] = '\0';
HeadlessDispatch(HeadlessCmdPutString, Utf8ConversionBuffer, strlen(Utf8ConversionBuffer) + sizeof(UCHAR), NULL, NULL );
}
}
VOID SpTermTerminate( VOID )
/*++
Routine Description:
Close down connection to the dumb terminal
Arguments:
None.
Return Value:
None.
--*/
{ HEADLESS_CMD_ENABLE_TERMINAL Command;
//
// Don't do anything if we aren't running headless.
//
if( !HeadlessTerminalConnected ) { return; }
Command.Enable = FALSE; HeadlessDispatch(HeadlessCmdEnableTerminal, &Command, sizeof(HEADLESS_CMD_ENABLE_TERMINAL), NULL, NULL );
HeadlessTerminalConnected = FALSE; }
BOOLEAN SpTermIsKeyWaiting( VOID )
/*++
Routine Description:
Probe for a read.
Arguments:
None.
Return Value:
TRUE if there is a character waiting for input, else FALSE.
--*/
{ HEADLESS_RSP_POLL Response; NTSTATUS Status; SIZE_T Length;
//
// Don't do anything if we aren't running headless.
//
if( !HeadlessTerminalConnected ) { return FALSE; }
Length = sizeof(HEADLESS_RSP_POLL);
Response.QueuedInput = FALSE;
Status = HeadlessDispatch(HeadlessCmdTerminalPoll, NULL, 0, &Response, &Length );
return (NT_SUCCESS(Status) && Response.QueuedInput); }
ULONG SpTermGetKeypress( VOID )
/*++
Routine Description:
Read in a (possible) sequence of keystrokes and return a Key value.
Arguments:
None.
Return Value:
0 if no key is waiting, else a ULONG key value.
--*/
{ UCHAR Byte; BOOLEAN Success; TIME_FIELDS StartTime; TIME_FIELDS EndTime; HEADLESS_RSP_GET_BYTE Response; SIZE_T Length; NTSTATUS Status;
//
// Don't do anything if we aren't running headless.
//
if( !HeadlessTerminalConnected ) { return 0; }
//
// Read first character
//
Length = sizeof(HEADLESS_RSP_GET_BYTE);
Status = HeadlessDispatch(HeadlessCmdGetByte, NULL, 0, &Response, &Length );
if (NT_SUCCESS(Status)) { Byte = Response.Value; } else { Byte = 0; }
//
// Handle all the special escape codes.
//
if (Byte == 0x8) { // backspace (^h)
return ASCI_BS; } if (Byte == 0x7F) { // delete
return KEY_DELETE; } if ((Byte == '\r') || (Byte == '\n')) { // return
return ASCI_CR; }
if (Byte == 0x1b) { // Escape key
do {
Success = HalQueryRealTimeClock(&StartTime); ASSERT(Success);
//
// Adjust StartTime to be our ending time.
//
StartTime.Second += 2; if (StartTime.Second > 59) { StartTime.Second -= 60; }
while (!SpTermIsKeyWaiting()) {
//
// Give the user 1 second to type in a follow up key.
//
Success = HalQueryRealTimeClock(&EndTime); ASSERT(Success);
if (StartTime.Second == EndTime.Second) { break; } }
if (!SpTermIsKeyWaiting()) { return ASCI_ESC; }
//
// Read the next keystroke
//
Length = sizeof(HEADLESS_RSP_GET_BYTE);
Status = HeadlessDispatch(HeadlessCmdGetByte, NULL, 0, &Response, &Length );
if (NT_SUCCESS(Status)) { Byte = Response.Value; } else { Byte = 0; }
//
// Some terminals send ESC, or ESC-[ to mean
// they're about to send a control sequence. We've already
// gotten an ESC key, so ignore an '[' if it comes in.
//
} while ( Byte == '[' );
switch (Byte) { case '@': return KEY_F12; case '!': return KEY_F11; case '0': return KEY_F10; case '9': return KEY_F9; case '8': return KEY_F8; case '7': return KEY_F7; case '6': return KEY_F6; case '5': return KEY_F5; case '4': return KEY_F4; case '3': return KEY_F3; case '2': return KEY_F2; case '1': return KEY_F1; case '+': return KEY_INSERT; case '-': return KEY_DELETE; case 'H': return KEY_HOME; case 'K': return KEY_END; case '?': return KEY_PAGEUP; case '/': return KEY_PAGEDOWN; case 'A': return KEY_UP; case 'B': return KEY_DOWN; case 'C': return KEY_RIGHT; case 'D': return KEY_LEFT;
}
//
// We didn't get anything we recognized after the
// ESC key. Just return the ESC key.
//
return ASCI_ESC;
} // Escape key
//
// The incoming byte isn't an escape code.
//
// Decode it as if it's a UTF8 stream.
//
if( SpTranslateUtf8ToUnicode( Byte, IncomingUtf8ConversionBuffer, &IncomingUnicodeValue ) ) {
//
// He returned TRUE, so we must have recieved a complete
// UTF8-encoded character.
//
return IncomingUnicodeValue; } else { //
// The UTF8 stream isn't complete yet, so we don't have
// a decoded character to return yet.
//
return 0; }
}
VOID SpTermDrain( VOID )
/*++
Routine Description:
Read in and throw out all characters in input stream
Arguments:
None.
Return Value:
None.
--*/
{ while (SpTermIsKeyWaiting()) { SpTermGetKeypress(); } }
|