3

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);
    };
8
  • You have to handle the signals Commented Oct 25, 2024 at 23:49
  • Have a look at Catch Ctrl-C in C for the signal handling. Commented Oct 26, 2024 at 0:08
  • I know about signal handling, but since I would have to remove ISIG from my mask, I assume I would have to handle multiple signals, not only the Ctrl+C one? I already tried just adding signal handling code to what I have, but it wasn't called in the given environment. Commented Oct 26, 2024 at 0:32
  • 1
    What we need is A Minimal Complete Reproducible Example. You say you tried adding signal handling code, but if we can't see what you did, it makes it impossible to help. Did you use signal or the recommended sigaction? How did you implement the signals and signal handlers? Add the MCRE at the bottom of your question (don't erase what you have so far) Commented Oct 26, 2024 at 7:18
  • 1
    There's no need to edit the question to say you found a solution. Posting an answer serves that purpose. Commented Oct 26, 2024 at 17:58

2 Answers 2

1

refer to The GNU C Library, 17.4.10 Noncanonical Input

In the terminal’s c_lflag and c_iflag, probably can try to use noncanonical input, and turns off most processing to give an unmodified channel to the terminal.

tNew.c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN);
tNew.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON);
Sign up to request clarification or add additional context in comments.

Comments

0

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

7 Comments

This answer is not very meaningful if the code in the question doesn't have the relevant #ifdef. Show the actual original code in the question, and put the fix in the answer.
Good job. I'm glad you solved your problem. (funny how that happens sometimes when you go back over your code having a chat with the duck... see: How to debug small programs to understand the duck reference :) If you do a good write-up, I'm happy to come back by and vote for a good answer.
@Barmar The original code is extremely long, as many input 'strings' are handled (regular ASCII, Ctrl+letter, Backspace, Del, Esc, ...), both for Windows and Linux with separate switch statements. But I now added a short summary of what was wrong.
@RobinLe We don't need the full code, but a minimal reproducible example that shows what it was doing wrong.
@Barmar But that's exactly what I did now, right? 😅
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.