Output errors after setting text red, then normal while in raw mode #12034

Closed
opened 2026-01-31 03:04:31 +00:00 by claunia · 1 comment
Owner

Originally created by @clinton-r on GitHub (Jan 12, 2021).

Originally assigned to: @DHowett on GitHub.

Environment

Microsoft Windows [Version 10.0.19041.685]
Windows Terminal Version: 1.4.3243.0
Microsoft Visual Studio Community 2019 Version 16.8.3

Steps to reproduce

  1. Compile the program below
  2. Open Windows Terminal, with tabs for both cmd and Powershell
  3. Run the program in the cmd tab
  4. Run the program in the Powershell tab
  5. Type 'exit' in the Powershell tab
  6. Open conhost running cmd
  7. Run the program in conhost
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>

static HANDLE hStdin = NULL;
static HANDLE hStdout = NULL;
static int savedConsoleOutputModeIsValid = 0;
static DWORD savedConsoleOutputMode = 0;
static int savedConsoleInputModeIsValid = 0;
static DWORD savedConsoleInputMode = 0;

static void restoreConsoleState(void)
{
    if (savedConsoleOutputModeIsValid)
    {
        SetConsoleMode(hStdout, savedConsoleOutputMode);
    }
    if (savedConsoleInputModeIsValid)
    {
        SetConsoleMode(hStdin, savedConsoleInputMode);
    }
    //printf("\r\nRestored console state\r\n");
}

#define USE_WRITECONSOLE 0

void writeString(char *str)
{
#if USE_WRITECONSOLE
    WriteConsoleA(hStdout, str, strlen(str), NULL, NULL);
#else
    printf(str);
#endif
}

int main(void)
{
    DWORD newInputMode;
    DWORD newOutputMode;

    // Make sure the console state will be returned to its original state
    // when this program ends
    atexit(restoreConsoleState);

    // Get handles for stdin and stdout
    hStdin = GetStdHandle(STD_INPUT_HANDLE);
    if (hStdin == INVALID_HANDLE_VALUE || hStdin == NULL)
    {
        puts("Failed to get handle for stdin.  Exiting.");
        return 1;
    }
    hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
    if (hStdout == INVALID_HANDLE_VALUE || hStdout == NULL)
    {
        puts("Failed to get handle for stdout.  Exiting.");
        return 1;
    }

    // Get original modes to be restored later

    if (!GetConsoleMode(hStdout, &savedConsoleOutputMode))
    {
        puts("Failed to get console mode for stdout.  Exiting.");
        return 1;
    }
    savedConsoleOutputModeIsValid = 1;

    if (!GetConsoleMode(hStdin, &savedConsoleInputMode))
    {
        puts("Failed to get console mode for stdin.  Exiting.");
        return 1;
    }
    savedConsoleInputModeIsValid = 1;

    // Print message normal, then red, then normal
    char buf[200];
    sprintf_s(buf, sizeof(buf), "\nInput mode 0x%08x, Output mode 0x%08x\n", (int)savedConsoleInputMode, (int)savedConsoleOutputMode);
    writeString(buf);
    writeString("This text should be normal\n");
    writeString("\x1b[31m");
    writeString("I think (?) this text should be red\n");
    writeString("\x1b[0m");
    writeString("This text should be normal\n");

    // Enable escape sequences

    newOutputMode = savedConsoleOutputMode;
    newOutputMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
    if (!SetConsoleMode(hStdout, newOutputMode))
    {
        puts("Failed to set console mode for stdout.  Exiting.");
        return 1;
    }

    newInputMode = savedConsoleInputMode;
    newInputMode |= ENABLE_VIRTUAL_TERMINAL_INPUT;
    if (!SetConsoleMode(hStdin, newInputMode))
    {
        puts("Failed to set console mode for stdin.  Exiting.");
        return 1;
    }

    // Print message normal, then red, then normal
    sprintf_s(buf, sizeof(buf), "\nInput mode 0x%08x, Output mode 0x%08x\n", (int)newInputMode, (int)newOutputMode);
    writeString(buf);
    writeString("This text should be normal\n");
    writeString("\x1b[31m");
    writeString("This text should be red\n");
    writeString("\x1b[0m");
    writeString("This text should be normal\n");

    // Set console to "raw" mode

    newOutputMode = savedConsoleOutputMode;
    newOutputMode &= ~ENABLE_PROCESSED_OUTPUT;
    newOutputMode &= ~ENABLE_WRAP_AT_EOL_OUTPUT;
    newOutputMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
    if (!SetConsoleMode(hStdout, newOutputMode))
    {
        puts("Failed to set console mode for stdout.  Exiting.");
        return 1;
    }

    newInputMode = savedConsoleInputMode;
    newInputMode &= ~ENABLE_ECHO_INPUT;
    newInputMode &= ~ENABLE_LINE_INPUT;
    newInputMode &= ~ENABLE_PROCESSED_INPUT;
    newInputMode |= ENABLE_VIRTUAL_TERMINAL_INPUT;
    if (!SetConsoleMode(hStdin, newInputMode))
    {
        puts("Failed to set console mode for stdin.  Exiting.");
        return 1;
    }

    // Print message normal, then red, then normal
    sprintf_s(buf, sizeof(buf), "\r\nInput mode 0x%08x, Output mode 0x%08x\r\n", (int)newInputMode, (int)newOutputMode);
    writeString(buf);
    writeString("This text should be normal\r\n");
    writeString("\x1b[31m");
    writeString("This text should be red\r\n");
    writeString("\x1b[0m");
    writeString("This text should be normal\r\n");

    return 0;
}

Expected behavior

I expect to see as output in all 3 cases:

Input mode 0x000001f7, Output mode 0x00000007
This text should be normal
I think (?) this text should be red
This text should be normal

Input mode 0x000003f7, Output mode 0x00000007
This text should be normal
This text should be red
This text should be normal

Input mode 0x000003f0, Output mode 0x00000004
This text should be normal
This text should be red
This text should be normal

In the case of the Powershell tab I also expect that when 'exit' is typed it will be echoed to the right of the prompt after the above output.

Actual behavior

In all 3 cases, the final "This text should be normal" line is truncated.
In the Powershell tab when 'exit' is typed, the first 2 characters 'ex' are echoed where they should be, then the cursor jumps up 5 lines and the complete word 'exit' is written there.
WT_cmd
WT_Ps
conhost_cmd

Extra notes and questions

Happens whether using printf() or WriteConsoleA()

Am I using the modes properly? I'm just learning about console programming...

Also, is VT output supposed to be disabled when conhost is first started? That surprised me.

Thanks!

Originally created by @clinton-r on GitHub (Jan 12, 2021). Originally assigned to: @DHowett on GitHub. <!-- 🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨 I ACKNOWLEDGE THE FOLLOWING BEFORE PROCEEDING: 1. If I delete this entire template and go my own path, the core team may close my issue without further explanation or engagement. 2. If I list multiple bugs/concerns in this one issue, the core team may close my issue without further explanation or engagement. 3. If I write an issue that has many duplicates, the core team may close my issue without further explanation or engagement (and without necessarily spending time to find the exact duplicate ID number). 4. If I leave the title incomplete when filing the issue, the core team may close my issue without further explanation or engagement. 5. If I file something completely blank in the body, the core team may close my issue without further explanation or engagement. All good? Then proceed! --> <!-- This bug tracker is monitored by Windows Terminal development team and other technical folks. **Important: When reporting BSODs or security issues, DO NOT attach memory dumps, logs, or traces to Github issues**. Instead, send dumps/traces to secure@microsoft.com, referencing this GitHub issue. If this is an application crash, please also provide a Feedback Hub submission link so we can find your diagnostic data on the backend. Use the category "Apps > Windows Terminal (Preview)" and choose "Share My Feedback" after submission to get the link. Please use this form and describe your issue, concisely but precisely, with as much detail as possible. --> # Environment Microsoft Windows [Version 10.0.19041.685] Windows Terminal Version: 1.4.3243.0 Microsoft Visual Studio Community 2019 Version 16.8.3 # Steps to reproduce <!-- A description of how to trigger this bug. --> 1. Compile the program below 2. Open Windows Terminal, with tabs for both cmd and Powershell 3. Run the program in the cmd tab 4. Run the program in the Powershell tab 5. Type 'exit' in the Powershell tab 6. Open conhost running cmd 7. Run the program in conhost ```C #include <stdio.h> #include <stdlib.h> #include <windows.h> static HANDLE hStdin = NULL; static HANDLE hStdout = NULL; static int savedConsoleOutputModeIsValid = 0; static DWORD savedConsoleOutputMode = 0; static int savedConsoleInputModeIsValid = 0; static DWORD savedConsoleInputMode = 0; static void restoreConsoleState(void) { if (savedConsoleOutputModeIsValid) { SetConsoleMode(hStdout, savedConsoleOutputMode); } if (savedConsoleInputModeIsValid) { SetConsoleMode(hStdin, savedConsoleInputMode); } //printf("\r\nRestored console state\r\n"); } #define USE_WRITECONSOLE 0 void writeString(char *str) { #if USE_WRITECONSOLE WriteConsoleA(hStdout, str, strlen(str), NULL, NULL); #else printf(str); #endif } int main(void) { DWORD newInputMode; DWORD newOutputMode; // Make sure the console state will be returned to its original state // when this program ends atexit(restoreConsoleState); // Get handles for stdin and stdout hStdin = GetStdHandle(STD_INPUT_HANDLE); if (hStdin == INVALID_HANDLE_VALUE || hStdin == NULL) { puts("Failed to get handle for stdin. Exiting."); return 1; } hStdout = GetStdHandle(STD_OUTPUT_HANDLE); if (hStdout == INVALID_HANDLE_VALUE || hStdout == NULL) { puts("Failed to get handle for stdout. Exiting."); return 1; } // Get original modes to be restored later if (!GetConsoleMode(hStdout, &savedConsoleOutputMode)) { puts("Failed to get console mode for stdout. Exiting."); return 1; } savedConsoleOutputModeIsValid = 1; if (!GetConsoleMode(hStdin, &savedConsoleInputMode)) { puts("Failed to get console mode for stdin. Exiting."); return 1; } savedConsoleInputModeIsValid = 1; // Print message normal, then red, then normal char buf[200]; sprintf_s(buf, sizeof(buf), "\nInput mode 0x%08x, Output mode 0x%08x\n", (int)savedConsoleInputMode, (int)savedConsoleOutputMode); writeString(buf); writeString("This text should be normal\n"); writeString("\x1b[31m"); writeString("I think (?) this text should be red\n"); writeString("\x1b[0m"); writeString("This text should be normal\n"); // Enable escape sequences newOutputMode = savedConsoleOutputMode; newOutputMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; if (!SetConsoleMode(hStdout, newOutputMode)) { puts("Failed to set console mode for stdout. Exiting."); return 1; } newInputMode = savedConsoleInputMode; newInputMode |= ENABLE_VIRTUAL_TERMINAL_INPUT; if (!SetConsoleMode(hStdin, newInputMode)) { puts("Failed to set console mode for stdin. Exiting."); return 1; } // Print message normal, then red, then normal sprintf_s(buf, sizeof(buf), "\nInput mode 0x%08x, Output mode 0x%08x\n", (int)newInputMode, (int)newOutputMode); writeString(buf); writeString("This text should be normal\n"); writeString("\x1b[31m"); writeString("This text should be red\n"); writeString("\x1b[0m"); writeString("This text should be normal\n"); // Set console to "raw" mode newOutputMode = savedConsoleOutputMode; newOutputMode &= ~ENABLE_PROCESSED_OUTPUT; newOutputMode &= ~ENABLE_WRAP_AT_EOL_OUTPUT; newOutputMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; if (!SetConsoleMode(hStdout, newOutputMode)) { puts("Failed to set console mode for stdout. Exiting."); return 1; } newInputMode = savedConsoleInputMode; newInputMode &= ~ENABLE_ECHO_INPUT; newInputMode &= ~ENABLE_LINE_INPUT; newInputMode &= ~ENABLE_PROCESSED_INPUT; newInputMode |= ENABLE_VIRTUAL_TERMINAL_INPUT; if (!SetConsoleMode(hStdin, newInputMode)) { puts("Failed to set console mode for stdin. Exiting."); return 1; } // Print message normal, then red, then normal sprintf_s(buf, sizeof(buf), "\r\nInput mode 0x%08x, Output mode 0x%08x\r\n", (int)newInputMode, (int)newOutputMode); writeString(buf); writeString("This text should be normal\r\n"); writeString("\x1b[31m"); writeString("This text should be red\r\n"); writeString("\x1b[0m"); writeString("This text should be normal\r\n"); return 0; } ``` # Expected behavior <!-- A description of what you're expecting, possibly containing screenshots or reference material. --> I expect to see as output in all 3 cases: ``` Input mode 0x000001f7, Output mode 0x00000007 This text should be normal I think (?) this text should be red This text should be normal Input mode 0x000003f7, Output mode 0x00000007 This text should be normal This text should be red This text should be normal Input mode 0x000003f0, Output mode 0x00000004 This text should be normal This text should be red This text should be normal ``` In the case of the Powershell tab I also expect that when 'exit' is typed it will be echoed to the right of the prompt after the above output. # Actual behavior <!-- What's actually happening? --> In all 3 cases, the final "This text should be normal" line is truncated. In the Powershell tab when 'exit' is typed, the first 2 characters 'ex' are echoed where they should be, then the cursor jumps up 5 lines and the complete word 'exit' is written there. ![WT_cmd](https://user-images.githubusercontent.com/50687925/104279257-3b687c00-5467-11eb-99d7-f97cffe37f05.png) ![WT_Ps](https://user-images.githubusercontent.com/50687925/104279282-46231100-5467-11eb-8081-73e4d11010be.png) ![conhost_cmd](https://user-images.githubusercontent.com/50687925/104279295-4a4f2e80-5467-11eb-86da-4f15b828c24d.png) # Extra notes and questions Happens whether using printf() or WriteConsoleA() Am I using the modes properly? I'm just learning about console programming... Also, is VT output supposed to be disabled when conhost is first started? That surprised me. Thanks!
claunia added the Issue-QuestionNeeds-Tag-FixResolution-Answered labels 2026-01-31 03:04:31 +00:00
Author
Owner

@DHowett commented on GitHub (Jan 12, 2021):

Hey @clinton-r! Thanks for reaching out.

So, here's the skinny.

You almost never want to turn off ENABLE_PROCESSED_OUTPUT on your output handle. This is doubly true if you're trying to use control sequences (like the ones you have above for changing the color) or using ENABLE_VIRTUAL_TERMINAL_PROCESSING.

When you turn off ENABLE_PROCESSED_OUTPUT, the control sequences are written directly to the console buffer, as you can see in your second screenshot in conhost. Once they've been inserted into the backing buffer as-is, the escape characters and other control sequences take up physical space and do not get interpreted.

image

Thanks to a choice we made in the console hosting API that Terminal (and Visual Studio, VS Code, Alacritty, ...) uses, those control sequences are passed through directly from the console session into the terminal. Now, this is where things go off the rails. The terminal takes a second chance to parse them. This is clearly a mistake (on our end).

The terminal, when it parses them, is effectively taking what the console host told it ("put this literal escape character HERE, and this one HERE") and corrupting the buffer contents by trying to interpret those control sequences. That is tracked by https://github.com/microsoft/terminal/issues/4363.

In short, always use PROCESSED_OUTPUT and TERMINAL_PROCESSING at the same time when you're using VT.


As for your other question about why conhost and Terminal act differently, that's an artifact of our compatibility history. The console host must start up in the mode most likely to keep old applications working. The terminal doesn't really have the same constraint, so it starts out by default with VIRTUAL_TERMINAL_PROCESSING enabled.

CMD and PowerShell do some additional work on top of that, because they do want VIRTUAL_TERMINAL_PROCESSING. If they turn it on (and they were the first ones to turn it on), they turn it back off before running another application. Again, for maximal compatibility.

If you have an application that is sensitive to these things, you should just always enforce the specific modes that work best for your app. 😄

We're working on making the output modes per-process so that they don't need to be saved/restored by each/every application (which is clearly crazy). That work is tracked in #4954.

Hope that helps! I'm gonna close this one out as an answered question, but do feel free to ask more.

@DHowett commented on GitHub (Jan 12, 2021): Hey @clinton-r! Thanks for reaching out. So, here's the skinny. You almost never want to turn off `ENABLE_PROCESSED_OUTPUT` on your output handle. This is doubly true if you're trying to use control sequences (like the ones you have above for changing the color) or using `ENABLE_VIRTUAL_TERMINAL_PROCESSING`. When you turn off `ENABLE_PROCESSED_OUTPUT`, the control sequences are written directly to the console buffer, as you can see in your second screenshot in conhost. Once they've been inserted into the backing buffer as-is, the escape characters and other control sequences take up physical space and do not get interpreted. ![image](https://user-images.githubusercontent.com/189190/104383637-00a52900-54e5-11eb-8b1a-37f57fbd6db2.png) Thanks to a choice we made in the console hosting API that Terminal (and Visual Studio, VS Code, Alacritty, ...) uses, those control sequences are passed through _directly_ from the console session into the terminal. Now, this is where things go off the rails. **The terminal takes a second chance to parse them**. This is clearly a mistake (on our end). The terminal, when it parses them, is effectively taking what the console host told it ("put this literal escape character HERE, and this one HERE") and corrupting the buffer contents by trying to interpret those control sequences. That is tracked by https://github.com/microsoft/terminal/issues/4363. In short, always use `PROCESSED_OUTPUT` and `TERMINAL_PROCESSING` at the same time when you're using VT. --- As for your other question about why conhost and Terminal act differently, that's an artifact of our compatibility history. The console host must start up in the mode most likely to keep old applications working. The terminal doesn't _really_ have the same constraint, so it starts out by default with `VIRTUAL_TERMINAL_PROCESSING` enabled. CMD and PowerShell do some additional work on top of that, because they _do_ want `VIRTUAL_TERMINAL_PROCESSING`. If they turn it on (and they were the first ones to turn it on), they turn it back off before running another application. Again, for maximal compatibility. If you have an application that is sensitive to these things, you should just always enforce the specific modes that work best for your app. :smile: We're working on making the output modes per-process so that they don't need to be saved/restored by each/every application (which is clearly crazy). That work is tracked in #4954. Hope that helps! I'm gonna close this one out as an answered question, but do feel free to ask more.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/terminal#12034