diff --git a/src/lib/internal.h b/src/lib/internal.h index ca1497a4b..a62febde8 100644 --- a/src/lib/internal.h +++ b/src/lib/internal.h @@ -113,6 +113,12 @@ typedef struct ncplane { int margin_b, margin_r;// bottom and right margins, stored for resize bool scrolling; // is scrolling enabled? always disabled by default bool fixedbound; // are we fixed relative to the parent's scrolling? + + // we need to track any widget to which we are bound, so that (1) we don't + // end up bound to two widgets and (2) we can clean them up on shutdown + // (assuming they weren't explicitly cleaned up by the client). + void* widget; // widget to which we are bound, can be NULL + void(*wdestruct)(void*); // widget destructor, NULL iff widget is NULL } ncplane; // current presentation state of the terminal. it is carried across render @@ -428,7 +434,25 @@ ncplane_stdplane_const(const ncplane* n){ return notcurses_stdplane_const(ncplane_notcurses_const(n)); } -// initialize libav +// set the plane's widget and wdestruct fields, returning non-zero if they're +// already non-NULL (i.e. if the plane is already bound), unless we pass NULL +// (which ought be done from the widget destructor, to avoid corecursion). +static inline int +ncplane_set_widget(ncplane* n, void* w, void(*wdestruct)(void*)){ + if(n->widget){ + if(w){ + logerror("plane is already bound to a widget\n"); + return -1; + } + }else if(w == NULL){ + return -1; + } + n->widget = w; + n->wdestruct = wdestruct; + return 0; +} + +// initialize visualization backend (ffmpeg/oiio) int ncvisual_init(int loglevel); static inline int diff --git a/src/lib/menu.c b/src/lib/menu.c index a24c713b6..18152a908 100644 --- a/src/lib/menu.c +++ b/src/lib/menu.c @@ -412,6 +412,7 @@ ncmenu* ncmenu_create(ncplane* n, const ncmenu_options* opts){ }; ret->ncp = ncplane_create(n, &nopts); if(ret->ncp){ + ncplane_set_widget(ret->ncp, ret, ncmenu_destroy); ret->unrolledsection = -1; ret->headerchannels = opts->headerchannels; ret->dissectchannels = opts->headerchannels; @@ -769,7 +770,9 @@ int ncmenu_destroy(ncmenu* n){ int ret = 0; if(n){ free_menu_sections(n); - ncplane_destroy(n->ncp); + if(ncplane_set_widget(n->ncp, NULL, NULL) == 0){ + ncplane_destroy(n->ncp); + } free(n); } return ret; diff --git a/src/lib/notcurses.c b/src/lib/notcurses.c index 0662d0d5f..fdd0a0831 100644 --- a/src/lib/notcurses.c +++ b/src/lib/notcurses.c @@ -346,6 +346,13 @@ ncpile_destroy(ncpile* pile){ void free_plane(ncplane* p){ if(p){ + if(p->widget){ + void* w = p->widget; + void (*wdestruct)(void*) = p->wdestruct; + p->widget = NULL; + p->wdestruct = NULL; + wdestruct(w); + } // ncdirect fakes an ncplane with no ->pile if(ncplane_pile(p)){ notcurses* nc = ncplane_notcurses(p); @@ -444,6 +451,8 @@ ncplane* ncplane_new_internal(notcurses* nc, ncplane* n, } p->scrolling = false; p->fixedbound = nopts->flags & NCPLANE_OPTION_FIXED; + p->widget = NULL; + p->wdestruct = NULL; if(nopts->flags & NCPLANE_OPTION_MARGINALIZED){ p->margin_b = nopts->margin_b; p->margin_r = nopts->margin_r; diff --git a/src/poc/procroller.c b/src/poc/procroller.c index d947a8c6c..36d822a94 100644 --- a/src/poc/procroller.c +++ b/src/poc/procroller.c @@ -54,7 +54,8 @@ int main(int argc, char** argv){ struct ncplane* std = notcurses_stdplane(nc); ncplane_set_scrolling(std, true); ncsubproc_options nopts = {}; - struct ncsubproc* nsproc = ncsubproc_createvp(std, &nopts, *argv, argv, cb, eofcb); + struct ncsubproc* nsproc = ncsubproc_createvp(std, &nopts, *argv, + (const char* const*)argv, cb, eofcb); if(nsproc == NULL){ notcurses_stop(nc); return EXIT_FAILURE; diff --git a/src/tests/menu.cpp b/src/tests/menu.cpp index c7416a11b..b24d66417 100644 --- a/src/tests/menu.cpp +++ b/src/tests/menu.cpp @@ -76,7 +76,7 @@ TEST_CASE("Menu") { ncmenu_destroy(ncm); } - // don't call ncmenu_destroy(), invoking destruction in notcurses_stop() + // don't call ncmenu_destroy(), invoking destruction in notcurses_stop(). SUBCASE("MenuNoFree") { struct ncmenu_item file_items[] = { { .desc = "I would like a new file", .shortcut = ncinput(), },