Search code examples
cncurses

NCurses stdin redirection


I don't understand why the getch() function returns ERR all the time if I have an application set up in this manner (stripped to the bones):

static char data[DATA_SIZE]; // Very big - covers all input for sure

int main(int argn, char ** argv)
{

  // Slurp the file in
  int length = read(0, &data[0], DATA_SIZE);
  if (length == 0)
  {
    fprintf(stderr, "Nothing to read\n");
    return 0;
  }

  initscr();
  cbreak();
  refresh();
  WINDOW * woutput = newwin(LINES - 1, COLS, 0, 0);
  WINDOW * winput  = newwin(1, COLS, LINES - 1, 0);
  wattron(winput, A_REVERSE);
  keypad(winput, TRUE);

   //print the data buffer into a window

  int c;
  while ((c = wgetch(winput)) != 'q')
  {
  }
}

I run the application in this manner:

./application < path/to/file

But the result of wgetch is always 'ERR'.


Solution

  • Since you have read stdin up to the end of file, wgetch will immediately receive an end of file when it attempts to read. But in any case, you don't want ncurses to use the redirected stdin; you want it to use the terminal.

    One way of doing this is to simply reopen stdin as the terminal before calling initscr:

    #include <stdio.h>
    #include <ncurses.h>
    
    int main(int argn, char ** argv)
    {
      // Just discard all of stdin for a minimal example
      while (fgetc(stdin) != EOF) { }
    
      // See note
      if (!freopen("/dev/tty", "r", stdin)) {
        perror("/dev/tty");
        exit(1);
      }
      initscr();
    
      cbreak();
      refresh();
      WINDOW * woutput = newwin(LINES - 1, COLS, 0, 0);
      WINDOW * winput  = newwin(1, COLS, LINES - 1, 0);
      wattron(winput, A_REVERSE);
      keypad(winput, TRUE);
    
      // Echo input into full screen
      int c;
      while ((c = wgetch(winput)) != 'q') { wechochar(woutput, c); }
      endwin();
      return 0;
    }
    

    If you find freopen hard to follow, you could replace it and the following call to initscr with

    FILE* tty = fopen("/dev/tty", "r");
    if (!tty) { perror("/dev/tty"); exit(1); }
    if (!newterm(NULL, stdout, tty) {
      fprintf(stderr, "%s\n", "Unable to initialise ncurses");
      exit(1);
    }
    

    Note:

    In the above code, I just used /dev/tty as the name of the console, which will probably work in any moderately Posix compliant system. However, Posix also provides an interface which lets you discover the pathname for the current "controlling terminal", and importantly lets you know if there isn't one for your process. See man ctermid for details and an example of use.