/*++

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];

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;

    //
    // 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) {

        if( (*SourceBuffer & 0xFF80) == 0 ) {
            //
            // if the top 9 bits are zero, then just
            // encode as 1 byte.  (ASCII passes through unchanged).
            //
            DestinationBuffer[Count++] = (UCHAR)(*SourceBuffer & 0x7F);
        } else if( (*SourceBuffer & 0xF700) == 0 ) {
            //
            // if the top 5 bits are zero, then encode as 2 bytes
            //
            DestinationBuffer[Count++] = (UCHAR)((*SourceBuffer >> 6) & 0x1F) | 0xC0;
            DestinationBuffer[Count++] = (UCHAR)(*SourceBuffer & 0xBF) | 0x80;
        } else {
            //
            // encode as 3 bytes
            //
            DestinationBuffer[Count++] = (UCHAR)((*SourceBuffer >> 12) & 0xF) | 0xE0;
            DestinationBuffer[Count++] = (UCHAR)((*SourceBuffer >> 6) & 0x3F) | 0x80;
            DestinationBuffer[Count++] = (UCHAR)(*SourceBuffer & 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;
    //
    // if we're in an FE build, we do UTF8, otherwise we do oem codepage
    //
    BOOL DoUtf8 = (VideoFunctionVector != &VgaVideoVector);


    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 (DoUtf8) {
            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('\0'),
                     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 (DoUtf8) {
                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('\0'),
                                 NULL,
                                 NULL
                                );

            }

            i = 0;
        }

        String++;
    }

    LocalBuffer[i] = L'\0';
    if (DoUtf8) {
        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('\0'),
                     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();
    }
}