I'm trying to write a program using ncurses that polls various file descriptors in the main loop. I managed to trim it down to an example with a single file descriptor, and this expected behavior:
- There are two windows, managed by the panel library.
- Pressing space moves the cursor right, wrapping around when it reaches the end of the first window.
- Pressing 't' changes the text in the top left corner.
- Pressing 'q' quits the program.
Code:
#include <stdio.h>
#include <stdlib.h>
#include <panel.h>
#include <unistd.h>
#include <poll.h>
#include <sys/eventfd.h>
#define POLL_STDIN
int main() {
int event_fd, cursor_x, cursor_y, ch, n_fds;
bool trigd;
uint64_t event;
WINDOW *win_a, *win_b;
PANEL *panel_a, *panel_b;
event_fd = eventfd(0, 0);
if (event_fd < 0) {
perror("eventfd");
exit(1);
}
struct pollfd poll_fds[2];
poll_fds[0].fd = STDIN_FILENO;
#ifdef POLL_STDIN
poll_fds[0].events = POLLIN;
#else
poll_fds[0].events = 0;
#endif
poll_fds[1].fd = event_fd;
poll_fds[1].events = POLLIN;
initscr();
halfdelay(1);
noecho();
win_a = newwin(10, 10, 0, 0);
win_b = newwin(10, 10, 0, 10);
panel_a = new_panel(win_a);
panel_b = new_panel(win_b);
cursor_x = 0;
cursor_y = 0;
wmove(win_a, cursor_y, cursor_x);
do {
for (int i=0; i<10; ++i) {
for (int j=0; j<10; ++j) {
mvwaddch(win_a, i, j, '.');
mvwaddch(win_b, i, j, '_');
}
}
mvwprintw(win_a, 0, 0, trigd ? "foo" : "bar");
update_panels();
doupdate();
wmove(win_a, cursor_y, cursor_x);
#ifdef POLL_STDIN
n_fds = poll(poll_fds, 2, -1);
#else
n_fds = poll(poll_fds, 2, 0);
#endif
if (n_fds < 0) {
perror("poll");
break;
}
ch = wgetch(win_a);
if (poll_fds[1].revents & POLLIN) {
if (read(event_fd, &event, 8) != 8) {
perror("read");
break;
}
trigd = !trigd;
}
if (' ' == ch) {
cursor_x = (cursor_x + 1) % 10;
} else if ('t' == ch) {
event = 1;
if (write(event_fd, &event, 8) != 8) {
perror("write");
break;
}
}
} while ('q' != ch);
endwin();
return 0;
}
If POLL_STDIN
is not defined, the program works as expected. If it is defined, the program works almost the same, but the cursor is displayed in the lower right corner in window B, and doesn't move. After pressing t
the cursor temporarily moves to the expected position.
I have looked at the program after running the preprocessor and found nothing unexpected, only mvaddch
gets expanded.
I just think that the busy-waiting version is kind of inelegant, and also would like to know why the cursor is displayed in the wrong place after a seemingly unrelated change.
EDIT:
I've figured out that calling getch
is what makes the cursor to show. After using nodelay
on both windows, and calling getch
twice, the program now works in both versions, and the second getch
can be made conditional. Here's the updated code:
#include <stdio.h>
#include <stdlib.h>
#include <panel.h>
#include <unistd.h>
#include <poll.h>
#include <sys/eventfd.h>
int main() {
int event_fd, cursor_x, cursor_y, ch, n_fds;
bool trigd;
uint64_t event;
WINDOW *win_a, *win_b;
PANEL *panel_a, *panel_b;
event_fd = eventfd(0, 0);
if (event_fd < 0) {
perror("eventfd");
exit(1);
}
struct pollfd poll_fds[2];
poll_fds[0].fd = STDIN_FILENO;
poll_fds[0].events = POLLIN;
poll_fds[1].fd = event_fd;
poll_fds[1].events = POLLIN;
initscr();
cbreak();
noecho();
win_a = newwin(10, 10, 0, 0);
win_b = newwin(10, 10, 0, 10);
panel_a = new_panel(win_a);
panel_b = new_panel(win_b);
cursor_x = 0;
cursor_y = 0;
wmove(win_a, cursor_y, cursor_x);
do {
for (int i=0; i<10; ++i) {
for (int j=0; j<10; ++j) {
mvwaddch(win_a, i, j, '.');
mvwaddch(win_b, i, j, '_');
}
}
mvwprintw(win_a, 0, 0, trigd ? "foo" : "bar");
update_panels();
doupdate();
wmove(win_a, cursor_y, cursor_x);
wrefresh(win_a);
n_fds = poll(poll_fds, 2, -1);
if (n_fds < 0) {
perror("poll");
break;
}
if (poll_fds[0].revents & POLLIN) {
ch = wgetch(win_a);
}
if (poll_fds[1].revents & POLLIN) {
if (read(event_fd, &event, 8) != 8) {
perror("read");
break;
}
trigd = !trigd;
}
if (' ' == ch) {
cursor_x = (cursor_x + 1) % 10;
} else if ('t' == ch) {
event = 1;
if (write(event_fd, &event, 8) != 8) {
perror("write");
break;
}
}
} while ('q' != ch);
endwin();
return 0;
}
I'm not sure if this is the best way to show the cursor, but I'll post it as the answer if no one else posts one.
EDIT2:
Edited code for posterity after @Thomas Dickey's comment.