Write colored text
May 16, 2026 · View on GitHub
NAME
Notcurses::Native - Complete NativeCall bindings for the notcurses TUI library
SYNOPSIS
use Notcurses::Native;
use Notcurses::Native::Types;
use Notcurses::Native::Plane;
# Initialize notcurses
my $nc = notcurses_init(NotcursesOptions.new, Pointer);
my $std = notcurses_stdplane($nc);
# Write colored text
ncplane_set_fg_rgb8($std, 0, 255, 128);
ncplane_putstr_yx($std, 0, 0, 'Hello from notcurses!');
notcurses_render($nc);
# Wait for input
my $ni = Ncinput.new;
notcurses_get_blocking($nc, $ni);
notcurses_stop($nc);
DESCRIPTION
Notcurses::Native provides complete 1:1 NativeCall bindings for notcurses v3.0.17, a modern terminal UI library supporting rich text, colors, images, video, and pixel-perfect rendering via Sixel and Kitty graphics protocols.
This module vendors notcurses and builds it from source, so no system installation of notcurses is required. FFmpeg is used for multimedia support (image/video loading).
606 functions are bound across 9 modules, covering 100% of the bindable notcurses API. The only unbound functions are 4 vprintf variants that take va_list, which cannot be bridged through any FFI.
MODULES
Notcurses::Native
Core context management: init, stop, render, input, capabilities.
use Notcurses::Native;
my $nc = notcurses_init(NotcursesOptions.new, Pointer);
notcurses_render($nc);
my $ni = Ncinput.new;
my $key = notcurses_get_blocking($nc, $ni);
notcurses_stop($nc);
Key functions: notcurses_init, notcurses_stop, notcurses_render, notcurses_stdplane, notcurses_get_blocking, notcurses_get_nblock, notcurses_cantruecolor, notcurses_canutf8, notcurses_mice_enable.
Notcurses::Native::Types
All CStruct definitions, enums, constants, and opaque handle types.
CStruct types: NotcursesOptions, NcplaneOptions, Nccell, Ncinput, Ncstats, Nccapabilities, Ncvgeom, NcvisualOptions, Timespec, and all widget options structs (NcselectorOptions, NcmenuOptions, NctabbedOptions, NcplotOptions, NcprogbarOptions, NcreaderOptions, etc.)
Enums: NcLogLevel, NcAlign, NcBlitter, NcScale, NcInputType, NcPixelImpl.
Key constants: 130 NCKEY_* key codes (NCKEY_UP, NCKEY_ESC, NCKEY_F01, NCKEY_BUTTON1, etc.), NCSTYLE_, NCOPTION_, NCALPHA_, NCVISUAL_OPTION_, NCMICE_, NCBOX_, NCKEY_MOD_*.
Notcurses::Native::Plane
133 plane functions: create, destroy, write text, read back, cursor, colors, styles, channels, box drawing, lines, gradients, merge, resize, reparent, z-ordering, printf (variadic).
use Notcurses::Native::Plane;
my $child = ncplane_create($std, NcplaneOptions.new(:rows(10), :cols(40)));
ncplane_set_fg_rgb8($child, 255, 0, 0);
ncplane_putstr_yx($child, 0, 0, 'Red text');
ncplane_rounded_box($child, 0, 0, 9, 39, 0);
ncplane_destroy($child);
Notcurses::Native::Cell
56 cell functions: load characters, get/set colors, styles, channels, alpha, palette index, duplicate, compare, box cell helpers.
use Notcurses::Native::Cell;
my $c = Nccell.new;
nccell_load($plane, $c, 'A');
nccell_set_fg_rgb($c, 0xFF0000);
nccell_set_styles($c, NCSTYLE_BOLD);
my $text = nccell_strdup($plane, $c);
nccell_release($plane, $c);
Notcurses::Native::Channel
60 channel functions: pure computation on 32-bit single channels and 64-bit dual channels. Set/get RGB, alpha, palette index, default flags. Also pixel (ABGR uint32) creation and component access.
use Notcurses::Native::Channel;
my uint64 $channels = 0;
ncchannels_set_fg_rgb($channels, 0xFF0000);
ncchannels_set_bg_rgb($channels, 0x0000FF);
my $reversed = ncchannels_reverse($channels);
Notcurses::Native::Context
55 functions: pile operations, palette management, capabilities queries, statistics, alignment, string width, fade context, metric formatting, system info.
Notcurses::Native::Direct
70 direct mode functions: simple terminal control without full-screen takeover. Colors, styles, cursor, box drawing, input, capabilities.
use Notcurses::Native::Direct;
my $ncd = ncdirect_core_init(Str, Pointer, 0);
ncdirect_set_fg_rgb8($ncd, 255, 0, 0);
ncdirect_putstr($ncd, 0, "Red text\n");
ncdirect_stop($ncd);
Notcurses::Native::Input
15 input query functions: modifier key predicates, key classification.
use Notcurses::Native::Input;
if ncinput_ctrl_p($ni) { say "Ctrl held" }
if nckey_synthesized_p($ni.id) { say "Synthesized key" }
if nckey_mouse_p($ni.id) { say "Mouse event" }
Notcurses::Native::Visual
23 visual/image functions: load from file, decode, resize, pixel manipulation, blit to planes, geometry queries.
use Notcurses::Native::Visual;
my $v = ncvisual_from_file('photo.png');
my $vopts = NcvisualOptions.new(:scaling(NCSCALE_SCALE), :blitter(NCBLIT_PIXEL));
$vopts.set-plane($std);
ncvisual_blit($nc, $v, $vopts);
ncvisual_destroy($v);
Notcurses::Native::Widgets
124 widget functions: progress bar, reel, selector, multiselector, tree, menu, tabbed, plot (uint64 and double), reader, FD plane, subprocess.
use Notcurses::Native::Widgets;
my $bar = ncprogbar_create($plane, NcprogbarOptions.new);
ncprogbar_set_progress($bar, 0.75e0);
ncprogbar_destroy($bar);
IMAGE VIEWING
Notcurses supports multiple rendering backends for images. On terminals that support it (Kitty, iTerm2), pixel-perfect rendering is available:
my $v = ncvisual_from_file('image.png');
# Check for pixel protocol support
my $pixel-ok = notcurses_check_pixel_support($nc);
my $blitter = $pixel-ok > 0 ?? NCBLIT_PIXEL
!! ncvisual_media_defblitter($nc, NCSCALE_SCALE);
my $plane = ncplane_create($std, NcplaneOptions.new(:rows($rows), :cols($cols)));
my $vopts = NcvisualOptions.new(:scaling(NCSCALE_SCALE), :blitter($blitter));
$vopts.set-plane($plane);
ncvisual_blit($nc, $v, $vopts);
INPUT HANDLING
loop {
my $ni = Ncinput.new;
notcurses_get_blocking($nc, $ni);
given $ni.id {
when NCKEY_UP { say "Up arrow" }
when NCKEY_DOWN { say "Down arrow" }
when NCKEY_ESC { last }
when NCKEY_ENTER { say "Enter" }
when NCKEY_F01 { say "F1" }
default { say "Key: {chr($ni.id)}" if $ni.id >= 32 }
}
if ncinput_ctrl_p($ni) { say " +Ctrl" }
if ncinput_shift_p($ni) { say " +Shift" }
}
MOUSE SUPPORT
notcurses_mice_enable($nc, NCMICE_ALL_EVENTS);
my $ni = Ncinput.new;
notcurses_get_blocking($nc, $ni);
if nckey_mouse_p($ni.id) {
say "Mouse at ({$ni.y}, {$ni.x})";
say "Button 1" if $ni.id == NCKEY_BUTTON1;
say "Scroll up" if $ni.id == NCKEY_SCROLL_UP;
}
notcurses_mice_disable($nc);
BUILD REQUIREMENTS
Notcurses is vendored and built from source. You need:
-
CMake 3.14+
-
A C compiler (gcc, clang, or mingw-w64 under MSYS2)
-
ncurses,libunistring,libdeflatedevelopment headers -
FFmpeg for image/video support (Linux, macOS, and Windows)
Linux (Debian / Ubuntu)
sudo apt install \
cmake pkg-config \
libncurses-dev libunistring-dev libdeflate-dev \
libavformat-dev libavcodec-dev libavdevice-dev \
libavutil-dev libswscale-dev
Fedora / RHEL equivalents:
sudo dnf install cmake pkgconf-pkg-config \
ncurses-devel libunistring-devel libdeflate-devel ffmpeg-devel
Arch / Manjaro equivalents:
sudo pacman -S cmake pkgconf base-devel \
ncurses libunistring libdeflate ffmpeg
Linux (openSUSE Tumbleweed)
openSUSE splits FFmpeg's libraries into per-component ffmpeg-7-* packages. With thanks to user feedback from the Hacker News thread, the verified minimum set is:
sudo zypper in cmake pkg-config gcc \
ncurses-devel libunistring-devel libdeflate-devel \
ffmpeg-7-libavcodec-devel ffmpeg-7-libavformat-devel \
ffmpeg-7-libavutil-devel ffmpeg-7-libavdevice-devel \
ffmpeg-7-libswscale-devel
libswresample, libavfilter, and libpostproc devel packages are pulled in automatically as transitive dependencies; notcurses itself doesn't link them directly.
macOS (Homebrew)
brew install cmake pkg-config ffmpeg ncurses libunistring libdeflate
Windows (MSYS2 UCRT64)
Windows support requires MSYS2 in its UCRT64 environment — this produces native Windows DLLs via mingw-w64 GCC. Visual Studio / MSVC are not supported. Upstream notcurses docs recommend OpenImageIO on Windows, but current MSYS2 OIIO (3.1.x) has ABI drift vs notcurses 3.0.17's oiio.cpp, so we use FFmpeg instead — same media path as Linux/macOS.
Install MSYS2 from https://www.msys2.org/, open a UCRT64 shell, and:
pacman -S \
mingw-w64-ucrt-x86_64-cmake \
mingw-w64-ucrt-x86_64-ninja \
mingw-w64-ucrt-x86_64-toolchain \
mingw-w64-ucrt-x86_64-libdeflate \
mingw-w64-ucrt-x86_64-libunistring \
mingw-w64-ucrt-x86_64-ncurses \
mingw-w64-ucrt-x86_64-ffmpeg
Build tests (notcurses-tester) do not run on Windows — upstream limitation. The module builds and loads; terminal-dependent tests (xt/) need to be run on Linux or macOS.
Core-only (no multimedia)
If you don't need image/video support, you can omit the FFmpeg dependency. The build detects missing multimedia libraries and falls back automatically.
INSTALLATION
zef install Notcurses::Native
On supported platforms this downloads a prebuilt self-contained archive from GitHub Releases, SHA256-verifies it against a checksum baked into the dist, and stages it into the user's XDG data dir. No system packages are touched. See PREBUILT BINARIES below for the platform matrix.
If you're on an unsupported platform, the build falls back to compiling notcurses from source via CMake — see BUILD REQUIREMENTS above for the dev packages that needs.
Installation runs t/ tests only — pure-Raku channel math and input struct tests that don't need a terminal. The full terminal-dependent test suite lives in xt/ and can be run manually:
prove -e 'raku -I lib -I t/lib' xt/*.rakutest
prove (Perl 5) is recommended for xt/ tests because prove6 has a bug where terminal escape sequences from C libraries corrupt its TAP parser.
PREBUILT BINARIES
Each prebuilt archive contains the notcurses libraries plus every non-system runtime dependency, with linker paths (@loader_path on macOS, $ORIGIN on Linux, sibling-DLL on Windows) rewritten so the binaries find each other inside the staged directory without touching the host system's libraries.
Supported platforms
-
macOS arm64 (Apple Silicon, macOS 11.0 Big Sur and newer)
-
macOS x86_64 (Intel Mac / Hackintosh, macOS 10.15 Catalina and newer)
-
Linux x86_64 glibc — manylinux_2_28 baseline (glibc 2.28; RHEL 8+ / Ubuntu 18.10+ / Debian 10+ / Fedora 28+ / Arch / Manjaro)
-
Linux aarch64 glibc — same baseline
-
Linux x86_64 musl — alpine:3.20 baseline (musl 1.20+; Alpine 3.13+ / Postmarket OS / Void / Adelie)
-
Linux aarch64 musl — same baseline
-
Windows x86_64 — mingw-w64 / UCRT
-
Windows arm64 — clang / UCRT (not CI-verified; see below)
The CI release pipeline runs a codec capability probe against every artefact before publish: dlopens the bundled libavcodec, confirms the accelerated decoder libraries (libdav1d, libvpx, libvpx-vp9, libopus) are registered, and actually decodes PNG / JPEG / BMP fixtures end-to-end. A build with a misconfigured or broken libavcodec doesn't reach the release.
Windows arm64 caveat: we build the prebuilt and ship it, but the end-to-end Raku verify lane is currently disabled. Rakudo's source-build path (rakubrew → MoarVM) fails on Windows ARM64 MSYS2 CLANGARM64 — NQP's Configure.pl probe trips on a perl-output parse — and setup-raku@v1 has no native Windows ARM64 prebuilt yet, so there's no Rakudo to test against in CI. The bundle audit on the build side (objdump-based import-table walk, sibling-DLL self-containment check) still runs, so a broken bundle would still fail the release. Users on Windows ARM64 are encouraged to report issues — see https://github.com/invisietch/data-pipes/issues.
Codec coverage
Every prebuilt bundles libavcodec configured with:
-
`libdav1d$ — \text{AV1} \text{video} \text{decode}, ~10 \times \text{faster} \text{than} \text{ffmpeg}'\text{s} \text{internal} \text{AV1} \text{decoder} \text{for} 4\text{K} \text{content}
-
$libvpx` — VP8 / VP9 video decode
-
libopus— Opus audio decode
Image formats (PNG, JPEG, GIF, WebP, TIFF, BMP, etc.) and the other common video / audio codecs (H.264, HEVC, MPEG-4, MP3, AAC, Vorbis, FLAC, …) use ffmpeg's internal decoders — same code path on every platform.
Source-build fallback
For platforms outside the matrix (FreeBSD, OpenBSD, i686, riscv64, ppc64le, …) or when you explicitly set NOTCURSES_NATIVE_BUILD_FROM_SOURCE=1, Notcurses::Native compiles notcurses from source via CMake. That path needs the system packages listed in BUILD REQUIREMENTS. The source build takes 5–15 minutes depending on the machine; the prebuilt download path is seconds.
Environment knobs
-
NOTCURSES_NATIVE_BUILD_FROM_SOURCE=1— skip the prebuilt download and always compile from source. -
NOTCURSES_NATIVE_BINARY_ONLY=1— refuse to fall back to source build; fail loudly if no prebuilt is available for this platform. Useful in CI where source-build is undesired. -
NOTCURSES_NATIVE_BINARY_URL=I<url>— override the GitHub Release base URL (point at a mirror). -
NOTCURSES_NATIVE_CACHE_DIR=I<path>— override the prebuilt-download cache directory. -
NOTCURSES_NATIVE_DATA_DIR=I<path>— override the staged-libs directory (defaults to XDG_DATA_HOME). -
NOTCURSES_NATIVE_LIB_DIR=I<path>— at runtime, load notcurses from this directory instead of the staged data dir (escape hatch for custom builds).
EXAMPLES
See the examples/ directory for complete working programs:
-
01-hello.raku— Hello world -
02-colors.raku— Color palette and style showcase -
03-boxes.raku— Box drawing with nesting and colors -
04-input.raku— Interactive keypress event viewer -
05-clock.raku— Color-cycling real-time clock -
06-imgview.raku— Terminal image viewer with pixel protocol support -
07-direct.raku— Direct mode (no full-screen takeover) -
08-progress.raku— Animated progress bar widgets
AUTHOR
Matt Doughty
COPYRIGHT AND LICENSE
Copyright 2026 Matt Doughty
This library is free software; you can redistribute it and/or modify it under the Artistic License 2.0.