Icod.DCurses
1.1.0
See the version list below for details.
dotnet add package Icod.DCurses --version 1.1.0
NuGet\Install-Package Icod.DCurses -Version 1.1.0
<PackageReference Include="Icod.DCurses" Version="1.1.0" />
<PackageVersion Include="Icod.DCurses" Version="1.1.0" />
<PackageReference Include="Icod.DCurses" />
paket add Icod.DCurses --version 1.1.0
#r "nuget: Icod.DCurses, 1.1.0"
#:package Icod.DCurses@1.1.0
#addin nuget:?package=Icod.DCurses&version=1.1.0
#tool nuget:?package=Icod.DCurses&version=1.1.0
Icod.DCurses

Icod.DCurses is a managed, cross-platform curses-style terminal UI library for .NET.
It sits above Icod.Terminal and Icod.TermInfo:
Icod.TermInfois the immutable terminal-capability authority;Icod.Terminalowns the live terminal session, host mode, dimensions, lifecycle, input decoding, semantic terminal protocols, presentation leases, and input-protocol leases;Icod.DCursesowns curses-shaped events, logical screens and windows, pads and viewports, terminal cells and styles, semantic drawing/content, and retained refresh/damage policy.
Status
Icod.DCurses 1.0.0 remains the currently published stable release while the 1.1.0 source candidate completes final qualification in PR #25.
T1101 froze the stable 1.0 compatibility floor and version policy. T1102 selected the row-sparse semantic metadata plane. T1103 introduced the logical semantic API. T1104 added retained physical hyperlink rendering through Terminal-owned OSC 8. T1105 propagated semantics through editing/composition/pads/resize. T1106 hardened failure, cancellation, cleanup, and lifecycle replay. T1107 completed application/performance/allocation acceptance. T1108 completed the pre-RC regret gate and froze the accepted 1.1 public contract. T1109 qualified 1.1.0-rc.1 and now validates the unchanged stable 1.1.0 source before any merge, tag, release, or package publication.
Current source identity:
Version 1.1.0
PackageVersion 1.1.0
AssemblyVersion 1.0.0.0
Icod.Terminal 1.6.0
Icod.TermInfo 1.10.0
Stable 1.0 compatibility floor:
43 exported types
309 canonical declared contract lines
sha256 274b87ec28a253e4891f7f72dea847eaf7d57f45e7b6dd2ae4b464e783046639
Accepted 1.1 contract:
45 exported types
337 canonical declared contract lines
sha256 21dff2e57d8bbc9b2f0e40aa4ee4dfd575dd765d02d0f670424c93b2bdc1c039
The two intentional new exported types are CursesHyperlink and CursesCellMetadata.
Installation
Until the 1.1.0 tag/package is explicitly published, the current published stable package remains 1.0.0:
dotnet add package Icod.DCurses --version 1.0.0
Architecture
top / slabtop / watch / editors / pagers / other TUIs
|
Icod.DCurses
windows / pads / cells / refresh
rendition / semantic content / events
|
Icod.Terminal
session / input / lifecycle / dimensions
presentation / semantic protocols
|
Icod.TermInfo
terminal capability model
|
terminal / tty
Icod.DCurses does not hard-code one terminal family, maintain a second capability database, install a second raw input loop, own terminal modes independently of Icod.Terminal, emulate a terminal, or create/manage PTYs.
Targets
- .NET 8
- .NET 9
- .NET 10
- C# 13
- Windows x64/ARM64
- Linux x64/ARM64
- macOS x64/ARM64
Quick start
using Icod.DCurses;
await using CursesSession session = await CursesSession.OpenAsync();
CursesWindow screen = session.StandardScreen;
screen.Clear();
screen.Move(
0,
0
);
screen.Write(
"Hello from Icod.DCurses",
new CursesStyle(
CursesColor.Default,
CursesColor.Default,
CursesTextAttributes.Bold
)
);
await session.RefreshAsync();
CursesEvent terminalEvent = await session.ReadEventAsync();
A CursesSession restores the presentation and Terminal-owned state it acquires when disposed. Applications should consume terminal input and lifecycle activity through the curses/Terminal ownership model rather than adding a parallel byte reader.
Stable 1.0 compatibility contract
The 1.1 release carries forward the contract frozen in 0.9 and published as 1.0.0.
The accepted compatibility rules include:
- coordinates are zero-based and row/column ordered;
- subwindow origins are relative to their immediate parent;
- rows/columns mean height/width;
- column text intervals are half-open and never split a two-column text element;
- Unicode width data is Unicode 17.0.0;
- East Asian Ambiguous characters are narrow by default and wide only through
UnicodeCursesTextWidthProvider.WideAmbiguousInstance; - semantic line cells remain distinct from ordinary Unicode box-drawing text;
- logical screen/window/pad/viewport mutation is single-writer unless explicitly documented otherwise;
- one Terminal-owned event consumer may wait concurrently with serialized refresh/output activity;
- caller cancellation remains cancellation, while disposal-unblocked waits surface
ObjectDisposedException; - repeated disposal shares one restoration operation;
- uncertain/partial output invalidates retained physical knowledge so a later refresh can repaint safely;
- independently meaningful primary/restoration failures remain observable;
- Terminal remains the authoritative owner of host-state restoration.
The only lower-layer type definitions intentionally visible in the stable DCurses contract are:
Icod.Terminal.TerminalSession
Icod.Terminal.TerminalEndpoint
Icod.Terminal.TerminalControlResult<T>
Icod.TermInfo.TerminalDescription
Icod.TermInfo.TerminalSize
No additional Terminal/TermInfo type may enter a public DCurses signature without an explicit compatibility decision.
See:
docs/Public-API-Fingerprint-1.0.jsondocs/Public-API-Baseline-1.0.mddocs/1.0-Stable-Compatibility-and-Migration-Guide.md
1.1 semantic metadata and hyperlinks
Version 1.1 adds semantic meaning attached to retained content, beginning with hyperlinks.
visual rendition -> CursesStyle
semantic meaning -> CursesCellMetadata / CursesHyperlink
wire protocol/state -> Icod.Terminal
Sparse representation
T1102 measured the portable incremental cost of adding a metadata/token slot rather than assuming one private CursesCell size across architectures:
cell + metadata-reference wrapper overhead +8 bytes per cell
cell + int-token wrapper overhead +8 bytes per cell
At the established 2,048 × 256 pad scale, one unconditional extra eight-byte slot would add exactly 4 MiB even when metadata is unused.
The selected row-sparse metadata plane allocates no per-row metadata storage until needed, releases empty rows, provides O(1) coordinate lookup, composes with row snapshots, and leaves CursesCell unchanged.
T1107 binds that representation to the reference scale: the 2,048 top-level row references contribute 16 KiB of deterministic reference payload, and ten populated 256-column rows contribute another 20 KiB, for 36 KiB of reference payload. This deliberately excludes runtime-specific array/object headers from the portable contract.
Logical public contract
T1103 introduced immutable semantic values. T1108 froze the source-compatible convenience name:
CursesCellMetadata metadata = new(
new CursesHyperlink(
"https://example.test/docs",
"docs"
)
);
screen.WriteWithMetadata(
"documentation",
metadata
);
The explicit style-bearing semantic form is:
screen.Write(
"documentation",
style,
metadata
);
WriteWithMetadata(string, CursesCellMetadata) deliberately avoids adding a second Write(string, T) overload beside stable 1.0's Write(string, CursesStyle), so existing source such as screen.Write("text", default) remains unambiguous.
CursesCellMetadata.Hyperlink is nullable so later additive metadata kinds can be introduced without weakening a published non-null return contract. The 1.1 constructor still requires a real CursesHyperlink.
Metadata can also be inspected or changed at window/virtual-screen coordinates through GetMetadata(...) and SetMetadata(...).
Two-column text elements carry one coherent metadata value across the leader/continuation footprint. Ordinary unannotated replacement clears overwritten semantic metadata even if the glyph/style value is otherwise unchanged. Metadata-only changes participate in logical damage tracking.
Retained physical hyperlink rendering
T1104 compares retained physical metadata independently of glyph/style equality. A hyperlink change therefore repaints even when the visible cell is unchanged, while an unchanged linked second refresh emits no repeated hyperlink payload.
Adjacent cells with equal style and metadata are coalesced into one semantic text run. Each linked payload is sent through Terminal's typed hyperlink operation; DCurses does not construct OSC 8 frames.
A real synchronized TerminalSession/CursesSession integration test verifies canonical Terminal OSC 8 begin/text/end output inside the synchronized-output bracket without deadlock.
Structural semantic propagation
T1105 moves semantic metadata together with surviving logical content through:
- insert/delete cells;
- insert/delete lines;
- upward/downward scrolling;
- destructive rectangle copy;
- transparent overlay;
- pad presentation and independent pad viewports;
- preserved screen resize;
- wide-cell normalization and clipping repair.
The internal transient CursesLogicalCellState pairs CursesCell with optional CursesCellMetadata during these transformations without altering the public cell layout.
CopyRectangleTo transfers semantic state, including an annotated source blank. OverlayRectangleTo retains the stable transparency rule: a source blank replaces neither destination content nor destination metadata. Multiple pad viewports acknowledge semantic-only changes independently.
Failure, cancellation, and recovery
T1106 distinguishes two Terminal-owned cleanup models.
A failed synchronized-output final release is retryable because DCurses still owns the TerminalSynchronizedOutputLease. DCurses retains that failed lease, invalidates physical state, and retries the same cleanup before starting another synchronized refresh or resetting rendition during lifecycle/disposal. Repeated cleanup failure blocks the new refresh body.
Terminal's bounded hyperlink operation is different. A non-cancellation failure may leave an internal synthetic hyperlink lease owned by Terminal, but that lease is not returned to DCurses. DCurses therefore cannot safely determine whether OSC 8 begin, application text, or close was the uncertain stage. After such a failure, the Terminal-backed curses output fails closed for further application text until the owning CursesSession is disposed. Terminal control cleanup and flush remain available, and Terminal session disposal remains the authoritative final hyperlink cleanup path.
Caller cancellation reported before hyperlink transmission does not poison later semantic output. If cancellation arrives after one bounded semantic run has completed but before the full refresh finishes, the refresh engine invalidates retained physical state and a later fresh-token refresh repaints the complete logical image.
Terminal's meaningful hyperlink text + cleanup dual failure remains visible as an aggregate through CursesSession.RefreshAsync(). The existing refresh + synchronized-output restoration dual-failure rule likewise remains intact.
Suspend/resume invalidates retained physical semantic knowledge, so visible linked content is repainted after lifecycle re-entry.
Application, performance, and optimization acceptance
T1107 exercises the semantic implementation in editor-, pager-, and large-pad-shaped workloads rather than only isolated unit examples.
Editor coverage includes linked wide Unicode, style changes that retain hyperlink identity, insertion before and inside linked spans, and repeated semantic-only retargeting. Pager/help coverage includes many links, settled no-op refresh, viewport movement, line insertion/deletion, and scrolling while semantic identity follows the surviving line. The exact 2,048 × 256 reference pad exercises sparse and dense linked rows plus independent viewports.
Equivalent links are coalesced: a dense 256-cell linked row produces one bounded semantic hyperlink write. Intentionally distinct adjacent links remain distinct: 32 different links produce 32 semantic transactions.
Real Terminal-backed tests cover synchronized output both enabled and disabled, one rich-input event wait concurrent with semantic refresh, live resize, suspend/resume, and deterministic input-protocol cleanup.
Terminal-native erase, character-shift, line-shift, and scrolling shortcuts remain conservatively disabled while desired or retained physical semantic metadata exists. The non-semantic cost wins are established, but terminfo does not provide a portable guarantee that those physical transformations preserve emulator-side OSC 8 cell associations. Direct semantic rewriting remains the 1.1 correctness policy.
Public regret gate and stable contract
T1108 regenerated the public API fingerprint on all three target frameworks and accepted one identical contract:
45 exported types
337 canonical declared contract lines
sha256 21dff2e57d8bbc9b2f0e40aa4ee4dfd575dd765d02d0f670424c93b2bdc1c039
The fresh NuGet-only consumer exercises hyperlink construction, semantic writing, inspection, removal, and reassignment. The minimal executable sample contains a retained hyperlink example. No new Terminal/TermInfo public type enters the DCurses contract.
T1108's documentation-complete alpha.8 head 290508c69ed7e76179f168cc748edf38a4091b76 passed workflow #543 (34422961869) across all seven jobs.
T1109 then qualified the unchanged 1.1.0-rc.1 source at b90e54c668ccb8a02142c434470a020775fd375f in workflow #552 (34426070109) across all seven jobs. The stable 1.1.0 source now requires one final exact-head qualification before any release action.
See:
docs/Public-API-Fingerprint-1.1.jsondocs/Public-API-Baseline-1.1.mddocs/T1103-Hyperlink-Value-and-Public-Logical-Metadata-Contract.mddocs/T1104-Retained-Physical-Hyperlink-Renderer.mddocs/T1105-Editing-Composition-and-Pad-Semantic-Propagation.mddocs/T1106-Semantic-Output-Lifecycle-Failure-and-Recovery-Hardening.mddocs/T1107-Application-Performance-Allocation-and-Optimization-Acceptance.mddocs/T1108-Public-API-Package-Documentation-and-Regret-Gate.mddocs/T1109-RC-and-Stable-Closure.md
Concurrency and production hardening
The stable library uses a deliberately narrow concurrency model rather than pervasive per-cell locking:
- logical screens, windows, pads, and viewports are single-writer;
- one Terminal-owned event wait may coexist with refresh/output work;
- terminal-mutating curses operations are serialized internally;
- caller cancellation does not discard Terminal decoder state;
- disposal unblocks pending DCurses event/lifecycle waits while preserving authoritative restoration;
- output uncertainty invalidates retained physical state;
- semantic cleanup uncertainty is never hidden by optimistic continued application output.
No public scheduler, lock/token abstraction, hardening helper, or diagnostics/statistics surface is part of the stable API.
Refresh and output optimization
The retained logical/physical screen model remains authoritative. DCurses may choose a cheaper terminal operation only when the active TermInfo description advertises the required capability, retained state proves the same final result, and the emitted cost is a strict win. Otherwise it uses the ordinary renderer.
Synchronized presentation is opt-in:
await using CursesSession session = await CursesSession.OpenAsync(
new CursesSessionOptions {
UseSynchronizedOutput = true
}
);
UseSynchronizedOutput defaults to false. DCurses delegates synchronized-output ownership to Icod.Terminal; it does not infer support from a terminal name or construct private mode sequences itself.
Internal refresh optimization can select safe cursor motion, erase operations, character/line insertion and deletion, full-width scrolling, temporary scroll regions, and differential rendition transitions for non-semantic content. Semantic state keeps structural terminal shortcuts disabled unless a future portable semantic-equivalence contract proves that the physical operation preserves hyperlink associations.
Representative deterministic maintainer fixtures include:
T701 established-default -> bold: 19 bytes / 4 writes
T707 established-default -> bold: 13 bytes / 3 writes
editor two-column insertion: 2 optimized vs 34 fallback bytes
pager one-line deletion: 4 optimized vs 166 fallback bytes
160 x 60 full repaint: 9661 bytes / 121 writes / 1 flush
1000 one-cell updates: 2000 bytes / 2000 writes / 1000 flushes
These are comparison fixtures, not universal performance claims for every terminal.
Presentation and semantic drawing
Logical styles remain terminal-independent. The physical renderer resolves them against advertised TermInfo capabilities and degrades unsupported presentation without mutating logical CursesStyle values.
CursesStyle heading = new(
CursesColor.Indexed( 14 ),
CursesColor.Default,
CursesTextAttributes.Bold
| CursesTextAttributes.Italic
| CursesTextAttributes.Underline
);
screen.Write( "Presentation-aware heading", heading );
screen.DrawHorizontalLine( 12, 4, 30 );
CursesPresentationCapabilities exposes curses-level presentation information without requiring ordinary applications to inspect raw terminfo strings.
Pads and large surfaces
CursesPad is an off-screen logical surface and reuses ordinary CursesWindow editing semantics.
CursesPad pad = new( 200, 5_000 );
CursesWindow content = pad.ContentWindow;
content.Move( 100, 20 );
content.Write( "A界B — large logical document" );
CursesPadViewport viewport = pad.CreateViewport(
session.StandardScreen,
padRow: 95,
padColumn: 10,
rows: 20,
columns: 70,
destinationRow: 1,
destinationColumn: 2
);
viewport.Present();
await session.RefreshAsync();
Multiple viewports may observe one pad independently. Pads and viewports do not own terminal sessions or physical refresh state. Semantic-only changes are observed and presented independently by multiple viewports.
Window editing and composition
Windows are shared logical views. Editing, copying, overlay, drawing, and damage operations preserve the Unicode/wide-cell contract and, where content moves, its semantic metadata.
CursesScreen logical = new( 80, 24 );
CursesWindow editor = logical.CreateWindow( 2, 4, 18, 60 );
editor.Move( 1, 2 );
editor.Write( "A界B" );
editor.InsertCells( 2 );
editor.DrawHorizontalLine( 16, 1, 58 );
editor.TouchRegion( 0, 0, 18, 60 );
CopyRectangleTo copies source blanks and their semantic state; OverlayRectangleTo treats source blanks as fully transparent and therefore preserves destination semantic metadata at those coordinates.
Unicode column helpers
int columns = CursesText.MeasureColumns( "A界B" );
string prefix = CursesText.TruncateToColumns( "A界B", 3 );
string slice = CursesText.SliceByColumns( "A界B", 1, 2 );
The helpers normalize malformed UTF-16, reject terminal controls, operate on complete Unicode text elements, and never return half of a two-column element.
Modern keyboard and rich input
Applications can request richer keyboard reporting through curses-owned protocol options while Terminal remains the decoder and lease owner:
var keyboard = await session.AcquireInputProtocolsAsync(
new CursesInputProtocolOptions {
KeyboardReportingMode = CursesKeyboardReportingMode.EventTypes
}
);
The curses event facade also carries stable focus, paste, mouse, lifecycle, and end-of-input semantics.
Validation and packaging
Local wrappers use Debug configuration:
build.cmd
or:
./build.sh
Pull requests use Staging with warnings-as-errors. Pushes to main and release tags use Release. Runtime validation covers Windows/Linux/macOS x64 and ARM64; the library/test matrix covers net8.0, net9.0, and net10.0.
Package validation verifies the generated .nupkg/.snupkg, package and assembly identity, exact dependency groups, README/license/icon/repository metadata, XML documentation, portable symbols, and a fresh package-only consumer rather than relying only on project references.
The release workflow derives displayed Icod.Terminal and Icod.TermInfo dependency versions directly from the project PackageReference values so GitHub Release notes cannot silently drift from package metadata.
Release documentation
Current post-1.0 authorities:
Icod.DCurses-Development-Roadmap.mdIcod.DCurses-1.1.0-to-1.4.0-Development-Roadmap.mdIcod.DCurses-1.1.0-Development-Roadmap.mddocs/T1101-1.1.0-Contract-Reference-and-Version-Policy-Freeze.mddocs/T1102-Semantic-Metadata-Representation-and-Memory-Gate.mddocs/T1103-Hyperlink-Value-and-Public-Logical-Metadata-Contract.mddocs/T1104-Retained-Physical-Hyperlink-Renderer.mddocs/T1105-Editing-Composition-and-Pad-Semantic-Propagation.mddocs/T1106-Semantic-Output-Lifecycle-Failure-and-Recovery-Hardening.mddocs/T1107-Application-Performance-Allocation-and-Optimization-Acceptance.mddocs/T1108-Public-API-Package-Documentation-and-Regret-Gate.mddocs/T1109-RC-and-Stable-Closure.mddocs/Public-API-Fingerprint-1.1.jsondocs/Public-API-Baseline-1.1.md
The published 1.0 closure records remain stable compatibility authorities and are not rewritten merely to reflect later development state.
Authors
Inspired by original work from Bill Joy, author of the original termcap; Mary Ann (born Mark) Horton, author of terminfo; Pavel Curtis, author of pcurses; and Zeyd Ben-Halim, Eric S. Raymond, and Thomas Dickey, whose work developed and maintained libtinfo and ncurses.
Managed .NET implementation by Timothy J. Bruce uniblab@hotmail.com.
Copyright
Copyright (c) 2026 Timothy J. Bruce
License
Licensed under the GNU Lesser General Public License v3.0 or later. See LICENSE.
The NuGet package declares license acceptance as required. Package clients which honor NuGet's requireLicenseAcceptance metadata must obtain acceptance of the license terms before installation.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Icod.Terminal (>= 1.8.1)
- Icod.TermInfo (>= 1.10.0)
-
net8.0
- Icod.Terminal (>= 1.8.1)
- Icod.TermInfo (>= 1.10.0)
-
net9.0
- Icod.Terminal (>= 1.8.1)
- Icod.TermInfo (>= 1.10.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 2.2.0 | 191 | 9/25/2026 |
| 2.1.0 | 88 | 9/24/2026 |
| 2.0.0 | 92 | 9/23/2026 |
| 1.6.0 | 229 | 9/17/2026 |
| 1.5.0 | 97 | 9/15/2026 |
| 1.4.0 | 95 | 9/14/2026 |
| 1.3.0 | 104 | 9/12/2026 |
| 1.2.0 | 146 | 9/10/2026 |
| 1.1.0 | 112 | 9/10/2026 |
| 1.0.0 | 104 | 9/9/2026 |
| 0.9.0 | 106 | 9/9/2026 |
| 0.8.0 | 103 | 9/9/2026 |
| 0.7.0 | 108 | 9/9/2026 |
| 0.6.0 | 107 | 9/9/2026 |
| 0.5.0 | 109 | 9/8/2026 |
| 0.4.0 | 105 | 9/8/2026 |
| 0.3.0 | 101 | 9/8/2026 |
| 0.2.0 | 110 | 9/8/2026 |
| 0.1.1 | 112 | 9/8/2026 |
| 0.1.0 | 4,704 | 8/29/2026 |
1.1.0 adds retained semantic cell metadata and hyperlinks while preserving the stable 1.0 compatibility floor. The accepted public contract is 45 exported types, 337 canonical contract lines, and SHA-256 21dff2e57d8bbc9b2f0e40aa4ee4dfd575dd765d02d0f670424c93b2bdc1c039 across net8.0, net9.0, and net10.0. The two intentional new exported types are CursesHyperlink and CursesCellMetadata. Semantic metadata uses a lazily allocated row-sparse plane, moves with surviving content through editing/composition/pads/resize, and participates independently in retained physical refresh. Adjacent equivalent links coalesce into Terminal-owned bounded OSC 8 operations. Synchronized-output, cancellation, lifecycle replay, and uncertain hyperlink cleanup are handled conservatively. The source-compatible two-argument semantic convenience is WriteWithMetadata(string, CursesCellMetadata); the explicit Write(string, CursesStyle, CursesCellMetadata) and WriteCell(CursesCell, CursesCellMetadata) operations remain available. CursesCellMetadata.Hyperlink is nullable for future additive semantic kinds while the current constructor requires a real CursesHyperlink. Dependencies are Icod.Terminal 1.8.1 and Icod.TermInfo 1.10.0; AssemblyVersion remains 1.0.0.0.