c++linuxinputgetchar

Linux: Catching Ctrl+C breaks stdin


I'm writing a test application for a console text input library. The idea of that part of the library is to just treat any input as a regular text input, even Ctrl+[letter] combinations. And for the most part, it works rather well. But for some reason, every time I try out Ctrl+C, regular input just stops working - I can continue reading input character by character via getchar and read, but with regular stdin, I seem to be only able to press Ctrl+C again, nothing happens when I just try to type like normal. I succeed at using std::cin by just hitting the return key though.

I tested it on Ubuntu, both as WSL in the Windows Terminal and on a full Ubuntu VM.

Here's the relevant part of my code:

termios tOld, tNew;

tcgetattr(STDIN_FILENO, &tOld);
tNew = tOld;

tNew.c_lflag &= ~(ICANON | ISIG | IEXTEN | IXON | ECHO);
tNew.c_iflag &= ~(IXON | IXOFF);

tcsetattr(STDIN_FILENO, TCSANOW, &tNew);

// further processing (reading via read and getchar and 'parsing' the input)

tcsetattr(STDIN_FILENO, TCSANOW, &tOld);

I wonder what I'm doing wrong.

The other possible cause for this error is this code that I use for processing ESC presses:

std::function<bool(unsigned)> fnWaitForNextKey =
    [](unsigned iMillis) -> bool
    {
        struct timeval tv;
        fd_set fds;

        tv.tv_sec = 0;
        tv.tv_usec = iMillis * 1000;
        FD_ZERO(&fds);
        FD_SET(STDIN_FILENO, &fds);

        return select(STDIN_FILENO + 1, &fds, NULL, NULL, &tv);
    };

Solution

  • I found my error: the 2nd call to tcsetattr (to restore the previous settings) was not always called. A silly copy-and-paste error.

    For anyone interested, I have to pieces of code handling the actual input, divided via #ifdef and #elif. One is for Windows and one is for Linux. In the Windows code, I immediately return, while in the Linux code, I set a variable that I return later. But on Ctrl+[letter] combinations, I copied the return and didn't notice it was wrong 😅

    Something like this:

    #ifdef _WIN32
      const uint8_t input = _getch();
    
      // Ctrl + [letter] => uppercase letter + CTRL flag
      if (input >= 0x01 && input <= 0x1A)
        return input | 0x40 | FLAG_CTRL;
    
      // regular ASCII character
      else if (input >= 0x20 && input <= 0x7E)
        return input;
    
    #elif __linux__
    
      termios tOld, tNew;
    
      tcgetattr(STDIN_FILENO, &tOld);
      tNew = tOld;
    
      tNew.c_lflag &= ~(ICANON | ISIG | IEXTEN | IXON | ECHO);
      tNew.c_iflag &= ~(IXON | IXOFF);
    
      tcsetattr(STDIN_FILENO, TCSANOW, &tNew);
    
    
      uint8_t result;
    
      const uint8_t input = getchar();
      // Ctrl + [letter] => uppercase letter + CTRL flag
      if (input >= 0x01 && input <= 0x1A)
        return input | 0x40 | FLAG_CTRL; // <-- should be "result = "!
    
      // regular ASCII character
      else if (input >= 0x20 && input <= 0x7E)
        result = input;
    
    
      tcsetattr(STDIN_FILENO, TCSANOW, &tOld);
    
      return result;
    
    #endif