diff --git a/CMakeLists.txt b/CMakeLists.txt index fa5c34b0b..232312259 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -60,6 +60,12 @@ if("${USE_COVERAGE}") string(APPEND CMAKE_CXX_FLAGS_DEBUG " --coverage -fprofile-instr-generate -fcoverage-mapping") endif() +if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") +set(PKGCONFIG_DIR "${CMAKE_INSTALL_LIBDIR}/pkgconfig") +else() +set(PKGCONFIG_DIR "${CMAKE_INSTALL_PREFIX}/libdata/pkgconfig") +endif() + # global compiler flags add_compile_definitions(FORTIFY_SOURCE=2) add_compile_options(-Wall -Wextra -W -Wshadow -Wformat -fexceptions) @@ -114,32 +120,31 @@ endif() set_property(GLOBAL APPEND PROPERTY PACKAGES_FOUND qrcodegen) endif() find_library(LIBRT rt) +feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) file(GLOB COMPATSRC CONFIGURE_DEPENDS src/compat/compat.c) -# libnotcurses (core shared library, core static library) -file(GLOB NCSRCS CONFIGURE_DEPENDS src/lib/*.c src/lib/*.cpp) -add_library(notcurses SHARED ${NCSRCS} ${COMPATSRC}) +############################################################################ +# libnotcurses-core (core shared library, core static library) +file(GLOB NCCORESRCS CONFIGURE_DEPENDS src/lib/*.c src/lib/*.cpp) +add_library(notcurses-core SHARED ${NCCORESRCS} ${COMPATSRC}) if(${USE_STATIC}) -add_library(notcurses-static STATIC ${NCSRCS}) +add_library(notcurses-core-static STATIC ${NCCORESRCS}) else() -add_library(notcurses-static STATIC EXCLUDE_FROM_ALL ${NCSRCS}) +add_library(notcurses-core-static STATIC EXCLUDE_FROM_ALL ${NCCORESRCS}) endif() set_target_properties( - notcurses-static PROPERTIES - OUTPUT_NAME notcurses + notcurses-core-static PROPERTIES + OUTPUT_NAME notcurses-core ) - -feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) - -set_target_properties(notcurses PROPERTIES +set_target_properties(notcurses-core PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION ${PROJECT_VERSION_MAJOR} ) -set_target_properties(notcurses-static PROPERTIES +set_target_properties(notcurses-core-static PROPERTIES VERSION ${PROJECT_VERSION} ) -target_include_directories(notcurses +target_include_directories(notcurses-core PRIVATE include src @@ -147,7 +152,7 @@ target_include_directories(notcurses "${TERMINFO_INCLUDE_DIRS}" "${READLINE_INCLUDE_DIRS}" ) -target_include_directories(notcurses-static +target_include_directories(notcurses-core-static PRIVATE include src @@ -155,7 +160,7 @@ target_include_directories(notcurses-static "${TERMINFO_STATIC_INCLUDE_DIRS}" "${READLINE_STATIC_INCLUDE_DIRS}" ) -target_link_libraries(notcurses +target_link_libraries(notcurses-core PRIVATE "${TERMINFO_LIBRARIES}" "${READLINE_LIBRARIES}" @@ -164,7 +169,7 @@ target_link_libraries(notcurses PUBLIC Threads::Threads ) -target_link_libraries(notcurses-static +target_link_libraries(notcurses-core-static PRIVATE "${TERMINFO_STATIC_LIBRARIES}" "${READLINE_STATIC_LIBRARIES}" @@ -173,32 +178,81 @@ target_link_libraries(notcurses-static PUBLIC Threads::Threads ) -target_link_directories(notcurses +target_link_directories(notcurses-core PRIVATE "${TERMINFO_LIBRARY_DIRS}" "${READLINE_LIBRARY_DIRS}" ) -target_link_directories(notcurses-static +target_link_directories(notcurses-core-static PRIVATE "${TERMINFO_STATIC_LIBRARY_DIRS}" "${READLINE_STATIC_LIBRARY_DIRS}" ) - +# don't want these on freebsd/dragonfly/osx +if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") +target_compile_definitions(notcurses-core + PUBLIC + _XOPEN_SOURCE=700 # wcwidth(3) requires _XOPEN_SOURCE, and is in our headers + PRIVATE + _GNU_SOURCE _DEFAULT_SOURCE +) +target_compile_definitions(notcurses-core-static + PUBLIC + _XOPEN_SOURCE=700 # wcwidth(3) requires _XOPEN_SOURCE, and is in our headers + PRIVATE + _GNU_SOURCE _DEFAULT_SOURCE +) +endif() if(${USE_QRCODEGEN}) -target_link_libraries(notcurses PRIVATE qrcodegen) -target_link_libraries(notcurses-static PRIVATE qrcodegen) +target_link_libraries(notcurses-core PRIVATE qrcodegen) +target_link_libraries(notcurses-core-static PRIVATE qrcodegen) endif() -if(${USE_FFMPEG}) +############################################################################ +# libnotcurses (multimedia shared library+static library) +file(GLOB NCSRCS CONFIGURE_DEPENDS src/media/*.c src/media/*.cpp) +add_library(notcurses SHARED ${NCSRCS} ${COMPATSRC}) +if(${USE_STATIC}) +add_library(notcurses-static STATIC ${NCSRCS}) +else() +add_library(notcurses-static STATIC EXCLUDE_FROM_ALL ${NCSRCS}) +endif() +set_target_properties( + notcurses-static PROPERTIES + OUTPUT_NAME notcurses +) target_include_directories(notcurses + PRIVATE + include + src + src/lib + "${PROJECT_BINARY_DIR}/include" +) +target_include_directories(notcurses-static + PRIVATE + include + src + src/lib + "${PROJECT_BINARY_DIR}/include" +) +target_link_libraries(notcurses + PUBLIC + notcurses-core +) +target_link_libraries(notcurses-static PUBLIC + notcurses-core-static +) +if(${USE_FFMPEG}) +target_include_directories(notcurses + PRIVATE "${AVCODEC_INCLUDE_DIRS}" "${AVFORMAT_INCLUDE_DIRS}" "${AVUTIL_INCLUDE_DIRS}" "${SWSCALE_INCLUDE_DIRS}" ) target_include_directories(notcurses-static - PUBLIC + PRIVATE "${AVCODEC_STATIC_INCLUDE_DIRS}" "${AVFORMAT_STATIC_INCLUDE_DIRS}" "${AVUTIL_STATIC_INCLUDE_DIRS}" @@ -209,7 +263,6 @@ target_link_libraries(notcurses "${AVCODEC_LIBRARIES}" "${AVFORMAT_LIBRARIES}" "${SWSCALE_LIBRARIES}" - PUBLIC "${AVUTIL_LIBRARIES}" ) target_link_libraries(notcurses-static @@ -217,7 +270,6 @@ target_link_libraries(notcurses-static "${AVCODEC_STATIC_LIBRARIES}" "${AVFORMAT_STATIC_LIBRARIES}" "${SWSCALE_STATIC_LIBRARIES}" - PUBLIC "${AVUTIL_STATIC_LIBRARIES}" ) target_link_directories(notcurses @@ -225,7 +277,6 @@ target_link_directories(notcurses "${AVCODEC_LIBRARY_DIRS}" "${AVFORMAT_LIBRARY_DIRS}" "${SWSCALE_LIBRARY_DIRS}" - PUBLIC "${AVUTIL_LIBRARY_DIRS}" ) target_link_directories(notcurses-static @@ -233,7 +284,6 @@ target_link_directories(notcurses-static "${AVCODEC_STATIC_LIBRARY_DIRS}" "${AVFORMAT_STATIC_LIBRARY_DIRS}" "${SWSCALE_STATIC_LIBRARY_DIRS}" - PUBLIC "${AVUTIL_STATIC_LIBRARY_DIRS}" ) elseif(${USE_OIIO}) @@ -245,28 +295,8 @@ target_link_directories(notcurses PRIVATE ${OIIO_LIBRARY_DIRS}) target_link_directories(notcurses-static PRIVATE ${OIIO_STATIC_LIBRARY_DIRS}) endif() -# don't want these on freebsd -if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") -target_compile_definitions(notcurses - PUBLIC - _XOPEN_SOURCE=700 # wcwidth(3) requires _XOPEN_SOURCE, and is in our headers - PRIVATE - _GNU_SOURCE _DEFAULT_SOURCE -) -target_compile_definitions(notcurses-static - PUBLIC - _XOPEN_SOURCE=700 # wcwidth(3) requires _XOPEN_SOURCE, and is in our headers - PRIVATE - _GNU_SOURCE _DEFAULT_SOURCE -) -set(PKGCONFIG_DIR "${CMAKE_INSTALL_LIBDIR}/pkgconfig") -elseif(${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD") -set(PKGCONFIG_DIR "${CMAKE_INSTALL_PREFIX}/libdata/pkgconfig") -elseif(${CMAKE_SYSTEM_NAME} STREQUAL "DragonFly") -set(PKGCONFIG_DIR "${CMAKE_INSTALL_PREFIX}/libdata/pkgconfig") -endif() - -# libnotcurses++ +############################################################################ +# libnotcurses++ (C++ wrappers) set(NCPP_SOURCES src/libcpp/FDPlane.cc src/libcpp/Menu.cc @@ -375,31 +405,6 @@ install(FILES ${NOTCURSES_HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/notcu install(FILES ${NCPP_HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/ncpp) install(FILES ${NCPP_INTERNAL_HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/ncpp/internal) -# notcurses-demo -file(GLOB DEMOSRCS CONFIGURE_DEPENDS src/demo/*.c) -add_executable(notcurses-demo ${DEMOSRCS} ${COMPATSRC}) -target_compile_definitions(notcurses-demo - PRIVATE - _GNU_SOURCE -) -target_include_directories(notcurses-demo - PRIVATE - include - src - "${PROJECT_BINARY_DIR}/include" - PUBLIC - "${AVCODEC_INCLUDE_DIRS}" - "${AVFORMAT_INCLUDE_DIRS}" - "${AVUTIL_INCLUDE_DIRS}" - "${SWSCALE_INCLUDE_DIRS}" -) -target_link_libraries(notcurses-demo - PRIVATE - notcurses - unistring - ${MATH_LIBRARIES} -) - # tiny proofs of concept, one binary per source file if(USE_POC) file(GLOB POCSRCS CONFIGURE_DEPENDS src/poc/*.c src/poc/*.cpp) @@ -496,19 +501,35 @@ if(USE_DOXYGEN) endif() endif() -# ncneofetch -file(GLOB FETCHSRCS CONFIGURE_DEPENDS src/fetch/*.c) -add_executable(ncneofetch ${FETCHSRCS}) -target_include_directories(ncneofetch +############################################################################ +# notcurses-demo +file(GLOB DEMOSRCS CONFIGURE_DEPENDS src/demo/*.c) +add_executable(notcurses-demo ${DEMOSRCS} ${COMPATSRC}) +target_compile_definitions(notcurses-demo + PRIVATE + _GNU_SOURCE +) +target_include_directories(notcurses-demo PRIVATE include + src "${PROJECT_BINARY_DIR}/include" + PUBLIC + "${AVCODEC_INCLUDE_DIRS}" + "${AVFORMAT_INCLUDE_DIRS}" + "${AVUTIL_INCLUDE_DIRS}" + "${SWSCALE_INCLUDE_DIRS}" ) -target_link_libraries(ncneofetch +target_link_libraries(notcurses-demo PRIVATE notcurses + unistring + ${MATH_LIBRARIES} + PUBLIC + Threads::Threads ) +############################################################################ # notcurses-input file(GLOB INPUTSRCS CONFIGURE_DEPENDS src/input/input.cpp) add_executable(notcurses-input ${INPUTSRCS}) @@ -522,34 +543,53 @@ target_link_libraries(notcurses-input notcurses++ ) -# ncls -file(GLOB LSSRC CONFIGURE_DEPENDS src/ls/*.cpp) -add_executable(ncls ${LSSRC}) -target_include_directories(ncls +############################################################################ +# notcurses-tetris +file(GLOB TETRISSRC CONFIGURE_DEPENDS src/tetris/*.cpp) +add_executable(notcurses-tetris ${TETRISSRC}) +target_include_directories(notcurses-tetris PRIVATE include "${PROJECT_BINARY_DIR}/include" ) -target_link_libraries(ncls +target_link_libraries(notcurses-tetris PRIVATE notcurses++ ) -# notcurses-tetris -file(GLOB TETRISSRC CONFIGURE_DEPENDS src/tetris/*.cpp) -add_executable(notcurses-tetris ${TETRISSRC}) -target_include_directories(notcurses-tetris +# all further binaries require multimedia support +if(NOT ${USE_MULTIMEDIA} STREQUAL "none") +############################################################################ +# ncneofetch +file(GLOB FETCHSRCS CONFIGURE_DEPENDS src/fetch/*.c) +add_executable(ncneofetch ${FETCHSRCS}) +target_include_directories(ncneofetch PRIVATE include "${PROJECT_BINARY_DIR}/include" ) -target_link_libraries(notcurses-tetris +target_link_libraries(ncneofetch + PRIVATE + notcurses +) + +############################################################################ +# ncls +file(GLOB LSSRC CONFIGURE_DEPENDS src/ls/*.cpp) +add_executable(ncls ${LSSRC}) +target_include_directories(ncls + PRIVATE + include + "${PROJECT_BINARY_DIR}/include" +) +target_link_libraries(ncls PRIVATE notcurses++ ) +############################################################################ # notcurses-view -if(${USE_FFMPEG} OR ${USE_OIIO}) +if(NOT ${USE_MULTIMEDIA} STREQUAL "none") file(GLOB VIEWSRCS CONFIGURE_DEPENDS src/view/*.cpp) add_executable(notcurses-view ${VIEWSRCS} ${COMPATSRC}) target_include_directories(notcurses-view @@ -563,8 +603,10 @@ target_link_libraries(notcurses-view notcurses++ ) endif() +endif() -# Testing +############################################################################ +# testing if(${BUILD_TESTING}) #set(CMAKE_CTEST_ARGUMENTS "-V") if(${USE_DOCTEST}) @@ -579,8 +621,8 @@ target_include_directories(notcurses-tester ) target_link_libraries(notcurses-tester PRIVATE - unistring notcurses++ + unistring "${TERMINFO_LIBRARIES}" ) add_test( @@ -629,11 +671,14 @@ add_custom_target(demo ) # pkg-config support +configure_file(tools/notcurses-core.pc.in + ${CMAKE_CURRENT_BINARY_DIR}/notcurses-core.pc + @ONLY +) configure_file(tools/notcurses.pc.in ${CMAKE_CURRENT_BINARY_DIR}/notcurses.pc @ONLY ) - configure_file(tools/notcurses++.pc.in ${CMAKE_CURRENT_BINARY_DIR}/notcurses++.pc @ONLY @@ -686,7 +731,7 @@ install(FILES DESTINATION ${PKGCONFIG_DIR} ) -if(${USE_FFMPEG} OR ${USE_OIIO}) +if(NOT ${USE_MULTIMEDIA} STREQUAL "none") file(GLOB TESTDATA CONFIGURE_DEPENDS data/*) # Don't install source materia for self-originated multimedia list(FILTER TESTDATA EXCLUDE REGEX ".*xcf$") @@ -701,14 +746,14 @@ install(FILES ${MARKDOWN} DESTINATION ${CMAKE_INSTALL_DOCDIR}) install(TARGETS notcurses-demo DESTINATION bin) install(TARGETS notcurses-input DESTINATION bin) -install(TARGETS ncls DESTINATION bin) -install(TARGETS ncneofetch DESTINATION bin) install(TARGETS notcurses-tetris DESTINATION bin) -if(${USE_FFMPEG} OR ${USE_OIIO}) +if(NOT ${USE_MULTIMEDIA} STREQUAL "none") +install(TARGETS ncneofetch DESTINATION bin) +install(TARGETS ncls DESTINATION bin) install(TARGETS notcurses-view DESTINATION bin) endif() -install(TARGETS notcurses notcurses++ +install(TARGETS notcurses-core notcurses notcurses++ LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT Libraries @@ -716,7 +761,7 @@ install(TARGETS notcurses notcurses++ ) if(${USE_STATIC}) install( - TARGETS notcurses-static notcurses++-static + TARGETS notcurses-core-static notcurses-static notcurses++-static LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT Libraries diff --git a/INSTALL.md b/INSTALL.md index 64e1cfe64..7aa1b2da9 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -14,6 +14,14 @@ On an APT-based distribution, run: `apt-get install build-essential cmake doctest-dev libavformat-dev libavutil-dev libncurses-dev libreadline-dev libqrcodegen-dev libswscale-dev libunistring-dev pandoc pkg-config` +If you only intend to build core Notcurses (without multimedia support), run: + +`apt-get install build-essential cmake libncurses-dev libreadline-dev libqrcodegen-dev pandoc pkg-config` + +If you only intend to build core Notcurses (without multimedia support), run: + +`apt-get install build-essential cmake libncurses-dev libreadline-dev libqrcodegen-dev pandoc pkg-config` + If you want to build the Python wrappers, you'll also need: `apt-get install python3-cffi python3-dev python3-pypandoc python3-setuptools` @@ -28,8 +36,11 @@ If you want to build the Rust wrappers, you'll also need: ## Building -* Create a subdirectory, traditionally `build`. Enter the directory. -* `cmake ..`. You might want to set e.g. `CMAKE_BUILD_TYPE`. +* Create a subdirectory, traditionally `build` (this is not strictly necessary, + but it keeps your source tree clean). Enter the directory. +* `cmake ..` +** You might want to set e.g. `CMAKE_BUILD_TYPE`. Use `-DVAR=val`. +** To build without multimedia support, use `-DUSE_MULTIMEDIA=none`. * `make` * `make test` * `make install` @@ -89,4 +100,3 @@ but must be `Debug` for use of `USE_COVERAGE`. * `USE_POC`: build small, uninstalled proof-of-concept binaries * `USE_QRCODEGEN`: build qrcode support via libqrcodegen * `USE_STATIC`: build static libraries (in addition to shared ones) - diff --git a/NEWS.md b/NEWS.md index cf0cf5868..6bacf6c40 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,6 +1,12 @@ This document attempts to list user-visible changes and any major internal rearrangements of Notcurses. +* 2.1.6 (not yet released): + * Notcurses has been split into two libraries, `notcurses-core` and + `notcurses`. The latter contains the heavyweight multimedia code, + so that applications which don't need this functionality can link against + only the former. `pkg-config` support is present for both. + * 2.1.5 (2021-01-15): * Notcurses **now depends on GNU Readline at build and runtime**, entirely for the benefit of direct mode, which now prepares GNU Readline for safe diff --git a/README.md b/README.md index 9288f3c0d..c0a2522c4 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,8 @@ Why use this non-standard library? * The library object exports a minimal set of symbols. Where reasonable, `static inline` header-only code is used. This facilitates compiler optimizations, and reduces loader time. + * Notcurses can be built without its multimedia functionality, requiring a + significantly lesser set of dependencies. * All APIs natively support the Universal Character Set (Unicode). The `cell` API is based around Unicode's [Extended Grapheme Cluster](https://unicode.org/reports/tr29/) concept. diff --git a/USAGE.md b/USAGE.md index 75699adf0..91c0ee4be 100644 --- a/USAGE.md +++ b/USAGE.md @@ -1,7 +1,7 @@ # Usage -As of version 2.0.0, Notcurses honors Semantic Versioning, the API is stable, -and the project is committed to backwards compatibility. +As of version 2.0.0, the Notcurses API is stable, and the project is committed +to backwards compatibility. * [Direct Mode](#direct-mode) * [Alignment](#alignment) @@ -15,16 +15,17 @@ and the project is committed to backwards compatibility. * [Stats](#stats) * [C++](#c++) -A full API reference [is available](https://nick-black.com/notcurses/). Manual -pages ought have been installed along with Notcurses. This document is a +A full API reference [is available](https://nick-black.com/notcurses/) in the +form of manual pages; these ought have been installed along with Notcurses. This document is a secondary reference, and should not be considered authoritative. For a more unified commentary, consider the [paperback](https://www.amazon.com/dp/B086PNVNC9) -(also available as a free PDF from https://nick-black.com). +(also available as a [free PDF](https://nick-black.com/dankwiki/index.php?title=Hacking_The_Planet!_with_Notcurses). A program wishing to use Notcurses will need to link it, ideally using the output of `pkg-config --libs notcurses`. It is advised to compile with the -output of `pkg-config --cflags notcurses`. If using CMake, a support file is -provided, and can be accessed as `Notcurses`. +output of `pkg-config --cflags notcurses`. To use the minimal core Notcurses, +without multimedia support, use `pkg-config --libs notcurses-core` etc. If +using CMake, a support file is provided, and can be accessed as `Notcurses`. Before calling into Notcurses—and usually as one of the first calls of the program—be sure to call `setlocale(3)` with an appropriate UTF-8 locale. It is diff --git a/data/tetris-background.jpg b/data/tetris-background.jpg index 82d3382cb..91ceeb8a3 100644 Binary files a/data/tetris-background.jpg and b/data/tetris-background.jpg differ diff --git a/doc/man/man3/notcurses.3.md b/doc/man/man3/notcurses.3.md index 472a0dd54..983b3dfc2 100644 --- a/doc/man/man3/notcurses.3.md +++ b/doc/man/man3/notcurses.3.md @@ -8,9 +8,10 @@ notcurses - TUI library for modern terminal emulators # SYNOPSIS -**#include ** +**#include ** or +**#include ** -**-lnotcurses** +**-lnotcurses** or **-lnotcurses-core** # DESCRIPTION @@ -23,7 +24,8 @@ A program wishing to use Notcurses will need to link it, ideally using the output of **pkg-config --libs notcurses** (see **pkg-config(1)**). It is advised to compile with the output of **pkg-config --cflags notcurses**. If using CMake, a support file is provided, and can be accessed as **Notcurses** -(see **cmake(1)**). +(see **cmake(1)**). If multimedia capabilities are not needed, it is possible +to link against a minimal Notcurses using **pkg-config --libs notcurses-core**. **notcurses_init(3)** can then be used to initialize a Notcurses instance for a given **FILE** (usually **stdout**, usually attached to a terminal). diff --git a/src/demo/dragon.c b/src/demo/dragon.c index 42b9ee965..ca3071bd8 100644 --- a/src/demo/dragon.c +++ b/src/demo/dragon.c @@ -105,7 +105,6 @@ int dragon_demo(struct notcurses* nc){ } DEMO_RENDER(nc); demo_nanosleep(nc, &scaled); - ncplane_erase(n); }while(lasttotal != r); ncvisual_destroy(ncv); return 0; diff --git a/src/lib/blit.c b/src/lib/blit.c index b206b3f66..c3629428c 100644 --- a/src/lib/blit.c +++ b/src/lib/blit.c @@ -878,13 +878,6 @@ int ncblit_rgba(const void* data, int linesize, const struct ncvisual_options* v leny, lenx, blend); } -int rgba_blit_dispatch(ncplane* nc, const struct blitset* bset, int placey, - int placex, int linesize, const void* data, int begy, - int begx, int leny, int lenx, bool blendcolors){ - return bset->blit(nc, placey, placex, linesize, data, begy, begx, - leny, lenx, blendcolors); -} - ncblitter_e ncvisual_media_defblitter(const notcurses* nc, ncscale_e scale){ return rgba_blitter_default(nc->utf8, scale, nc->tcache.sextants); } diff --git a/src/lib/ffmpeg.h b/src/lib/ffmpeg.h deleted file mode 100644 index f02b87782..000000000 --- a/src/lib/ffmpeg.h +++ /dev/null @@ -1,72 +0,0 @@ -#ifndef NOTCURSES_FFMPEG -#define NOTCURSES_FFMPEG - -#include "version.h" -#ifdef USE_FFMPEG - -extern "C" { - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -} // extern "C" - -struct AVFormatContext; -struct AVCodecContext; -struct AVFrame; -struct AVCodec; -struct AVCodecParameters; -struct AVPacket; - -typedef struct ncvisual_details { - int packet_outstanding; - struct AVFormatContext* fmtctx; - struct AVCodecContext* codecctx; // video codec context - struct AVCodecContext* subtcodecctx; // subtitle codec context - struct AVFrame* frame; // frame as read - struct AVFrame* oframe; // RGBA frame - struct AVCodec* codec; - struct AVCodecParameters* cparams; - struct AVCodec* subtcodec; - struct AVPacket* packet; - struct SwsContext* swsctx; - AVSubtitle subtitle; - int stream_index; // match against this following av_read_frame() - int sub_stream_index; // subtitle stream index, can be < 0 if no subtitles -} ncvisual_details; - -static inline auto -ncvisual_details_init(ncvisual_details* deets) -> int { - memset(deets, 0, sizeof(*deets)); - deets->stream_index = -1; - deets->sub_stream_index = -1; - if((deets->frame = av_frame_alloc()) == nullptr){ - return -1; - } - return 0; -} - -static inline auto -ncvisual_details_destroy(ncvisual_details* deets) -> void { - avcodec_close(deets->codecctx); - avcodec_free_context(&deets->codecctx); - av_frame_free(&deets->frame); - av_freep(&deets->oframe); - //avcodec_parameters_free(&ncv->cparams); - sws_freeContext(deets->swsctx); - av_packet_free(&deets->packet); - avformat_close_input(&deets->fmtctx); - avsubtitle_free(&deets->subtitle); -} - -#endif - -#endif diff --git a/src/lib/internal.h b/src/lib/internal.h index 351c3cbfc..48035ea81 100644 --- a/src/lib/internal.h +++ b/src/lib/internal.h @@ -681,9 +681,13 @@ memdup(const void* src, size_t len){ void* bgra_to_rgba(const void* data, int rows, int rowstride, int cols); -int rgba_blit_dispatch(ncplane* nc, const struct blitset* bset, int placey, - int placex, int linesize, const void* data, int begy, - int begx, int leny, int lenx, bool blendcolors); +static inline int +rgba_blit_dispatch(ncplane* nc, const struct blitset* bset, int placey, + int placex, int linesize, const void* data, int begy, + int begx, int leny, int lenx, bool blendcolors){ + return bset->blit(nc, placey, placex, linesize, data, begy, begx, + leny, lenx, blendcolors); +} // find the "center" cell of two lengths. in the case of even rows/columns, we // place the center on the top/left. in such a case there will be one more @@ -1068,6 +1072,8 @@ int setup_signals(void* nc, bool no_quit_sigs, bool no_winch_sig, int(*handler)(void*)); int drop_signals(void* nc); +void ncvisual_printbanner(const notcurses* nc); + #ifdef __cplusplus } #endif diff --git a/src/lib/notcurses.c b/src/lib/notcurses.c index b6483cd53..2a769596c 100644 --- a/src/lib/notcurses.c +++ b/src/lib/notcurses.c @@ -802,19 +802,7 @@ init_banner(const notcurses* nc){ #error "No __BYTE_ORDER__ definition" #endif , curses_version()); -#ifdef USE_FFMPEG - printf(" avformat %u.%u.%u avutil %u.%u.%u swscale %u.%u.%u\n", - LIBAVFORMAT_VERSION_MAJOR, LIBAVFORMAT_VERSION_MINOR, LIBAVFORMAT_VERSION_MICRO, - LIBAVUTIL_VERSION_MAJOR, LIBAVUTIL_VERSION_MINOR, LIBAVUTIL_VERSION_MICRO, - LIBSWSCALE_VERSION_MAJOR, LIBSWSCALE_VERSION_MINOR, LIBSWSCALE_VERSION_MICRO); -#else -#ifdef USE_OIIO - printf(" openimageio %s\n", oiio_version()); -#else - term_fg_palindex(nc, stderr, nc->tcache.colors <= 88 ? 1 % nc->tcache.colors : 0xcb); - fprintf(stderr, "\n Warning! Notcurses was built without multimedia support.\n"); -#endif -#endif + ncvisual_printbanner(nc); fflush(stdout); term_fg_palindex(nc, stderr, nc->tcache.colors <= 88 ? 1 % nc->tcache.colors : 0xcb); if(!nc->tcache.RGBflag){ // FIXME diff --git a/src/lib/oiio.h b/src/lib/oiio.h deleted file mode 100644 index a7e983ca0..000000000 --- a/src/lib/oiio.h +++ /dev/null @@ -1,38 +0,0 @@ -#ifndef NOTCURSES_OIIO -#define NOTCURSES_OIIO - -// OpenImageIO implementation of ncvisual_details -#include "version.h" -#ifdef USE_OIIO -#include -#include -#include -#include -#include - -typedef struct ncvisual_details { - std::unique_ptr image; // must be close()d - std::unique_ptr frame; - std::unique_ptr ibuf; - uint64_t framenum; -} ncvisual_details; - -static inline auto -ncvisual_details_init(ncvisual_details *deets) -> int { - deets->image = nullptr; - deets->frame = nullptr; - deets->ibuf = nullptr; - deets->framenum = 0; - return 0; -} - -static inline auto -ncvisual_details_destroy(ncvisual_details* deets) -> void { - if(deets->image){ - deets->image->close(); - } -} - -#endif - -#endif diff --git a/src/lib/visual-details.h b/src/lib/visual-details.h index a22493fa3..f2d9a2916 100644 --- a/src/lib/visual-details.h +++ b/src/lib/visual-details.h @@ -4,48 +4,40 @@ #include "builddef.h" #include "notcurses/notcurses.h" -#ifdef USE_FFMPEG -#include "ffmpeg.h" -#else -#ifdef USE_OIIO -#include "oiio.h" -#else - -typedef struct ncvisual_details { -} ncvisual_details; - -static inline auto ncvisual_details_init(ncvisual_details* deets) -> int { - (void)deets; - return 0; -} - -static inline auto -ncvisual_details_destroy(ncvisual_details* deets) -> void { - (void)deets; -} -#endif -#endif - struct ncplane; +struct ncvisual_details; typedef struct ncvisual { + struct ncvisual_details* details;// implementation-specific details + uint32_t* data; // (scaled) RGBA image data, rowstride bytes per row int cols, rows; // lines are sometimes padded. this many true bytes per row in data. int rowstride; - ncvisual_details details;// implementation-specific details - uint32_t* data; // (scaled) RGBA image data, rowstride bytes per row bool owndata; // we own data iff owndata == true } ncvisual; -static inline auto -ncvisual_create(void) -> ncvisual* { - auto ret = new ncvisual{}; - if(ret == nullptr){ - return nullptr; - } - ncvisual_details_init(&ret->details); - return ret; -} +typedef struct ncvisual_implementation { + int (*ncvisual_init)(int loglevel); + int (*ncvisual_decode)(ncvisual*); + int (*ncvisual_blit)(ncvisual* ncv, int rows, int cols, ncplane* n, + const struct blitset* bset, int placey, int placex, + int begy, int begx, int leny, int lenx, + bool blendcolors); + ncvisual* (*ncvisual_create)(void); + ncvisual* (*ncvisual_from_file)(const char* s); + void (*ncvisual_printbanner)(const struct notcurses* nc); + // ncv constructors other than ncvisual_from_file() need to set up the + // AVFrame* 'frame' according to their own data, which is assumed to + // have been prepared already in 'ncv'. + void (*ncvisual_details_seed)(ncvisual* ncv); + void (*ncvisual_details_destroy)(struct ncvisual_details* deets); + bool canopen_images; + bool canopen_videos; +} ncvisual_implementation; + +// ugh! need export this for pluggable multimedia modules without dlopen() +__attribute__((visibility("default"))) +int notcurses_set_ncvisual_implementation(const ncvisual_implementation* imp); static inline auto ncvisual_set_data(ncvisual* ncv, uint32_t* data, bool owned) -> void { diff --git a/src/lib/visual.cpp b/src/lib/visual.cpp index 6738ed879..070880131 100644 --- a/src/lib/visual.cpp +++ b/src/lib/visual.cpp @@ -4,10 +4,111 @@ #include "visual-details.h" #include "internal.h" -// ncv constructors other than ncvisual_from_file() need to set up the -// AVFrame* 'frame' according to their own data, which is assumed to -// have been prepared already in 'ncv'. -auto ncvisual_details_seed(struct ncvisual* ncv) -> void; +// FIXME make this a weak symbol instead so we work with static linking +static const ncvisual_implementation* impl; +static pthread_rwlock_t impllock = PTHREAD_RWLOCK_INITIALIZER; + +int notcurses_set_ncvisual_implementation(const ncvisual_implementation* imp){ + int ret = -1; + if(pthread_rwlock_wrlock(&impllock)){ + return -1; + } + if(impl == nullptr){ + impl = imp; + } + ret |= pthread_rwlock_unlock(&impllock); + return ret; +} + +auto ncvisual_decode(ncvisual* nc) -> int { + int ret = -1; + if(pthread_rwlock_rdlock(&impllock)){ + return -1; + } + if(impl){ + ret = impl->ncvisual_decode(nc); + } + ret |= pthread_rwlock_unlock(&impllock); + return ret; +} + +auto ncvisual_blit(ncvisual* ncv, int rows, int cols, ncplane* n, + const struct blitset* bset, int placey, int placex, + int begy, int begx, int leny, int lenx, + bool blendcolors) -> int { + int ret = -1; + if(pthread_rwlock_rdlock(&impllock)){ + return -1; + } + if(impl){ + if(impl->ncvisual_blit(ncv, rows, cols, n, bset, placey, placex, + begy, begx, leny, lenx, blendcolors) >= 0){ + ret = 0; + } + }else{ + if(rgba_blit_dispatch(n, bset, placey, placex, ncv->rowstride, ncv->data, + begy, begx, leny, lenx, blendcolors) >= 0){ + ret = 0; + } + } + ret |= pthread_rwlock_unlock(&impllock); + return ret; +} + +auto ncvisual_details_seed(struct ncvisual* ncv) -> void { + pthread_rwlock_rdlock(&impllock); + if(impl){ + impl->ncvisual_details_seed(ncv); + } + pthread_rwlock_unlock(&impllock); +} + +auto ncvisual_init(int loglevel) -> int { + int ret = 0; // default to success here + if(pthread_rwlock_rdlock(&impllock)){ + return -1; + } + if(impl){ + ret = impl->ncvisual_init(loglevel); + } + ret |= pthread_rwlock_unlock(&impllock); + return ret; +} + +auto ncvisual_from_file(const char* filename) -> ncvisual* { + ncvisual* ret = nullptr; + if(pthread_rwlock_rdlock(&impllock) == 0){ + if(impl){ + ret = impl->ncvisual_from_file(filename); + } + pthread_rwlock_unlock(&impllock); + } + return ret; +} + +auto ncvisual_create(void) -> ncvisual* { + ncvisual* ret = nullptr; + if(pthread_rwlock_rdlock(&impllock) == 0){ + if(impl){ + ret = impl->ncvisual_create(); + }else{ + ret = new ncvisual{}; + } + pthread_rwlock_unlock(&impllock); + } + return ret; +} + +auto ncvisual_printbanner(const notcurses* nc) -> void { + pthread_rwlock_rdlock(&impllock); + if(impl){ + impl->ncvisual_printbanner(nc); + }else{ + term_fg_palindex(nc, stderr, nc->tcache.colors <= 88 ? 1 % nc->tcache.colors : 0xcb); + fprintf(stderr, "\n Warning! Notcurses was built without multimedia support.\n"); + } + pthread_rwlock_unlock(&impllock); +} auto ncvisual_geom(const notcurses* nc, const ncvisual* n, const struct ncvisual_options* vopts, @@ -484,11 +585,15 @@ auto ncvisual_from_plane(const ncplane* n, ncblitter_e blit, int begy, int begx, auto ncvisual_destroy(ncvisual* ncv) -> void { if(ncv){ - ncvisual_details_destroy(&ncv->details); + pthread_rwlock_rdlock(&impllock); + if(impl){ + impl->ncvisual_details_destroy(ncv->details); + } if(ncv->owndata){ free(ncv->data); } delete ncv; + pthread_rwlock_unlock(&impllock); } } @@ -567,24 +672,18 @@ auto ncvisual_polyfill_yx(ncvisual* n, int y, int x, uint32_t rgba) -> int { return ncvisual_polyfill_recurse(n, y, x, rgba, *pixel); } -#ifndef USE_OIIO // built without ffmpeg or oiio -#ifndef USE_FFMPEG -auto ncvisual_from_file(const char* filename) -> ncvisual* { - (void)filename; - return nullptr; -} - auto notcurses_canopen_images(const notcurses* nc __attribute__ ((unused))) -> bool { - return false; + if(!impl){ + return false; + } + return impl->canopen_images; } auto notcurses_canopen_videos(const notcurses* nc __attribute__ ((unused))) -> bool { - return false; -} - -auto ncvisual_decode(ncvisual* nc) -> int { - (void)nc; - return -1; + if(!impl){ + return false; + } + return impl->canopen_videos; } auto ncvisual_decode_loop(ncvisual* nc) -> int { @@ -609,28 +708,6 @@ auto ncvisual_subtitle(const ncvisual* ncv) -> char* { return nullptr; } -auto ncvisual_init(int loglevel) -> int { - (void)loglevel; - return 0; // allow success here -} - -auto ncvisual_blit(ncvisual* ncv, int rows, int cols, ncplane* n, - const struct blitset* bset, int placey, int placex, - int begy, int begx, int leny, int lenx, - bool blendcolors) -> int { - (void)rows; - (void)cols; - if(rgba_blit_dispatch(n, bset, placey, placex, ncv->rowstride, ncv->data, - begy, begx, leny, lenx, blendcolors) < 0){ - return -1; - } - return 0; -} - -auto ncvisual_details_seed(struct ncvisual* ncv) -> void { - (void)ncv; -} - auto ncvisual_resize(ncvisual* nc, int rows, int cols) -> int { // we'd need to verify that it's RGBA as well, except that if we've got no // multimedia engine, we've only got memory-assembled ncvisuals, which are @@ -640,6 +717,3 @@ auto ncvisual_resize(ncvisual* nc, int rows, int cols) -> int { } return -1; } - -#endif -#endif diff --git a/src/lib/ffmpeg.cpp b/src/media/ffmpeg.cpp similarity index 63% rename from src/lib/ffmpeg.cpp rename to src/media/ffmpeg.cpp index df6412afc..1bba247d2 100644 --- a/src/lib/ffmpeg.cpp +++ b/src/media/ffmpeg.cpp @@ -1,18 +1,47 @@ #include "builddef.h" #ifdef USE_FFMPEG -#include "ffmpeg.h" +extern "C" { +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +} #include "internal.h" #include "visual-details.h" -#define IMGALLOCALIGN 32 +struct AVFormatContext; +struct AVCodecContext; +struct AVFrame; +struct AVCodec; +struct AVCodecParameters; +struct AVPacket; -bool notcurses_canopen_images(const notcurses* nc __attribute__ ((unused))) { - return true; -} +typedef struct ncvisual_details { + int packet_outstanding; + struct AVFormatContext* fmtctx; + struct AVCodecContext* codecctx; // video codec context + struct AVCodecContext* subtcodecctx; // subtitle codec context + struct AVFrame* frame; // frame as read + struct AVFrame* oframe; // RGBA frame + struct AVCodec* codec; + struct AVCodecParameters* cparams; + struct AVCodec* subtcodec; + struct AVPacket* packet; + struct SwsContext* swsctx; + AVSubtitle subtitle; + int stream_index; // match against this following av_read_frame() + int sub_stream_index; // subtitle stream index, can be < 0 if no subtitles +} ncvisual_details; -bool notcurses_canopen_videos(const notcurses* nc __attribute__ ((unused))) { - return true; -} +#define IMGALLOCALIGN 32 + +static void inject_implementation(void) __attribute__ ((constructor)); /*static void print_frame_summary(const AVCodecContext* cctx, const AVFrame* f) { @@ -86,8 +115,8 @@ deass(const char* ass) { } auto ncvisual_subtitle(const ncvisual* ncv) -> char* { - for(unsigned i = 0 ; i < ncv->details.subtitle.num_rects ; ++i){ - const AVSubtitleRect* rect = ncv->details.subtitle.rects[i]; + for(unsigned i = 0 ; i < ncv->details->subtitle.num_rects ; ++i){ + const AVSubtitleRect* rect = ncv->details->subtitle.rects[i]; if(rect->type == SUBTITLE_ASS){ return deass(rect->ass); }else if(rect->type == SUBTITLE_TEXT) {; @@ -107,49 +136,50 @@ averr2ncerr(int averr){ return -1; } -int ncvisual_decode(ncvisual* nc){ - if(nc->details.fmtctx == nullptr){ // not a file-backed ncvisual +static int +ffmpeg_decode(ncvisual* nc){ + if(nc->details->fmtctx == nullptr){ // not a file-backed ncvisual return -1; } bool have_frame = false; bool unref = false; // FIXME what if this was set up with e.g. ncvisual_from_rgba()? - if(nc->details.oframe){ - av_freep(&nc->details.oframe->data[0]); + if(nc->details->oframe){ + av_freep(&nc->details->oframe->data[0]); } do{ do{ - if(nc->details.packet_outstanding){ + if(nc->details->packet_outstanding){ break; } if(unref){ - av_packet_unref(nc->details.packet); + av_packet_unref(nc->details->packet); } int averr; - if((averr = av_read_frame(nc->details.fmtctx, nc->details.packet)) < 0){ + if((averr = av_read_frame(nc->details->fmtctx, nc->details->packet)) < 0){ /*if(averr != AVERROR_EOF){ fprintf(stderr, "Error reading frame info (%s)\n", av_err2str(averr)); }*/ return averr2ncerr(averr); } unref = true; - if(nc->details.packet->stream_index == nc->details.sub_stream_index){ + if(nc->details->packet->stream_index == nc->details->sub_stream_index){ int result = 0, ret; - ret = avcodec_decode_subtitle2(nc->details.subtcodecctx, &nc->details.subtitle, &result, nc->details.packet); + ret = avcodec_decode_subtitle2(nc->details->subtcodecctx, &nc->details->subtitle, &result, nc->details->packet); if(ret >= 0 && result){ // FIXME? } } - }while(nc->details.packet->stream_index != nc->details.stream_index); - ++nc->details.packet_outstanding; - int averr = avcodec_send_packet(nc->details.codecctx, nc->details.packet); + }while(nc->details->packet->stream_index != nc->details->stream_index); + ++nc->details->packet_outstanding; + int averr = avcodec_send_packet(nc->details->codecctx, nc->details->packet); if(averr < 0){ //fprintf(stderr, "Error processing AVPacket\n"); return averr2ncerr(averr); } - --nc->details.packet_outstanding; - av_packet_unref(nc->details.packet); - averr = avcodec_receive_frame(nc->details.codecctx, nc->details.frame); + --nc->details->packet_outstanding; + av_packet_unref(nc->details->packet); + averr = avcodec_receive_frame(nc->details->codecctx, nc->details->frame); if(averr >= 0){ have_frame = true; }else if(averr == AVERROR(EAGAIN) || averr == AVERROR_EOF){ @@ -159,12 +189,12 @@ int ncvisual_decode(ncvisual* nc){ return averr2ncerr(averr); } }while(!have_frame); -//print_frame_summary(nc->details.codecctx, nc->details.frame); - const AVFrame* f = nc->details.frame; +//print_frame_summary(nc->details->codecctx, nc->details->frame); + const AVFrame* f = nc->details->frame; nc->rowstride = f->linesize[0]; - nc->cols = nc->details.frame->width; - nc->rows = nc->details.frame->height; -//fprintf(stderr, "good decode! %d/%d %d %p\n", nc->details.frame->height, nc->details.frame->width, nc->rowstride, f->data); + nc->cols = nc->details->frame->width; + nc->rows = nc->details->frame->height; +//fprintf(stderr, "good decode! %d/%d %d %p\n", nc->details->frame->height, nc->details->frame->width, nc->rowstride, f->data); ncvisual_set_data(nc, reinterpret_cast(f->data[0]), false); return 0; } @@ -172,7 +202,7 @@ int ncvisual_decode(ncvisual* nc){ // resize frame to oframe, converting to RGBA (if necessary) along the way int ncvisual_resize(ncvisual* nc, int rows, int cols) { const int targformat = AV_PIX_FMT_RGBA; - AVFrame* inf = nc->details.oframe ? nc->details.oframe : nc->details.frame; + AVFrame* inf = nc->details->oframe ? nc->details->oframe : nc->details->frame; //fprintf(stderr, "got format: %d (%d/%d) want format: %d (%d/%d)\n", inf->format, nc->rows, nc->cols, targformat, rows, cols); if(inf->format == targformat && nc->rows == rows && nc->cols == cols){ return 0; @@ -231,72 +261,92 @@ int ncvisual_resize(ncvisual* nc, int rows, int cols) { nc->rows = rows; nc->cols = cols; ncvisual_set_data(nc, reinterpret_cast(sframe->data[0]), true); - if(nc->details.oframe){ - //av_freep(nc->details.oframe->data); - av_freep(&nc->details.oframe); + if(nc->details->oframe){ + //av_freep(nc->details->oframe->data); + av_freep(&nc->details->oframe); } - nc->details.oframe = sframe; + nc->details->oframe = sframe; -//fprintf(stderr, "SIZE SCALED: %d %d (%u)\n", nc->details.oframe->height, nc->details.oframe->width, nc->details.oframe->linesize[0]); +//fprintf(stderr, "SIZE SCALED: %d %d (%u)\n", nc->details->oframe->height, nc->details->oframe->width, nc->details->oframe->linesize[0]); return 0; } -ncvisual* ncvisual_from_file(const char* filename) { +auto ffmpeg_details_init(void) -> ncvisual_details* { + auto deets = new ncvisual_details{}; + deets->stream_index = -1; + deets->sub_stream_index = -1; + if((deets->frame = av_frame_alloc()) == nullptr){ + delete deets; + return nullptr; + } + return deets; +} + +auto ffmpeg_create() -> ncvisual* { + auto nc = new ncvisual{}; + if((nc->details = ffmpeg_details_init()) == nullptr){ + delete nc; + return nullptr; + } + return nc; +} + +ncvisual* ffmpeg_from_file(const char* filename) { AVStream* st; - ncvisual* ncv = ncvisual_create(); + ncvisual* ncv = ffmpeg_create(); if(ncv == nullptr){ // fprintf(stderr, "Couldn't create %s (%s)\n", filename, strerror(errno)); return nullptr; } -//fprintf(stderr, "FRAME FRAME: %p\n", ncv->details.frame); - int averr = avformat_open_input(&ncv->details.fmtctx, filename, nullptr, nullptr); +//fprintf(stderr, "FRAME FRAME: %p\n", ncv->details->frame); + int averr = avformat_open_input(&ncv->details->fmtctx, filename, nullptr, nullptr); if(averr < 0){ //fprintf(stderr, "Couldn't open %s (%d)\n", filename, averr); goto err; } - averr = avformat_find_stream_info(ncv->details.fmtctx, nullptr); + averr = avformat_find_stream_info(ncv->details->fmtctx, nullptr); if(averr < 0){ //fprintf(stderr, "Error extracting stream info from %s (%d)\n", filename, averr); goto err; } -//av_dump_format(ncv->details.fmtctx, 0, filename, false); - if((averr = av_find_best_stream(ncv->details.fmtctx, AVMEDIA_TYPE_SUBTITLE, -1, -1, &ncv->details.subtcodec, 0)) >= 0){ - ncv->details.sub_stream_index = averr; - if((ncv->details.subtcodecctx = avcodec_alloc_context3(ncv->details.subtcodec)) == nullptr){ +//av_dump_format(ncv->details->fmtctx, 0, filename, false); + if((averr = av_find_best_stream(ncv->details->fmtctx, AVMEDIA_TYPE_SUBTITLE, -1, -1, &ncv->details->subtcodec, 0)) >= 0){ + ncv->details->sub_stream_index = averr; + if((ncv->details->subtcodecctx = avcodec_alloc_context3(ncv->details->subtcodec)) == nullptr){ //fprintf(stderr, "Couldn't allocate decoder for %s\n", filename); goto err; } // FIXME do we need avcodec_parameters_to_context() here? - if(avcodec_open2(ncv->details.subtcodecctx, ncv->details.subtcodec, nullptr) < 0){ + if(avcodec_open2(ncv->details->subtcodecctx, ncv->details->subtcodec, nullptr) < 0){ //fprintf(stderr, "Couldn't open codec for %s (%s)\n", filename, av_err2str(*averr)); goto err; } }else{ - ncv->details.sub_stream_index = -1; + ncv->details->sub_stream_index = -1; } -//fprintf(stderr, "FRAME FRAME: %p\n", ncv->details.frame); - if((ncv->details.packet = av_packet_alloc()) == nullptr){ +//fprintf(stderr, "FRAME FRAME: %p\n", ncv->details->frame); + if((ncv->details->packet = av_packet_alloc()) == nullptr){ // fprintf(stderr, "Couldn't allocate packet for %s\n", filename); goto err; } - if((averr = av_find_best_stream(ncv->details.fmtctx, AVMEDIA_TYPE_VIDEO, -1, -1, &ncv->details.codec, 0)) < 0){ + if((averr = av_find_best_stream(ncv->details->fmtctx, AVMEDIA_TYPE_VIDEO, -1, -1, &ncv->details->codec, 0)) < 0){ // fprintf(stderr, "Couldn't find visuals in %s (%s)\n", filename, av_err2str(*averr)); goto err; } - ncv->details.stream_index = averr; - if(ncv->details.codec == nullptr){ + ncv->details->stream_index = averr; + if(ncv->details->codec == nullptr){ //fprintf(stderr, "Couldn't find decoder for %s\n", filename); goto err; } - st = ncv->details.fmtctx->streams[ncv->details.stream_index]; - if((ncv->details.codecctx = avcodec_alloc_context3(ncv->details.codec)) == nullptr){ + st = ncv->details->fmtctx->streams[ncv->details->stream_index]; + if((ncv->details->codecctx = avcodec_alloc_context3(ncv->details->codec)) == nullptr){ //fprintf(stderr, "Couldn't allocate decoder for %s\n", filename); goto err; } - if(avcodec_parameters_to_context(ncv->details.codecctx, st->codecpar) < 0){ + if(avcodec_parameters_to_context(ncv->details->codecctx, st->codecpar) < 0){ goto err; } - if(avcodec_open2(ncv->details.codecctx, ncv->details.codec, nullptr) < 0){ + if(avcodec_open2(ncv->details->codecctx, ncv->details->codec, nullptr) < 0){ //fprintf(stderr, "Couldn't open codec for %s (%s)\n", filename, av_err2str(*averr)); goto err; } @@ -304,11 +354,11 @@ ncvisual* ncvisual_from_file(const char* filename) { //fprintf(stderr, "Couldn't allocate codec params for %s\n", filename); goto err; } - if((*averr = avcodec_parameters_from_context(ncv->cparams, ncv->details.codecctx)) < 0){ + if((*averr = avcodec_parameters_from_context(ncv->cparams, ncv->details->codecctx)) < 0){ //fprintf(stderr, "Couldn't get codec params for %s (%s)\n", filename, av_err2str(*averr)); goto err; }*/ -//fprintf(stderr, "FRAME FRAME: %p\n", ncv->details.frame); +//fprintf(stderr, "FRAME FRAME: %p\n", ncv->details->frame); // frame is set up in prep_details(), so that format can be set there, as // is necessary when it is prepared from inputs other than files. oframe // is set up whenever we convert to RGBA. @@ -344,8 +394,8 @@ int ncvisual_stream(notcurses* nc, ncvisual* ncv, float timescale, do{ // codecctx seems to be off by a factor of 2 regularly. instead, go with // the time_base from the avformatctx. - double tbase = av_q2d(ncv->details.fmtctx->streams[ncv->details.stream_index]->time_base); - int64_t ts = ncv->details.frame->best_effort_timestamp; + double tbase = av_q2d(ncv->details->fmtctx->streams[ncv->details->stream_index]->time_base); + int64_t ts = ncv->details->frame->best_effort_timestamp; if(frame == 1 && ts){ usets = true; } @@ -362,8 +412,8 @@ int ncvisual_stream(notcurses* nc, ncvisual* ncv, float timescale, activevopts.n = newn; } ++frame; - uint64_t duration = ncv->details.frame->pkt_duration * tbase * NANOSECS_IN_SEC; -//fprintf(stderr, "use: %u dur: %ju ts: %ju cctx: %f fctx: %f\n", usets, duration, ts, av_q2d(ncv->details.codecctx->time_base), av_q2d(ncv->details.fmtctx->streams[ncv->stream_index]->time_base)); + uint64_t duration = ncv->details->frame->pkt_duration * tbase * NANOSECS_IN_SEC; +//fprintf(stderr, "use: %u dur: %ju ts: %ju cctx: %f fctx: %f\n", usets, duration, ts, av_q2d(ncv->details->codecctx->time_base), av_q2d(ncv->details->fmtctx->streams[ncv->stream_index]->time_base)); double schedns = nsbegin; if(usets){ if(tbase == 0){ @@ -401,7 +451,7 @@ int ncvisual_stream(notcurses* nc, ncvisual* ncv, float timescale, int ncvisual_decode_loop(ncvisual* ncv){ int r = ncvisual_decode(ncv); if(r == 1){ - if(av_seek_frame(ncv->details.fmtctx, ncv->details.stream_index, 0, AVSEEK_FLAG_FRAME) < 0){ + if(av_seek_frame(ncv->details->fmtctx, ncv->details->stream_index, 0, AVSEEK_FLAG_FRAME) < 0){ // FIXME log error return -1; } @@ -412,12 +462,11 @@ int ncvisual_decode_loop(ncvisual* ncv){ return r; } -int ncvisual_blit(ncvisual* ncv, int rows, int cols, ncplane* n, - const struct blitset* bset, int placey, int placex, - int begy, int begx, int leny, int lenx, - bool blendcolors) { - const AVFrame* inframe = ncv->details.oframe ? ncv->details.oframe : ncv->details.frame; -//fprintf(stderr, "inframe: %p oframe: %p frame: %p\n", inframe, ncv->details.oframe, ncv->details.frame); +int ffmpeg_blit(ncvisual* ncv, int rows, int cols, ncplane* n, + const struct blitset* bset, int placey, int placex, + int begy, int begx, int leny, int lenx, bool blendcolors) { + const AVFrame* inframe = ncv->details->oframe ? ncv->details->oframe : ncv->details->frame; +//fprintf(stderr, "inframe: %p oframe: %p frame: %p\n", inframe, ncv->details->oframe, ncv->details->frame); void* data = nullptr; int stride = 0; AVFrame* sframe = nullptr; @@ -431,14 +480,14 @@ int ncvisual_blit(ncvisual* ncv, int rows, int cols, ncplane* n, return -1; } //fprintf(stderr, "WHN NCV: %d/%d\n", inframe->width, inframe->height); - ncv->details.swsctx = sws_getCachedContext(ncv->details.swsctx, + ncv->details->swsctx = sws_getCachedContext(ncv->details->swsctx, ncv->cols, ncv->rows, static_cast(inframe->format), cols, rows, static_cast(targformat), SWS_LANCZOS, nullptr, nullptr, nullptr); - if(ncv->details.swsctx == nullptr){ -//fprintf(stderr, "Error retrieving details.swsctx\n"); + if(ncv->details->swsctx == nullptr){ +//fprintf(stderr, "Error retrieving details->swsctx\n"); return -1; } memcpy(sframe, inframe, sizeof(*inframe)); @@ -453,7 +502,7 @@ int ncvisual_blit(ncvisual* ncv, int rows, int cols, ncplane* n, //fprintf(stderr, "Error allocating visual data (%d X %d)\n", sframe->height, sframe->width); return -1; } - int height = sws_scale(ncv->details.swsctx, (const uint8_t* const*)inframe->data, + int height = sws_scale(ncv->details->swsctx, (const uint8_t* const*)inframe->data, inframe->linesize, 0, inframe->height, sframe->data, sframe->linesize); if(height < 0){ @@ -484,20 +533,58 @@ int ncvisual_blit(ncvisual* ncv, int rows, int cols, ncplane* n, return 0; } -auto ncvisual_details_seed(ncvisual* ncv) -> void { - assert(nullptr == ncv->details.oframe); - ncv->details.frame->data[0] = reinterpret_cast(ncv->data); - ncv->details.frame->data[1] = nullptr; - ncv->details.frame->linesize[0] = ncv->rowstride; - ncv->details.frame->linesize[1] = 0; - ncv->details.frame->width = ncv->cols; - ncv->details.frame->height = ncv->rows; - ncv->details.frame->format = AV_PIX_FMT_RGBA; +auto ffmpeg_details_seed(ncvisual* ncv) -> void { + assert(nullptr == ncv->details->oframe); + ncv->details->frame->data[0] = reinterpret_cast(ncv->data); + ncv->details->frame->data[1] = nullptr; + ncv->details->frame->linesize[0] = ncv->rowstride; + ncv->details->frame->linesize[1] = 0; + ncv->details->frame->width = ncv->cols; + ncv->details->frame->height = ncv->rows; + ncv->details->frame->format = AV_PIX_FMT_RGBA; } -int ncvisual_init(int loglevel) { +auto ffmpeg_init(int loglevel) -> int { av_log_set_level(loglevel); // FIXME could also use av_log_set_callback() and capture the message... return 0; } + +void ffmpeg_printbanner(const notcurses* nc __attribute__ ((unused))){ + printf(" avformat %u.%u.%u avutil %u.%u.%u swscale %u.%u.%u\n", + LIBAVFORMAT_VERSION_MAJOR, LIBAVFORMAT_VERSION_MINOR, LIBAVFORMAT_VERSION_MICRO, + LIBAVUTIL_VERSION_MAJOR, LIBAVUTIL_VERSION_MINOR, LIBAVUTIL_VERSION_MICRO, + LIBSWSCALE_VERSION_MAJOR, LIBSWSCALE_VERSION_MINOR, LIBSWSCALE_VERSION_MICRO); +} + +auto ffmpeg_details_destroy(ncvisual_details* deets) -> void { + avcodec_close(deets->codecctx); + avcodec_free_context(&deets->codecctx); + av_frame_free(&deets->frame); + av_freep(&deets->oframe); + //avcodec_parameters_free(&ncv->cparams); + sws_freeContext(deets->swsctx); + av_packet_free(&deets->packet); + avformat_close_input(&deets->fmtctx); + avsubtitle_free(&deets->subtitle); + delete deets; +} + +const static ncvisual_implementation ffmpeg_impl = { + .ncvisual_init = ffmpeg_init, + .ncvisual_decode = ffmpeg_decode, + .ncvisual_blit = ffmpeg_blit, + .ncvisual_create = ffmpeg_create, + .ncvisual_from_file = ffmpeg_from_file, + .ncvisual_printbanner = ffmpeg_printbanner, + .ncvisual_details_seed = ffmpeg_details_seed, + .ncvisual_details_destroy = ffmpeg_details_destroy, + .canopen_images = true, + .canopen_videos = true, +}; + +static void inject_implementation(void){ + notcurses_set_ncvisual_implementation(&ffmpeg_impl); +} + #endif diff --git a/src/lib/oiio.cpp b/src/media/oiio.cpp similarity index 55% rename from src/lib/oiio.cpp rename to src/media/oiio.cpp index 938662e25..b2f98d7b2 100644 --- a/src/lib/oiio.cpp +++ b/src/media/oiio.cpp @@ -1,29 +1,63 @@ #include "builddef.h" #ifdef USE_OIIO -#include "oiio.h" +#include +#include +#include +#include +#include #include "internal.h" #include "visual-details.h" -bool notcurses_canopen_images(const notcurses* nc __attribute__ ((unused))) { - return true; +static void inject_implementation(void) __attribute__ ((constructor)); + +typedef struct ncvisual_details { + std::unique_ptr image; // must be close()d + std::unique_ptr frame; + std::unique_ptr ibuf; + uint64_t framenum; +} ncvisual_details; + +static inline auto +oiio_details_init(void) -> ncvisual_details* { + auto deets = new ncvisual_details{}; + if(deets){ + deets->image = nullptr; + deets->frame = nullptr; + deets->ibuf = nullptr; + deets->framenum = 0; + } + return deets; } -bool notcurses_canopen_videos(const notcurses* nc __attribute__ ((unused))) { - return false; // too slow for reliable use at the moment +static inline auto +oiio_details_destroy(ncvisual_details* deets) -> void { + if(deets->image){ + deets->image->close(); + } + delete deets; } -ncvisual* ncvisual_from_file(const char* filename) { - ncvisual* ncv = ncvisual_create(); +auto oiio_create() -> ncvisual* { + auto nc = new ncvisual{}; + if((nc->details = oiio_details_init()) == nullptr){ + delete nc; + return nullptr; + } + return nc; +} + +ncvisual* oiio_from_file(const char* filename) { + ncvisual* ncv = oiio_create(); if(ncv == nullptr){ return nullptr; } - ncv->details.image = OIIO::ImageInput::open(filename); - if(!ncv->details.image){ + ncv->details->image = OIIO::ImageInput::open(filename); + if(!ncv->details->image){ // fprintf(stderr, "Couldn't create %s (%s)\n", filename, strerror(errno)); ncvisual_destroy(ncv); return nullptr; } -/*const auto &spec = ncv->details.image->spec_dimensions(0); +/*const auto &spec = ncv->details->image->spec_dimensions(0); std::cout << "Opened " << filename << ": " << spec.height << "x" << spec.width << "@" << spec.nchannels << " (" << spec.format << ")" << std::endl;*/ if(ncvisual_decode(ncv)){ @@ -33,38 +67,38 @@ spec.width << "@" << spec.nchannels << " (" << spec.format << ")" << std::endl;* return ncv; } -int ncvisual_decode(ncvisual* nc) { -//fprintf(stderr, "current subimage: %d frame: %p\n", nc->details.image->current_subimage(), nc->details.frame.get()); - const auto &spec = nc->details.image->spec_dimensions(nc->details.framenum); - if(nc->details.frame){ -//fprintf(stderr, "seeking subimage: %d\n", nc->details.image->current_subimage() + 1); +int oiio_decode(ncvisual* nc) { +//fprintf(stderr, "current subimage: %d frame: %p\n", nc->details->image->current_subimage(), nc->details->frame.get()); + const auto &spec = nc->details->image->spec_dimensions(nc->details->framenum); + if(nc->details->frame){ +//fprintf(stderr, "seeking subimage: %d\n", nc->details->image->current_subimage() + 1); OIIO::ImageSpec newspec; - if(!nc->details.image->seek_subimage(nc->details.image->current_subimage() + 1, 0, newspec)){ + if(!nc->details->image->seek_subimage(nc->details->image->current_subimage() + 1, 0, newspec)){ return 1; } // FIXME check newspec vis-a-vis image->spec()? } -//fprintf(stderr, "SUBIMAGE: %d\n", nc->details.image->current_subimage()); +//fprintf(stderr, "SUBIMAGE: %d\n", nc->details->image->current_subimage()); auto pixels = spec.width * spec.height;// * spec.nchannels; if(spec.nchannels < 3 || spec.nchannels > 4){ return -1; // FIXME get some to test with } - nc->details.frame = std::make_unique(pixels); + nc->details->frame = std::make_unique(pixels); if(spec.nchannels == 3){ // FIXME replace with channel shuffle - std::fill(nc->details.frame.get(), nc->details.frame.get() + pixels, 0xfffffffful); + std::fill(nc->details->frame.get(), nc->details->frame.get() + pixels, 0xfffffffful); } -//fprintf(stderr, "READING: %d %ju\n", nc->details.image->current_subimage(), nc->details.framenum); - if(!nc->details.image->read_image(nc->details.framenum++, 0, 0, spec.nchannels, OIIO::TypeDesc(OIIO::TypeDesc::UINT8, 4), nc->details.frame.get(), 4)){ +//fprintf(stderr, "READING: %d %ju\n", nc->details->image->current_subimage(), nc->details->framenum); + if(!nc->details->image->read_image(nc->details->framenum++, 0, 0, spec.nchannels, OIIO::TypeDesc(OIIO::TypeDesc::UINT8, 4), nc->details->frame.get(), 4)){ return -1; } -//fprintf(stderr, "READ: %d %ju\n", nc->details.image->current_subimage(), nc->details.framenum); +//fprintf(stderr, "READ: %d %ju\n", nc->details->image->current_subimage(), nc->details->framenum); /*for(int i = 0 ; i < pixels ; ++i){ //fprintf(stderr, "%06d %02x %02x %02x %02x\n", i, fprintf(stderr, "%06d %d %d %d %d\n", i, - (nc->details.frame[i]) & 0xff, - (nc->details.frame[i] >> 8) & 0xff, - (nc->details.frame[i] >> 16) & 0xff, - nc->details.frame[i] >> 24 + (nc->details->frame[i]) & 0xff, + (nc->details->frame[i] >> 8) & 0xff, + (nc->details->frame[i] >> 16) & 0xff, + nc->details->frame[i] >> 24 ); }*/ nc->cols = spec.width; @@ -72,10 +106,10 @@ int ncvisual_decode(ncvisual* nc) { nc->rowstride = nc->cols * 4; OIIO::ImageSpec rgbaspec = spec; rgbaspec.nchannels = 4; - nc->details.ibuf = std::make_unique(rgbaspec, nc->details.frame.get()); -//fprintf(stderr, "SUBS: %d\n", nc->details.ibuf->nsubimages()); - ncvisual_set_data(nc, static_cast(nc->details.ibuf->localpixels()), false); -//fprintf(stderr, "POST-DECODE DATA: %d %d %p %p\n", nc->rows, nc->cols, nc->data, nc->details.ibuf->localpixels()); + nc->details->ibuf = std::make_unique(rgbaspec, nc->details->frame.get()); +//fprintf(stderr, "SUBS: %d\n", nc->details->ibuf->nsubimages()); + ncvisual_set_data(nc, static_cast(nc->details->ibuf->localpixels()), false); +//fprintf(stderr, "POST-DECODE DATA: %d %d %p %p\n", nc->rows, nc->cols, nc->data, nc->details->ibuf->localpixels()); return 0; } @@ -83,10 +117,10 @@ int ncvisual_decode_loop(ncvisual* ncv){ int r = ncvisual_decode(ncv); if(r == 1){ OIIO::ImageSpec newspec; - if(ncv->details.image->seek_subimage(0, 0, newspec)){ + if(ncv->details->image->seek_subimage(0, 0, newspec)){ return -1; } - ncv->details.framenum = 0; + ncv->details->framenum = 0; if(ncvisual_decode(ncv) < 0){ return -1; } @@ -98,13 +132,13 @@ int ncvisual_decode_loop(ncvisual* ncv){ int ncvisual_resize(ncvisual* nc, int rows, int cols) { //fprintf(stderr, "%d/%d -> %d/%d on the resize\n", ncv->rows, ncv->cols, rows, cols); auto ibuf = std::make_unique(); - if(nc->details.ibuf && (nc->cols != cols || nc->rows != rows)){ // scale it + if(nc->details->ibuf && (nc->cols != cols || nc->rows != rows)){ // scale it OIIO::ImageSpec sp{}; sp.width = cols; sp.height = rows; ibuf->reset(sp, OIIO::InitializePixels::Yes); OIIO::ROI roi(0, cols, 0, rows, 0, 1, 0, 4); - if(!OIIO::ImageBufAlgo::resize(*ibuf, *nc->details.ibuf, "", 0, roi)){ + if(!OIIO::ImageBufAlgo::resize(*ibuf, *nc->details->ibuf, "", 0, roi)){ return -1; } nc->cols = cols; @@ -112,12 +146,12 @@ int ncvisual_resize(ncvisual* nc, int rows, int cols) { nc->rowstride = cols * 4; ncvisual_set_data(nc, static_cast(ibuf->localpixels()), false); //fprintf(stderr, "HAVE SOME NEW DATA: %p\n", ibuf->localpixels()); - nc->details.ibuf = std::move(ibuf); + nc->details->ibuf = std::move(ibuf); } return 0; } -int ncvisual_blit(struct ncvisual* ncv, int rows, int cols, +int oiio_blit(struct ncvisual* ncv, int rows, int cols, ncplane* n, const struct blitset* bset, int placey, int placex, int begy, int begx, int leny, int lenx, bool blendcolors) { @@ -125,13 +159,13 @@ int ncvisual_blit(struct ncvisual* ncv, int rows, int cols, void* data = nullptr; int stride = 0; auto ibuf = std::make_unique(); - if(ncv->details.ibuf && (ncv->cols != cols || ncv->rows != rows)){ // scale it + if(ncv->details->ibuf && (ncv->cols != cols || ncv->rows != rows)){ // scale it OIIO::ImageSpec sp{}; sp.width = cols; sp.height = rows; ibuf->reset(sp, OIIO::InitializePixels::Yes); OIIO::ROI roi(0, cols, 0, rows, 0, 1, 0, 4); - if(!OIIO::ImageBufAlgo::resize(*ibuf, *ncv->details.ibuf, "", 0, roi)){ + if(!OIIO::ImageBufAlgo::resize(*ibuf, *ncv->details->ibuf, "", 0, roi)){ return -1; } stride = cols * 4; @@ -205,37 +239,53 @@ char* ncvisual_subtitle(const ncvisual* ncv) { // no support in OIIO /* auto ncvisual_rotate(ncvisual* ncv, double rads) -> int { OIIO::ROI roi(0, ncv->cols, 0, ncv->rows, 0, 1, 0, 4); - auto tmpibuf = std::move(*ncv->details.ibuf); - ncv->details.ibuf = std::make_unique(); + auto tmpibuf = std::move(*ncv->details->ibuf); + ncv->details->ibuf = std::make_unique(); OIIO::ImageSpec sp{}; sp.set_format(OIIO::TypeDesc(OIIO::TypeDesc::UINT8, 4)); sp.nchannels = 4; - ncv->details.ibuf->reset(); - if(!OIIO::ImageBufAlgo::rotate(*ncv->details.ibuf, tmpibuf, rads, "", 0, true, roi)){ + ncv->details->ibuf->reset(); + if(!OIIO::ImageBufAlgo::rotate(*ncv->details->ibuf, tmpibuf, rads, "", 0, true, roi)){ return NCERR_DECODE; // FIXME need we do anything further? } ncv->rowstride = ncv->cols * 4; - ncvisual_set_data(ncv, static_cast(ncv->details.ibuf->localpixels()), false); + ncvisual_set_data(ncv, static_cast(ncv->details->ibuf->localpixels()), false); return NCERR_SUCCESS; } */ -auto ncvisual_details_seed(ncvisual* ncv) -> void { +auto oiio_details_seed(ncvisual* ncv) -> void { (void)ncv; // FIXME? } -int ncvisual_init(int loglevel) { +int oiio_init(int loglevel) { // FIXME set OIIO global attribute "debug" based on loglevel (void)loglevel; // FIXME check OIIO_VERSION_STRING components against linked openimageio_version() return 0; // allow success here } -extern "C" { // FIXME would be nice to have OIIO::attributes("libraries") in here -const char* oiio_version(void){ - return OIIO_VERSION_STRING; +void oiio_printbanner(const notcurses* nc __attribute__ ((unused))){ + printf(" openimageio %s\n", OIIO_VERSION_STRING); } + +const static ncvisual_implementation oiio_impl = { + .ncvisual_init = oiio_init, + .ncvisual_decode = oiio_decode, + .ncvisual_blit = oiio_blit, + .ncvisual_create = oiio_create, + .ncvisual_from_file = oiio_from_file, + .ncvisual_printbanner = oiio_printbanner, + .ncvisual_details_seed = oiio_details_seed, + .ncvisual_details_destroy = oiio_details_destroy, + .canopen_images = true, + .canopen_videos = false, +}; + +static void inject_implementation(void){ + notcurses_set_ncvisual_implementation(&oiio_impl); } + #endif diff --git a/tools/notcurses++.pc.in b/tools/notcurses++.pc.in index 42fa811ea..8b88c33cf 100644 --- a/tools/notcurses++.pc.in +++ b/tools/notcurses++.pc.in @@ -8,5 +8,5 @@ Description: C++ bindings for notcurses Version: @PROJECT_VERSION@ Requires: notcurses >= @PROJECT_VERSION@ -Libs: -L${libdir} -lnotcurses++ +Libs: -L${libdir} -lnotcurses -lnotcurses++ Cflags: -I${includedir} diff --git a/tools/notcurses-core.pc.in b/tools/notcurses-core.pc.in new file mode 100644 index 000000000..e6bfe4f76 --- /dev/null +++ b/tools/notcurses-core.pc.in @@ -0,0 +1,12 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +exec_prefix=@CMAKE_INSTALL_PREFIX@ +libdir=${prefix}/@CMAKE_INSTALL_LIBDIR@ +includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@ + +Name: @PROJECT_NAME@ +Description: TUI library for modern terminal emulators +Version: @PROJECT_VERSION@ + +Requires: +Libs: -L${libdir} -lnotcurses-core +Cflags: -I${includedir} diff --git a/tools/notcurses.pc.in b/tools/notcurses.pc.in index 367523693..e6652d358 100644 --- a/tools/notcurses.pc.in +++ b/tools/notcurses.pc.in @@ -8,5 +8,5 @@ Description: TUI library for modern terminal emulators Version: @PROJECT_VERSION@ Requires: -Libs: -L${libdir} -lnotcurses +Libs: -L${libdir} -lnotcurses-core -lnotcurses Cflags: -I${includedir}