/*++ 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 #include #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 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; } // // %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(); } }