From ab47ab8976822e064b1918695251b64b9ed73c74 Mon Sep 17 00:00:00 2001 From: nick black Date: Mon, 23 Dec 2019 02:47:34 -0500 Subject: [PATCH 01/14] notcurses_mouse_on()/_off() #165 --- README.md | 22 ++++++++++++++++++++++ debian/changelog | 7 +++++++ debian/control | 3 ++- include/notcurses.h | 8 ++++++++ src/lib/internal.h | 1 + src/lib/notcurses.c | 22 +++++++++++++++++++--- tests/input.cpp | 38 ++++++++++++++++++++++++++++++++++++++ 7 files changed, 97 insertions(+), 4 deletions(-) create mode 100644 tests/input.cpp diff --git a/README.md b/README.md index 279b2cdf0..8cb52f6e7 100644 --- a/README.md +++ b/README.md @@ -360,6 +360,27 @@ notcurses_getc_blocking(struct notcurses* n){ } ``` +### Mice + +notcurses supports mice, though only through brokers such as X or +[GPM](https://www.nico.schottelius.org/software/gpm/). It does not speak +directly to hardware. Mouse events must be explicitly enabled with a +successful call to `notcurses_mouse_enable()`, and can later be disabled. + +```c +// Enable the mouse in "button-event tracking" mode with focus detection and +// UTF8-style extended coordinates. On failure, -1 is returned. On success, 0 +// is returned, and mouse events will be published to notcurses_getc(). +int notcurses_mouse_enable(struct notcurses* n); + +// Disable mouse events. Any events in the input queue can still be delivered. +int notcurses_mouse_disable(struct notcurses* n); +``` + +"Button-event tracking mode" implies the ability to detect mouse button +presses, and also mouse movement while holding down a mouse button (i.e. to +effect drag-and-drop). + ### Planes Fundamental to notcurses is a z-buffer of rectilinear virtual screens, known @@ -2178,6 +2199,7 @@ up someday **FIXME**. * Linux: [ioctl_tty(2)](http://man7.org/linux/man-pages/man2/ioctl_tty.2.html) * Linux: [ioctl_console(2)](http://man7.org/linux/man-pages/man2/ioctl_console.2.html) * Portable: [terminfo(5)](http://man7.org/linux/man-pages/man5/terminfo.5.html) +* Portable: [user_caps(5)](http://man7.org/linux/man-pages/man5/user_caps.5.html) ### Other TUI libraries of note diff --git a/debian/changelog b/debian/changelog index 43d2e69a3..98fbb1200 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,10 @@ +notcurses (1.0.0) UNRELEASED; urgency=medium + + * notcurses-bin depends on ncurses-term + * libnotcurses0 recommends ncurses-term + + -- Nick Black Mon, 23 Dec 2019 00:57:22 -0500 + notcurses (0.9.2-1) unstable; urgency=medium * New upstream diff --git a/debian/control b/debian/control index 7cac534f1..843680d8b 100644 --- a/debian/control +++ b/debian/control @@ -22,6 +22,7 @@ Description: Development files for notcurses Package: libnotcurses0 Architecture: any Multi-Arch: same +Recommends: ncurses-term (>= 6.1) Depends:${shlibs:Depends}, ${misc:Depends} Description: Shared libraries for notcurses TUI notcurses facilitates the creation of modern TUI programs, @@ -31,7 +32,7 @@ Description: Shared libraries for notcurses TUI Package: notcurses-bin Architecture: any Multi-Arch: foreign -Depends:${shlibs:Depends}, ${misc:Depends} +Depends:${shlibs:Depends}, ${misc:Depends}, ncurses-term (>= 6.1) Description: Binaries from libnotcurses notcurses facilitates the creation of modern TUI programs, making full use of Unicode and 24-bit direct color. It presents diff --git a/include/notcurses.h b/include/notcurses.h index 06a43bb00..62a713cd4 100644 --- a/include/notcurses.h +++ b/include/notcurses.h @@ -250,6 +250,14 @@ notcurses_getc_blocking(struct notcurses* n){ return notcurses_getc(n, NULL, &sigmask); } +// Enable the mouse in "button-event tracking" mode with focus detection and +// UTF8-style extended coordinates. On failure, -1 is returned. On success, 0 +// is returned, and mouse events will be published to notcurses_getc(). +API int notcurses_mouse_enable(struct notcurses* n); + +// Disable mouse events. Any events in the input queue can still be delivered. +API int notcurses_mouse_disable(struct notcurses* n); + // Refresh our idea of the terminal's dimensions, reshaping the standard plane // if necessary. Without a call to this function following a terminal resize // (as signaled via SIGWINCH), notcurses_render() might not function properly. diff --git a/src/lib/internal.h b/src/lib/internal.h index 74e9755f7..facfa3077 100644 --- a/src/lib/internal.h +++ b/src/lib/internal.h @@ -128,6 +128,7 @@ typedef struct notcurses { char* italoff; // CELL_STYLE_ITALIC (disable) char* smkx; // enter keypad transmit mode (keypad_xmit) char* rmkx; // leave keypad transmit mode (keypad_local) + char* getm; // get mouse events bool RGBflag; // terminfo-reported "RGB" flag for 24bpc directcolor bool CCCflag; // terminfo-reported "CCC" flag for palette set capability diff --git a/src/lib/notcurses.c b/src/lib/notcurses.c index c3f2d6a08..afd81eaa3 100644 --- a/src/lib/notcurses.c +++ b/src/lib/notcurses.c @@ -23,6 +23,8 @@ #include "version.h" #include "egcpool.h" +#define ESC "\x1b" + // only one notcurses object can be the target of signal handlers, due to their // process-wide nature. static notcurses* _Atomic signal_nc = ATOMIC_VAR_INIT(NULL); // ugh @@ -571,9 +573,7 @@ interrogate_terminfo(notcurses* nc, const notcurses_options* opts){ // support for the style in that case. int nocolor_stylemask = tigetnum("ncv"); if(nocolor_stylemask > 0){ - // FIXME this doesn't work if we're using sgr, which we are at the moment! - // ncv is defined in terms of curses style bits, which differ from ours - if(nocolor_stylemask & WA_STANDOUT){ + if(nocolor_stylemask & WA_STANDOUT){ // ncv is composed of terminfo bits, not ours nc->standout = NULL; } if(nocolor_stylemask & WA_UNDERLINE){ @@ -595,6 +595,7 @@ interrogate_terminfo(notcurses* nc, const notcurses_options* opts){ nc->italics = NULL; } } + term_verify_seq(&nc->getm, "getm"); // get mouse events // Not all terminals support setting the fore/background independently term_verify_seq(&nc->setaf, "setaf"); term_verify_seq(&nc->setab, "setab"); @@ -1543,3 +1544,18 @@ ncplane* notcurses_top(notcurses* n){ ncplane* ncplane_below(ncplane* n){ return n->z; } + +#define SET_BTN_EVENT_MOUSE "1002" +#define SET_FOCUS_EVENT_MOUSE "1004" +#define SET_EXT_MODE_MOUSE "1005" +int notcurses_mouse_enable(notcurses* n){ + return term_emit("mouse", ESC "[?" SET_BTN_EVENT_MOUSE ";" + SET_FOCUS_EVENT_MOUSE ";" SET_EXT_MODE_MOUSE "h", + n->ttyfp, true); +} + +int notcurses_mouse_disable(notcurses* n){ + return term_emit("mouse", ESC "[?" SET_BTN_EVENT_MOUSE ";" + SET_FOCUS_EVENT_MOUSE ";" SET_EXT_MODE_MOUSE "l", + n->ttyfp, true); +} diff --git a/tests/input.cpp b/tests/input.cpp new file mode 100644 index 000000000..9384ae46b --- /dev/null +++ b/tests/input.cpp @@ -0,0 +1,38 @@ +#include "main.h" + +class InputTest : public :: testing::Test { + protected: + void SetUp() override { + setlocale(LC_ALL, ""); + if(getenv("TERM") == nullptr){ + GTEST_SKIP(); + } + notcurses_options nopts{}; + nopts.inhibit_alternate_screen = true; + nopts.suppress_bannner = true; + outfp_ = fopen("/dev/tty", "wb"); + ASSERT_NE(nullptr, outfp_); + nc_ = notcurses_init(&nopts, outfp_); + ASSERT_NE(nullptr, nc_); + } + + void TearDown() override { + if(nc_){ + EXPECT_EQ(0, notcurses_stop(nc_)); + } + if(outfp_){ + fclose(outfp_); + } + } + + struct notcurses* nc_{}; + FILE* outfp_{}; +}; + +TEST_F(InputTest, TestMouseOn){ + ASSERT_EQ(0, notcurses_mouse_enable(nc_)); +} + +TEST_F(InputTest, TestMouseOff){ + ASSERT_EQ(0, notcurses_mouse_disable(nc_)); +} From ae52d0443570e01f390cc3a58c58bc932186f5c1 Mon Sep 17 00:00:00 2001 From: nick black Date: Mon, 23 Dec 2019 03:36:13 -0500 Subject: [PATCH 02/14] notcurses-input: dim previous lines --- src/input/input.cpp | 55 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 50 insertions(+), 5 deletions(-) diff --git a/src/input/input.cpp b/src/input/input.cpp index 739b6d3d0..2d03b6387 100644 --- a/src/input/input.cpp +++ b/src/input/input.cpp @@ -77,7 +77,7 @@ const char* nckeystr(wchar_t spkey){ } } -// print the utf8 Control Pictures for otherwise unprintable chars +// Print the utf8 Control Pictures for otherwise unprintable ASCII wchar_t printutf8(wchar_t kp){ if(kp <= 27 && kp >= 0){ return 0x2400 + kp; @@ -85,11 +85,49 @@ wchar_t printutf8(wchar_t kp){ return kp; } +// Dim all text on the plane by the same amount. This will stack for +// older text, and thus clearly indicate the current output. +static int +dim_rows(struct ncplane* n){ + int y, x; + cell c = CELL_TRIVIAL_INITIALIZER; + for(y = 2 ; y < dimy ; ++y){ + for(x = 0 ; x < dimx ; ++x){ + if(ncplane_at_yx(n, y, x, &c) < 0){ + cell_release(n, &c); + return -1; + } + unsigned r, g, b; + cell_get_fg_rgb(&c, &r, &g, &b); + r -= r / 32; + g -= g / 32; + b -= b / 32; + if(r > 247){ r = 0; } + if(g > 247){ g = 0; } + if(b > 247){ b = 0; } + if(cell_set_fg_rgb(&c, r, g, b)){ + cell_release(n, &c); + return -1; + } + if(ncplane_putc_yx(n, y, x, &c) < 0){ + cell_release(n, &c); + return -1; + } + if(cell_double_wide_p(&c)){ + ++x; + } + } + } + cell_release(n, &c); + return 0; +} + int main(void){ if(setlocale(LC_ALL, "") == nullptr){ return EXIT_FAILURE; } notcurses_options opts{}; + opts.clear_screen_start = true; if((nc = notcurses_init(&opts, stdout)) == nullptr){ return EXIT_FAILURE;; } @@ -98,16 +136,20 @@ int main(void){ ncplane_set_fg(n, 0); ncplane_set_bg(n, 0xbb64bb); ncplane_styles_set(n, CELL_STYLE_UNDERLINE); - if(ncplane_putstr_aligned(n, 0, "mash some keys, yo", NCALIGN_CENTER) <= 0){ + if(ncplane_putstr_aligned(n, 0, "mash some keys, yo. give that mouse some waggle!", NCALIGN_CENTER) <= 0){ notcurses_stop(nc); return EXIT_FAILURE; } ncplane_styles_off(n, CELL_STYLE_UNDERLINE); ncplane_set_bg_default(n); notcurses_render(nc); - int y = 1; + int y = 2; std::deque cells; wchar_t r; + if(notcurses_mouse_enable(nc)){ + notcurses_stop(nc); + return EXIT_FAILURE; + } while(errno = 0, (r = notcurses_getc_blocking(nc)) >= 0){ if(r == 0){ // interrupted by signal continue; @@ -133,12 +175,14 @@ int main(void){ ncplane_printf(n, "Got UTF-8: [0x%08x] '%lc'\n", r, r); } } - // FIXME reprint all lines, fading older ones + if(dim_rows(n)){ + break; + } if(notcurses_render(nc)){ break; } if(++y >= dimy - 2){ // leave a blank line at the bottom - y = 1; // and at the top + y = 2; // and at the top } while(cells.size() >= dimy - 3u){ cells.pop_back(); @@ -146,6 +190,7 @@ int main(void){ cells.push_front(r); } int e = errno; + notcurses_mouse_disable(nc); notcurses_stop(nc); if(r < 0 && e){ std::cerr << "Error reading from terminal (" << strerror(e) << "?)\n"; From 78633de4272f1799d8eebc4708055c25307f035c Mon Sep 17 00:00:00 2001 From: nick black Date: Mon, 23 Dec 2019 03:36:29 -0500 Subject: [PATCH 03/14] mouse: use SGR extended coordinates --- src/lib/notcurses.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/lib/notcurses.c b/src/lib/notcurses.c index afd81eaa3..a3d07594c 100644 --- a/src/lib/notcurses.c +++ b/src/lib/notcurses.c @@ -763,7 +763,6 @@ notcurses* notcurses_init(const notcurses_options* opts, FILE* outfp){ free_plane(ret->top); goto err; } - // term_emit("clear", ret->clear, ret->ttyfp, false); ret->suppress_banner = opts->suppress_bannner; if(!opts->suppress_bannner){ char prefixbuf[BPREFIXSTRLEN + 1]; @@ -793,6 +792,9 @@ notcurses* notcurses_init(const notcurses_options* opts, FILE* outfp){ fprintf(ret->ttyfp, "Are you specifying a proper DirectColor TERM?\n"); } } + if(opts->clear_screen_start){ + term_emit("clear", ret->clearscr, ret->ttyfp, false); + } return ret; err: @@ -1547,15 +1549,15 @@ ncplane* ncplane_below(ncplane* n){ #define SET_BTN_EVENT_MOUSE "1002" #define SET_FOCUS_EVENT_MOUSE "1004" -#define SET_EXT_MODE_MOUSE "1005" +#define SET_SGR_MODE_MOUSE "1006" int notcurses_mouse_enable(notcurses* n){ return term_emit("mouse", ESC "[?" SET_BTN_EVENT_MOUSE ";" - SET_FOCUS_EVENT_MOUSE ";" SET_EXT_MODE_MOUSE "h", + SET_FOCUS_EVENT_MOUSE ";" SET_SGR_MODE_MOUSE "h", n->ttyfp, true); } int notcurses_mouse_disable(notcurses* n){ return term_emit("mouse", ESC "[?" SET_BTN_EVENT_MOUSE ";" - SET_FOCUS_EVENT_MOUSE ";" SET_EXT_MODE_MOUSE "l", + SET_FOCUS_EVENT_MOUSE ";" SET_SGR_MODE_MOUSE "l", n->ttyfp, true); } From fc17a104b45fa87cf7938e17149216ee8fe42c65 Mon Sep 17 00:00:00 2001 From: nick black Date: Mon, 23 Dec 2019 03:36:37 -0500 Subject: [PATCH 04/14] notcurses_options: clear screen on start --- README.md | 2 ++ include/notcurses.h | 2 ++ 2 files changed, 4 insertions(+) diff --git a/README.md b/README.md index 8cb52f6e7..10235274a 100644 --- a/README.md +++ b/README.md @@ -158,6 +158,8 @@ typedef struct notcurses_options { // Notcurses typically prints version info in notcurses_init() and // performance info in notcurses_stop(). This inhibits that output. bool suppress_bannner; + // Notcurses does not clear the screen on startup unless thus requested to. + bool clear_screen_start; // If non-NULL, notcurses_render() will write each rendered frame to this // FILE* in addition to outfp. This is used primarily for debugging. FILE* renderfp; diff --git a/include/notcurses.h b/include/notcurses.h index 62a713cd4..86a5165cd 100644 --- a/include/notcurses.h +++ b/include/notcurses.h @@ -121,6 +121,8 @@ typedef struct notcurses_options { // Notcurses typically prints version info in notcurses_init() and performance // info in notcurses_stop(). This inhibits that output. bool suppress_bannner; + // Notcurses does not clear the screen on startup unless thus requested to. + bool clear_screen_start; // If non-NULL, notcurses_render() will write each rendered frame to this // FILE* in addition to outfp. This is used primarily for debugging. FILE* renderfp; From 1555edfa6c3bdd98b4840aaf447e68b489f9e796 Mon Sep 17 00:00:00 2001 From: nick black Date: Mon, 23 Dec 2019 03:38:38 -0500 Subject: [PATCH 05/14] notcurses: disable mouse events on startup/shutdown --- src/lib/notcurses.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/notcurses.c b/src/lib/notcurses.c index a3d07594c..99a3e651d 100644 --- a/src/lib/notcurses.c +++ b/src/lib/notcurses.c @@ -713,6 +713,7 @@ notcurses* notcurses_init(const notcurses_options* opts, FILE* outfp){ free(ret); return NULL; } + notcurses_mouse_disable(ret); if(tcgetattr(ret->ttyfd, &ret->tpreserved)){ fprintf(stderr, "Couldn't preserve terminal state for %d (%s)\n", ret->ttyfd, strerror(errno)); @@ -822,6 +823,7 @@ int notcurses_stop(notcurses* nc){ if(nc->sgr0 && term_emit("sgr0", nc->sgr0, nc->ttyfp, true)){ ret = -1; } + ret |= notcurses_mouse_disable(nc); ret |= tcsetattr(nc->ttyfd, TCSANOW, &nc->tpreserved); while(nc->top){ ncplane* p = nc->top; From c819063d80350e00ba1c9c3bc12ca1dbe31a6942 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Tue, 24 Dec 2019 00:32:40 +0100 Subject: [PATCH 06/14] Install *.avi test files too --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4c6d54412..07cd4bbaa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -212,7 +212,7 @@ install(FILES DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig ) -file(GLOB TESTDATA CONFIGURE_DEPENDS tests/*.png tests/*.jpg tests/*.mkv tests/*.bmp) +file(GLOB TESTDATA CONFIGURE_DEPENDS tests/*.png tests/*.jpg tests/*.mkv tests/*.bmp tests/*.avi) install(FILES ${TESTDATA} DESTINATION ${CMAKE_INSTALL_PREFIX}/share/notcurses From 1bc2bcf31e342d265e95e42dfe73fff630d73ae2 Mon Sep 17 00:00:00 2001 From: nick black Date: Mon, 23 Dec 2019 04:18:51 -0500 Subject: [PATCH 07/14] multiple pipelines --- .drone.yml | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/.drone.yml b/.drone.yml index 2f6aa4484..7b5b59c18 100644 --- a/.drone.yml +++ b/.drone.yml @@ -1,5 +1,27 @@ --- -pipeline: +kind: pipeline: +type: docker +name: debianunstable + prep: + image: dankamongmen/unstable_builder + docker: + tty: true + commands: + - apt-get update + - apt-get -y remove man-db + - apt-get -y install devscripts git-buildpackage locales + - echo 'en_US.UTF-8 UTF-8' > /etc/locale.gen + - locale-gen + - mk-build-deps --install -t'apt-get -y' + - mkdir build + - cd build + - cmake .. + - make + - LANG="en_US.UTF-8" ./notcurses-tester +--- +kind: pipeline: +type: docker +name: debianstable prep: image: library/debian:unstable docker: From b4b4b3684a2cb4f167786b90d6fac8af57fb39db Mon Sep 17 00:00:00 2001 From: nick black Date: Mon, 23 Dec 2019 04:20:52 -0500 Subject: [PATCH 08/14] yaml --- .drone.yml | 68 ++++++++++++++++++++++----------------------- src/input/input.cpp | 4 +-- src/lib/input.c | 3 +- 3 files changed, 37 insertions(+), 38 deletions(-) diff --git a/.drone.yml b/.drone.yml index 7b5b59c18..114ffb235 100644 --- a/.drone.yml +++ b/.drone.yml @@ -1,40 +1,40 @@ --- -kind: pipeline: +kind: pipeline type: docker name: debianunstable - prep: - image: dankamongmen/unstable_builder - docker: - tty: true - commands: - - apt-get update - - apt-get -y remove man-db - - apt-get -y install devscripts git-buildpackage locales - - echo 'en_US.UTF-8 UTF-8' > /etc/locale.gen - - locale-gen - - mk-build-deps --install -t'apt-get -y' - - mkdir build - - cd build - - cmake .. - - make - - LANG="en_US.UTF-8" ./notcurses-tester +steps: +- name: prep + image: dankamongmen/unstable_builder + commands: + - apt-get update + - apt-get -y remove man-db + - apt-get -y install devscripts git-buildpackage locales + - echo 'en_US.UTF-8 UTF-8' > /etc/locale.gen + - locale-gen + - mk-build-deps --install -t'apt-get -y' + - mkdir build + - cd build + - cmake .. + - make + - LANG="en_US.UTF-8" ./notcurses-tester --- -kind: pipeline: +kind: pipeline type: docker name: debianstable - prep: - image: library/debian:unstable - docker: - tty: true - commands: - - apt-get update - - apt-get -y remove man-db - - apt-get -y install devscripts git-buildpackage locales - - echo 'en_US.UTF-8 UTF-8' > /etc/locale.gen - - locale-gen - - mk-build-deps --install -t'apt-get -y' - - mkdir build - - cd build - - cmake .. - - make - - LANG="en_US.UTF-8" ./notcurses-tester +steps: + name: prep + image: library/debian:unstable + docker: + tty: true + commands: + - apt-get update + - apt-get -y remove man-db + - apt-get -y install devscripts git-buildpackage locales + - echo 'en_US.UTF-8 UTF-8' > /etc/locale.gen + - locale-gen + - mk-build-deps --install -t'apt-get -y' + - mkdir build + - cd build + - cmake .. + - make + - LANG="en_US.UTF-8" ./notcurses-tester diff --git a/src/input/input.cpp b/src/input/input.cpp index 2d03b6387..3bc1025fb 100644 --- a/src/input/input.cpp +++ b/src/input/input.cpp @@ -135,12 +135,12 @@ int main(void){ notcurses_term_dim_yx(nc, &dimy, &dimx); ncplane_set_fg(n, 0); ncplane_set_bg(n, 0xbb64bb); - ncplane_styles_set(n, CELL_STYLE_UNDERLINE); + ncplane_styles_on(n, CELL_STYLE_UNDERLINE); if(ncplane_putstr_aligned(n, 0, "mash some keys, yo. give that mouse some waggle!", NCALIGN_CENTER) <= 0){ notcurses_stop(nc); return EXIT_FAILURE; } - ncplane_styles_off(n, CELL_STYLE_UNDERLINE); + ncplane_styles_set(n, 0); ncplane_set_bg_default(n); notcurses_render(nc); int y = 2; diff --git a/src/lib/input.c b/src/lib/input.c index 44bea3ea1..74c9419e4 100644 --- a/src/lib/input.c +++ b/src/lib/input.c @@ -112,11 +112,10 @@ handle_getc(notcurses* nc, int kpress){ return -1; } if(kpress == ESC){ - // FIXME delay a little waiting for more? const esctrie* esc = nc->inputescapes; while(esc && esc->special == NCKEY_INVALID && nc->inputbuf_occupied){ int candidate = pop_input_keypress(nc); -//fprintf(stderr, "CANDIDATE: %c\n", candidate); +fprintf(stderr, "CANDIDATE: 0x%02x %d '%c'\n", candidate, candidate, candidate); if(esc->trie == NULL){ esc = NULL; }else if(candidate >= 0x80 || candidate < 0){ From aceda892618853ce0c58313b9aeb9bfd21fb553d Mon Sep 17 00:00:00 2001 From: nick black Date: Mon, 23 Dec 2019 15:36:35 -0500 Subject: [PATCH 09/14] input: add NCKEY_MOUSEEVENT, match against CSI prefix #165 --- include/notcurses.h | 1 + src/input/input.cpp | 1 + src/lib/input.c | 25 +++++++++++++++++++++---- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/include/notcurses.h b/include/notcurses.h index 86a5165cd..05a8edf53 100644 --- a/include/notcurses.h +++ b/include/notcurses.h @@ -228,6 +228,7 @@ wchar_supppuab_p(char32_t w){ #define NCKEY_EXIT suppuabize(133) #define NCKEY_PRINT suppuabize(134) #define NCKEY_REFRESH suppuabize(135) +#define NCKEY_MOUSEEVENT suppuabize(200) // See ppoll(2) for more detail. Provide a NULL 'ts' to block at length, a 'ts' // of 0 for non-blocking operation, and otherwise a timespec to bound blocking. diff --git a/src/input/input.cpp b/src/input/input.cpp index 3bc1025fb..d9d243d32 100644 --- a/src/input/input.cpp +++ b/src/input/input.cpp @@ -73,6 +73,7 @@ const char* nckeystr(wchar_t spkey){ case NCKEY_EXIT: return "exit"; case NCKEY_PRINT: return "print"; case NCKEY_REFRESH: return "refresh"; + case NCKEY_MOUSEEVENT: return "mouseevent"; default: return "unknown"; } } diff --git a/src/lib/input.c b/src/lib/input.c index 74c9419e4..fa06e219a 100644 --- a/src/lib/input.c +++ b/src/lib/input.c @@ -3,6 +3,12 @@ #include #include "internal.h" +// CSI (Control Sequence Indicators) originate in the terminal itself, and are +// not reported in their bare form to the user. For our purposes, these usually +// indicate a mouse event. +#define CSIPREFIX "\x1b[<" +static const char32_t NCKEY_CSI = 1; + static const unsigned char ESC = 0x1b; // 27 sig_atomic_t resize_seen = 0; @@ -30,7 +36,7 @@ unpop_keypress(notcurses* nc, int kpress){ // we assumed escapes can only be composed of 7-bit chars typedef struct esctrie { - int special; // composed key terminating here + char32_t special; // composed key terminating here struct esctrie** trie; // if non-NULL, next level of radix-128 trie } esctrie; @@ -66,7 +72,7 @@ notcurses_add_input_escape(notcurses* nc, const char* esc, char32_t special){ fprintf(stderr, "Not an escape: %s (0x%x)\n", esc, special); return -1; } - if(!wchar_supppuab_p(special)){ + if(!wchar_supppuab_p(special) && special != NCKEY_CSI){ fprintf(stderr, "Not a supplementary-b PUA char: %lc (0x%x)\n", special, special); return -1; } @@ -102,6 +108,12 @@ notcurses_add_input_escape(notcurses* nc, const char* esc, char32_t special){ return 0; } +// We received the CSI prefix. Extract the data payload. +static char32_t +handle_csi(notcurses* nc){ + return NCKEY_MOUSEEVENT; +} + // 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. @@ -115,7 +127,6 @@ handle_getc(notcurses* nc, int kpress){ const esctrie* esc = nc->inputescapes; while(esc && esc->special == NCKEY_INVALID && nc->inputbuf_occupied){ int candidate = pop_input_keypress(nc); -fprintf(stderr, "CANDIDATE: 0x%02x %d '%c'\n", candidate, candidate, candidate); if(esc->trie == NULL){ esc = NULL; }else if(candidate >= 0x80 || candidate < 0){ @@ -124,8 +135,10 @@ fprintf(stderr, "CANDIDATE: 0x%02x %d '%c'\n", candidate, candidate, candidate); esc = esc->trie[candidate]; } } -//fprintf(stderr, "esc? %c special: %d\n", esc ? 'y' : 'n', esc ? esc->special : NCKEY_INVALID); if(esc && esc->special != NCKEY_INVALID){ + if(esc->special == NCKEY_CSI){ + return handle_csi(nc); + } return esc->special; } // FIXME ungetc on failure! walk trie backwards or something @@ -301,5 +314,9 @@ int prep_special_keys(notcurses* nc){ return -1; } } + if(notcurses_add_input_escape(nc, CSIPREFIX, NCKEY_CSI)){ + fprintf(stderr, "Couldn't add support for %s\n", k->tinfo); + return -1; + } return 0; } From f742676aee3414bca7fc322e8f54afabc5e449cc Mon Sep 17 00:00:00 2001 From: nick black Date: Mon, 23 Dec 2019 18:54:28 -0500 Subject: [PATCH 10/14] Mouse support using button event tracking #165 Request and parse up mouse messages. We handle up to 11 mouse buttons, 3 modifiers (currently thrown away), motion while holding down a button, and loss/gain of focus. I've added twelve new NCKEYs: one for each button, and one for release. In addition, I've introduced the 'ncinput' struct, which encodes the nckey plus extra data. The only extra data thus far is coordinates for mouse events. It is not necessary to provide a ncinput to all input functions; NULL can be provided if the caller doesn't care about details. All demos are updated. notcurses-input has been updated to decode full information of returned ncinputs. The primary resource for this work was Dickey at al's "XTerm Control Sequences", https://invisible-island.net/xterm/ctlseqs/ctlseqs.html. --- .drone.yml | 56 ++++++++----------------- README.md | 42 +++++++++++++++---- include/notcurses.h | 60 ++++++++++++++++++++------ src/demo/panelreel.c | 2 +- src/demo/view.c | 2 +- src/demo/witherworm.c | 2 +- src/input/input.cpp | 45 ++++++++++++++------ src/lib/input.c | 95 +++++++++++++++++++++++++++++++++++++----- src/lib/notcurses.c | 2 + src/planereel/main.cpp | 2 +- src/view/view.cpp | 4 +- 11 files changed, 225 insertions(+), 87 deletions(-) diff --git a/.drone.yml b/.drone.yml index 114ffb235..2f6aa4484 100644 --- a/.drone.yml +++ b/.drone.yml @@ -1,40 +1,18 @@ --- -kind: pipeline -type: docker -name: debianunstable -steps: -- name: prep - image: dankamongmen/unstable_builder - commands: - - apt-get update - - apt-get -y remove man-db - - apt-get -y install devscripts git-buildpackage locales - - echo 'en_US.UTF-8 UTF-8' > /etc/locale.gen - - locale-gen - - mk-build-deps --install -t'apt-get -y' - - mkdir build - - cd build - - cmake .. - - make - - LANG="en_US.UTF-8" ./notcurses-tester ---- -kind: pipeline -type: docker -name: debianstable -steps: - name: prep - image: library/debian:unstable - docker: - tty: true - commands: - - apt-get update - - apt-get -y remove man-db - - apt-get -y install devscripts git-buildpackage locales - - echo 'en_US.UTF-8 UTF-8' > /etc/locale.gen - - locale-gen - - mk-build-deps --install -t'apt-get -y' - - mkdir build - - cd build - - cmake .. - - make - - LANG="en_US.UTF-8" ./notcurses-tester +pipeline: + prep: + image: library/debian:unstable + docker: + tty: true + commands: + - apt-get update + - apt-get -y remove man-db + - apt-get -y install devscripts git-buildpackage locales + - echo 'en_US.UTF-8 UTF-8' > /etc/locale.gen + - locale-gen + - mk-build-deps --install -t'apt-get -y' + - mkdir build + - cd build + - cmake .. + - make + - LANG="en_US.UTF-8" ./notcurses-tester diff --git a/README.md b/README.md index 10235274a..879f7f801 100644 --- a/README.md +++ b/README.md @@ -337,29 +337,56 @@ wchar_supppuab_p(char32_t w){ #define NCKEY_EXIT suppuabize(133) #define NCKEY_PRINT suppuabize(134) #define NCKEY_REFRESH suppuabize(135) +// Mouse events. We try to encode some details into the char32_t (i.e. which +// button was pressed), but some is embedded in the ncinput event. +#define NCKEY_MOUSEB1 suppuabize(201) +#define NCKEY_MOUSEB2 suppuabize(202) +#define NCKEY_MOUSEB3 suppuabize(203) + +// An input event. Cell coordinates are currently defined only for mouse events. +typedef struct ncinput { + char32_t id; // identifier. Unicode codepoint or synthesized NCKEY event + int y; // y cell coordinate of event, -1 for undefined + int x; // x cell coordinate of event, -1 for undefined + // FIXME modifiers (alt, etc?) +} ncinput; // See ppoll(2) for more detail. Provide a NULL 'ts' to block at length, 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. // Returns a single Unicode code point, or (char32_t)-1 on error. 'sigmask' may -// be NULL. -char32_t notcurses_getc(struct notcurses* n, const struct timespec* ts, sigset_t* sigmask); +// be NULL. Returns 0 on a timeout. If an event is processed, the return value +// is the 'id' field from that event. 'ni' may be NULL. +API char32_t notcurses_getc(struct notcurses* n, const struct timespec* ts, + sigset_t* sigmask, ncinput* ni); +// 'ni' may be NULL if the caller is uninterested in event details. If no event +// is ready, returns 0. static inline char32_t -notcurses_getc_nblock(struct notcurses* n){ +notcurses_getc_nblock(struct notcurses* n, ncinput* ni){ sigset_t sigmask; sigfillset(&sigmask); struct timespec ts = { .tv_sec = 0, .tv_nsec = 0 }; - return notcurses_getc(n, &ts, &sigmask); + return notcurses_getc(n, &ts, &sigmask, ni); } +// 'ni' may be NULL if the caller is uninterested in event details. Blocks +// until an event is processed or a signal is received. static inline char32_t -notcurses_getc_blocking(struct notcurses* n){ +notcurses_getc_blocking(struct notcurses* n, ncinput* ni){ sigset_t sigmask; sigemptyset(&sigmask); - return notcurses_getc(n, NULL, &sigmask); + return notcurses_getc(n, NULL, &sigmask, ni); } + +// Enable the mouse in "button-event tracking" mode with focus detection and +// UTF8-style extended coordinates. On failure, -1 is returned. On success, 0 +// is returned, and mouse events will be published to notcurses_getc(). +API int notcurses_mouse_enable(struct notcurses* n); + +// Disable mouse events. Any events in the input queue can still be delivered. +API int notcurses_mouse_disable(struct notcurses* n); ``` ### Mice @@ -381,7 +408,8 @@ int notcurses_mouse_disable(struct notcurses* n); "Button-event tracking mode" implies the ability to detect mouse button presses, and also mouse movement while holding down a mouse button (i.e. to -effect drag-and-drop). +effect drag-and-drop). Mouse events are returned via the `NCKEY_MOUSE*` values, +with coordinate information in the `ncinput` struct. ### Planes diff --git a/include/notcurses.h b/include/notcurses.h index 05a8edf53..4b6370e9b 100644 --- a/include/notcurses.h +++ b/include/notcurses.h @@ -161,12 +161,6 @@ API struct ncplane* notcurses_top(struct notcurses* n); #define suppuabize(w) ((w) + 0x100000) -// is this wide character a Supplementary Private Use Area-B codepoint? -static inline bool -wchar_supppuab_p(char32_t w){ - return w >= 0x100000 && w <= 0x10fffd; -} - // Special composed key defintions. These values are added to 0x100000. #define NCKEY_INVALID suppuabize(0) #define NCKEY_RESIZE suppuabize(1) // generated interally in response to SIGWINCH @@ -228,29 +222,69 @@ wchar_supppuab_p(char32_t w){ #define NCKEY_EXIT suppuabize(133) #define NCKEY_PRINT suppuabize(134) #define NCKEY_REFRESH suppuabize(135) -#define NCKEY_MOUSEEVENT suppuabize(200) +// Mouse events. We try to encode some details into the char32_t (i.e. which +// button was pressed), but some is embedded in the ncinput event. The release +// event is generic across buttons; callers must maintain state, if they care. +#define NCKEY_BUTTON1 suppuabize(201) +#define NCKEY_BUTTON2 suppuabize(202) +#define NCKEY_BUTTON3 suppuabize(203) +#define NCKEY_BUTTON4 suppuabize(204) +#define NCKEY_BUTTON5 suppuabize(205) +#define NCKEY_BUTTON6 suppuabize(206) +#define NCKEY_BUTTON7 suppuabize(207) +#define NCKEY_BUTTON8 suppuabize(208) +#define NCKEY_BUTTON9 suppuabize(209) +#define NCKEY_BUTTON10 suppuabize(210) +#define NCKEY_BUTTON11 suppuabize(211) +#define NCKEY_RELEASE suppuabize(212) + +// Is this char32_t a Supplementary Private Use Area-B codepoint? +static inline bool +wchar_supppuab_p(char32_t w){ + return w >= 0x100000 && w <= 0x10fffd; +} + +// Is the event a synthesized mouse event? +static inline bool +nckey_mouse_p(char32_t r){ + return r >= NCKEY_BUTTON1 && r <= NCKEY_RELEASE; +} + +// An input event. Cell coordinates are currently defined only for mouse events. +typedef struct ncinput { + char32_t id; // identifier. Unicode codepoint or synthesized NCKEY event + int y; // y cell coordinate of event, -1 for undefined + int x; // x cell coordinate of event, -1 for undefined + // FIXME modifiers (alt, etc?) +} ncinput; // See ppoll(2) for more detail. Provide a NULL 'ts' to block at length, 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. // Returns a single Unicode code point, or (char32_t)-1 on error. 'sigmask' may -// be NULL. -API char32_t notcurses_getc(struct notcurses* n, const struct timespec* ts, sigset_t* sigmask); +// be NULL. Returns 0 on a timeout. If an event is processed, the return value +// is the 'id' field from that event. 'ni' may be NULL. +API char32_t notcurses_getc(struct notcurses* n, const struct timespec* ts, + sigset_t* sigmask, ncinput* ni); +// 'ni' may be NULL if the caller is uninterested in event details. If no event +// is ready, returns 0. static inline char32_t -notcurses_getc_nblock(struct notcurses* n){ +notcurses_getc_nblock(struct notcurses* n, ncinput* ni){ sigset_t sigmask; sigfillset(&sigmask); struct timespec ts = { .tv_sec = 0, .tv_nsec = 0 }; - return notcurses_getc(n, &ts, &sigmask); + return notcurses_getc(n, &ts, &sigmask, ni); } +// 'ni' may be NULL if the caller is uninterested in event details. Blocks +// until an event is processed or a signal is received. static inline char32_t -notcurses_getc_blocking(struct notcurses* n){ +notcurses_getc_blocking(struct notcurses* n, ncinput* ni){ sigset_t sigmask; sigemptyset(&sigmask); - return notcurses_getc(n, NULL, &sigmask); + return notcurses_getc(n, NULL, &sigmask, ni); } // Enable the mouse in "button-event tracking" mode with focus detection and diff --git a/src/demo/panelreel.c b/src/demo/panelreel.c index a78e59944..98ecffd19 100644 --- a/src/demo/panelreel.c +++ b/src/demo/panelreel.c @@ -231,7 +231,7 @@ handle_input(struct notcurses* nc, struct panelreel* pr, int efd, return (wchar_t)-1; }else{ if(fds[0].revents & POLLIN){ - key = notcurses_getc_blocking(nc); + key = notcurses_getc_blocking(nc, NULL); if(key < 0){ return -1; } diff --git a/src/demo/view.c b/src/demo/view.c index 156912839..5e63219e4 100644 --- a/src/demo/view.c +++ b/src/demo/view.c @@ -5,7 +5,7 @@ static int watch_for_keystroke(struct notcurses* nc, struct ncvisual* ncv __attribute__ ((unused))){ wchar_t w; // we don't want a keypress, but should handle NCKEY_RESIZE - if((w = notcurses_getc_nblock(nc)) != (wchar_t)-1){ + if((w = notcurses_getc_nblock(nc, NULL)) != (wchar_t)-1){ if(w == NCKEY_RESIZE){ // FIXME resize that sumbitch }else{ diff --git a/src/demo/witherworm.c b/src/demo/witherworm.c index 210430b30..9c1a33fce 100644 --- a/src/demo/witherworm.c +++ b/src/demo/witherworm.c @@ -673,7 +673,7 @@ int witherworm_demo(struct notcurses* nc){ struct timespec left, cur; clock_gettime(CLOCK_MONOTONIC, &cur); timespec_subtract(&left, &screenend, &cur); - key = notcurses_getc(nc, &left, NULL); + key = notcurses_getc(nc, &left, NULL, NULL); clock_gettime(CLOCK_MONOTONIC, &cur); int64_t ns = timespec_subtract_ns(&cur, &screenend); if(ns > 0){ diff --git a/src/input/input.cpp b/src/input/input.cpp index d9d243d32..4ea53dea6 100644 --- a/src/input/input.cpp +++ b/src/input/input.cpp @@ -4,13 +4,14 @@ #include #include #include +#include #include static int dimy, dimx; static struct notcurses* nc; // return the string version of a special composed key -const char* nckeystr(wchar_t spkey){ +const char* nckeystr(char32_t spkey){ switch(spkey){ // FIXME case NCKEY_RESIZE: notcurses_resize(nc, &dimy, &dimx); @@ -73,14 +74,25 @@ const char* nckeystr(wchar_t spkey){ case NCKEY_EXIT: return "exit"; case NCKEY_PRINT: return "print"; case NCKEY_REFRESH: return "refresh"; - case NCKEY_MOUSEEVENT: return "mouseevent"; + case NCKEY_BUTTON1: return "mouse (button 1 pressed)"; + case NCKEY_BUTTON2: return "mouse (button 2 pressed)"; + case NCKEY_BUTTON3: return "mouse (button 3 pressed)"; + case NCKEY_BUTTON4: return "mouse (button 4 pressed)"; + case NCKEY_BUTTON5: return "mouse (button 5 pressed)"; + case NCKEY_BUTTON6: return "mouse (button 6 pressed)"; + case NCKEY_BUTTON7: return "mouse (button 7 pressed)"; + case NCKEY_BUTTON8: return "mouse (button 8 pressed)"; + case NCKEY_BUTTON9: return "mouse (button 9 pressed)"; + case NCKEY_BUTTON10: return "mouse (button 10 pressed)"; + case NCKEY_BUTTON11: return "mouse (button 11 pressed)"; + case NCKEY_RELEASE: return "mouse (button released)"; default: return "unknown"; } } // Print the utf8 Control Pictures for otherwise unprintable ASCII -wchar_t printutf8(wchar_t kp){ - if(kp <= 27 && kp >= 0){ +char32_t printutf8(char32_t kp){ + if(kp <= 27){ return 0x2400 + kp; } return kp; @@ -137,7 +149,7 @@ int main(void){ ncplane_set_fg(n, 0); ncplane_set_bg(n, 0xbb64bb); ncplane_styles_on(n, CELL_STYLE_UNDERLINE); - if(ncplane_putstr_aligned(n, 0, "mash some keys, yo. give that mouse some waggle!", NCALIGN_CENTER) <= 0){ + if(ncplane_putstr_aligned(n, 0, "mash keys, yo. give that mouse some waggle! ctrl+d exits.", NCALIGN_CENTER) <= 0){ notcurses_stop(nc); return EXIT_FAILURE; } @@ -146,34 +158,44 @@ int main(void){ notcurses_render(nc); int y = 2; std::deque cells; - wchar_t r; + char32_t r; if(notcurses_mouse_enable(nc)){ notcurses_stop(nc); return EXIT_FAILURE; } - while(errno = 0, (r = notcurses_getc_blocking(nc)) >= 0){ + ncinput ni; + while(errno = 0, (r = notcurses_getc_blocking(nc, &ni)) != (char32_t)-1){ if(r == 0){ // interrupted by signal continue; } + if(r == CEOT){ + notcurses_stop(nc); + return EXIT_SUCCESS; + } if(ncplane_cursor_move_yx(n, y, 0)){ break; } if(r < 0x80){ ncplane_set_fg_rgb(n, 128, 250, 64); - if(ncplane_printf(n, "Got ASCII: [0x%02x (%03d)] '%lc'\n", + if(ncplane_printf(n, "Got ASCII: [0x%02x (%03d)] '%lc'", r, r, iswprint(r) ? r : printutf8(r)) < 0){ break; } }else{ if(wchar_supppuab_p(r)){ ncplane_set_fg_rgb(n, 250, 64, 128); - if(ncplane_printf(n, "Got special key: [0x%02x (%02d)] '%s'\n", + if(ncplane_printf(n, "Got special key: [0x%02x (%02d)] '%s'", r, r, nckeystr(r)) < 0){ break; } + if(nckey_mouse_p(r)){ + if(ncplane_printf(n, " x: %d y: %d", ni.x, ni.y) < 0){ + break; + } + } }else{ ncplane_set_fg_rgb(n, 64, 128, 250); - ncplane_printf(n, "Got UTF-8: [0x%08x] '%lc'\n", r, r); + ncplane_printf(n, "Got UTF-8: [0x%08x] '%lc'", r, r); } } if(dim_rows(n)){ @@ -191,9 +213,8 @@ int main(void){ cells.push_front(r); } int e = errno; - notcurses_mouse_disable(nc); notcurses_stop(nc); - if(r < 0 && e){ + if(r == (char32_t)-1 && e){ std::cerr << "Error reading from terminal (" << strerror(e) << "?)\n"; } return EXIT_FAILURE; diff --git a/src/lib/input.c b/src/lib/input.c index fa06e219a..b3ec0a7b4 100644 --- a/src/lib/input.c +++ b/src/lib/input.c @@ -1,5 +1,6 @@ #include // needed for some definitions, see terminfo(3ncurses) #include +#include #include #include "internal.h" @@ -110,15 +111,88 @@ notcurses_add_input_escape(notcurses* nc, const char* esc, char32_t special){ // We received the CSI prefix. Extract the data payload. static char32_t -handle_csi(notcurses* nc){ - return NCKEY_MOUSEEVENT; +handle_csi(notcurses* nc, ncinput* ni){ + enum { + PARAM1, // reading first param (button + modifiers) plus delimiter + PARAM2, // reading second param (x coordinate) plus delimiter + PARAM3, // reading third param (y coordinate) plus terminator + } state = PARAM1; + int param = 0; // numeric translation of param thus far + char32_t id = (char32_t)-1; + while(nc->inputbuf_occupied){ + int candidate = pop_input_keypress(nc); + if(state == PARAM1){ + if(candidate == ';'){ + state = PARAM2; + // modifiers: 32 (motion) 16 (control) 8 (alt) 4 (shift) + // buttons 4, 5, 6, 7: adds 64 + // buttons 8, 9, 10, 11: adds 128 + if(param >= 0 && param < 64){ + if(param % 4 == 3){ + id = NCKEY_RELEASE; + }else{ + id = NCKEY_BUTTON1 + (param % 4); + } + }else if(param >= 64 && param < 128){ + id = NCKEY_BUTTON4 + (param % 4); + }else if(param >= 128 && param < 192){ + id = NCKEY_BUTTON8 + (param % 4); + }else{ + break; + } + param = 0; + }else if(isdigit(candidate)){ + param *= 10; + param += candidate - '0'; + }else{ + break; + } + }else if(state == PARAM2){ + if(candidate == ';'){ + state = PARAM3; + if(param == 0){ + break; + } + if(ni){ + ni->x = param - 1; + } + param = 0; + }else if(isdigit(candidate)){ + param *= 10; + param += candidate - '0'; + }else{ + break; + } + }else if(state == PARAM3){ + if(candidate == 'm' || candidate == 'M'){ + if(candidate == 'm'){ + id = NCKEY_RELEASE; + } + if(param == 0){ + break; + } + if(ni){ + ni->y = param - 1; + ni->id = id; + } + return id; + }else if(isdigit(candidate)){ + param *= 10; + param += candidate - '0'; + }else{ + break; + } + } + } + // FIXME ungetc on failure! walk trie backwards or something + return (char32_t)-1; } // 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 char32_t -handle_getc(notcurses* nc, int kpress){ +handle_getc(notcurses* nc, int kpress, ncinput* ni){ // fprintf(stderr, "KEYPRESS: %d\n", kpress); if(kpress < 0){ return -1; @@ -137,7 +211,7 @@ handle_getc(notcurses* nc, int kpress){ } if(esc && esc->special != NCKEY_INVALID){ if(esc->special == NCKEY_CSI){ - return handle_csi(nc); + return handle_csi(nc, ni); } return esc->special; } @@ -196,7 +270,7 @@ input_queue_full(const notcurses* nc){ } static char32_t -handle_input(notcurses* nc){ +handle_input(notcurses* nc, ncinput* ni){ int r; // getc() returns unsigned chars cast to ints while(!input_queue_full(nc) && (r = getc(nc->ttyinfp)) >= 0){ @@ -217,17 +291,18 @@ handle_input(notcurses* nc){ return -1; } r = pop_input_keypress(nc); - return handle_getc(nc, r); + return handle_getc(nc, r, ni); } -// infp has always been set non-blocking -char32_t notcurses_getc(notcurses* nc, const struct timespec *ts, sigset_t* sigmask){ +// infp has already been set non-blocking +char32_t notcurses_getc(notcurses* nc, const struct timespec *ts, + sigset_t* sigmask, ncinput* ni){ errno = 0; - char32_t r = handle_input(nc); + char32_t r = handle_input(nc, ni); if(r == (char32_t)-1){ if(errno == EAGAIN || errno == EWOULDBLOCK){ block_on_input(nc->ttyinfp, ts, sigmask); - return handle_input(nc); + return handle_input(nc, ni); } return r; } diff --git a/src/lib/notcurses.c b/src/lib/notcurses.c index 99a3e651d..74827d43d 100644 --- a/src/lib/notcurses.c +++ b/src/lib/notcurses.c @@ -1558,6 +1558,8 @@ int notcurses_mouse_enable(notcurses* n){ n->ttyfp, true); } +// this seems to work (note difference in suffix, 'l' vs 'h'), but what about +// the sequences 1000 etc? int notcurses_mouse_disable(notcurses* n){ return term_emit("mouse", ESC "[?" SET_BTN_EVENT_MOUSE ";" SET_FOCUS_EVENT_MOUSE ";" SET_SGR_MODE_MOUSE "l", diff --git a/src/planereel/main.cpp b/src/planereel/main.cpp index 036e8d70f..dba5ffaa9 100644 --- a/src/planereel/main.cpp +++ b/src/planereel/main.cpp @@ -64,7 +64,7 @@ int main(void){ } PR = pr; // FIXME eliminate char32_t key; - while((key = notcurses_getc_blocking(nc)) != (char32_t)-1){ + while((key = notcurses_getc_blocking(nc, nullptr)) != (char32_t)-1){ switch(key){ case 'q': return notcurses_stop(nc) ? EXIT_FAILURE : EXIT_SUCCESS; diff --git a/src/view/view.cpp b/src/view/view.cpp index c8f0903c3..6e02dc890 100644 --- a/src/view/view.cpp +++ b/src/view/view.cpp @@ -37,7 +37,7 @@ int ncview(struct notcurses* nc, struct ncvisual* ncv, int* averr){ .tv_sec = start.tv_sec + (long)(ns / 1000000000), .tv_nsec = start.tv_nsec + (long)(ns % 1000000000), }; - clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &interval, NULL); + clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &interval, nullptr); } if(*averr == AVERROR_EOF){ return 0; @@ -78,7 +78,7 @@ int main(int argc, char** argv){ std::cerr << "Error decoding " << argv[i] << ": " << errbuf.data() << std::endl; return EXIT_FAILURE; } - notcurses_getc_blocking(nc); + notcurses_getc_blocking(nc, nullptr); ncvisual_destroy(ncv); } if(notcurses_stop(nc)){ From 7dd875a23006ae885a2e284514a80f0f87ca7907 Mon Sep 17 00:00:00 2001 From: nick black Date: Mon, 23 Dec 2019 19:02:13 -0500 Subject: [PATCH 11/14] sync documentation to new input code #165 --- README.md | 56 +++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 46 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 879f7f801..553deb9f0 100644 --- a/README.md +++ b/README.md @@ -288,12 +288,6 @@ must be readable without delay for it to be interpreted as such. // returned to indicate that no input was available, but only by // notcurses_getc(). Otherwise (including on EOF) (char32_t)-1 is returned. -// is this wide character a Supplementary Private Use Area-B codepoint? -static inline bool -wchar_supppuab_p(char32_t w){ - return w >= 0x100000 && w <= 0x10fffd; -} - #define suppuabize(w) ((w) + 0x100000) // Special composed key defintions. These values are added to 0x100000. @@ -321,6 +315,26 @@ wchar_supppuab_p(char32_t w){ #define NCKEY_F08 suppuabize(28) #define NCKEY_F09 suppuabize(29) #define NCKEY_F10 suppuabize(30) +#define NCKEY_F11 suppuabize(31) +#define NCKEY_F12 suppuabize(32) +#define NCKEY_F13 suppuabize(33) +#define NCKEY_F14 suppuabize(34) +#define NCKEY_F15 suppuabize(35) +#define NCKEY_F16 suppuabize(36) +#define NCKEY_F17 suppuabize(37) +#define NCKEY_F18 suppuabize(38) +#define NCKEY_F19 suppuabize(39) +#define NCKEY_F20 suppuabize(40) +#define NCKEY_F21 suppuabize(41) +#define NCKEY_F22 suppuabize(42) +#define NCKEY_F23 suppuabize(43) +#define NCKEY_F24 suppuabize(44) +#define NCKEY_F25 suppuabize(45) +#define NCKEY_F26 suppuabize(46) +#define NCKEY_F27 suppuabize(47) +#define NCKEY_F28 suppuabize(48) +#define NCKEY_F29 suppuabize(49) +#define NCKEY_F30 suppuabize(50) // ... leave room for up to 100 function keys, egads #define NCKEY_ENTER suppuabize(121) #define NCKEY_CLS suppuabize(122) // "clear-screen or erase" @@ -338,10 +352,32 @@ wchar_supppuab_p(char32_t w){ #define NCKEY_PRINT suppuabize(134) #define NCKEY_REFRESH suppuabize(135) // Mouse events. We try to encode some details into the char32_t (i.e. which -// button was pressed), but some is embedded in the ncinput event. -#define NCKEY_MOUSEB1 suppuabize(201) -#define NCKEY_MOUSEB2 suppuabize(202) -#define NCKEY_MOUSEB3 suppuabize(203) +// button was pressed), but some is embedded in the ncinput event. The release +// event is generic across buttons; callers must maintain state, if they care. +#define NCKEY_BUTTON1 suppuabize(201) +#define NCKEY_BUTTON2 suppuabize(202) +#define NCKEY_BUTTON3 suppuabize(203) +#define NCKEY_BUTTON4 suppuabize(204) +#define NCKEY_BUTTON5 suppuabize(205) +#define NCKEY_BUTTON6 suppuabize(206) +#define NCKEY_BUTTON7 suppuabize(207) +#define NCKEY_BUTTON8 suppuabize(208) +#define NCKEY_BUTTON9 suppuabize(209) +#define NCKEY_BUTTON10 suppuabize(210) +#define NCKEY_BUTTON11 suppuabize(211) +#define NCKEY_RELEASE suppuabize(212) + +// Is this char32_t a Supplementary Private Use Area-B codepoint? +static inline bool +wchar_supppuab_p(char32_t w){ + return w >= 0x100000 && w <= 0x10fffd; +} + +// Is the event a synthesized mouse event? +static inline bool +nckey_mouse_p(char32_t r){ + return r >= NCKEY_BUTTON1 && r <= NCKEY_RELEASE; +} // An input event. Cell coordinates are currently defined only for mouse events. typedef struct ncinput { From 1de8611e2f130395fd01ab524dba2aed3085e1b8 Mon Sep 17 00:00:00 2001 From: nick black Date: Mon, 23 Dec 2019 19:16:49 -0500 Subject: [PATCH 12/14] move test data to data/, simplify install rule --- CMakeLists.txt | 2 +- {tests => data}/PurpleDrank.jpg | Bin {tests => data}/changes.jpg | Bin {tests => data}/dsscaw-purp.png | Bin {tests => data}/eagles.png | Bin {tests => data}/fm6.mkv | Bin {tests => data}/lamepatents.jpg | Bin {tests => data}/megaman2.bmp | Bin {tests => data}/notcursesI.avi | Bin {tests => data}/profoundchangesbeautiful-bare.jpg | Bin {tests => data}/samoa.avi | Bin 11 files changed, 1 insertion(+), 1 deletion(-) rename {tests => data}/PurpleDrank.jpg (100%) rename {tests => data}/changes.jpg (100%) rename {tests => data}/dsscaw-purp.png (100%) rename {tests => data}/eagles.png (100%) rename {tests => data}/fm6.mkv (100%) rename {tests => data}/lamepatents.jpg (100%) rename {tests => data}/megaman2.bmp (100%) rename {tests => data}/notcursesI.avi (100%) rename {tests => data}/profoundchangesbeautiful-bare.jpg (100%) rename {tests => data}/samoa.avi (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 07cd4bbaa..7be458688 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -212,7 +212,7 @@ install(FILES DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig ) -file(GLOB TESTDATA CONFIGURE_DEPENDS tests/*.png tests/*.jpg tests/*.mkv tests/*.bmp tests/*.avi) +file(GLOB TESTDATA CONFIGURE_DEPENDS data/*) install(FILES ${TESTDATA} DESTINATION ${CMAKE_INSTALL_PREFIX}/share/notcurses diff --git a/tests/PurpleDrank.jpg b/data/PurpleDrank.jpg similarity index 100% rename from tests/PurpleDrank.jpg rename to data/PurpleDrank.jpg diff --git a/tests/changes.jpg b/data/changes.jpg similarity index 100% rename from tests/changes.jpg rename to data/changes.jpg diff --git a/tests/dsscaw-purp.png b/data/dsscaw-purp.png similarity index 100% rename from tests/dsscaw-purp.png rename to data/dsscaw-purp.png diff --git a/tests/eagles.png b/data/eagles.png similarity index 100% rename from tests/eagles.png rename to data/eagles.png diff --git a/tests/fm6.mkv b/data/fm6.mkv similarity index 100% rename from tests/fm6.mkv rename to data/fm6.mkv diff --git a/tests/lamepatents.jpg b/data/lamepatents.jpg similarity index 100% rename from tests/lamepatents.jpg rename to data/lamepatents.jpg diff --git a/tests/megaman2.bmp b/data/megaman2.bmp similarity index 100% rename from tests/megaman2.bmp rename to data/megaman2.bmp diff --git a/tests/notcursesI.avi b/data/notcursesI.avi similarity index 100% rename from tests/notcursesI.avi rename to data/notcursesI.avi diff --git a/tests/profoundchangesbeautiful-bare.jpg b/data/profoundchangesbeautiful-bare.jpg similarity index 100% rename from tests/profoundchangesbeautiful-bare.jpg rename to data/profoundchangesbeautiful-bare.jpg diff --git a/tests/samoa.avi b/data/samoa.avi similarity index 100% rename from tests/samoa.avi rename to data/samoa.avi From 6156fa72b017aacc27a75d336359117ce3632141 Mon Sep 17 00:00:00 2001 From: nick black Date: Mon, 23 Dec 2019 19:16:57 -0500 Subject: [PATCH 13/14] build/run documentation --- README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/README.md b/README.md index 553deb9f0..4037c4da3 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ for more information, see [my wiki](https://nick-black.com/dankwiki/index.php/No * [Introduction](#introduction) * [Requirements](#requirements) + * [Building](#building) * [Use](#use) * [Input](#input) * [Planes](#planes) ([Plane Channels API](#plane-channels-api), [Wide chars](#wide-chars)) @@ -105,6 +106,20 @@ that fine library. * From NCURSES: terminfo 6.1+ * From FFMpeg: libswscale 5.0+, libavformat 57.0+, libavutil 56.0+ +### Building + +* Create a subdirectory, traditionally `build`. Enter the directory. +* `cmake ..`. You might want to set e.g. `CMAKE_BUILD_TYPE`. +* `make` +* `make test` + +If you have unit test failures, *please* file a bug including the output of +`./notcurses-tester > log 2>&1` (`make test` also runs `notcurses-tester`, but +hides important output). + +To watch the bitchin' demo, run `./notcurses-demo -p ../data`. More details can +be found on the `notcurses-demo(1)` man page. + ## Use A program wishing to use notcurses will need to link it, ideally using the From 1d9bf7bb59a4629a53c62935759d70082abf8537 Mon Sep 17 00:00:00 2001 From: nick black Date: Mon, 23 Dec 2019 19:44:30 -0500 Subject: [PATCH 14/14] update unit tests for ../data move --- tests/libav.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/libav.cpp b/tests/libav.cpp index e9521ceda..eee105437 100644 --- a/tests/libav.cpp +++ b/tests/libav.cpp @@ -37,7 +37,7 @@ TEST_F(LibavTest, LoadImage) { int averr; int dimy, dimx; ncplane_dim_yx(ncp_, &dimy, &dimx); - auto ncv = ncplane_visual_open(ncp_, "../tests/dsscaw-purp.png", &averr); + auto ncv = ncplane_visual_open(ncp_, "../data/dsscaw-purp.png", &averr); ASSERT_NE(nullptr, ncv); ASSERT_EQ(0, averr); auto frame = ncvisual_decode(ncv, &averr); @@ -58,7 +58,7 @@ TEST_F(LibavTest, LoadVideo) { int averr; int dimy, dimx; ncplane_dim_yx(ncp_, &dimy, &dimx); - auto ncv = ncplane_visual_open(ncp_, "../tests/fm6.mkv", &averr); + auto ncv = ncplane_visual_open(ncp_, "../data/fm6.mkv", &averr); ASSERT_NE(nullptr, ncv); EXPECT_EQ(0, averr); auto frame = ncvisual_decode(ncv, &averr); @@ -75,7 +75,7 @@ TEST_F(LibavTest, LoadVideoCreatePlane) { int averr; int dimy, dimx; ncplane_dim_yx(ncp_, &dimy, &dimx); - auto ncv = ncvisual_open_plane(nc_, "../tests/fm6.mkv", &averr, 0, 0, NCSCALE_STRETCH); + auto ncv = ncvisual_open_plane(nc_, "../data/fm6.mkv", &averr, 0, 0, NCSCALE_STRETCH); ASSERT_NE(nullptr, ncv); EXPECT_EQ(0, averr); auto frame = ncvisual_decode(ncv, &averr);