How to read from a pipe while keeping interactive keyboard in C
I'm writing a similar version of less and am using ncurses to make it easier. However, it needs to read from stdin when data is piped-in while maintaining the ability to interact with the keyboard such as "Move Up" and "Move Down".
The issue I'm having is that the call to fgets freezes the whole interactive program when using tail -f. e.g.,
Code:
tail -f logfile |./app
tail -f logfile | grep cron |./app
Note that it works fine when using cat, but not tail -f.
Is there a way to read from stdin (from a pipe) while keep the interactive keys enabled without affecting its functionality?
Would I need to create a named pipe to pipe tail into it, and my program read from it like a file and still have access to stdin via getch?
Code:
// gcc app.c -lncurses
#include <stdio.h>
#include <ncurses.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
void print_status(WINDOW * win, const char *str) {
init_pair(1, COLOR_RED, COLOR_WHITE);
clear();
wattron(win, COLOR_PAIR(1));
mvwhline(win, 0, 0, ' ', COLS);
mvwaddnstr(win, 0, 0, str, COLS);
wattroff(win, COLOR_PAIR(1));
wrefresh(win);
}
void print_line(WINDOW * win, const char *str, int *i) {
init_pair(2, COLOR_RED, COLOR_BLACK);
if (*i > LINES - 2) {
*i = 1;
wclear(win);
}
wattron(win, COLOR_PAIR(2));
mvwprintw(win, (*i)++, 0, str);
wattroff(win, COLOR_PAIR(2));
wrefresh(win);
}
#define TTY "/dev/tty"
static int open_terminal(char **result, int mode) {
const char *device = TTY;
*result = strdup(device);
return open(device, mode);
}
int main() {
WINDOW *menu_win, *tail;
int i = 1, q = 1, fd1, fd2, is_atty = 1;
char buf[1024];
initscr();
clear();
noecho();
halfdelay(5);
start_color();
nonl();
keypad(stdscr, TRUE);
intrflush(stdscr, FALSE);
menu_win = newwin(1, COLS, 0, 0);
tail = newwin(LINES - 1, COLS, 1, 0);
keypad(menu_win, TRUE);
char *device = NULL;
FILE *pipe_input = stdin;
if(!isatty(fileno(stdin))) {
is_atty = 0;
if((fd1 = open_terminal(&device, O_RDONLY)) >= 0) {
if((fd2 = dup(fileno(stdin))) >= 0) {
pipe_input = fdopen(fd2, "r");
if(freopen(device, "r", stdin) == 0)
perror("cannot open tty-input");
if(fileno(stdin) != 0) /* some functions may read fd #0 */
(void)dup2(fileno(stdin), 0);
}
}
}
// freopen("/dev/tty", "r", stdin);
print_status(menu_win, "Use arrow keys to go up and down");
while(q) {
switch (wgetch(menu_win)) {
case KEY_UP:
print_status(menu_win, "Moved up");
break;
case KEY_DOWN:
print_status(menu_win, "Moved down");
break;
case 'q':
q = 0;
break;
default:
if(is_atty)
break;
while (fgets(buf, 1024, pipe_input) != NULL) {
print_line(tail, buf, &i);
}
break;
}
}
endwin();
return 0;
}