diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..d986f5ce --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,101 @@ +name: CI for CMake + +on: + push: + pull_request: + release: + types: [published] + + +jobs: + + unix: + + strategy: + matrix: + os: [ubuntu-latest] +# mac, clang etc. work, just saving CI time + + runs-on: ${{ matrix.os }} + timeout-minutes: 5 + + steps: + + - name: install prereqs (Linux) + if: runner.os == 'Linux' + run: | + sudo apt update + sudo apt install --no-install-recommends libxt-dev libxaw7-dev libxpm-dev libxmu-dev libx11-dev + + - name: install prereqs (macOS) + if: runner.os == 'macOS' + run: brew install libxt libxaw libx11 libxpm libxmu + + - &checkout + uses: actions/checkout@v6 + + - name: CMake build + run: cmake --workflow build + + - name: CMake install (for examples) + run: cmake --install build + + - name: CMake example build and test + working-directory: demos + run: cmake --workflow default + + - &cpack + name: Create package + if: github.event.action == 'published' + run: cpack --config build/CPackConfig.cmake + + - &upload_pkg + name: Upload package + if: github.event.action == 'published' + uses: actions/upload-artifact@v7 + with: + name: ${{ runner.os }}-pkg + path: build/package + + + windows: + runs-on: windows-2025-vs2026 + timeout-minutes: 10 + + steps: + - *checkout + + - name: CMake build + run: cmake --workflow msvc-build + + - name: CMake install (for examples) + run: cmake --install build --config Release + + - name: CMake configure examples + working-directory: demos + run: cmake --preset msvc + + - *cpack + + - *upload_pkg + + + dos: + timeout-minutes: 10 + runs-on: ubuntu-latest + + steps: + - uses: open-watcom/setup-watcom@v1 + with: + version: "2.0" + target: "dos" + + - *checkout + + - name: CMake build + run: cmake --workflow dos + + - name: CMake install (for examples) + run: cmake --install build + + # cannot build examples, even locally diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..61970098 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,121 @@ +cmake_minimum_required(VERSION 3.19...4.3) + +if(CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR) + message(FATAL_ERROR "Please use out-of-source build + cmake -B build") +endif() + +project(PDCurses +LANGUAGES C +VERSION 3.9 +DESCRIPTION "PDCurses - a curses library for environments that don't fit the termcap/terminfo model." +HOMEPAGE_URL "https://github.com/wmcbrine/PDCurses") + +include(CheckIncludeFile) +include(CheckSymbolExists) + +# --- system config +option(PDCurses_WIDE "Enable wide-character support" ON) +option(PDCurses_UTF8 "Enable UTF-8 support") +option(PDCurses_sdl "Use SDL2") +option(PDCurses_xaw3d "Use Xaw3d instead of Xaw") +option(PDCurses_nextaw "Use NeXT Athena widgets instead of Xaw") + +include(cmake/compilers.cmake) + +add_library(pdcurses pdcurses/addch.c pdcurses/addchstr.c pdcurses/addstr.c +pdcurses/attr.c pdcurses/beep.c pdcurses/bkgd.c pdcurses/border.c pdcurses/clear.c +pdcurses/color.c pdcurses/delch.c pdcurses/deleteln.c pdcurses/getch.c +pdcurses/getstr.c pdcurses/getyx.c pdcurses/inch.c pdcurses/inchstr.c +pdcurses/initscr.c pdcurses/inopts.c pdcurses/insch.c pdcurses/insstr.c +pdcurses/instr.c pdcurses/kernel.c pdcurses/keyname.c pdcurses/mouse.c pdcurses/move.c +pdcurses/outopts.c pdcurses/overlay.c pdcurses/pad.c pdcurses/panel.c pdcurses/printw.c +pdcurses/refresh.c pdcurses/scanw.c pdcurses/scr_dump.c pdcurses/scroll.c +pdcurses/slk.c pdcurses/termattr.c pdcurses/touch.c pdcurses/util.c +pdcurses/window.c pdcurses/debug.c +) +set_property(TARGET pdcurses PROPERTY EXPORT_NAME Curses) +target_include_directories(pdcurses +PUBLIC +$ +$ +PRIVATE pdcurses +) +target_compile_definitions(pdcurses +PUBLIC +$<$:PDC_WIDE> +PRIVATE +$<$:PDC_FORCE_UTF8> +$<$:USE_XAW3D> +$<$:USE_NEXTAW> +$<$:PDCDEBUG> +) + +add_library(Curses::Curses ALIAS pdcurses) + +check_symbol_exists("vsnprintf" "stdio.h" HAVE_VSNPRINTF) +check_symbol_exists("vsscanf" "stdio.h" HAVE_VSSCANF) + +target_compile_definitions(pdcurses PRIVATE +$<$:HAVE_VSNPRINTF> +$<$:HAVE_VSSCANF> +) + +if(PDCurses_sdl) + find_package(SDL2 CONFIG REQUIRED) + + target_link_libraries(pdcurses PRIVATE SDL2::SDL2) + target_include_directories(pdcurses PRIVATE sdl2) + + target_sources(pdcurses PRIVATE sdl2/pdcclip.c sdl2/pdcdisp.c sdl2/pdcgetsc.c + sdl2/pdckbd.c sdl2/pdcscrn.c sdl2/pdcsetsc.c sdl2/pdcutil.c sdl2/pdcsdl.h + ) +elseif(WIN32) + target_compile_definitions(pdcurses PRIVATE + $<$:_CRT_SECURE_NO_WARNINGS> + $<$:PDC_DLL_BUILD> + ) + target_include_directories(pdcurses PRIVATE wincon) + + target_sources(pdcurses PRIVATE wincon/pdcclip.c wincon/pdcdisp.c wincon/pdcgetsc.c + wincon/pdckbd.c wincon/pdcscrn.c wincon/pdcsetsc.c wincon/pdcutil.c wincon/pdcwin.h + ) +elseif(CMAKE_SYSTEM_NAME STREQUAL "DOS") + target_sources(pdcurses PRIVATE dos/pdcclip.c dos/pdcdisp.c dos/pdcgetsc.c + dos/pdckbd.c dos/pdcscrn.c dos/pdcsetsc.c dos/pdcutil.c) + target_include_directories(pdcurses PRIVATE dos) +else() + check_include_file("DECkeySym.h" HAVE_DECKEYSYM_H) + check_include_file("Sunkeysym.h" HAVE_SUNKEYSYM_H) + check_include_file("unistd.h" HAVE_UNISTD_H) + check_symbol_exists("poll" "poll.h" HAVE_POLL) + check_symbol_exists("usleep" "unistd.h" HAVE_USLEEP) + + target_compile_definitions(pdcurses PRIVATE + $<$:HAVE_DECKEYSYM_H> + $<$:HAVE_SUNKEYSYM_H> + $<$:HAVE_UNISTD_H> + $<$:HAVE_POLL> + $<$:HAVE_USLEEP> + ) + + include(cmake/x11.cmake) + + target_compile_definitions(pdcurses PRIVATE XCURSES) + + target_include_directories(pdcurses PRIVATE ${X11_INCLUDE_DIR} x11) + target_link_libraries(pdcurses PRIVATE X11::X11 X11::Xaw X11::Xt X11::Xpm X11::Xmu) + + target_sources(pdcurses PRIVATE x11/pdcclip.c x11/pdcdisp.c x11/pdcgetsc.c + x11/pdckbd.c x11/pdcscrn.c x11/pdcsetsc.c x11/pdcutil.c x11/sb.c x11/scrlbox.c + x11/pdcx11.h x11/scrlbox.h + ) +endif() + +install(TARGETS pdcurses EXPORT ${PROJECT_NAME}-targets) +install(FILES curses.h panel.h TYPE INCLUDE) + +include(cmake/install.cmake) + +# --- auto-ignore build directory +file(GENERATE OUTPUT .gitignore CONTENT "*") diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 00000000..05e80bc0 --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,90 @@ +{ + "version": 6, + +"configurePresets": [ +{ + "name": "default", + "generator": "Ninja", + "binaryDir": "build", + "installDir": "${fileDir}/build/local", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", + "CMAKE_COMPILE_WARNING_AS_ERROR": true, + "CMAKE_LINK_WARNING_AS_ERROR": true + } +}, +{ "name": "build", "inherits": "default" }, +{ "name": "msvc", "inherits": "default", "generator": "Visual Studio 18 2026" }, +{ "name": "msvc-build", "inherits": "msvc" }, +{ "name": "dos", "generator": "Watcom WMake", "inherits": "default", + "toolchainFile": "${sourceDir}/cmake/dos.cmake", + "displayName": "OpenWatcom DOS" +} +], +"buildPresets": [ + { "name": "default", "configurePreset": "default", "configuration": "Release" }, + { "name": "build", "configurePreset": "build", "inherits": "default" }, + { "name": "dos", "configurePreset": "dos", "inherits": "default" }, + { "name": "msvc", "configurePreset": "msvc", "configuration": "Release", "inherits": "default" }, + { "name": "msvc-build", "inherits": "msvc" } +], +"testPresets": [ +{ + "name": "default", + "configurePreset": "default", + "configuration": "Release", + "output": { + "outputOnFailure": true, + "verbosity": "verbose" + }, + "execution": { + "noTestsAction": "error", + "scheduleRandom": true, + "stopOnFailure": false, + "jobs": 8, + "$comment": "don't put 0 or ulimit too many open files failures result" + } +}, +{ "name": "msvc", "configurePreset": "msvc", "inherits": "default" }, +{ "name": "msvc-build", "inherits": "msvc" } +], +"workflowPresets": [ + { + "name": "default", + "steps": [ + { "type": "configure", "name": "default" }, + { "type": "build", "name": "default" }, + { "type": "test", "name": "default" } + ] + }, + { + "name": "build", + "steps": [ + { "type": "configure", "name": "default" }, + { "type": "build", "name": "default" } + ] + }, + { + "name": "dos", + "steps": [ + { "type": "configure", "name": "dos" }, + { "type": "build", "name": "dos" } + ] + }, + { + "name": "msvc-build", + "steps": [ + { "type": "configure", "name": "msvc" }, + { "type": "build", "name": "msvc" } + ] + }, + { + "name": "msvc", + "steps": [ + { "type": "configure", "name": "msvc" }, + { "type": "build", "name": "msvc" }, + { "type": "test", "name": "msvc" } + ] + } +] +} diff --git a/cmake/compilers.cmake b/cmake/compilers.cmake new file mode 100644 index 00000000..30b3e675 --- /dev/null +++ b/cmake/compilers.cmake @@ -0,0 +1,8 @@ +if(CMAKE_C_COMPILER_ID STREQUAL "OpenWatcom") + message(STATUS "OpenWatcom: $ENV{WATCOM} + Host: ${CMAKE_HOST_SYSTEM_NAME} ${CMAKE_HOST_SYSTEM_VERSION} + Target: ${CMAKE_SYSTEM_NAME} ${CMAKE_SYSTEM_VERSION} + ") + # https://wiki.archlinux.org/title/Open_Watcom + add_compile_options(-bt=dos -bcl=dos) +endif() diff --git a/cmake/config.cmake.in b/cmake/config.cmake.in new file mode 100644 index 00000000..c285d217 --- /dev/null +++ b/cmake/config.cmake.in @@ -0,0 +1,10 @@ +@PACKAGE_INIT@ +include(CMakeFindDependencyMacro) +include(${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@-targets.cmake) + +set(PDCurses_X11 @X11_FOUND@) +if(PDCurses_X11) + find_dependency(X11) +endif() + +check_required_components(@PROJECT_NAME@) diff --git a/cmake/dos.cmake b/cmake/dos.cmake new file mode 100644 index 00000000..d3ecdf8d --- /dev/null +++ b/cmake/dos.cmake @@ -0,0 +1,27 @@ +set(CMAKE_SYSTEM_NAME DOS) + +if(NOT DEFINED ENV{WATCOM}) + if(WIN32) + set(scr ${CMAKE_CURRENT_LIST_DIR}/dos.ps1) + else() + set(scr ${CMAKE_CURRENT_LIST_DIR}/dos.sh) + endif() + message(FATAL_ERROR "WATCOM environment variable not set. Try running ${scr} first.") +endif() + +file(TO_CMAKE_PATH "$ENV{WATCOM}" watcom_root) + +set(CMAKE_SYSROOT ${watcom_root}) + +if(WIN32) + set(bin ${watcom_root}/binnt64 ${watcom_root}/binnt) +else() + set(bin ${watcom_root}/binl64 ${watcom_root}/binl) +endif() + +find_program(CMAKE_C_COMPILER +NAMES wcl386 +HINTS ${bin} +NO_DEFAULT_PATH +REQUIRED +) diff --git a/cmake/dos.ps1 b/cmake/dos.ps1 new file mode 100644 index 00000000..f20cfb76 --- /dev/null +++ b/cmake/dos.ps1 @@ -0,0 +1,15 @@ +$watcom = "$Env:SYSTEMDRIVE:\WATCOM" + +if (!(Test-Path -Path $watcom)) { + Throw "OpenWatcom not found at $watcom" +} + +$env:WATCOM = $watcom +$env:EDPATH = "$watcom\EDDAT" +$env:WHTMLHELP = "$watcom\BINNT\HELP" +$env:WIPFC = "$watcom\WIPFC" + +$env:LIBPATH = "$watcom\lib386\" +$env:INCLUDE = "$watcom\H" + +$env:Path += ";$watcom\BINNT64;$watcom\BINNT" diff --git a/cmake/dos.sh b/cmake/dos.sh new file mode 100644 index 00000000..2faf25f1 --- /dev/null +++ b/cmake/dos.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +watcom="/opt/watcom/" + +if [ ! -d $watcom ]; then + echo "OpenWatcom not found at $watcom" >&2 + exit 1 +fi + +export WATCOM=$watcom +export EDPATH="$watcom/eddat" +export WIPFC="$watcom/wipfc" + +export INCLUDE="$watcom/h" +export PATH="$watcom/binl64:$watcom/binl:$PATH" diff --git a/cmake/install.cmake b/cmake/install.cmake new file mode 100644 index 00000000..7c5909e8 --- /dev/null +++ b/cmake/install.cmake @@ -0,0 +1,38 @@ +# --- BOILERPLATE: install / packaging + +include(CMakePackageConfigHelpers) + +configure_package_config_file(${CMAKE_CURRENT_LIST_DIR}/config.cmake.in +${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/${PROJECT_NAME}Config.cmake +INSTALL_DESTINATION cmake +) + +write_basic_package_version_file( +${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/${PROJECT_NAME}ConfigVersion.cmake +COMPATIBILITY SameMinorVersion +) + +install(EXPORT ${PROJECT_NAME}-targets +NAMESPACE Curses:: +DESTINATION cmake +) + +install(FILES +${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/${PROJECT_NAME}Config.cmake +${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/${PROJECT_NAME}ConfigVersion.cmake +DESTINATION cmake +) + +# --- CPack + +set(CPACK_GENERATOR "TBZ2") +set(CPACK_SOURCE_GENERATOR "TBZ2") +set(CPACK_PACKAGE_VENDOR "William McBrine") +set(CPACK_PACKAGE_CONTACT "William McBrine") +set(CPACK_RESOURCE_FILE_README "${CMAKE_CURRENT_SOURCE_DIR}/README.md") + +install(FILES ${CPACK_RESOURCE_FILE_README} +DESTINATION share/docs/${PROJECT_NAME} +) + +include(CPack) diff --git a/cmake/x11.cmake b/cmake/x11.cmake new file mode 100644 index 00000000..bb1f4e2a --- /dev/null +++ b/cmake/x11.cmake @@ -0,0 +1,65 @@ +# extra parts of X11 needed +find_package(X11 REQUIRED) + +find_package(PkgConfig) + +# xt.pc is broken on Ubuntu +if(PKG_CONFIG_FOUND) + +pkg_check_modules(_xt xt) +pkg_check_modules(_x11 x11) +pkg_check_modules(_xmu xmu) +pkg_check_modules(_xpm xpm) +pkg_check_modules(_xaw xaw7) + +endif() + +# Ubuntu may need this +find_path(_xt_inc +NAMES Intrinsic.h +HINTS ${_xt_INCLUDE_DIRS} ${X11_INCLUDE_DIR} +PATH_SUFFIXES X11 +REQUIRED +) + +# macOS may need this +find_path(_xatom_inc +NAMES Xatom.h +HINTS ${_x11_INCLUDE_DIRS} ${X11_INCLUDE_DIR} +PATH_SUFFIXES X11 +REQUIRED +) + +# macOS may need this +find_path(_xmu_inc +NAMES StdSel.h +HINTS ${_xmu_INCLUDE_DIRS} ${X11_INCLUDE_DIR} +PATH_SUFFIXES X11/Xmu +REQUIRED +) + +# macOS may need this +find_path(_xpm_inc +NAMES xpm.h +HINTS ${_xpm_INCLUDE_DIRS} ${X11_INCLUDE_DIR} +PATH_SUFFIXES X11 +REQUIRED +) + +# macOS may need this +find_path(_xaw_inc +NAMES Box.h +HINTS ${_xaw_INCLUDE_DIRS} ${X11_INCLUDE_DIR} +PATH_SUFFIXES X11/Xaw +REQUIRED +) + +list(APPEND X11_INCLUDE_DIR +${_xt_inc} ${_xatom_inc} +${_xmu_inc} ${_xmu_inc}/.. +${_xpm_inc} +${_xaw_inc} ${_xaw_inc}/.. +) + +message(STATUS "X11_INCLUDE_DIR: ${X11_INCLUDE_DIR} +X11_LIBRARIES: ${X11_LIBRARIES}") diff --git a/demos/CMakeLists.txt b/demos/CMakeLists.txt new file mode 100644 index 00000000..de6b1310 --- /dev/null +++ b/demos/CMakeLists.txt @@ -0,0 +1,30 @@ +cmake_minimum_required(VERSION 3.19) + +if(CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR) + message(FATAL_ERROR "Please use out-of-source build + cmake -B build") +endif() + +project(PDcursesDemos LANGUAGES C) + +enable_testing() + +find_package(PDCurses CONFIG REQUIRED) + +foreach(f IN ITEMS testcurs ozdemo xmas firework ptest rain worm test_init) + add_executable(${f} ${f}.c) + target_link_libraries(${f} PRIVATE Curses::Curses) +endforeach() + +add_executable(tuidemo tui.c tuidemo.c) +target_link_libraries(tuidemo PRIVATE Curses::Curses) + +file(GENERATE OUTPUT .gitignore CONTENT "*") + +add_test(NAME Init COMMAND test_init) +set_tests_properties(Init PROPERTIES +SKIP_REGULAR_EXPRESSION "Can't open display|no DISPLAY variable set" +SKIP_RETURN_CODE 77 +) + +# Note: using STDIN INPUT_FILE for CTest doesn't work as Curses wants an actual keypress, not pipe. diff --git a/demos/CMakePresets.json b/demos/CMakePresets.json new file mode 100644 index 00000000..48fb8c3a --- /dev/null +++ b/demos/CMakePresets.json @@ -0,0 +1,4 @@ +{ + "version": 6, + "include": [ "../CMakePresets.json" ] +} diff --git a/demos/README.md b/demos/README.md index 107873bb..da62fd33 100644 --- a/demos/README.md +++ b/demos/README.md @@ -17,6 +17,13 @@ and link with any curses library; e.g., "cc -orain rain.c -lcurses". There are no dependencies besides curses and the standard C library, and no configuration is needed. +To use CMake, assuming the main PDcurses has been built and installed: + +```sh +cmake -S demos -B demos/build -DPDCurses_ROOT=/path/to/pdcurses + +cmake --build demos/build +``` Distribution Status ------------------- diff --git a/demos/test_init.c b/demos/test_init.c new file mode 100644 index 00000000..ca1328a3 --- /dev/null +++ b/demos/test_init.c @@ -0,0 +1,32 @@ +// test the very basics are working +// no refresh() or getch() as this requires a terminal and wouldn't work on CI +// without additional measures. + +#include +#include + +#include + +int main(void){ + WINDOW* w = initscr(); + if(w == NULL){ + fprintf(stderr, "Error initialising ncurses.\n"); + return 77; + } + + int i = noecho(); + if(i == ERR){ + fprintf(stderr, "Error turning off echo.\n"); + return 77; + } + + i = printw("Hello, PDCurses!"); + if(i == ERR){ + fprintf(stderr, "Error printing to screen.\n"); + return 77; + } + + endwin(); + + return EXIT_SUCCESS; +} diff --git a/demos/tui.c b/demos/tui.c index f49a3b8a..9db5e30a 100644 --- a/demos/tui.c +++ b/demos/tui.c @@ -70,9 +70,9 @@ static char wordchar(void) static char *padstr(char *s, int length) { static char buf[MAXSTRLEN]; - char fmt[10]; + char fmt[20]; - sprintf(fmt, (int)strlen(s) > length ? "%%.%ds" : "%%-%ds", length); + snprintf(fmt, sizeof(fmt), (int)strlen(s) > length ? "%%.%ds" : "%%-%ds", length); sprintf(buf, fmt, s); return buf; diff --git a/docs/README.md b/docs/README.md index 020504b2..fa721c79 100644 --- a/docs/README.md +++ b/docs/README.md @@ -21,6 +21,27 @@ Also consult the README for each specific platform you'll be using: Building -------- +The PDcurses library can be built for Unix-like systems and Windows via Makefiles +or CMake. +To build with CMake: + +```sh +cmake --workflow build +``` + +optionally, install the library binary and headers: + +```sh +cmake -B build --install-prefix /path/to/install +cmake --build build +cmake --install build +``` + +If using X11, install X11 like: + +* Linux: `apt install --no-install-recommends libxt-dev libxaw7-dev libxpm-dev libxmu-dev libx11-dev` +* macOS: `brew install xorgproto libxt libxaw libx11 libxpm libxmu pkgconf` + - To rebuild MANUAL.md from the "man page" sections of the source code, type "./mkman.sh". Needs a Unix-like shell and an Awk interpreter.