0

I'm writing a VT100 terminal engine but input handling is somehow tricky. First I initialize my tty with no echoing.

static void init_tty() {
    if (tcgetattr(STDIN_FILENO, &ctty) != 0)
        throw std::runtime_error("can not get tty attribute");

    ctty.c_lflag &= ~(FLAG(ICANON) | FLAG(ECHO) | FLAG(ISIG));

    if (tcsetattr(STDIN_FILENO, TCSANOW, &ctty) != 0)
        throw std::runtime_error("can not set tty attribute");
}

Next I initialize a non-blocking keyboard file descriptor.

static void init_keyboard(const char *device) {
    if ((kfd = open(device, FLAG(O_RDONLY) | FLAG(O_SYNC))) == -1)
        throw std::runtime_error("can not open keyboard fd");

    unsigned flags = fcntl(kfd, F_GETFL, 0);

    flags |= FLAG(O_NONBLOCK);

    if (fcntl(kfd, F_SETFL, flags) == -1)
        throw std::runtime_error("can not set keyboard fd flags");
}

So far so good. The main loop is just polling events and will break if pressing ESC.

int main() {
    while (true) {
        read(kfd, evt, sizeof(input_event));

        if (evt->type == EV_KEY && evt->value == 1 && evt->code == KEY_ESC)
            break;
    }
}

So I type some characters. Meanwhile my terminal is buffering all characters that I am typing in. After the program exits all characters got displayed.

So here my question: How can I disable terminal output stream buffering while my program is running?

Here is a GIF to illustrate the problem.

Steps:

  • sudo ./clac
  • engine is running but not echoing; typing some characters
  • pressing ESC
  • typed in characters got displayed; program exit

enter image description here

Michael
  • 177
  • 2
  • 15
  • 1
    Why are you using `read` instead of `fgetc`? `read` with a nonblocking file descriptor will issue the error `EAGAIN` (either or `EWOULDBLOCK`) if you are reading from a file other than a socket and the read would block. (which given it would be attempting to read `sizeof(input_event)` bytes seems likely). I would have to dig further to determine the exact behavior as you have the terminal and file descriptor set. All can be avoided simply by reading with `fgetc` in non-cannonical mode. – David C. Rankin Jun 27 '19 at 00:03
  • I can't tell from your question what your desired outcome is: a) discarding typed-in characters, or b) immediately displaying typed-in characters. If b), how does your program attempt to display them? – Armali Jun 27 '19 at 07:03
  • fgetc seems to work but i cant handle specials keys like 'ESC' with it, besides there is no way of checking if a certain character is pressed or not. @Armali its a) discarding typed-in characters. The program should not display typed keys thats why i turned of echoing but in the background the keys get buffered. and when the program exits those characters got printed. like i have to flush some buffers befor exit. – Michael Jun 27 '19 at 11:10

2 Answers2

0

The program should not display typed keys thats why i turned of echoing but in the background the keys get buffered. and when the program exits those characters got printed. like i have to flush some buffers befor exit.

This can be accomplished by calling

    tcflush(STDIN_FILENO, TCIFLUSH);

just before exiting; see tcflush(3) - Linux man page.

Armali
  • 18,255
  • 14
  • 57
  • 171
0

I've just solve my problem. (https://unix.stackexchange.com/questions/126974/where-do-i-find-ioctl-eviocgrab-documented)

So I am grabbing all events immediately after my program starts including ENTER

Example: sudo ./clac ENTER

The solution was to add 1000 milliseconds delay at the start and then GRAB the fd events with

std::this_thread::sleep_for(std::chrono::seconds(1));

if (ioctl(kfd, EVIOCGRAB, FD_GRAB) == -1)
    throw std::runtime_error("can not set keyboard fd grab");

And when the program exits UNGRAB the fd with

if (ioctl(kfd, EVIOCGRAB, FD_UNGRAB) == -1)
    throw std::runtime_error("can not set keyboard fd ungrab");
Michael
  • 177
  • 2
  • 15