From cab19cf790cd278ca5afd1b53fe28062c06f733c Mon Sep 17 00:00:00 2001 From: nick black Date: Tue, 25 Aug 2020 02:54:46 -0400 Subject: [PATCH] Cursor work (placement, drop RETAIN_CURSOR) #953 notcurses_enable_cursor() now accepts placement arguments. both it and notcurses_disable_cursor() now return int rather than void. add notcurses_cursor_move_yx(). --- NEWS.md | 7 ++++ USAGE.md | 14 +++++-- doc/man/man3/notcurses_init.3.md | 16 ++++---- include/ncpp/NotCurses.hh | 13 +++++-- include/notcurses/notcurses.h | 18 ++++++--- python/src/notcurses/build_notcurses.py | 3 ++ python/src/notcurses/notcurses.py | 1 - src/lib/internal.h | 5 ++- src/lib/notcurses.c | 25 ++---------- src/lib/render.c | 51 ++++++++++++++++++++++++- src/poc/reader.cpp | 5 ++- 11 files changed, 112 insertions(+), 46 deletions(-) diff --git a/NEWS.md b/NEWS.md index 63da8a194..46099e3a3 100644 --- a/NEWS.md +++ b/NEWS.md @@ -6,6 +6,13 @@ rearrangements of Notcurses. functions which once returned `nc_err_e` now return a bimodal `int`. Those functions which accepted a value-result `nc_err_e*` no longer take this argument. + * `notcurses_cursor_move_yx()` has been added for placement of the terminal + cursor. Remember, you must call `notcurses_cursor_enable()` before it will + be made visible. + * `notcurses_cursor_enable()` now takes two `int` parameters specifying the + desired location of the cursor. Both `notcurses_cursor_enable()` and + `notcurses_cursor_disable()` now return `int` rather than `void`. + * `NCOPTION_RETAIN_CURSOR` has been removed. * 1.6.17 (2020-08-22) * `ncdirect_flush()` now takes a `const struct ncdirect*`. diff --git a/USAGE.md b/USAGE.md index 6412d0e15..00d1aa3ed 100644 --- a/USAGE.md +++ b/USAGE.md @@ -99,10 +99,6 @@ typedef enum { // registration of these signal handlers. #define NCOPTION_NO_QUIT_SIGHANDLERS 0x0008 -// By default, we hide the cursor if possible. This flag inhibits use of -// the civis capability, retaining the cursor. -#define NCOPTION_RETAIN_CURSOR 0x0010 - // Notcurses typically prints version info in notcurses_init() and performance // info in notcurses_stop(). This inhibits that output. #define NCOPTION_SUPPRESS_BANNERS 0x0020 @@ -231,6 +227,16 @@ notcurses_term_dim_yx(const struct notcurses* n, int* restrict rows, // NCKEY_RESIZE event has been read and you're not ready to render. int notcurses_refresh(struct notcurses* n, int* restrict y, int* restrict x); +// Enable or disable the terminal's cursor, if supported. Immediate effect. +void notcurses_cursor_enable(struct notcurses* nc); +void notcurses_cursor_disable(struct notcurses* nc); + +// Move the terminal cursor to the specified location. If 'y' or 'x' is +// negative, there is no movement along that axis. Returns error if the +// coordinates are outside the viewing area. The cursor must be explicitly +// enabled with notcurses_cursor_enable() to be seen. +int notcurses_cursor_move_yx(struct notcurses* nc, int y, int x); + // Returns a 16-bit bitmask in the LSBs of supported curses-style attributes // (NCSTYLE_UNDERLINE, NCSTYLE_BOLD, etc.) The attribute is only // indicated as supported if the terminal can support it together with color. diff --git a/doc/man/man3/notcurses_init.3.md b/doc/man/man3/notcurses_init.3.md index f5b4dc67f..1dfbeb0a4 100644 --- a/doc/man/man3/notcurses_init.3.md +++ b/doc/man/man3/notcurses_init.3.md @@ -15,7 +15,6 @@ notcurses_init - initialize a notcurses instance #define NCOPTION_VERIFY_SIXEL 0x0002ull #define NCOPTION_NO_WINCH_SIGHANDLER 0x0004ull #define NCOPTION_NO_QUIT_SIGHANDLERS 0x0008ull -#define NCOPTION_RETAIN_CURSOR 0x0010ull #define NCOPTION_SUPPRESS_BANNERS 0x0020ull #define NCOPTION_NO_ALTERNATE_SCREEN 0x0040ull #define NCOPTION_NO_FONT_CHANGES 0x0080ull @@ -47,6 +46,12 @@ typedef struct notcurses_options { **struct notcurses* notcurses_init(const notcurses_options* opts, FILE* fp);** +**int notcurses_cursor_enable(struct notcurses* nc, int y, int x);** + +**int notcurses_cursor_move_yx(struct notcurses* nc, int y, int x);** + +**int notcurses_cursor_disable(struct notcurses* nc);** + # DESCRIPTION **notcurses_init** prepares the terminal for cursor-addressable (multiline) @@ -74,9 +79,9 @@ by setting **NCOPTION_NO_ALTERNATE_SCREEN** in **flags**. Users tend to have strong opinions regarding the alternate screen, so it's often useful to expose this via a command-line option. -notcurses furthermore hides the cursor by default, but **NCOPTION_RETAIN_CURSOR** -can prevent this (the cursor can be dynamically enabled or disabled during -execution via **notcurses_cursor_enable(3)** and **notcurses_cursor_disable(3)**). +notcurses hides the cursor by default. It can be dynamically enabled or +disabled during execution via **notcurses_cursor_enable(3)** and +**notcurses_cursor_disable(3)**, and moved with **notcurses_cursor_move_yx()**. **notcurses_init** typically emits some diagnostics at startup, including version information and some details of the configured terminal. This can be inhibited @@ -123,9 +128,6 @@ zero. The following flags are defined: cleaning up the terminal on such exceptions. With this flag, the handler will not be installed. -* **NCOPTION_RETAIN_CURSOR**: Notcurses typically disables the cursor on - startup. With this flag, the cursor will be left enabled. - * **NCOPTION_SUPPRESS_BANNERS**: Disables the diagnostics and version information printed on startup, and the performance summary on exit. diff --git a/include/ncpp/NotCurses.hh b/include/ncpp/NotCurses.hh index 9571ba643..d8f1d85a8 100644 --- a/include/ncpp/NotCurses.hh +++ b/include/ncpp/NotCurses.hh @@ -151,14 +151,19 @@ namespace ncpp return notcurses_cantruecolor (nc); } - void cursor_enable () const noexcept + int cursor_enable (int y, int x) const noexcept { - notcurses_cursor_enable (nc); + return error_guard (notcurses_cursor_enable (nc, y, x), -1); } - void cursor_disable () const noexcept + int cursor_disable () const noexcept { - notcurses_cursor_disable (nc); + return error_guard (notcurses_cursor_disable (nc), -1); + } + + int cursor_move_yx (int y, int x) const noexcept + { + return error_guard (notcurses_cursor_move_yx (nc, y, x), -1); } void get_stats (ncstats *stats) const noexcept diff --git a/include/notcurses/notcurses.h b/include/notcurses/notcurses.h index 4e4ebf9a4..fac58601c 100644 --- a/include/notcurses/notcurses.h +++ b/include/notcurses/notcurses.h @@ -781,9 +781,7 @@ typedef enum { // registration of these signal handlers. #define NCOPTION_NO_QUIT_SIGHANDLERS 0x0008ull -// By default, we hide the cursor if possible. This flag inhibits use of -// the civis capability, retaining the cursor. -#define NCOPTION_RETAIN_CURSOR 0x0010ull +// NCOPTION_RETAIN_CURSOR was removed in 1.6.18. It ought be repurposed. FIXME. // Notcurses typically prints version info in notcurses_init() and performance // info in notcurses_stop(). This inhibits that output. @@ -2570,9 +2568,17 @@ bprefix(uintmax_t val, uintmax_t decimal, char* buf, int omitdec){ return ncmetric(val, decimal, buf, omitdec, 1024, 'i'); } -// Enable or disable the terminal's cursor, if supported. Immediate effect. -API void notcurses_cursor_enable(struct notcurses* nc); -API void notcurses_cursor_disable(struct notcurses* nc); +// Enable or disable the terminal's cursor, if supported, placing it at +// 'y', 'x'. Immediate effect (no need for a call to notcurses_render()). +// It is an error if 'y', 'x' lies outside the standard plane. +API int notcurses_cursor_enable(struct notcurses* nc, int y, int x); +API int notcurses_cursor_disable(struct notcurses* nc); + +// Move the terminal cursor to the specified location. If 'y' or 'x' is +// negative, there is no movement along that axis. Returns error if the +// coordinates are outside the viewing area. The cursor must be explicitly +// enabled with notcurses_cursor_enable() to be seen. +API int notcurses_cursor_move_yx(struct notcurses* nc, int y, int x); // Palette API. Some terminals only support 256 colors, but allow the full // palette to be specified with arbitrary RGB colors. In all cases, it's more diff --git a/python/src/notcurses/build_notcurses.py b/python/src/notcurses/build_notcurses.py index ce174a3e9..f031c3e15 100644 --- a/python/src/notcurses/build_notcurses.py +++ b/python/src/notcurses/build_notcurses.py @@ -431,6 +431,9 @@ bool ncdirect_canutf8(const struct ncdirect* n); int ncdirect_render_image(struct ncdirect* n, const char* filename, ncalign_e align, ncblitter_e blitter, ncscale_e scale); struct ncplane* ncplane_parent(struct ncplane* n); const struct ncplane* ncplane_parent_const(const struct ncplane* n); +int notcurses_cursor_enable(struct notcurses* nc, int y, int x); +int notcurses_cursor_move_yx(struct notcurses* nc, int y, int x); +int notcurses_cursor_disable(struct notcurses* nc); """) if __name__ == "__main__": diff --git a/python/src/notcurses/notcurses.py b/python/src/notcurses/notcurses.py index 307e5cdc9..5b82f1ccb 100755 --- a/python/src/notcurses/notcurses.py +++ b/python/src/notcurses/notcurses.py @@ -14,7 +14,6 @@ NCOPTION_INHIBIT_SETLOCALE = 0x0001 NCOPTION_VERIFY_SIXEL = 0x0002 NCOPTION_NO_WINCH_SIGHANDLER = 0x0004 NCOPTION_NO_QUIT_SIGHANDLERS = 0x0008 -NCOPTION_RETAIN_CURSOR = 0x0010 NCOPTION_SUPPRESS_BANNERS = 0x0020 NCOPTION_NO_ALTERNATE_SCREEN = 0x0040 NCOPTION_NO_FONT_CHANGES = 0x0080 diff --git a/src/lib/internal.h b/src/lib/internal.h index 8a6599f92..4ce22bcb2 100644 --- a/src/lib/internal.h +++ b/src/lib/internal.h @@ -101,7 +101,7 @@ typedef struct renderstate { // the current cursor position. this is independent of whether the cursor is // visible. it is the cell at which the next write will take place. this is - // modified by: output, cursor moves, clearing the screen (during refresh) + // modified by: output, cursor moves, clearing the screen (during refresh). int y, x; uint32_t curattr;// current attributes set (does not include colors) @@ -275,6 +275,9 @@ typedef struct notcurses { int lfdimy; // lfdimx/lfdimy are 0 until first render egcpool pool; // duplicate EGCs into this pool + int cursory; // desired cursor placement according to user. -1 is a don't- + int cursorx; // care, otherwise moved here after each render. + ncstats stats; // some statistics across the lifetime of the notcurses ctx ncstats stashstats; // cumulative stats, unaffected by notcurses_reset_stats() diff --git a/src/lib/notcurses.c b/src/lib/notcurses.c index 65bfadcc7..083a360fc 100644 --- a/src/lib/notcurses.c +++ b/src/lib/notcurses.c @@ -859,6 +859,7 @@ notcurses* notcurses_init(const notcurses_options* opts, FILE* outfp){ ret->margin_b = opts->margin_b; ret->margin_l = opts->margin_l; ret->margin_r = opts->margin_r; + ret->cursory = ret->cursorx = -1; ret->stats.fbbytes = 0; ret->stashstats.fbbytes = 0; reset_stats(&ret->stats); @@ -966,11 +967,9 @@ notcurses* notcurses_init(const notcurses_options* opts, FILE* outfp){ free_plane(ret->top); goto err; } - if(!(opts->flags & NCOPTION_RETAIN_CURSOR)){ - if(ret->tcache.civis && tty_emit("civis", ret->tcache.civis, ret->ttyfd)){ - free_plane(ret->top); - goto err; - } + if(ret->tcache.civis && tty_emit("civis", ret->tcache.civis, ret->ttyfd)){ + free_plane(ret->top); + goto err; } } if((ret->rstate.mstreamfp = open_memstream(&ret->rstate.mstream, &ret->rstate.mstrsize)) == NULL){ @@ -1885,22 +1884,6 @@ void ncplane_erase(ncplane* n){ n->y = n->x = 0; } -void notcurses_cursor_enable(notcurses* nc){ - if(nc->ttyfd >= 0){ - if(nc->tcache.cnorm){ - tty_emit("cnorm", nc->tcache.cnorm, nc->ttyfd); - } - } -} - -void notcurses_cursor_disable(notcurses* nc){ - if(nc->ttyfd >= 0){ - if(nc->tcache.civis){ - tty_emit("civis", nc->tcache.civis, nc->ttyfd); - } - } -} - ncplane* notcurses_top(notcurses* n){ return n->top; } diff --git a/src/lib/render.c b/src/lib/render.c index e20591c61..b8a2d0425 100644 --- a/src/lib/render.c +++ b/src/lib/render.c @@ -968,7 +968,7 @@ int notcurses_refresh(notcurses* nc, int* restrict dimy, int* restrict dimx){ return 0; } -int notcurses_render_to_file(struct notcurses* nc, FILE* fp){ +int notcurses_render_to_file(notcurses* nc, FILE* fp){ if(nc->lfdimx == 0 || nc->lfdimy == 0){ return 0; } @@ -1101,3 +1101,52 @@ int ncdirect_fg(ncdirect* nc, unsigned rgb){ nc->fgrgb = rgb; return 0; } + +int notcurses_cursor_enable(notcurses* nc, int y, int x){ + if(y < 0 || x < 0){ + logerror(nc, "Illegal cursor placement: %d, %d\n", y, x); + return -1; + } + if(y >= nc->stdplane->leny || x >= nc->stdplane->lenx){ + logerror(nc, "Illegal cursor placement: %d, %d\n", y, x); + return -1; + } + if(nc->ttyfd >= 0){ + if(nc->tcache.cnorm){ + if(stage_cursor(nc, nc->ttyfp, y, x) || fflush(nc->ttyfp)){ + return -1; + } + nc->cursory = y; + nc->cursorx = x; + if(tty_emit("cnorm", nc->tcache.cnorm, nc->ttyfd) == 0){ + return 0; + } + } + } + return -1; +} + +int notcurses_cursor_disable(notcurses* nc){ + nc->cursory = -1; + nc->cursorx = -1; + if(nc->ttyfd >= 0){ + if(nc->tcache.civis){ + if(tty_emit("civis", nc->tcache.civis, nc->ttyfd) == 0){ + return 0; + } + } + } + return -1; +} + +int notcurses_cursor_move_yx(notcurses* nc, int y, int x){ + if(nc->cursory >= 0 && nc->cursorx >= 0){ + return -1; + } + if(stage_cursor(nc, nc->ttyfp, y, x) || fflush(nc->ttyfp)){ + return -1; + } + nc->cursory = y; + nc->cursorx = x; + return 0; +} diff --git a/src/poc/reader.cpp b/src/poc/reader.cpp index 3ecd7c907..c24a0c4bd 100644 --- a/src/poc/reader.cpp +++ b/src/poc/reader.cpp @@ -28,6 +28,9 @@ auto main() -> int { if(nr == nullptr){ return EXIT_FAILURE; } + if(!nc.cursor_enable(2 + opts.physrows, 2 + opts.physcols)){ + return EXIT_FAILURE; + } ncinput ni; nc.render(); while(nc.getc(true, &ni) != (char32_t)-1){ @@ -41,7 +44,7 @@ auto main() -> int { ncreader_destroy(nr, &contents); nc.stop(); if(contents){ - printf("%s\n", contents); + printf("input: %s\n", contents); } return EXIT_SUCCESS; }