Search code examples
cstdinncurses

How to correctly read stdin in C?


I want to write a app like sgtpep/pmenu in C.

Then I start to look at the ncurses library. My first try is able to select the menu. Such as when I do ls | ./a.out, it should be able to show all the files, and highlight the first one, when I press UP or DOWN, it will change highlight different item accordingly.

The full code is here.

The program does not receive any keypresses. mvprintw(n+1, 0, "%d\n", ch); inside the loop always print -1.

Then I remove the unrelated code, and get the minimal example.

#include <stdio.h>

char buf[100];

int main(int argc, char **argv) {
    int ch;

    while (fgets(buf, sizeof(buf), stdin)) puts(buf);

    ch = getch();
    printf("%d\n", ch);
    ch = getch();
    printf("%d\n", ch);
    ch = getch();
    printf("%d\n", ch);
    ch = getch();
    printf("%d\n", ch);

    return 0;
}

ch is always -1. I suspect the stdin is not clean, so I use fflush(stdin) after fgets, but it results the same.

So what is the correct way to read from stdin?


UPD1

#include <stdio.h>

char buf[100];

int main(int argc, char **argv) {
    int ch;

    while (fgets(buf, sizeof(buf), stdin)) puts(buf);

    fflush(stdin);

    ch = getchar();
    printf("%d\n", ch);
    ch = getchar();
    printf("%d\n", ch);
    ch = getchar();
    printf("%d\n", ch);
    ch = getchar();
    printf("%d\n", ch);

    return 0;
}

I have modified the program, so it is not related to ncurses anymore, but when running ls | ./a.out, ch keeps showing -1.


UPD2

Use newterm to redirect in and out works.

FILE *fd = fopen("/dev/tty", "r+");
set_term(newterm(NULL, fd, fd)); // instead of initscr()
noecho();
cbreak();
keypad(stdscr, TRUE);

print_menu(cur, n);

while (true) {
    ch = getch();

    if (ch == KEY_UP || ch == 'k') --cur;
    else if (ch == KEY_DOWN || ch == 'j') ++cur;

    cur = (cur + n) % n;

    print_menu(cur, n);
}

endwin();

Solution

  • If you're going to switch from stdin to curses in that way, you'll have to open the terminal device, e.g., /dev/tty when you're done reading the standard input. Once you've opened the terminal, you can initialize curses using newterm (which has parameters for the input/output streams unlike initscr).

    For examples, see the ncurses test-program, or dialog.