From 37b0e416c04851a4be20d3ed427f645bdb8f2c49 Mon Sep 17 00:00:00 2001 From: nick black Date: Wed, 4 Dec 2019 22:53:20 -0500 Subject: [PATCH] get everything prepped for reliable keyboard escapes #78 --- README.md | 2 - include/notcurses.h | 26 ++++++++-- src/demo/panelreel.c | 2 +- src/input/input.cpp | 11 +++- src/lib/input.c | 117 +++++++++++++++++++++++++++---------------- src/lib/internal.h | 2 +- 6 files changed, 108 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index b6608622b..1af04fc0b 100644 --- a/README.md +++ b/README.md @@ -783,8 +783,6 @@ to implement". * There is no concept of subwindows which share memory with their parents. * There is no tracing functionality ala `trace(3NCURSES)`. Superior external tracing solutions exist, such as `bpftrace`. -* There is no timeout functionality for input (`timeout()`, `halfdelay()`, etc.). - Roll your own with any of the four thousand ways to do it. ## Environment notes diff --git a/include/notcurses.h b/include/notcurses.h index 5982759af..cb84083db 100644 --- a/include/notcurses.h +++ b/include/notcurses.h @@ -6,6 +6,7 @@ #include #include #include +#include #include #ifdef __cplusplus @@ -157,10 +158,27 @@ typedef enum { // FIXME... } ncspecial_key; -API int notcurses_getc(const struct notcurses* n, cell* c, - ncspecial_key* special); -API int notcurses_getc_blocking(const struct notcurses* n, cell* c, - ncspecial_key* special); +// See ppoll(2) for more detail. Provide a NULL 'ts' to block at lenghth, a 'ts' +// of 0 for non-blocking operation, and otherwise a timespec to bound blocking. +// Signals in sigmask (less several we handle internally) will be atomically +// masked and unmasked per ppoll(2). It should generally contain all signals. +API int notcurses_getc(struct notcurses* n, cell* c, ncspecial_key* special, + const struct timespec* ts, sigset_t* sigmask); + +static inline int +notcurses_getc_nblock(struct notcurses* n, cell* c, ncspecial_key* nkey){ + sigset_t sigmask; + sigfillset(&sigmask); + struct timespec ts = { .tv_sec = 0, .tv_nsec = 0 }; + return notcurses_getc(n, c, nkey, &ts, &sigmask); +} + +static inline int +notcurses_getc_blocking(struct notcurses* n, cell* c, ncspecial_key* nkey){ + sigset_t sigmask; + sigemptyset(&sigmask); + return notcurses_getc(n, c, nkey, NULL, &sigmask); +} // Refresh our idea of the terminal's dimensions, reshaping the standard plane // if necessary. Without a call to this function following a terminal resize diff --git a/src/demo/panelreel.c b/src/demo/panelreel.c index 708b83585..a9ca922ee 100644 --- a/src/demo/panelreel.c +++ b/src/demo/panelreel.c @@ -219,7 +219,7 @@ handle_input(struct notcurses* nc, struct panelreel* pr, int efd, fprintf(stderr, "Error polling on stdin/eventfd (%s)\n", strerror(errno)); }else{ if(fds[0].revents & POLLIN){ - key = notcurses_getc(nc, c, special); + key = notcurses_getc_blocking(nc, c, special); if(key < 0){ return -1; } diff --git a/src/input/input.cpp b/src/input/input.cpp index 76d385723..0f2be7ba0 100644 --- a/src/input/input.cpp +++ b/src/input/input.cpp @@ -23,6 +23,14 @@ const char* nckeystr(ncspecial_key key){ } } +// print the utf8 Control Pictures for otherwise unprintable chars +wchar_t printutf8(int kp){ + if(kp <= 27 && kp >= 0){ + return 0x2400 + kp; + } + return kp; +} + int main(void){ if(setlocale(LC_ALL, "") == nullptr){ return EXIT_FAILURE; @@ -57,7 +65,8 @@ int main(void){ special, special, nckeystr(special)) < 0){ break; } - }else if(ncplane_printf(n, "Got UTF-8: [0x%04x (%04d)] '%c'\n", kp, kp, kp) < 0){ + }else if(ncplane_printf(n, "Got ASCII: [0x%02x (%03d)] '%lc'\n", + kp, kp, isprint(kp) ? kp : printutf8(kp)) < 0){ break; } }else{ diff --git a/src/lib/input.c b/src/lib/input.c index 9b8098b71..30a8bf714 100644 --- a/src/lib/input.c +++ b/src/lib/input.c @@ -1,79 +1,110 @@ #include #include "internal.h" +static const unsigned char ESC = 0x1b; // 27 + sig_atomic_t resize_seen = 0; +static inline int +pop_input_keypress(notcurses* nc){ + int candidate = nc->inputbuf[nc->inputbuf_valid_starts]; +fprintf(stderr, "DEOCCUPY: %u@%u read: %d\n", nc->inputbuf_occupied, nc->inputbuf_valid_starts, nc->inputbuf[nc->inputbuf_valid_starts]); + if(++nc->inputbuf_valid_starts == sizeof(nc->inputbuf) / sizeof(*nc->inputbuf)){ + nc->inputbuf_valid_starts = 0; + } + --nc->inputbuf_occupied; + return candidate; +} + // add the keypress we just read to our input queue (assuming there is room). // if there is a full UTF8 codepoint or keystroke (composed or otherwise), // return it, and pop it from the queue. static int -handle_getc(const notcurses* nc __attribute__ ((unused)), cell* c, int kpress, - ncspecial_key* special){ +handle_getc(notcurses* nc, cell* c, int kpress, ncspecial_key* special){ fprintf(stderr, "KEYPRESS: %d\n", kpress); if(kpress < 0){ return -1; } - // FIXME add it to the queue, then consult queue + if(kpress == ESC){ +fprintf(stderr, "ESCAPE OH SHIT\n"); + // FIXME delay a little waiting for more? + while(nc->inputbuf_occupied){ + int candidate = pop_input_keypress(nc); + // FIXME walk trie via candidate, exiting (and ungetc()ing) on failure +fprintf(stderr, "CANDIDATE: %c\n", candidate); + } + } *special = 0; - if(kpress == 0x04){ // ctrl-d + if(kpress == 0x04){ // ctrl-d, treated as EOF return -1; } - // FIXME look for keypad if(kpress < 0x80){ c->gcluster = kpress; }else{ - // FIXME + // FIXME load up zee utf8 } return 1; } -int notcurses_getc(const notcurses* nc, cell* c, ncspecial_key* special){ +// blocks up through ts (infinite with NULL ts), returning number of events +// (0 on timeout) or -1 on error/interruption. +static int +block_on_input(FILE* fp, const struct timespec* ts, sigset_t* sigmask){ + struct pollfd pfd = { + .fd = fileno(fp), + .events = POLLIN | POLLRDHUP, + .revents = 0, + }; + sigdelset(sigmask, SIGWINCH); + sigdelset(sigmask, SIGINT); + sigdelset(sigmask, SIGQUIT); + sigdelset(sigmask, SIGSEGV); + return ppoll(&pfd, 1, ts, sigmask); +} + +static bool +input_queue_full(const notcurses* nc){ + return nc->inputbuf_occupied == sizeof(nc->inputbuf) / sizeof(*nc->inputbuf); +} + +static int +handle_input(notcurses* nc, cell* c, ncspecial_key* special){ + int r; + c->gcluster = 0; + // getc() returns unsigned chars cast to ints + while(!input_queue_full(nc) && (r = getc(nc->ttyinfp)) >= 0){ + nc->inputbuf[nc->inputbuf_write_at] = (unsigned char)r; +fprintf(stderr, "OCCUPY: %u@%u read: %d\n", nc->inputbuf_occupied, nc->inputbuf_write_at, nc->inputbuf[nc->inputbuf_write_at]); + if(++nc->inputbuf_write_at == sizeof(nc->inputbuf) / sizeof(*nc->inputbuf)){ + nc->inputbuf_write_at = 0; + } + ++nc->inputbuf_occupied; + } + // highest priority is resize notifications, since they don't queue if(resize_seen){ resize_seen = 0; - c->gcluster = 0; *special = NCKEY_RESIZE; return 1; } - // FIXME check queue - int r = getc(nc->ttyinfp); - if(r < 0){ - return r; + // if there was some error in getc(), we still dole out the existing queue + if(nc->inputbuf_occupied == 0){ + return -1; } + r = pop_input_keypress(nc); return handle_getc(nc, c, r, special); } -// we set our infd to non-blocking on entry, so to do a blocking call (without -// burning cpu) we'll need to set up a poll(). -int notcurses_getc_blocking(const notcurses* nc, cell* c, ncspecial_key* special){ - struct pollfd pfd = { - .fd = fileno(nc->ttyinfp), - .events = POLLIN | POLLRDHUP, - .revents = 0, - }; - int pret, r; - sigset_t smask; - sigfillset(&smask); - sigdelset(&smask, SIGWINCH); - sigdelset(&smask, SIGINT); - sigdelset(&smask, SIGQUIT); - sigdelset(&smask, SIGSEGV); - while((pret = ppoll(&pfd, 1, NULL, &smask)) >= 0){ - if(pret == 0){ - continue; - } - r = notcurses_getc(nc, c, special); - if(r < 0){ - break; // want EINTR handling below - } +// infp has always been set non-blocking +int notcurses_getc(notcurses* nc, cell* c, ncspecial_key* special, + const struct timespec *ts, sigset_t* sigmask){ + errno = 0; + int r = handle_input(nc, c, special); + if(r > 0){ return r; } - if(errno == EINTR){ - if(resize_seen){ - resize_seen = 0; - c->gcluster = 0; - *special = NCKEY_RESIZE; - return 1; - } + if(errno == EAGAIN || errno == EWOULDBLOCK){ + block_on_input(nc->ttyinfp, ts, sigmask); + r = handle_input(nc, c, special); } - return -1; + return r; } diff --git a/src/lib/internal.h b/src/lib/internal.h index 45b9b76a8..03279b75d 100644 --- a/src/lib/internal.h +++ b/src/lib/internal.h @@ -112,7 +112,7 @@ typedef struct notcurses { ncplane* top; // the contents of our topmost plane (initially entire screen) ncplane* stdscr;// aliases some plane from the z-buffer, covers screen FILE* renderfp; // debugging FILE* to which renderings are written - char inputbuf[BUFSIZ]; + unsigned char inputbuf[BUFSIZ]; // we keep a wee ringbuffer of input queued up for delivery. if // inputbuf_occupied == sizeof(inputbuf), there is no room. otherwise, data // can be read to inputbuf_write_at until we fill up. the first datum