diff --git a/.drone.yml b/.drone.yml index 3933d4551..48e4a49e6 100644 --- a/.drone.yml +++ b/.drone.yml @@ -5,7 +5,7 @@ name: debian-unstable steps: - name: debian-build - image: dankamongmen/unstable_builder:2020-04-18e + image: dankamongmen/unstable_builder:2020-04-20a commands: - export LANG=en_US.UTF-8 - mkdir build @@ -20,7 +20,7 @@ name: ubuntu-focal steps: - name: ubuntu-build - image: dankamongmen/focal:2020-04-18c + image: dankamongmen/focal:2020-04-20a commands: - export LANG=en_US.UTF-8 - mkdir build diff --git a/CMakeLists.txt b/CMakeLists.txt index daf4d3570..f3876e114 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,10 +14,11 @@ include(GNUInstallDirs) ###################### USER-SELECTABLE OPTIONS ########################### option(DFSG_BUILD "DFSG build (no non-free media/code)" OFF) option(USE_DOXYGEN "Build HTML cross reference with doxygen" OFF) -option(USE_FFMPEG "Disable FFmpeg image/video support (recommended)" ON) +option(USE_FFMPEG "Disable FFmpeg image/video support" ON) option(USE_NETWORK "Allow cargo to use the network" OFF) option(USE_PANDOC "Build man pages and HTML reference with pandoc" ON) option(USE_PYTHON "Build Python wrappers" ON) +option(USE_QRCODEGEN "Disable libqrcodegen QR code support" ON) option(USE_RUST "Build Rust wrappers (experimental)" OFF) option(USE_TESTS "Build doctest unit tests" ON) ############## END (additional) USER-SELECTABLE OPTIONS ################## @@ -67,7 +68,6 @@ target_include_directories(notcurses-static ) target_link_libraries(notcurses PRIVATE - Threads::Threads "${TERMINFO_LIBRARIES}" "${LIBRT}" PUBLIC @@ -89,6 +89,11 @@ target_link_directories(notcurses-static "${TERMINFO_STATIC_LIBRARY_DIRS}" ) +if(${USE_QRCODEGEN}) +target_link_libraries(notcurses PRIVATE qrcodegen) +target_link_libraries(notcurses-static PRIVATE qrcodegen) +endif() + if(${USE_FFMPEG}) target_include_directories(notcurses PUBLIC diff --git a/README.md b/README.md index c4f8ce146..a2dc1b5fc 100644 --- a/README.md +++ b/README.md @@ -113,8 +113,8 @@ that fine library. * (build) A C11 and a C++17 compiler * (build) CMake 3.14.0+ * (build+runtime) From NCURSES: terminfo 6.1+ +* (OPTIONAL) (build+runtime) From QR-Code-generator: [libqrcodegen](https://github.com/nayuki/QR-Code-generator) 1.5.0+ * (OPTIONAL) (build+runtime) From [FFMpeg](https://www.ffmpeg.org/): libswscale 5.0+, libavformat 57.0+, libavutil 56.0+ -* (OPTIONAL) (build+runtime) [libqrcodegen](https://github.com/nayuki/QR-Code-generator) 1.5.0+ * (OPTIONAL) (testing) [Doctest](https://github.com/onqtam/doctest) 2.3.5+ * (OPTIONAL) (documentation) [pandoc](https://pandoc.org/index.html) 1.19.2+ * (OPTIONAL) (python bindings): Python 3.7+, [CFFI](https://pypi.org/project/cffi/) 1.13.2+ diff --git a/doc/man/man1/notcurses-demo.1.md b/doc/man/man1/notcurses-demo.1.md index 63ce4a5d9..bd9d993b1 100644 --- a/doc/man/man1/notcurses-demo.1.md +++ b/doc/man/man1/notcurses-demo.1.md @@ -32,6 +32,7 @@ The demonstrations include (see NOTES below): * (j)ungle—low-bandwidth color cycling reveals ancient ruins * (l)uigi—a dashing Apennine plumber in a world of fire * (n)ormal—a normal map of a friend, with lambert shading +* (q)rcode—quick response codes (from ISO/IEC 18004:2015) * (r)eel—demonstration of the ncreel high-level widget * (s)liders—a missing-piece puzzle made up of colorful blocks * (t)rans—an exploration of various transparencies @@ -88,7 +89,8 @@ non-free under the Debian Free Software Guidelines. As a result, the are unavailable through the Debian package. If notcurses is built without FFmpeg, the **chunli**, **eagle**, -**outro**, **view**, and **xray** demos will be unavailable. +**outro**, **view**, and **xray** demos will be unavailable. If notcurses is +built without libqrcodegen, the **qrcode** demo will be unavailable. If **notcurses-demo** is run in a terminal lacking the **can_change** terminfo capability, **jungle** will be skipped. diff --git a/include/notcurses/notcurses.h b/include/notcurses/notcurses.h index b079ab68d..55d394c8b 100644 --- a/include/notcurses/notcurses.h +++ b/include/notcurses/notcurses.h @@ -27,10 +27,15 @@ extern "C" { // Get a human-readable string describing the running notcurses version. API const char* notcurses_version(void); -struct cell; // a coordinate on an ncplane: an EGC plus styling -struct ncplane; // a drawable notcurses surface, composed of cells -struct ncvisual; // a visual bit of multimedia opened with LibAV struct notcurses; // notcurses state for a given terminal, composed of ncplanes +struct ncplane; // a drawable notcurses surface, composed of cells +struct cell; // a coordinate on an ncplane: an EGC plus styling +struct ncvisual; // a visual bit of multimedia opened with LibAV +struct ncplot; // a histogram, bound to a plane +struct ncfdplane; // i/o wrapper to dump file descriptor to plane +struct ncsubproc; // ncfdplane wrapper with subprocess management +struct ncselector;// widget supporting selecting 1 from a list of options +struct ncmultiselector; // widget supporting selecting 0..n from n options // Initialize a direct-mode notcurses context on the connected terminal at 'fp'. // 'fp' must be a tty. You'll usually want stdout. Direct mode supportes a @@ -878,6 +883,8 @@ API int notcurses_mouse_disable(struct notcurses* n); // NCKEY_RESIZE event has been read and you're not ready to render. API int notcurses_refresh(struct notcurses* n, int* RESTRICT y, int* RESTRICT x); +API struct notcurses* ncplane_notcurses(struct ncplane* n); + // Return the dimensions of this ncplane. API void ncplane_dim_yx(const struct ncplane* n, int* RESTRICT y, int* RESTRICT x); @@ -2398,8 +2405,6 @@ typedef struct selector_options { uint64_t bgchannels; // background channels, used only in body } selector_options; -struct ncselector; - API struct ncselector* ncselector_create(struct ncplane* n, int y, int x, const selector_options* opts); @@ -2474,8 +2479,6 @@ typedef struct multiselector_options { uint64_t bgchannels; // background channels, used only in body } multiselector_options; -struct ncmultiselector; - API struct ncmultiselector* ncmultiselector_create(struct ncplane* n, int y, int x, const multiselector_options* opts); @@ -2649,6 +2652,53 @@ API int ncplot_set_sample(struct ncplot* n, uint64_t x, uint64_t y); API void ncplot_destroy(struct ncplot* n); +typedef int(*ncfdplane_callback)(struct ncfdplane* n, const void* buf, size_t s, void* curry); +typedef int(*ncfdplane_done_cb)(struct ncfdplane* n, int fderrno, void* curry); + +// read from an fd until EOF (or beyond, if follow is set), invoking the user's +// callback each time. runs in its own context. on EOF or error, the finalizer +// callback will be invoked, and the user ought destroy the ncfdplane. the +// data is *not* guaranteed to be nul-terminated, and may contain arbitrary +// zeroes. +typedef struct ncfdplane_options { + void* curry; // parameter provided to callbacks + bool follow; // keep reading after hitting end? (think tail -f) +} ncfdplane_options; + +// Create an ncfdplane around the fd 'fd'. Consider this function to take +// ownership of the file descriptor, which will be closed in ncfdplane_destroy(). +API struct ncfdplane* ncfdplane_create(struct ncplane* n, const ncfdplane_options* opts, + int fd, ncfdplane_callback cbfxn, ncfdplane_done_cb donecbfxn); +API struct ncplane* ncfdplane_plane(struct ncfdplane* n); +API int ncfdplane_destroy(struct ncfdplane* n); + +typedef struct ncsubproc_options { + ncfdplane_options popts; + uint64_t restart_period; // restart this many seconds after an exit (watch) +} ncsubproc_options; + +// see exec(2). p-types use $PATH. e-type passes environment vars. +API struct ncsubproc* ncsubproc_createv(struct ncplane* n, const ncsubproc_options* opts, + const char* bin, char* const arg[], + ncfdplane_callback cbfxn, ncfdplane_done_cb donecbfxn); +API struct ncsubproc* ncsubproc_createvp(struct ncplane* n, const ncsubproc_options* opts, + const char* bin, char* const arg[], + ncfdplane_callback cbfxn, ncfdplane_done_cb donecbfxn); +API struct ncsubproc* ncsubproc_createvpe(struct ncplane* n, const ncsubproc_options* opts, + const char* bin, char* const arg[], char* const env[], + ncfdplane_callback cbfxn, ncfdplane_done_cb donecbfxn); + +API int ncsubproc_destroy(struct ncsubproc* n); + +// Draw a QR code at the current position on the plane. If there is insufficient +// room to draw the code here, or there is any other error, non-zero will be +// returned. Otherwise, the QR code "version" (size) is returned. The QR code +// is (version * 4 + 17) columns wide, and ⌈version * 4 + 17 / 2⌉ rows tall. If +// maxversion is not zero, it plays a hard limit on the QR code size. Though the +// max version of current QR codes is 40, greater values are allowed, for +// future compatability (provide 0 for no artificail bound). +API int ncplane_qrcode(struct ncplane* n, int maxversion, const void* data, size_t len); + #undef API #ifdef __cplusplus diff --git a/src/demo/demo.c b/src/demo/demo.c index cd784c20c..4731329a4 100644 --- a/src/demo/demo.c +++ b/src/demo/demo.c @@ -20,14 +20,14 @@ static demoresult* results; static char *datadir = NOTCURSES_SHARE; // yes, these are in different orders in different configurations on purpose -// (since some transition into the next) +// (since some transition into the next). FIXME handle case sans libqrcodegen. #ifndef USE_FFMPEG -static const char DEFAULT_DEMO[] = "itfhbrgnswju"; +static const char DEFAULT_DEMO[] = "itfhbrgnswjqu"; #else #ifdef DFSG_BUILD -static const char DEFAULT_DEMO[] = "ixtfhbrgnswuo"; +static const char DEFAULT_DEMO[] = "ixtfhbrgnswuqo"; #else -static const char DEFAULT_DEMO[] = "ixethnbcgrwuvlfsjo"; +static const char DEFAULT_DEMO[] = "ixethnbcgrwuvlfsjqo"; #endif #endif @@ -113,7 +113,7 @@ static struct { { "normal", normal_demo, }, FREEFFMPEG("outro", outro), { NULL, NULL, }, - { NULL, NULL, }, + { "qrcode", qrcode_demo, }, // is blank without USE_QRCODEGEN { "reel", reel_demo, }, { "sliders", sliding_puzzle_demo, }, { "trans", trans_demo, }, diff --git a/src/demo/demo.h b/src/demo/demo.h index 6bbd3f490..fb3a69998 100644 --- a/src/demo/demo.h +++ b/src/demo/demo.h @@ -6,8 +6,8 @@ #include #include #include -#include #include +#include #ifdef USE_FFMPEG #include #include @@ -37,6 +37,7 @@ int witherworm_demo(struct notcurses* nc); int box_demo(struct notcurses* nc); int trans_demo(struct notcurses* nc); int chunli_demo(struct notcurses* nc); +int qrcode_demo(struct notcurses* nc); int grid_demo(struct notcurses* nc); int fallin_demo(struct notcurses* nc); int highcontrast_demo(struct notcurses* nc); diff --git a/src/demo/jungle.c b/src/demo/jungle.c index ab5a20b92..e8686c158 100644 --- a/src/demo/jungle.c +++ b/src/demo/jungle.c @@ -26652,7 +26652,7 @@ int jungle_demo(struct notcurses* nc){ free(buf); int iter = 0; // don't try to run faster than, eh, 140Hz - int64_t iterns = GIG / 140; + int64_t iterns = GIG / 100; int64_t nsrunning; do{ DEMO_RENDER(nc); diff --git a/src/demo/qrcode.c b/src/demo/qrcode.c new file mode 100644 index 000000000..5fc4bb8f3 --- /dev/null +++ b/src/demo/qrcode.c @@ -0,0 +1,39 @@ +#include "demo.h" +#include + +int qrcode_demo(struct notcurses* nc){ +#ifdef USE_QRCODEGEN + char data[128]; + int dimy, dimx; + struct ncplane* n = notcurses_stddim_yx(nc, &dimy, &dimx); + for(int i = 0 ; i < 1024 ; ++i){ + ncplane_erase(n); + size_t len = random() % sizeof(data) + 1; + ssize_t got = getrandom(data, len, 0); + if(got < 0 || (size_t)got != len){ + return -1; + } + if(ncplane_cursor_move_yx(n, 0, 0)){ + return -1; + } + if(ncplane_qrcode(n, 0, data, len) <= 0){ + return -1; + } + if(ncplane_cursor_move_yx(n, 0, 0)){ + return -1; + } + uint64_t tl = 0, bl = 0, br = 0, tr = 0; + channels_set_fg_rgb(&tl, random() % 255 + 1, random() % 255 + 1, random() % 255 + 1); + channels_set_fg_rgb(&tr, random() % 255 + 1, random() % 255 + 1, random() % 255 + 1); + channels_set_fg_rgb(&bl, random() % 255 + 1, random() % 255 + 1, random() % 255 + 1); + channels_set_fg_rgb(&br, random() % 255 + 1, random() % 255 + 1, random() % 255 + 1); + if(ncplane_stain(n, dimy - 1, dimx - 1, tl, tr, bl, br) <= 0){ + return -1; + } + DEMO_RENDER(nc); + } +#else + DEMO_RENDER(nc); +#endif + return 0; +} diff --git a/src/lib/fd.c b/src/lib/fd.c new file mode 100644 index 000000000..c81faefc1 --- /dev/null +++ b/src/lib/fd.c @@ -0,0 +1,141 @@ +#include +#include +#include "internal.h" + +// release the memory and fd, but don't join the thread (since we might be +// getting called within the thread's context, on a callback). +static int +ncfdplane_destroy_inner(ncfdplane* n){ + int ret = close(n->fd); + free(n); + return ret; +} + +static void * +ncfdplane_thread(void* vncfp){ + ncfdplane* ncfp = vncfp; + char* buf = malloc(BUFSIZ); + ssize_t r; + while((r = read(ncfp->fd, buf, BUFSIZ)) >= 0){ + if(r == 0){ + break; + } + if( (r = ncfp->cb(ncfp, buf, r, ncfp->curry)) ){ + break; + } + } + if(r <= 0){ + ncfp->donecb(ncfp, r == 0 ? 0 : errno, ncfp->curry); + } + free(buf); + if(ncfp->destroyed){ + ncfdplane_destroy_inner(ncfp); + } + return NULL; +} + +ncfdplane* ncfdplane_create(ncplane* n, const ncfdplane_options* opts, int fd, + ncfdplane_callback cbfxn, ncfdplane_done_cb donecbfxn){ + if(fd < 0 || !cbfxn || !donecbfxn){ + return NULL; + } + ncfdplane* ret = malloc(sizeof(*ret)); + if(ret){ + ret->cb = cbfxn; + ret->donecb = donecbfxn; + ret->follow = opts->follow; + ret->ncp = n; + ret->destroyed = false; + ncplane_set_scrolling(ret->ncp, true); + ret->fd = fd; + ret->curry = opts->curry; + if(pthread_create(&ret->tid, NULL, ncfdplane_thread, ret)){ + free(ret); + return NULL; + } + } + return ret; +} + +ncplane* ncfdplane_plane(ncfdplane* n){ + return n->ncp; +} + +int ncfdplane_destroy(ncfdplane* n){ + int ret = 0; + if(n){ + if(pthread_equal(pthread_self(), n->tid)){ + n->destroyed = true; // ncfdplane_destroy_inner() is called on thread exit + }else{ + void* vret = NULL; + pthread_cancel(n->tid); + ret |= pthread_join(n->tid, &vret); + ret |= ncfdplane_destroy_inner(n); + } + } + return ret; +} + +ncsubproc* ncsubproc_createv(ncplane* n, const ncsubproc_options* opts, + const char* bin, char* const arg[], + ncfdplane_callback cbfxn, ncfdplane_done_cb donecbfxn){ + if(!cbfxn || !donecbfxn){ + return NULL; + } + int fd = -1; + ncsubproc* ret = malloc(sizeof(*ret)); + if(ret){ + // FIXME launch process, create ncfdplane with pipe + if((ret->nfp = ncfdplane_create(n, &opts->popts, fd, cbfxn, donecbfxn)) == NULL){ + // FIXME kill process + free(ret); + return NULL; + } + } + return ret; +} + +ncsubproc* ncsubproc_createvp(ncplane* n, const ncsubproc_options* opts, + const char* bin, char* const arg[], + ncfdplane_callback cbfxn, ncfdplane_done_cb donecbfxn){ + if(!cbfxn || !donecbfxn){ + return NULL; + } + int fd = -1; + ncsubproc* ret = malloc(sizeof(*ret)); + if(ret){ + // FIXME launch process, create ncfdplane with pipe + if((ret->nfp = ncfdplane_create(n, &opts->popts, fd, cbfxn, donecbfxn)) == NULL){ + // FIXME kill process + free(ret); + return NULL; + } + } + return ret; +} + +ncsubproc* ncsubproc_createvpe(ncplane* n, const ncsubproc_options* opts, + const char* bin, char* const arg[], char* const env[], + ncfdplane_callback cbfxn, ncfdplane_done_cb donecbfxn){ + if(!cbfxn || !donecbfxn){ + return NULL; + } + int fd = -1; + ncsubproc* ret = malloc(sizeof(*ret)); + if(ret){ + // FIXME launch process, create ncfdplane with pipe + if((ret->nfp = ncfdplane_create(n, &opts->popts, fd, cbfxn, donecbfxn)) == NULL){ + // FIXME kill process + free(ret); + return NULL; + } + } + return ret; +} + +int ncsubproc_destroy(ncsubproc* n){ + if(n){ + free(n); + } + return 0; +} diff --git a/src/lib/fill.c b/src/lib/fill.c index a96a54ea2..bdd612723 100644 --- a/src/lib/fill.c +++ b/src/lib/fill.c @@ -1,4 +1,5 @@ #include "internal.h" +#include void ncplane_greyscale(ncplane *n){ for(int y = 0 ; y < n->leny ; ++y){ @@ -588,3 +589,96 @@ int ncplane_rotate_ccw(ncplane* n){ ret |= ncplane_destroy(newp); return ret; } + +#ifdef USE_QRCODEGEN +#define QR_BASE_SIZE 17 +#define PER_QR_VERSION 4 + +static inline int +qrcode_rows(int version){ + return QR_BASE_SIZE + (version * PER_QR_VERSION / 2); +} + +static inline int +qrcode_cols(int version){ + return QR_BASE_SIZE + (version * PER_QR_VERSION); +} + +int ncplane_qrcode(ncplane* n, int maxversion, const void* data, size_t len){ + const int MAX_QR_VERSION = 40; // QR library only supports up to 40 + if(maxversion < 0){ + return -1; + } + if(len == 0){ + return -1; + } + const int starty = n->y; + const int startx = n->x; + const int availx = n->lenx - startx; + const int availy = n->leny - starty; + if(availy < qrcode_rows(1)){ + return -1; + } + if(availx < qrcode_cols(1)){ + return -1; + } + const int availsquare = availy * 2 < availx ? availy * 2 : availx; + const int roomforver = (availsquare - QR_BASE_SIZE) / 4; + if(maxversion == 0){ + maxversion = roomforver; + }else if(maxversion > roomforver){ + maxversion = roomforver; + } + if(maxversion > MAX_QR_VERSION){ + maxversion = MAX_QR_VERSION; + } + const size_t bsize = qrcodegen_BUFFER_LEN_FOR_VERSION(maxversion); + if(bsize < len){ + return -1; + } + uint8_t* src = malloc(bsize); + uint8_t* dst = malloc(bsize); + if(src == NULL || dst == NULL){ + free(src); + free(dst); + return -1; + } + memcpy(src, data, len); + int ret = -1; + if(qrcodegen_encodeBinary(src, len, dst, qrcodegen_Ecc_HIGH, 1, maxversion, qrcodegen_Mask_AUTO, true)){ + ret = qrcodegen_getSize(dst); + for(int y = starty ; y < starty + (ret + 1) / 2 ; ++y){ + for(int x = startx ; x < startx + ret ; ++x){ + const bool top = qrcodegen_getModule(dst, x, y); + const bool bot = qrcodegen_getModule(dst, x, y + 1); + const char* egc; + if(top && bot){ + egc = "█"; + }else if(top){ + egc = "▀"; + }else if(bot){ + egc = "▄"; + }else{ + egc = " "; + } + int sbytes; + if(ncplane_putegc_yx(n, y, x, egc, &sbytes) <= 0){ + ret = -1; + break; + } + } + } + } + free(src); + free(dst); + return ret < 0 ? ret : (ret - QR_BASE_SIZE) / PER_QR_VERSION; +} +#else +int ncplane_qrcode(ncplane* n, int maxversion, const void* data, size_t len){ + (void)n; + (void)maxversion; + (void)data; + (void)len; + return -1; +} +#endif diff --git a/src/lib/internal.h b/src/lib/internal.h index 150dc824d..d4d601af9 100644 --- a/src/lib/internal.h +++ b/src/lib/internal.h @@ -187,6 +187,22 @@ typedef struct ncplot { bool detectdomain; // is domain detection in effect (stretch the domain)? } ncplot; +typedef struct ncfdplane { + ncfdplane_callback cb; // invoked with fresh hot data + ncfdplane_done_cb donecb; // invoked on EOF (if !follow) or error + void* curry; // passed to the callbacks + int fd; // we take ownership of the fd, and close it + bool follow; // keep trying to read past the end (event-based) + ncplane* ncp; // bound ncplane + pthread_t tid; // thread servicing this i/o + bool destroyed; // set in ncfdplane_destroy() in our own context +} ncfdplane; + +typedef struct ncsubproc { + ncfdplane* nfp; + pid_t pid; // subprocess +} ncsubproc; + typedef struct ncmenu { ncplane* ncp; int sectioncount; // must be positive diff --git a/src/lib/notcurses.c b/src/lib/notcurses.c index 23c70fd82..7affad56c 100644 --- a/src/lib/notcurses.c +++ b/src/lib/notcurses.c @@ -1210,6 +1210,22 @@ cell_obliterate(ncplane* n, cell* c){ cell_init(c); } +// increment y by 1 and rotate the framebuffer up one line. x moves to 0. +static inline void +scroll_down(ncplane* n){ + n->x = 0; + if(n->y == n->leny - 1){ + n->logrow = (n->logrow + 1) % n->leny; + cell* row = n->fb + nfbcellidx(n, n->y, 0); + for(int clearx = 0 ; clearx < n->lenx ; ++clearx){ + cell_release(n, &row[clearx]); + } + memset(row, 0, sizeof(*row) * n->lenx); + }else{ + ++n->y; + } +} + int ncplane_putc_yx(ncplane* n, int y, int x, const cell* c){ // if scrolling is enabled, check *before ncplane_cursor_move_yx()* whether // we're past the end of the line, and move to the next line if so. @@ -1218,21 +1234,17 @@ int ncplane_putc_yx(ncplane* n, int y, int x, const cell* c){ if(!n->scrolling){ return -1; } - n->x = 0; - if(n->y == n->leny - 1){ - n->logrow = (n->logrow + 1) % n->leny; - cell* row = n->fb + nfbcellidx(n, n->y, 0); - for(int clearx = 0 ; clearx < n->lenx ; ++clearx){ - cell_release(n, &row[clearx]); - } - memset(row, 0, sizeof(*row) * n->lenx); - }else{ - ++n->y; - } + scroll_down(n); } if(ncplane_cursor_move_yx(n, y, x)){ return -1; } + if(c->gcluster == '\n'){ + if(n->scrolling){ + scroll_down(n); + return 0; + } + } // A wide character obliterates anything to its immediate right (and marks // that cell as wide). Any character placed atop one half of a wide character // obliterates the other half. Note that a wide char can thus obliterate two @@ -1844,6 +1856,10 @@ void ncplane_translate(const ncplane* src, const ncplane* dst, } } +notcurses* ncplane_notcurses(ncplane* n){ + return n->nc; +} + ncplane* ncplane_reparent(ncplane* n, ncplane* newparent){ if(n == n->nc->stdscr){ return NULL; // can't reparent standard plane diff --git a/src/poc/fileroller.c b/src/poc/fileroller.c new file mode 100644 index 000000000..beebd38f7 --- /dev/null +++ b/src/poc/fileroller.c @@ -0,0 +1,65 @@ +#include +#include +#include +#include +#include +#include +#include + +static bool fddone; +static pthread_cond_t cond = PTHREAD_COND_INITIALIZER; +static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; + +static int +cb(struct ncfdplane* ncfd, const void* data, size_t len, void* curry){ + int ret = -1; + if(ncplane_putstr(ncfdplane_plane(ncfd), data) >= 0){ + if(!notcurses_render(ncplane_notcurses(ncfdplane_plane(ncfd)))){ + ret = 0; + } + } + (void)len; + (void)curry; + return ret; +} + +static int +eofcb(struct ncfdplane* ncfd, int nerrno, void* curry){ + (void)nerrno; + (void)curry; + pthread_mutex_lock(&lock); + fddone = true; + pthread_mutex_unlock(&lock); + pthread_cond_signal(&cond); + return ncfdplane_destroy(ncfd); +} + +int main(int argc, char** argv){ + setlocale(LC_ALL, ""); + notcurses_options opts = {}; + opts.inhibit_alternate_screen = true; + struct notcurses* nc = notcurses_init(&opts, stdout); + struct ncplane* n = notcurses_stdplane(nc); + int ret = -1; + while(*++argv){ + int fd = open(*argv, O_RDONLY|O_CLOEXEC); + if(fd < 0){ + fprintf(stderr, "Couldn't open %s (%s)\n", *argv, strerror(errno)); + goto done; + } + ncfdplane_options nopts = {}; + struct ncfdplane* ncfp = ncfdplane_create(n, &nopts, fd, cb, eofcb); + pthread_mutex_lock(&lock); + while(!fddone){ + pthread_cond_wait(&cond, &lock); + } + fddone = false; + pthread_mutex_unlock(&lock); + } + +done: + if(notcurses_stop(nc) || ret){ + return EXIT_FAILURE; + } + return EXIT_SUCCESS; +} diff --git a/src/poc/rgb.c b/src/poc/rgb.c index d3736f817..50000285f 100644 --- a/src/poc/rgb.c +++ b/src/poc/rgb.c @@ -22,19 +22,27 @@ int main(void){ r = 0; g = 0x80; b = 0; + ncplane_set_bg_rgb(n, 0x40, 0x20, 0x40); for(y = 0 ; y < dimy ; ++y){ for(x = 0 ; x < dimx ; ++x){ - ncplane_set_fg_rgb(n, r, g, b); - ncplane_putsimple(n, 'x'); + if(ncplane_set_fg_rgb(n, r, g, b)){ + goto err; + } + if(ncplane_cursor_move_yx(n, y, x)){ + goto err; + } + if(ncplane_putsimple(n, 'x') <= 0){ + goto err; + } if(g % 2){ - if(b-- == 0){ + if(--b <= 0){ ++g; b = 0; } }else{ - if(b++ >= 256){ + if(++b >= 256){ ++g; - b = 256; + b = 255; } } } @@ -45,4 +53,8 @@ int main(void){ } notcurses_stop(nc); return EXIT_SUCCESS; + +err: + notcurses_stop(nc); + return EXIT_FAILURE; } diff --git a/tests/fds.cpp b/tests/fds.cpp new file mode 100644 index 000000000..c5205d991 --- /dev/null +++ b/tests/fds.cpp @@ -0,0 +1,103 @@ +#include "main.h" +#include +#include +#include +#include +#include +#include + +std::mutex lock; +std::condition_variable cond; +bool inline_cancelled = false; +bool outofline_cancelled = false; + +int testfdcb(struct ncfdplane* ncfd, const void* buf, size_t s, void* curry){ + struct ncplane* n = ncfdplane_plane(ncfd); + lock.lock(); + if(ncplane_putstr(n, static_cast(buf)) <= 0){ + lock.unlock(); + return -1; + } + lock.unlock(); + (void)curry; + (void)s; + return 0; +} + +int testfdeof(struct ncfdplane* n, int fderrno, void* curry){ + lock.lock(); + outofline_cancelled = true; + lock.unlock(); + cond.notify_one(); + (void)curry; + (void)n; + (void)fderrno; + return 0; +} + +int testfdeofdestroys(struct ncfdplane* n, int fderrno, void* curry){ + lock.lock(); + inline_cancelled = true; + int ret = ncfdplane_destroy(n); + lock.unlock(); + cond.notify_one(); + (void)curry; + (void)fderrno; + return ret; +} + +// test ncfdplanes and ncsubprocs +TEST_CASE("FdsAndSubprocs") { + if(getenv("TERM") == nullptr){ + return; + } + notcurses_options nopts{}; + nopts.inhibit_alternate_screen = true; + nopts.suppress_banner = true; + FILE* outfp_ = fopen("/dev/tty", "wb"); + REQUIRE(outfp_); + struct notcurses* nc_ = notcurses_init(&nopts, outfp_); + REQUIRE(nc_); + struct ncplane* n_ = notcurses_stdplane(nc_); + REQUIRE(n_); + REQUIRE(0 == ncplane_cursor_move_yx(n_, 0, 0)); + + // destroy the ncfdplane outside of its own context + SUBCASE("FdPlaneDestroyOffline") { + REQUIRE(!outofline_cancelled); + ncfdplane_options opts{}; + int fd = open("/etc/sysctl.conf", O_RDONLY|O_CLOEXEC); + REQUIRE(0 <= fd); + auto ncfdp = ncfdplane_create(n_, &opts, fd, testfdcb, testfdeof); + REQUIRE(ncfdp); + std::unique_lock lck(lock); + CHECK(0 == notcurses_render(nc_)); + while(!outofline_cancelled){ + cond.wait(lck); + } + CHECK(0 == ncfdplane_destroy(ncfdp)); + CHECK(0 == notcurses_render(nc_)); + lock.unlock(); + } + + // destroy the ncfdplane within its own context, i.e. from the eof callback + SUBCASE("FdPlaneDestroyInline") { + REQUIRE(!inline_cancelled); + ncfdplane_options opts{}; + opts.curry = n_; + int fd = open("/etc/sysctl.conf", O_RDONLY|O_CLOEXEC); + REQUIRE(0 <= fd); + auto ncfdp = ncfdplane_create(n_, &opts, fd, testfdcb, testfdeofdestroys); + REQUIRE(ncfdp); + std::unique_lock lck(lock); + CHECK(0 == notcurses_render(nc_)); + while(!inline_cancelled){ + cond.wait(lck); + } + CHECK(0 == notcurses_render(nc_)); + lock.unlock(); + } + + CHECK(0 == notcurses_stop(nc_)); + CHECK(0 == fclose(outfp_)); +} diff --git a/tests/fills.cpp b/tests/fills.cpp index 0db03d3ab..c910570d0 100644 --- a/tests/fills.cpp +++ b/tests/fills.cpp @@ -436,6 +436,14 @@ TEST_CASE("Fills") { ncplane_destroy(p2); } +#ifdef USE_QRCODEGEN + SUBCASE("QRCodes") { + const char* qr = "a very simple qr code"; + CHECK(0 < ncplane_qrcode(n_, 0, qr, strlen(qr))); + CHECK(0 == notcurses_render(nc_)); + } +#endif + CHECK(0 == notcurses_stop(nc_)); CHECK(0 == fclose(outfp_)); diff --git a/tests/main.cpp b/tests/main.cpp index b302786cb..92edb1756 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -48,6 +48,7 @@ handle_opts(const char** argv){ // notcurses_stop()). so just whip up a new one, and free it immediately. static void reset_terminal(){ + // FIXME much more robust to just use termios here notcurses_options nopts{}; nopts.inhibit_alternate_screen = true; auto nc = notcurses_init(&nopts, NULL); diff --git a/tools/version.h.in b/tools/version.h.in index 73388c5df..9165cc963 100644 --- a/tools/version.h.in +++ b/tools/version.h.in @@ -3,4 +3,5 @@ #define notcurses_VERSION_PATCH "@notcurses_VERSION_PATCH@" #cmakedefine DFSG_BUILD #cmakedefine USE_FFMPEG +#cmakedefine USE_QRCODEGEN #define NOTCURSES_SHARE "@CMAKE_INSTALL_FULL_DATADIR@/notcurses"