Check out my first novel, midnight's simulacra!

Notcurses: Difference between revisions

From dankwiki
 
(142 intermediate revisions by the same user not shown)
Line 1: Line 1:
My library for building complex, vibrant textual user interfaces (TUIs) on modern terminal emulators. It does not use [[Ncurses]] (though it does make use of libtinfo from that package), nor is it an X/Open Curses source-compatible replacement. It is written in [[C]], with C++-safe headers.
[[File:Notcurses-1.0.0-chunli.png|thumb|right|When Bison graced her village, it was Tuesday]]
notcurses is a library for building complex, vibrant textual user interfaces (TUIs) on modern terminal emulators. It does not use [[Ncurses]] (though it does make use of libtinfo from that package), nor is it an X/Open Curses source-compatible replacement. It is written in [[C]], with C++-safe headers. [https://crates.io/crates/notcurses Rust], [https://github.com/dankamongmen/notcurses/issues/212 C++], and [https://pypi.org/project/notcurses Python] wrappers are available.


Source and issue-tracking live at [https://github.com/dankamongmen/notcurses Github].
Source and issuetracking live at [https://github.com/dankamongmen/notcurses Github]. Mailing list is at [https://groups.google.com/forum/#!forum/notcurses GoogleGroups].
 
[[File:Notcurses-0.4.0-bling.png|640px|center|Contact sheet from Notcurses 0.4.0 demo]]  
A full [https://nick-black.com/notcurses/ API reference] is available as man pages. Please file bugs on any missing or inaccurate elements. There's also [https://nick-black.com/notcurses/html Doxygen] output.
 
I published a coherent textbook, "[https://www.amazon.com/dp/B086PNVNC9 Hacking the Planet! with Notcurses]", in April 2020. More details are available [[Hacking_The_Planet!_with_Notcurses|here]].
 
I recorded a [[DANKTECH]] video, "[https://www.youtube.com/watch?v=mOmMcFXdd6k Console sex with Notcurses]" as a gentle intro (its demo has been superseded by the [https://www.youtube.com/watch?v=dcjkezf1ARY NOTCURSES III demo]). You can run this same demo on your local machine with <tt>[https://nick-black.com/notcurses/notcurses-demo.1.html notcurses-demo]</tt>.


==Features==
==Features==
* Optional use of "alternate screen" where available (<tt>smcup</tt>/<tt>rmcup</tt> terminfo capabilities)
* Advanced and extensive runtime querying for terminal capabilities
* Optional use of "alternate screen" where available (<tt>enter_ca_mode</tt>/<tt>exit_ca_mode</tt> terminfo capabilities)
* All APIs use 24-bit 8bpc RGB color natively
* All APIs use 24-bit 8bpc RGB color natively
** Color is quantized down for indexed palette terminals
** Color is quantized down for indexed palette terminals
* [[#Transparency/Contrasting|Transparency/semi-transparency]] plus dynamic high-contrast text
* [[#Transparency/Contrasting|Transparency/semi-transparency]] plus dynamic high-contrast text
** Lower planes can affect color of higher translucent ones
** Lower planes can affect color of higher translucent ones
* Full support for Unicode, including wide glyphs and bidirectional text
** [[#Sprites|Sprites!]]
** Composed keys (number pad, etc.) are mapped into Private Supplementary Area B
* Full support for [[#Unicode|Unicode]], including wide glyphs and bidirectional text
* Image/video support via ffmpeg
** Composed keys (number pad, etc.) are mapped outside the defined Unicode regions
* Subregion fade in/out
* Image/video support via ffmpeg or OpenImageIO
* Subregion fade in/out, text pulsing
* [[#Linear_interpolation|Linear interpolation]] for coloring geometric objects
* [[#Linear_interpolation|Linear interpolation]] for coloring geometric objects
* Multiple cell and pixel [[#Blitters|blitters]], rotation, and arbitrary scaling


==Rendering==
==Rendering==
[[File:Notcurses-Model.png|Data structures of a Notcurses context|thumb|right]]
The vast majority of functions draw to <tt>ncplane</tt> objects. A partial order (currently a total order) always exists over the planes. There is always at least one plane, the "standard plane". This plane cannot be resized, deleted, moved relative to the visible area, or reparented. Whenever notcurses updates its idea of the visible area's dimensions, it will resize the standard plane (references to the standard plane are <b>not</b> invalidated). Planes may otherwise be any size (invisible regions will not be rendered, and count only against memory), and can be moved to any position relative to the visible area. All planes, including the standard plane, can be freely moved along the z axis.
The vast majority of functions draw to <tt>ncplane</tt> objects. A partial order (currently a total order) always exists over the planes. There is always at least one plane, the "standard plane". This plane cannot be resized, deleted, moved relative to the visible area, or reparented. Whenever notcurses updates its idea of the visible area's dimensions, it will resize the standard plane (references to the standard plane are <b>not</b> invalidated). Planes may otherwise be any size (invisible regions will not be rendered, and count only against memory), and can be moved to any position relative to the visible area. All planes, including the standard plane, can be freely moved along the z axis.


Every plane, and the notcurses object as a whole, maintains a per-row <i>damage map</i>, one boolean entry per line of the plane. These damage maps are likely to be replaced with [https://github.com/dankamongmen/notcurses/issues/189 O(1) damage detection].
A render operation consists of two logical phases: generation of the rendered scene, and blitting this scene to the terminal (these two phases might actually be interleaved, streaming the output as it is rendered). All ncplanes are locked while generating the frame. Frame generation requires determining an extended grapheme cluster, foreground color, background color, and style for each cell of the physical terminal. Writing the scene requires synthesizing a set of UTF-8-encoded characters and escape codes appropriate for the terminal, and writing this sequence to the output.
 
Each cell can be rendered in isolation, though synthesis of the stream carries dependencies between cells.
 
Recall that there is a total ordering on the <i>N</i> ncplanes, and that the standard plane always exists, with geometry equal to the physical screen. Each cell of the physical screen is thus intersected by some totally ordered subset of planes <i>P0</i>, <i>P1</i>...<i>Pi</i>, where 0 &lt; <i>i</i> ≤ <i>N</i>. At each cell, rendering starts at the topmost intersecting plane <i>P0</i>. The algorithm descends until either:
 
* it has locked in an extended grapheme cluster, and fore/background colors, or
* all <i>i</i> planes have been examined
 
At each plane <i>P</i>, we consider a cell <i>C</i>. This cell is the intersecting cell, unless that cell has no EGC. In that case, <i>C</i> is the plane's default cell.
 
* If we have not yet determined an EGC, and <i>C</i> has a non-zero EGC, use the EGC and style of <i>C</i>.
* If we have not yet locked in a foreground color, and <i>C</i> is not foreground-transparent, use the foreground color of <i>C</i>. If <i>C</i> is <tt>CELL_ALPHA_OPAQUE</tt>, lock the color in.
* If we have not yet locked in a foreground color, and <i>C</i> is not background-transparent, use the background color of <i>C</i>. If <i>C</i> is <tt>CELL_ALPHA_OPAQUE</tt>, lock the color in.
 
If the algorithm concludes without an EGC, the cell is rendered with no glyph and a default background. If the algorithm concludes without a color locked in, the color as computed thus far is used.
 
==Unicode==
Notcurses understands Unicode wide characters, and accounts for them. It is not possible to split the colors or styling of a wide glyph. It is not possible to print half of a glyph. It is not possible to print a narrow glyph over half of a wide glyph without obliterating the other half. These are all terminal emulator limitations.
 
Whether and how a given Unicode codepoint will be rendered depends almost entirely on font support. In general, <i>with sufficient fonts</i>, emoji and other pictographs will be properly rendered as expected. The Linux console has particularly limited fonts, and most characters beyond ASCII are not reliable in that environment. Certain Unicode glyphs are used by notcurses for drawing. Failure to render these glyphs reasonably will have a negative impact on notcurses functionality. Most important of these are the [https://unicode.org/charts/PDF/U2500.pdf Box Drawing Characters], the [https://unicode.org/charts/PDF/U2580.pdf Block Elements], and the [https://unicode.org/charts/PDF/U2800.pdf Braille Patterns].


When <tt>notcurses_render()</tt> is called, the visual area is constructed from the top left to the bottom right, row by row and column by column.
A wide character can be written at any offset within a row save the very last column. If a character (wide or narrow) exists in the right cell of a wide character, the original character will be destroyed. A narrow character can be written at any offset within a row. If a character (wide or narrow) is written onto the right cell of a wide character, that original character will be destroyed. It is thus possible for a single wide character to obliterate four columns' worth of glyphs: if two wide characters exist next to one another, and a new wide character is written over the right half of the first, both original glyphs will be obliterated. Likewise, if another (higher) plane bisects a wide glyph, the entire glyph is obliterated.


Each row is considered undamaged until proven otherwise. At each column, the character to be emitted (and its styling) is computed. This will require consulting some number of planes (see [[#Transparency/Contrasting|Transparency]] for more information). A damage value is computed as the boolean union over all relevant damage map entries. Relevant entries are those from the toplevel damage map, and the damage maps of all planes which contributed to the output glyph or styling.
notcurses does not currently handle right-to-left text in any special way, but terminals often apply their own heuristics and stylings. Generally this means that a series of glyphs from right-to-left languages will be reversed in the terminal, but this will <b>not</b> be detectable by notcurses's reflective calls (e.g. <tt>ncplane_at_yx()</tt>).
 
==Blitters==
Multiple blitters are provided, and can be selected whenever pixel data is being rendered. This includes <tt>ncvisual</tt> objects and qrcodes. If Notcurses is started in ASCII mode (as opposed to UTF-8), all blitters will degrade to <tt>NCBLITTER_1x1</tt> (unless <tt>NCVISUAL_OPTION_NODEGRADE</tt> is provided).
{| class="wikitable"
! Value !! Geometry !! Comments
|-
| <tt>NCBLITTER_1x1</tt>
| 1x1->1
| Uses spaces and sets the background color. The only blitter available in ASCII mode, and the only reliable blitter on the console. Pixel aspect ratio is equivalent to cell aspect ratio, usually resulting in vertical stretching. Lossless. Reliable no matter the font.
|-
| <tt>NCBLITTER_2x1</tt>
| 2x1->1
| Default blitter. Pixel aspect ratio is one-half the cell aspect ratio, which is usually right where you want it. Uses Unicode upper- and lower-half blocks, and spaces. Lossless.
|-
| <tt>NCBLITTER_2x2</tt>
| 2x2->1
| Pixel aspect ratio is equivalent to cell aspect ratio, usually resulting in vertical stretching. Uses Unicode quadrant and three-quarter blocks (in addition to upper- and lower-half blocks, and spaces). Lossy whenever more than two colors are used within a 2x2 pixel square, lossless otherwise (bi- and tri-linear interpolation is used for more than two colors).
|-
| <tt>NCBLITTER_3x2</tt>
| 3x2->1
| Highest quality for most large images. Pixel aspect ratio improves over NCBLITTER_2x2 but is less perfect than NCBLITTER_2x1, leading to slight vertical stretching. Uses Unicode sextants, left and right half blocks, and spaces. Lossy whenever more than two colors are used within a 3x2 pixel square, lossless otherwise (generalized linear interpolation is used for more than two colors).
|-
| <tt>NCBLITTER_4x2</tt>
| 4x2->1
| Uses Braille characters, which have spotty font support.
|-
| <tt>NCBLITTER_PIXEL</tt>
| variable->1
| Pixel blitter (see below)
|-
|}
 
[[File:Worldmap-sexblitter..png|800px|center]]
 
===Pixel blitters===
[[File:pixel-orca.png|thumb|right|Ramble on, orca]]
Some terminals support pixel graphics, including the [[Sixel]] system pioneered by DEC. Notcurses can employ pixel graphics using the <tt>NCBLIT_PIXEL</tt> blitter.
 
* Sixel: xterm, mlterm, Alacritty (outstanding patch), WezTerm, foot, contour
* Kitty supports its own method (which is also supported by WezTerm)
* as does ITerm2 (which is also supported by WezTerm)
* the Linux framebuffer console supports its own graphics (via memory map)
 
Notcurses does not currently output ReGIS vector graphics, nor the iTerm2 protocol.
 
Some timings from notcurses 2.3.17, taken 2021-08-28 using:
 
{| class="wikitable"
|+ <tt>ncplayer -bpixel ../data/notcursesIII.mkv -d0 -t0 -q</tt>
! term
! version
! pgeom
! cgeom
! times
|-
| mlterm
| 3.9.0
| 880x1406
| 80x61
| 1m5.142s 1m.3.731s 1m3.631s
|-
| XTerm
| 368
| 880x1403
| 88x74
| 55.655s 55.433s 55.841s
|-
| alacritty
| 0.13.1
| 880x1400
| 88x70
| 55.716s 55.902s 55.709s
|-
| kitty
| 0.23.1
| 880x1440
| 88x70
| 33.179s 33.477s 33.385s
|-
| kitty
| 0.19.3
|880x1440
|88x70
| 54.722s 54.473s 54.867s
|-
|}
 
==Input==
Notcurses provides input from keyboards and pointers (mice). Single Unicode codepoints are received from the keyboard, directly encoded as <tt>char32_t</tt>s. The input system must deal with numerous keyboard signals which do not map to Unicode code points. This includes the keypad arrows and function keys. These "synthesized" codepoints are enumerated in <tt>notcurses.h</tt>, and mapped into the [https://unicode.org/charts/PDF/U100000.pdf Supplementary Private Use Area-B] (U+100000..U+10FFFD). Mouse button events are similarly mapped into the SPUA-B.
 
All events carry a <tt>ncinput</tt> structure with them. For mouse events, the x and y coordinates are reported within this struct. For all events, modifiers (e.g. "Alt") are carried as the <tt>modifiers</tt> bitfield in this struct.


==Transparency/Contrasting==
==Transparency/Contrasting==
Line 44: Line 164:
The value 11 is currently forbidden for a bg alpha setting, but might be used in the future. To "blend the color down" means to average the colors encountered until hitting an opaque channel, or running out of planes. To "select the next color" means to ignore this color, and instead take the color as computed by lower planes.
The value 11 is currently forbidden for a bg alpha setting, but might be used in the future. To "blend the color down" means to average the colors encountered until hitting an opaque channel, or running out of planes. To "select the next color" means to ignore this color, and instead take the color as computed by lower planes.


High-contrast text is not strictly defined. notcurses will attempt to make the glyph as readable as possible, given the background color <i>computed at the plane</i>.
High-contrast text is not strictly defined. notcurses will attempt to make the glyph as readable as possible, given the background color <i>computed at the cell</i>.


Note that a cell with the zero glyph will not have its channels considered. The containing plane's default channels will instead be factored into any color/transparency calculations (if a default glyph has been defined for the plane). A cell containing a zero glyph on a plane with a default zero glyph cannot impact the rendered scene; any associated channels will be ignored.
If loaded multimedia supports transparency (e.g. PNGs), transparent pixels will be considered as if <tt>CELL_ALPHA_TRANSPARENT</tt> was used.


If loaded multimedia supports transparency (e.g. PNGs), transparent pixels will be considered as if <tt>CELL_ALPHA_TRANSPARENT</tt> was used.
===Sprites===
Transparency plus bitmaps yield sprites. The "luigi" demo uses three bitmaps from Luigi's walking cycle in Super Mario Bros. Only one is shown at a time; the other two are hidden under the standard plane (onto which a background has been rendered).


==Linear interpolation==
==Linear interpolation==
notcurses can color lines via linear interpolation between the two endpoints. This is done with <tt>ncplane_hline_interp()</tt> and <tt>ncplane_vline_interp()</tt>. If provided two endpoints of the same color, the line will be that single color.
notcurses can color lines via linear interpolation between the two endpoints. This is done with <tt>ncplane_hline_interp()</tt> and <tt>ncplane_vline_interp()</tt>. If provided two endpoints of the same color, the line will be that single color.


==Releases==
==Terminal emulators==
More information is available from the source tree in [https://github.com/dankamongmen/notcurses/blob/master/TERMINALS.md TERMINALS.md].


The two primary environmental factors affecting notcurses performance are the terminal emulator and the configured fonts.
For performance evaluation, I run <tt>notcurses-demo</tt> in each terminal three times. Each terminal is configured to use Inconsolata Medium 12 as its primary font, to 70% opacity (if supported), and run at a 70x80 geometry. I report the average of the three wall clock times, and the three FPS (i.e. frames rendered divided by time spent within <tt>notcurses_render()</tt>) measurements. If DirectColor output could not be rendered, the terminal is reported as a <b>failure</b> (this is perhaps/probably due to my ignorance). Some demos are a fixed number of frames, with a fixed target time. Some are a fixed number of frames, to be rendered as quickly as possible. Some are fixed time, with an intention of rendering as many frames as possible.
For the FPS measurement, higher is better. For the time measurement, lower is better.
{| class="wikitable sortable"
|+ Last evaluated 2019-12-21 (notcurses 0.9.1)
! Emulator
! colspan="2"|Perf <br/> FPS / Seconds
! <tt>TERM</tt>
! Notes
! T.416<br/>
|-
| xterm<br>xterm-351
| 11.87
| 196.8
| xterm-direct
| <tt>eagle</tt> and <tt>view</tt> were very choppy<br/><tt>widecolor</tt> doesn't colorize flame emoji<br/><tt>uniblock</tt> ran without box breakage (only terminal to do so)
| Y
|-
| rxvt<br>urxvt v9.22
| x
| x
| <b>failure</b>
| <b>failure</b>
|
|-
| libvte<br/>(using xfce4-terminal)<br/>xfce4-terminal 0.8.8 / vte 0.58.2
| 205.9
| 75.8
| vte-direct
|
| Y
|-
| kitty<br/>kitty 0.15.0
| 682.3
| 72.9
| kitty-direct
| <tt>uniblock</tt> is deformed in a varying fashion<br/><tt>widecolor</tt> is missing some ranges<br/><tt>widecolor</tt> replaces some ranges with blocks<br/>seems very smooth under load
| Y
|-
| alacritty<br/>alacritty 0.4.1-dev
| 553.7
| 74.0
| alacritty-direct
| <tt>uniblock</tt> [https://github.com/jwilm/alacritty/issues/3124 doesn't fade in properly]
| N
|-
| GNOME terminator<br/>(also VTE, but python)<br/>terminator 1.91 / vte 0.58.2
| 206.0
| 75.7
| vte-direct
|
| Y
|-
| terminology<br/>terminology 1.3.2
|x
|x
| <b>failure</b>
| <b>failure</b>
|
|-
| konsole<br/>konsole 19.08.1 / QT 5.12.5
| 168.5
| 83.8
| konsole-direct
| <tt>uniblock</tt> doesn't fade in properly<br/><tt>uniblock</tt> persistent damage on one line<br/>boldface seems particularly strong
| N
|-
|}
Note: some of these terminals (e.g. xterm) export an RGB-capable <tt>setaf</tt>/<tt>setab</tt>, which notcurses does not use due to its special casing of low values. notcurses instead generates (for <tt>seta[bf]</tt> only) its own escapes outside of the terminfo framework. I am uncertain as to whether this might have performance effects. The "T.416" column indicates whether this terminal appears to support the colon-delimited RGB escapes from [https://www.itu.int/rec/T-REC-T.416/ ITU T.416].
Raw data (2019-12-21, [[Schwarzgerät]] running [[Debian|Debian Unstable]]):
<pre>
alacritty: 10140, 10121, 10143, 555.6, 553.8, 551.8, 1m13.9, 1m14.0, 1m14.1s
konsole: 5655, 6286, 6433, 152.9, 174.3, 178.3, 1m24.3, 1m23.6, 1m23.5
terminator: 4628, 4788, 4759, 211.1, 208.6, 198.4, 1m15.1, 1m15.6, 1m16.5
kitty: 9475, 9539, 9518, 679.4, 687.5, 679.9, 1m12.9, 1m12.8, 1m12.9
xfce4-terminal: 4873, 4431, 4787, 214.7, 191.5, 211.4, 1m16.0, 1m16.1, 1m15.2
xterm: 1926, 1914, 1918, 11.8, 12.0, 11.8, 3m18.0, 3m14.7, 3m17.8
</pre>
===Notes on particular terminals===
The [https://github.com/dankamongmen/notcurses/blob/master/TERMINALS.md TERMINALS.md] document in Notcurses is (hopefully) well worth checking out.
* [https://invisible-island.net/xterm/ xterm], the first and (in many ways) the best.
** For 24-bit color, compile with <tt>--enable-direct-color</tt> and define the resource <tt>directColor</tt> to <tt>true</tt>. Both of these are the default in recent xterm.
** Use the <tt>xterm-direct</tt> terminfo entry
** For Sixel and Regis, compile with <tt>--enable-sixel-graphics --enable-regis-graphics</tt>
* [https://wiki.gnome.org/Apps/Terminal/VTE VTE-based terminals] including <tt>gnome-terminal</tt> and <tt>xfce4-terminal</tt>
** I'm not sure whether these ought be using the <tt>xterm-</tt> family or <tt>vte-</tt> family of terminfo entries, oddly enough
===Screen multiplexers===
* <tt>screen</tt>: i've yet to see anything but a mess in screen
* <tt>tmux</tt>: works decently well, aside from consuming bitmaps
* <tt>mosh</tt>: we look like shit in mosh and i wish we didn't
===Linux [[Consoles|console]]===
The Linux "system console" is a virtual device mapping to some device. We're primarily interested in the VGA text console, and the framebuffer console. The console supports only 256 different glyphs (512 with diminished color support), and never more than 16 colors (8 with 512 glyphs).
====fbterm====
You'll typically need to be a member of the <tt>video</tt> group to run <tt>fbterm</tt>. It offers much better Unicode glyph coverage than the raw console, but still offers only 16 (programmable) colors.
====kmscon====
==Major Releases==
{| class="wikitable"
{| class="wikitable"
! Release !! Date !! Key features
! Release !! Date !! Key features
|-
| [https://github.com/dankamongmen/notcurses/releases/tag/v3.0.0 3.0.0] || 2021-12-01
| New input system, kitty keyboard/GPM/XTMODKEYS support
|-
| [https://github.com/dankamongmen/notcurses/releases/tag/v2.4.0 2.4.0] || 2021-09-06
| Microsoft Windows and macOS support, Linux framebuffer graphics, "CLI mode"
|-
| [https://github.com/dankamongmen/notcurses/releases/tag/v2.3.0 2.3.0] || 2021-05-09
| Tree selector, tabbed interface, pixel blitting
|-
| [https://github.com/dankamongmen/notcurses/releases/tag/v2.2.0 2.2.0] || 2021-02-08
| libreadline, progress bars, notcurses-core extraction
|-
| [https://github.com/dankamongmen/notcurses/releases/tag/v2.1.0 2.1.0] || 2020-12-13
| Sexblitter, ncneofetch
|-
| [https://github.com/dankamongmen/notcurses/releases/tag/v2.0.0 2.0.0] || 2020-10-12
| Stable API!
|-
| [https://github.com/dankamongmen/notcurses/releases/tag/v1.7.0 1.7.0] || 2020-08-30
| Linux font table reprogramming, EGC inlining, better Rust wrappers
|-
| [https://github.com/dankamongmen/notcurses/releases/tag/v1.6.0 1.6.0] || 2020-07-04
| True three-channel transparency (glyph, fg, bg), Quadblitter default, beefed up ncdirect
|-
| [https://github.com/dankamongmen/notcurses/releases/tag/v1.5.0 1.5.0] || 2020-06-08
| Quadblitter
|-
| [https://github.com/dankamongmen/notcurses/releases/tag/v1.4.0 1.4.0] || 2020-05-10
| Fdplane and subproc widgets, reader widget, true scrolling
|-
| [https://github.com/dankamongmen/notcurses/releases/tag/v1.3.0 1.3.0] || 2020-04-13
| Multiselector widget, plot widget, multiline output, margins, staining
|-
| [https://github.com/dankamongmen/notcurses/releases/tag/v1.2.0 1.2.0] || 2020-02-17
| Menu widget, selector widget, <tt>CELL_ALPHA_HIGHCONTRAST</tt>, Python/C++ wrappers
|-
| [https://github.com/dankamongmen/notcurses/releases/tag/v1.1.0 1.1.0] || 2020-01-19
| Massive speedups and much better video support
|-
| [https://github.com/dankamongmen/notcurses/releases/tag/v1.0.0 1.0.0] || 2019-01-04
| First GA release
|-
|-
| [https://github.com/dankamongmen/notcurses/releases/tag/v0.9.0 0.9.0] || 2019-12-18
| [https://github.com/dankamongmen/notcurses/releases/tag/v0.9.0 0.9.0] || 2019-12-18
Line 75: Line 347:
|}
|}


==See Also==
* The [https://vt100.net/emu/dec_ansi_parser Paul Williams] automaton for lexing DECspeak
* [https://invisible-island.net/xterm/ctlseqs/ctlseqs.html XTerm] control sequences
* [https://sw.kovidgoyal.net/kitty/protocol-extensions.html Kitty-specific] functionality
* [https://iterm2.com/documentation-escape-codes.html iTerm2 extension] escape sequences
* [https://jexer.sourceforge.io/ Jexer], a Java library of similar power
* [https://wezfurlong.org/wezterm/escape-sequences.html Wezterm extension] escape sequences
* [[outcurses]], my earlier effects/widgets library for [[Ncurses]]


==See Also==
[[File:Notcurses-0.4.0-bling.png|640px|center|Contact sheet from Notcurses 0.4.0 demo]]
* [[outcurses]], my effects and widgets library for [[Ncurses]]
[[CATEGORY: Terminals]]
[[CATEGORY: Projects]]

Latest revision as of 02:25, 3 February 2024

When Bison graced her village, it was Tuesday

notcurses is a library for building complex, vibrant textual user interfaces (TUIs) on modern terminal emulators. It does not use Ncurses (though it does make use of libtinfo from that package), nor is it an X/Open Curses source-compatible replacement. It is written in C, with C++-safe headers. Rust, C++, and Python wrappers are available.

Source and issuetracking live at Github. Mailing list is at GoogleGroups.

A full API reference is available as man pages. Please file bugs on any missing or inaccurate elements. There's also Doxygen output.

I published a coherent textbook, "Hacking the Planet! with Notcurses", in April 2020. More details are available here.

I recorded a DANKTECH video, "Console sex with Notcurses" as a gentle intro (its demo has been superseded by the NOTCURSES III demo). You can run this same demo on your local machine with notcurses-demo.

Features

  • Advanced and extensive runtime querying for terminal capabilities
  • Optional use of "alternate screen" where available (enter_ca_mode/exit_ca_mode terminfo capabilities)
  • All APIs use 24-bit 8bpc RGB color natively
    • Color is quantized down for indexed palette terminals
  • Transparency/semi-transparency plus dynamic high-contrast text
    • Lower planes can affect color of higher translucent ones
    • Sprites!
  • Full support for Unicode, including wide glyphs and bidirectional text
    • Composed keys (number pad, etc.) are mapped outside the defined Unicode regions
  • Image/video support via ffmpeg or OpenImageIO
  • Subregion fade in/out, text pulsing
  • Linear interpolation for coloring geometric objects
  • Multiple cell and pixel blitters, rotation, and arbitrary scaling

Rendering

Data structures of a Notcurses context

The vast majority of functions draw to ncplane objects. A partial order (currently a total order) always exists over the planes. There is always at least one plane, the "standard plane". This plane cannot be resized, deleted, moved relative to the visible area, or reparented. Whenever notcurses updates its idea of the visible area's dimensions, it will resize the standard plane (references to the standard plane are not invalidated). Planes may otherwise be any size (invisible regions will not be rendered, and count only against memory), and can be moved to any position relative to the visible area. All planes, including the standard plane, can be freely moved along the z axis.

A render operation consists of two logical phases: generation of the rendered scene, and blitting this scene to the terminal (these two phases might actually be interleaved, streaming the output as it is rendered). All ncplanes are locked while generating the frame. Frame generation requires determining an extended grapheme cluster, foreground color, background color, and style for each cell of the physical terminal. Writing the scene requires synthesizing a set of UTF-8-encoded characters and escape codes appropriate for the terminal, and writing this sequence to the output.

Each cell can be rendered in isolation, though synthesis of the stream carries dependencies between cells.

Recall that there is a total ordering on the N ncplanes, and that the standard plane always exists, with geometry equal to the physical screen. Each cell of the physical screen is thus intersected by some totally ordered subset of planes P0, P1...Pi, where 0 < iN. At each cell, rendering starts at the topmost intersecting plane P0. The algorithm descends until either:

  • it has locked in an extended grapheme cluster, and fore/background colors, or
  • all i planes have been examined

At each plane P, we consider a cell C. This cell is the intersecting cell, unless that cell has no EGC. In that case, C is the plane's default cell.

  • If we have not yet determined an EGC, and C has a non-zero EGC, use the EGC and style of C.
  • If we have not yet locked in a foreground color, and C is not foreground-transparent, use the foreground color of C. If C is CELL_ALPHA_OPAQUE, lock the color in.
  • If we have not yet locked in a foreground color, and C is not background-transparent, use the background color of C. If C is CELL_ALPHA_OPAQUE, lock the color in.

If the algorithm concludes without an EGC, the cell is rendered with no glyph and a default background. If the algorithm concludes without a color locked in, the color as computed thus far is used.

Unicode

Notcurses understands Unicode wide characters, and accounts for them. It is not possible to split the colors or styling of a wide glyph. It is not possible to print half of a glyph. It is not possible to print a narrow glyph over half of a wide glyph without obliterating the other half. These are all terminal emulator limitations.

Whether and how a given Unicode codepoint will be rendered depends almost entirely on font support. In general, with sufficient fonts, emoji and other pictographs will be properly rendered as expected. The Linux console has particularly limited fonts, and most characters beyond ASCII are not reliable in that environment. Certain Unicode glyphs are used by notcurses for drawing. Failure to render these glyphs reasonably will have a negative impact on notcurses functionality. Most important of these are the Box Drawing Characters, the Block Elements, and the Braille Patterns.

A wide character can be written at any offset within a row save the very last column. If a character (wide or narrow) exists in the right cell of a wide character, the original character will be destroyed. A narrow character can be written at any offset within a row. If a character (wide or narrow) is written onto the right cell of a wide character, that original character will be destroyed. It is thus possible for a single wide character to obliterate four columns' worth of glyphs: if two wide characters exist next to one another, and a new wide character is written over the right half of the first, both original glyphs will be obliterated. Likewise, if another (higher) plane bisects a wide glyph, the entire glyph is obliterated.

notcurses does not currently handle right-to-left text in any special way, but terminals often apply their own heuristics and stylings. Generally this means that a series of glyphs from right-to-left languages will be reversed in the terminal, but this will not be detectable by notcurses's reflective calls (e.g. ncplane_at_yx()).

Blitters

Multiple blitters are provided, and can be selected whenever pixel data is being rendered. This includes ncvisual objects and qrcodes. If Notcurses is started in ASCII mode (as opposed to UTF-8), all blitters will degrade to NCBLITTER_1x1 (unless NCVISUAL_OPTION_NODEGRADE is provided).

Value Geometry Comments
NCBLITTER_1x1 1x1->1 Uses spaces and sets the background color. The only blitter available in ASCII mode, and the only reliable blitter on the console. Pixel aspect ratio is equivalent to cell aspect ratio, usually resulting in vertical stretching. Lossless. Reliable no matter the font.
NCBLITTER_2x1 2x1->1 Default blitter. Pixel aspect ratio is one-half the cell aspect ratio, which is usually right where you want it. Uses Unicode upper- and lower-half blocks, and spaces. Lossless.
NCBLITTER_2x2 2x2->1 Pixel aspect ratio is equivalent to cell aspect ratio, usually resulting in vertical stretching. Uses Unicode quadrant and three-quarter blocks (in addition to upper- and lower-half blocks, and spaces). Lossy whenever more than two colors are used within a 2x2 pixel square, lossless otherwise (bi- and tri-linear interpolation is used for more than two colors).
NCBLITTER_3x2 3x2->1 Highest quality for most large images. Pixel aspect ratio improves over NCBLITTER_2x2 but is less perfect than NCBLITTER_2x1, leading to slight vertical stretching. Uses Unicode sextants, left and right half blocks, and spaces. Lossy whenever more than two colors are used within a 3x2 pixel square, lossless otherwise (generalized linear interpolation is used for more than two colors).
NCBLITTER_4x2 4x2->1 Uses Braille characters, which have spotty font support.
NCBLITTER_PIXEL variable->1 Pixel blitter (see below)

Pixel blitters

Ramble on, orca

Some terminals support pixel graphics, including the Sixel system pioneered by DEC. Notcurses can employ pixel graphics using the NCBLIT_PIXEL blitter.

  • Sixel: xterm, mlterm, Alacritty (outstanding patch), WezTerm, foot, contour
  • Kitty supports its own method (which is also supported by WezTerm)
  • as does ITerm2 (which is also supported by WezTerm)
  • the Linux framebuffer console supports its own graphics (via memory map)

Notcurses does not currently output ReGIS vector graphics, nor the iTerm2 protocol.

Some timings from notcurses 2.3.17, taken 2021-08-28 using:

ncplayer -bpixel ../data/notcursesIII.mkv -d0 -t0 -q
term version pgeom cgeom times
mlterm 3.9.0 880x1406 80x61 1m5.142s 1m.3.731s 1m3.631s
XTerm 368 880x1403 88x74 55.655s 55.433s 55.841s
alacritty 0.13.1 880x1400 88x70 55.716s 55.902s 55.709s
kitty 0.23.1 880x1440 88x70 33.179s 33.477s 33.385s
kitty 0.19.3 880x1440 88x70 54.722s 54.473s 54.867s

Input

Notcurses provides input from keyboards and pointers (mice). Single Unicode codepoints are received from the keyboard, directly encoded as char32_ts. The input system must deal with numerous keyboard signals which do not map to Unicode code points. This includes the keypad arrows and function keys. These "synthesized" codepoints are enumerated in notcurses.h, and mapped into the Supplementary Private Use Area-B (U+100000..U+10FFFD). Mouse button events are similarly mapped into the SPUA-B.

All events carry a ncinput structure with them. For mouse events, the x and y coordinates are reported within this struct. For all events, modifiers (e.g. "Alt") are carried as the modifiers bitfield in this struct.

Transparency/Contrasting

It is not obvious what "transparency" or "alpha blending" means in a character context. I have assigned my own meanings. Each of the foreground and background channel of an ncplane's cell have two bits dedicated to alpha. Channels are always initialized to 0, and thus the default alpha setting is CELL_ALPHA_OPAQUE. It is important to note that glyph selection is independent of alpha. The first glyph found while descending the planes intersecting a cell will be the glyph used. Only presentation of the glyph is modified by alpha.

Value Macro Foreground Background
00 CELL_ALPHA_OPAQUE Use the fg color unchanged. Use the bg color unchanged.
01 CELL_ALPHA_BLEND Blend the fg color down. Blend the bg color down.
10 CELL_ALPHA_TRANSPARENT Select the next fg color. Select the next bg color.
11 CELL_ALPHA_HIGHCONTRAST Complement bg color computed through this plane. Forbidden.

The value 11 is currently forbidden for a bg alpha setting, but might be used in the future. To "blend the color down" means to average the colors encountered until hitting an opaque channel, or running out of planes. To "select the next color" means to ignore this color, and instead take the color as computed by lower planes.

High-contrast text is not strictly defined. notcurses will attempt to make the glyph as readable as possible, given the background color computed at the cell.

If loaded multimedia supports transparency (e.g. PNGs), transparent pixels will be considered as if CELL_ALPHA_TRANSPARENT was used.

Sprites

Transparency plus bitmaps yield sprites. The "luigi" demo uses three bitmaps from Luigi's walking cycle in Super Mario Bros. Only one is shown at a time; the other two are hidden under the standard plane (onto which a background has been rendered).

Linear interpolation

notcurses can color lines via linear interpolation between the two endpoints. This is done with ncplane_hline_interp() and ncplane_vline_interp(). If provided two endpoints of the same color, the line will be that single color.

Terminal emulators

More information is available from the source tree in TERMINALS.md.

The two primary environmental factors affecting notcurses performance are the terminal emulator and the configured fonts.

For performance evaluation, I run notcurses-demo in each terminal three times. Each terminal is configured to use Inconsolata Medium 12 as its primary font, to 70% opacity (if supported), and run at a 70x80 geometry. I report the average of the three wall clock times, and the three FPS (i.e. frames rendered divided by time spent within notcurses_render()) measurements. If DirectColor output could not be rendered, the terminal is reported as a failure (this is perhaps/probably due to my ignorance). Some demos are a fixed number of frames, with a fixed target time. Some are a fixed number of frames, to be rendered as quickly as possible. Some are fixed time, with an intention of rendering as many frames as possible.

For the FPS measurement, higher is better. For the time measurement, lower is better.

Last evaluated 2019-12-21 (notcurses 0.9.1)
Emulator Perf
FPS / Seconds
TERM Notes T.416
xterm
xterm-351
11.87 196.8 xterm-direct eagle and view were very choppy
widecolor doesn't colorize flame emoji
uniblock ran without box breakage (only terminal to do so)
Y
rxvt
urxvt v9.22
x x failure failure
libvte
(using xfce4-terminal)
xfce4-terminal 0.8.8 / vte 0.58.2
205.9 75.8 vte-direct Y
kitty
kitty 0.15.0
682.3 72.9 kitty-direct uniblock is deformed in a varying fashion
widecolor is missing some ranges
widecolor replaces some ranges with blocks
seems very smooth under load
Y
alacritty
alacritty 0.4.1-dev
553.7 74.0 alacritty-direct uniblock doesn't fade in properly N
GNOME terminator
(also VTE, but python)
terminator 1.91 / vte 0.58.2
206.0 75.7 vte-direct Y
terminology
terminology 1.3.2
x x failure failure
konsole
konsole 19.08.1 / QT 5.12.5
168.5 83.8 konsole-direct uniblock doesn't fade in properly
uniblock persistent damage on one line
boldface seems particularly strong
N

Note: some of these terminals (e.g. xterm) export an RGB-capable setaf/setab, which notcurses does not use due to its special casing of low values. notcurses instead generates (for seta[bf] only) its own escapes outside of the terminfo framework. I am uncertain as to whether this might have performance effects. The "T.416" column indicates whether this terminal appears to support the colon-delimited RGB escapes from ITU T.416.

Raw data (2019-12-21, Schwarzgerät running Debian Unstable):

alacritty: 10140, 10121, 10143, 555.6, 553.8, 551.8, 1m13.9, 1m14.0, 1m14.1s
konsole: 5655, 6286, 6433, 152.9, 174.3, 178.3, 1m24.3, 1m23.6, 1m23.5
terminator: 4628, 4788, 4759, 211.1, 208.6, 198.4, 1m15.1, 1m15.6, 1m16.5
kitty: 9475, 9539, 9518, 679.4, 687.5, 679.9, 1m12.9, 1m12.8, 1m12.9
xfce4-terminal: 4873, 4431, 4787, 214.7, 191.5, 211.4, 1m16.0, 1m16.1, 1m15.2
xterm: 1926, 1914, 1918, 11.8, 12.0, 11.8, 3m18.0, 3m14.7, 3m17.8

Notes on particular terminals

The TERMINALS.md document in Notcurses is (hopefully) well worth checking out.

  • xterm, the first and (in many ways) the best.
    • For 24-bit color, compile with --enable-direct-color and define the resource directColor to true. Both of these are the default in recent xterm.
    • Use the xterm-direct terminfo entry
    • For Sixel and Regis, compile with --enable-sixel-graphics --enable-regis-graphics
  • VTE-based terminals including gnome-terminal and xfce4-terminal
    • I'm not sure whether these ought be using the xterm- family or vte- family of terminfo entries, oddly enough

Screen multiplexers

  • screen: i've yet to see anything but a mess in screen
  • tmux: works decently well, aside from consuming bitmaps
  • mosh: we look like shit in mosh and i wish we didn't

Linux console

The Linux "system console" is a virtual device mapping to some device. We're primarily interested in the VGA text console, and the framebuffer console. The console supports only 256 different glyphs (512 with diminished color support), and never more than 16 colors (8 with 512 glyphs).

fbterm

You'll typically need to be a member of the video group to run fbterm. It offers much better Unicode glyph coverage than the raw console, but still offers only 16 (programmable) colors.

kmscon

Major Releases

Release Date Key features
3.0.0 2021-12-01 New input system, kitty keyboard/GPM/XTMODKEYS support
2.4.0 2021-09-06 Microsoft Windows and macOS support, Linux framebuffer graphics, "CLI mode"
2.3.0 2021-05-09 Tree selector, tabbed interface, pixel blitting
2.2.0 2021-02-08 libreadline, progress bars, notcurses-core extraction
2.1.0 2020-12-13 Sexblitter, ncneofetch
2.0.0 2020-10-12 Stable API!
1.7.0 2020-08-30 Linux font table reprogramming, EGC inlining, better Rust wrappers
1.6.0 2020-07-04 True three-channel transparency (glyph, fg, bg), Quadblitter default, beefed up ncdirect
1.5.0 2020-06-08 Quadblitter
1.4.0 2020-05-10 Fdplane and subproc widgets, reader widget, true scrolling
1.3.0 2020-04-13 Multiselector widget, plot widget, multiline output, margins, staining
1.2.0 2020-02-17 Menu widget, selector widget, CELL_ALPHA_HIGHCONTRAST, Python/C++ wrappers
1.1.0 2020-01-19 Massive speedups and much better video support
1.0.0 2019-01-04 First GA release
0.9.0 2019-12-18 Recognize COLORTERM, damage map, quantized colors, _yx API extensions, alignment
0.4.0 2019-12-05 Cell API, input, resize handling
0.3.0 2019-12-02 Video support, transparent planes, fades
0.2.0 2019-12-02 Panelreels, image support
0.1.0 2019-11-30 Ncplanes, basic output

See Also

Contact sheet from Notcurses 0.4.0 demo
Contact sheet from Notcurses 0.4.0 demo