Enter key buffered when using raw input and getchar() #12111

Closed
opened 2026-01-31 03:06:34 +00:00 by claunia · 6 comments
Owner

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

Environment

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

Steps to reproduce

  1. Compile this C program and run it
  2. Type 'h', 'e', 'l', 'l', 'o', enter, 'c', 'a', 't', 'Q'
#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)
{
    printf("\x1b[0m");
    fflush(stdout);

    if (savedConsoleOutputModeIsValid)
        SetConsoleMode(hStdout, savedConsoleOutputMode);
    if (savedConsoleInputModeIsValid)
        SetConsoleMode(hStdin, savedConsoleInputMode);
}

int setConsoleRaw(void)
{
    // 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)
        return 1;
    hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
    if (hStdout == INVALID_HANDLE_VALUE || hStdout == NULL)
        return 1;

    // Set console to "raw" mode

    if (!GetConsoleMode(hStdout, &savedConsoleOutputMode))
        return 1;
    savedConsoleOutputModeIsValid = 1;
    DWORD newOutputMode = savedConsoleOutputMode;
    newOutputMode |= ENABLE_PROCESSED_OUTPUT;
    newOutputMode &= ~ENABLE_WRAP_AT_EOL_OUTPUT;
    newOutputMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
    if (!SetConsoleMode(hStdout, newOutputMode))
        return 1;
    if (!GetConsoleMode(hStdin, &savedConsoleInputMode))
        return 1;
    savedConsoleInputModeIsValid = 1;
    DWORD newInputMode = savedConsoleInputMode;
    newInputMode &= ~ENABLE_ECHO_INPUT;
    newInputMode &= ~ENABLE_LINE_INPUT;
    newInputMode &= ~ENABLE_PROCESSED_INPUT;
    newInputMode |= ENABLE_VIRTUAL_TERMINAL_INPUT;
    if (!SetConsoleMode(hStdin, newInputMode))
        return 1;

    return 0;
}

int mygetchar(void)
{
#if 1
    return getchar();
#elif 0
    char c;
    if (!scanf_s("%c", &c, 1))
    {
        return EOF;
    }
    return c;
#else
    char c;
    DWORD numRead;
    if (!ReadConsoleA(hStdin, &c, 1, &numRead, NULL) || numRead < 1)
    {
        return EOF;
    }
    return (int)c;
#endif
}

int main(void)
{
    setConsoleRaw();

    int c;
    do
    {
        c = mygetchar();

        printf("%02x", c);
        if (isprint(c))
            printf(" (%c)", (char)c);

        printf("\n");

    } while (c != 'Q');

    return 0;
}

Expected behavior

Immediately after each key press, the hex value received and (if printable) the corresponding character should be written to the terminal on its own line. Program ends after 'Q' is typed and written.

Actual behavior

Output is as expected until enter key is pressed:
enter pressed: no output
'c' pressed: "0d" is output
'a' pressed: we get 2 lines of output: "63 (c)" then "61 (a)"
... and then output is as expected again

Note

The problem also occurs if using scanf_s(), but does not occur if using ReadConsoleA().

Originally created by @clinton-r on GitHub (Jan 18, 2021). <!-- 🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨 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.746] 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 this C program and run it 2. Type 'h', 'e', 'l', 'l', 'o', enter, 'c', 'a', 't', 'Q' ```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) { printf("\x1b[0m"); fflush(stdout); if (savedConsoleOutputModeIsValid) SetConsoleMode(hStdout, savedConsoleOutputMode); if (savedConsoleInputModeIsValid) SetConsoleMode(hStdin, savedConsoleInputMode); } int setConsoleRaw(void) { // 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) return 1; hStdout = GetStdHandle(STD_OUTPUT_HANDLE); if (hStdout == INVALID_HANDLE_VALUE || hStdout == NULL) return 1; // Set console to "raw" mode if (!GetConsoleMode(hStdout, &savedConsoleOutputMode)) return 1; savedConsoleOutputModeIsValid = 1; DWORD newOutputMode = savedConsoleOutputMode; newOutputMode |= ENABLE_PROCESSED_OUTPUT; newOutputMode &= ~ENABLE_WRAP_AT_EOL_OUTPUT; newOutputMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; if (!SetConsoleMode(hStdout, newOutputMode)) return 1; if (!GetConsoleMode(hStdin, &savedConsoleInputMode)) return 1; savedConsoleInputModeIsValid = 1; DWORD newInputMode = savedConsoleInputMode; newInputMode &= ~ENABLE_ECHO_INPUT; newInputMode &= ~ENABLE_LINE_INPUT; newInputMode &= ~ENABLE_PROCESSED_INPUT; newInputMode |= ENABLE_VIRTUAL_TERMINAL_INPUT; if (!SetConsoleMode(hStdin, newInputMode)) return 1; return 0; } int mygetchar(void) { #if 1 return getchar(); #elif 0 char c; if (!scanf_s("%c", &c, 1)) { return EOF; } return c; #else char c; DWORD numRead; if (!ReadConsoleA(hStdin, &c, 1, &numRead, NULL) || numRead < 1) { return EOF; } return (int)c; #endif } int main(void) { setConsoleRaw(); int c; do { c = mygetchar(); printf("%02x", c); if (isprint(c)) printf(" (%c)", (char)c); printf("\n"); } while (c != 'Q'); return 0; } ``` # Expected behavior <!-- A description of what you're expecting, possibly containing screenshots or reference material. --> Immediately after each key press, the hex value received and (if printable) the corresponding character should be written to the terminal on its own line. Program ends after 'Q' is typed and written. # Actual behavior <!-- What's actually happening? --> Output is as expected until enter key is pressed: enter pressed: no output 'c' pressed: "0d" is output 'a' pressed: we get 2 lines of output: "63 (c)" then "61 (a)" ... and then output is as expected again # Note The problem also occurs if using scanf_s(), but does not occur if using ReadConsoleA().
claunia added the Issue-QuestionNeeds-TriageNeeds-Tag-FixNeeds-Attention labels 2026-01-31 03:06:34 +00:00
Author
Owner

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

Sorry, mind breaking down the behaviors for me?

API You get 0x0D immediately when you press enter
ReadConsoleA
getchar ✔️
scanf
@DHowett commented on GitHub (Jan 19, 2021): Sorry, mind breaking down the behaviors for me? API|You get 0x0D immediately when you press enter -|- ReadConsoleA|❓ getchar|✔️ scanf|❌
Author
Owner

@clinton-r commented on GitHub (Jan 19, 2021):

Happy to. Sorry, I made a mistake in my note above - I've corrected it.

  API           You get 0x0D immediately when you press enter
 ------------   ---------------------------------------------
 ReadConsoleA   YES
 getchar        NO
 scanf_s        NO

Using getchar and scanf_s:
when I press enter, my program doesn't get anything
when I press another key (say 'c') following enter, my program gets 0x0d
when I press another key (say 'a') following that, my program gets both 'c' and 'a'
Using ReadConsoleA, my program gets each character when it is typed, as I expect

I should also have mentioned that this happens both in conhost and Windows Terminal.

Thanks!

@clinton-r commented on GitHub (Jan 19, 2021): Happy to. Sorry, I made a mistake in my note above - I've corrected it. ``` API You get 0x0D immediately when you press enter ------------ --------------------------------------------- ReadConsoleA YES getchar NO scanf_s NO ``` Using getchar and scanf_s: when I press enter, my program doesn't get anything when I press another key (say 'c') following enter, my program gets 0x0d when I press another key (say 'a') following that, my program gets both 'c' and 'a' Using ReadConsoleA, my program gets each character when it is typed, as I expect I should also have mentioned that this happens both in conhost and Windows Terminal. Thanks!
Author
Owner

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

You know, I'm not sure what the answer is here. Looking through the sources for getchar (and in a way, scanf), it looks like it uses buffered I/O against the console handle. It's got some special handling for CR/LF, but...

The annoying thing here is that I can't help. The console machinery (ReadConsoleA, ReadConsoleInput, ReadFile) are working fine. There's an impedance mismatch between the console mode and the CRT's expectations.

The CRT looks like it sets the console mode itself when you call getch (different but identical-sounding function, eh)

Any ideas? I couldn't immediately find any APIs that make this more reliable.

(scanf operates on entire strings, so I'm not shocked that it wants a completed input line before it parses.)

@DHowett commented on GitHub (Jan 22, 2021): You know, I'm not sure what the answer is here. Looking through the sources for `getchar` (and in a way, `scanf`), it looks like it uses buffered I/O against the console handle. It's got some special handling for CR/LF, but... The annoying thing here is that I can't help. The console machinery (ReadConsoleA, ReadConsoleInput, ReadFile) are working fine. There's an impedance mismatch between the console mode and the CRT's expectations. The CRT looks like it sets the console mode itself when you call _`getch`_ (different but identical-sounding function, eh) Any ideas? I couldn't immediately find any APIs that make this more reliable. (scanf operates on entire strings, so I'm not shocked that it wants a completed input line before it parses.)
Author
Owner

@clinton-r commented on GitHub (Jan 22, 2021):

Thanks for looking into this and trying to help. I will probably use ReadConsoleInput() rather than getchar() for my current project. I reported this just to make sure you know about it.

@clinton-r commented on GitHub (Jan 22, 2021): Thanks for looking into this and trying to help. I will probably use ReadConsoleInput() rather than getchar() for my current project. I reported this just to make sure you know about it.
Author
Owner

@zadjii-msft commented on GitHub (Jan 25, 2021):

the-more-you-know

Thanks for letting us know! I think, we all learned something today 😄

@zadjii-msft commented on GitHub (Jan 25, 2021): ![the-more-you-know](https://user-images.githubusercontent.com/18356694/105739543-d3ea0c00-5efd-11eb-8bd9-40628517d158.gif) Thanks for letting us know! I think, we all learned something today 😄
Author
Owner

@JMRoderick commented on GitHub (Nov 9, 2025):

getchar() is still not functioning properly. ( In raw mode specifically )
getchar() on linux works as expected.
I am actually trying to port a simple app from linux to windows, and
am using getchar() for user input. ( It is a console/terminal app ).
I am using Microsoft 's current recomendation on setting up the
console modes, and I see all the other keys working with the exception of the enter key.
getchar() does not return when enter is pressed, and will not return anything until a 'useable' key is pressed, or
multiple enter's are pressed. Things get funky, just as the poster described.
I would expect that a standard C function to behave the same across all platforms.

@JMRoderick commented on GitHub (Nov 9, 2025): getchar() is still not functioning properly. ( In raw mode specifically ) getchar() on linux works as expected. I am actually trying to port a simple app from linux to windows, and am using getchar() for user input. ( It is a console/terminal app ). I am using Microsoft 's current recomendation on setting up the console modes, and I see all the other keys working with the exception of the enter key. getchar() does not return when enter is pressed, and will not return anything until a 'useable' key is pressed, or multiple enter's are pressed. Things get funky, just as the poster described. I would expect that a standard C function to behave the same across all platforms.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/terminal#12111