0

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.

Community
  • 1
  • 1
mjm
  • 11
  • 5
  • Where is the `` and ``? – EsmaeelE Dec 25 '18 at 16:54
  • It is right on the top :) – mjm Dec 25 '18 at 20:17
  • `getch` does a `refresh`, which appears to be the point of this question. – Thomas Dickey Dec 26 '18 at 00:14
  • Possible duplicate of [Refresh Behaviour (nCurses)](https://stackoverflow.com/questions/53251589/refresh-behaviour-ncurses) – Thomas Dickey Dec 26 '18 at 00:15
  • @ThomasDickey thanks! I was confused by the documentation for `update_panels`, which specifically tells the user not to use `refresh` and `wrefresh`, without mentioning that `getch` does it anyway. And `update_panels` by itself doesn't leave the cursor at the position of the last move, as per [this answer to Ncurses place cursor in correct panel](https://stackoverflow.com/a/42637846/10831755). Replacing the dummy `wgetch` with `wrefresh` and removing the `nodelay` indeed works. – mjm Dec 26 '18 at 01:36

1 Answers1

1

I think adding a wrefresh is the right way to show the cursor, judging by this answer (EDIT updated after @Thomas Dickey's comment: wgetch is not necessary). So it boils down to:

nodelay(win_a);           // to make wgetch non-blocking

/* ... */

wrefresh(win_a);          // to show the cursor
poll(poll_fds, 2, -1);
if (poll_fds[0].revents & POLLIN) {
    ch = wgetch(win_a);   // to get the actual character
}
mjm
  • 11
  • 5