ncfdplane, fileroller PoC, ncfdplane unit tests, qrcodes #24 #514 (#524)

* first draft of ncsubproc spec
* qrcode first draft #24
* demo: add qrcode demo skeleton, entries #24
* qrcode demo #24
* fedora python build changes from @dcantrell
* ncplane_qrcode() works #24
* add some flash to the qrcode demo #24
* drone: use newest builders
* fix up rgb PoC
* drop jungle demo to 100Hz target
* add fd.c
* ncfd skeletons
* more ncfdplane #514
* ncfdp i/o loop
* ncfp: improve uinit test, write core
* firm up ncfdplane #514
* fileroller PoC #514
* ncplane: allow '\n' in stream when scrolling #523
pull/531/head
Nick Black 4 years ago committed by GitHub
parent b264de6b8f
commit fe8034b5e0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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

@ -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

@ -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+

@ -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.

@ -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 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 notcurses; // notcurses state for a given terminal, composed of ncplanes
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

@ -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, },

@ -6,8 +6,8 @@
#include <unistd.h>
#include <limits.h>
#include <stdatomic.h>
#include <version.h>
#include <notcurses/notcurses.h>
#include <version.h>
#ifdef USE_FFMPEG
#include <libavutil/pixdesc.h>
#include <libavutil/avconfig.h>
@ -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);

@ -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);

@ -0,0 +1,39 @@
#include "demo.h"
#include <sys/random.h>
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;
}

@ -0,0 +1,141 @@
#include <unistd.h>
#include <pthread.h>
#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;
}

@ -1,4 +1,5 @@
#include "internal.h"
#include <qrcodegen/qrcodegen.h>
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

@ -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

@ -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

@ -0,0 +1,65 @@
#include <errno.h>
#include <fcntl.h>
#include <locale.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <notcurses/notcurses.h>
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;
}

@ -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;
}

@ -0,0 +1,103 @@
#include "main.h"
#include <cerrno>
#include <mutex>
#include <cstring>
#include <fcntl.h>
#include <unistd.h>
#include <condition_variable>
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<const char*>(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<std::mutex> 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<std::mutex> 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_));
}

@ -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_));

@ -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);

@ -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"

Loading…
Cancel
Save