Search code examples
cinputncursesgetch

Ncurses flickers when using pipes or redirection


Ncurses flickers when using "unix pipes" and "redirection" for input. That is, it draws fine if I input myself but doesn't when using '|' or '<'.

I thought this might be due to getch() delay modes(no delay, half delay and infinite delay). So I explicitly tried setting nodelay(stdscr, FALSE); but as obvious, it didn't solve it.

This is the minimal working code :

#include <ncurses.h>
#include <stdlib.h>
#include <string.h>

/* Default assumptions */
#define BUFSIZE         100
#define SELINDICATOR    ">>> "
#define MAXITEMS        LINES   /* Decides how many items are shown at a time. By default, it's (number of rows - 1) */

/* Declarations */
static void draw(char **data, short index, short selected);
static void handleInput(short *selected, short index);

int main(int argc, char *argv[]) {

    char buf[BUFSIZE], **data;
    short index = 0, selected = 1;
    size_t curSize = 0;

    /* Get the entries */
    while(fgets(buf, BUFSIZE, stdin)) {

        if(!(data = realloc(data, (curSize += sizeof(char *))))) {
            fprintf(stderr, "error reallocating memory!\n");
            exit(1);
        }

        if(!(data[index] = malloc(BUFSIZE))) {
            fprintf(stderr, "error reallocating memory!\n");
            exit(1);
        }

        strcpy(data[index], buf);
        index++;
    }

    /* Start nCurses */
    initscr();
    noecho();
    nodelay(stdscr, FALSE); // just tryin' it out if it works

    while(1) {

        draw(data, index, selected);
        handleInput(&selected, index);
    }

    /* Quit nCurses */
    endwin();

    /* Free allocated memories */
    for(short i = 0; i < index; i++)
        free(data[i]);
    free(data);

    return 0;
}

void
draw(char **data, short index, short selected) {

    static short posX = strlen(SELINDICATOR), posY; /* posY doesn't need to be static but it makes no difference and looks cleaner */

        /* Clear old garbage */
        clear();
        posY = 0;

        /* Draw line echoing inputs */
        mvaddch(posY, 0, '>');
        posY++;

        /* Draw the entries */
        for(short i = 0; posY < COLS && i < index; i++) {

            if(posY == selected) {
                mvprintw(posY, 0, SELINDICATOR);
            }

            mvprintw(posY, posX, "%s", data[i]);
            refresh();
            posY++;
        }

        /* Make the output visible */
        refresh();
}

void
handleInput(short *selected, short numOfEntries) {

    int input = getch();

    /* A whole bunch of other stuff........ */

    endwin();
    exit(0);
}

Much thanks for your efforts!


Solution

  • Ncurses is designed and built as a tool for providing an interactive user interface. To the extent that it reads input from from the standard input (as opposed to directly from the terminal), it is possible for an ncurses-based program to have its input redirected from a file or pipe, but it's unclear why it would be important to actually display the UI in that case. If doing so causes unwanted visual effects then the easiest mitigation might be to disable the UI in that case.

    In the program presented in the question, it appears that displaying the UI is cleanly separated from reading and processing input, and that reading input relies only minimally on ncurses. It should be very straightforward to modify such a program to enable it to switch between UI and no-UI modes, and my recommendation is that you do so. To that end, you may find the isatty() function useful for determining whether the standard input (and / or standard output) is a terminal.