Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ permissions:
env:
DOTNET_VERSION: 10.0.x
CONFIGURATION: Release
EXPECTED_VERSION: 1.29.0
EXPECTED_VERSION: 1.29.1
PACKAGE_CANDIDATE_ARTIFACT: icod-terminal-main-package-candidate

jobs:
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/pull-request.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ jobs:
if: runner.os == 'Windows'
shell: powershell
run: ./packaging/VerifyLicenseHeaders.ps1
- name: Verify native Windows console input and cancellation
if: runner.os == 'Windows'
timeout-minutes: 3
shell: pwsh
run: dotnet run --project tests/Icod.Terminal.WindowsInput.Smoke -c '${{ env.CONFIGURATION }}'
- name: Verify runtime and source integration
shell: pwsh
run: ./packaging/VerifyRuntime.ps1 -Configuration '${{ env.CONFIGURATION }}'
Expand Down
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,19 @@

Notable changes to `Icod.Terminal` are recorded here for consumers who need a concise release history. Detailed design evidence remains in the versioned roadmaps, tranche records, and public-API baseline documents.

## 1.29.1

- Decode legacy Escape-prefixed Alt letters as a single modified key, including
fragmented UTF-8. Keep standalone Escape and control-sequence recovery intact.
- Read Windows console input through `ReadConsoleW`, preserving Ctrl+Z instead
of treating it as EOF on affected console hosts. Encode Unicode as UTF-8
without depending on the console code page, including split surrogate pairs.
- Verify that raw sessions deliver Ctrl+C and Ctrl+Z as Control-modified
Character keys on Unix PTYs and through the native Windows console reader.
- Cancel and finish outstanding owned console reads before closing their handle.
- Add a Windows CI smoke using an isolated native console for Ctrl+Z, following
input, Alt+F, Unicode, and shutdown with an outstanding read.

## 1.29.0

- Add independent immutable session clipboard read/write budgets, preserving the
Expand Down
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project>
<PropertyGroup>
<!-- Repository-wide release version authority. -->
<VersionPrefix>1.29.0</VersionPrefix>
<VersionPrefix>1.29.1</VersionPrefix>
<Version Condition="'$(VersionSuffix)' == ''">$(VersionPrefix)</Version>
<Version Condition="'$(VersionSuffix)' != ''">$(VersionPrefix)-$(VersionSuffix)</Version>
<PackageVersion>$(Version)</PackageVersion>
Expand Down
2 changes: 1 addition & 1 deletion Icod.Terminal.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
<Title>Icod.Terminal</Title>
<Authors>Timothy J. Bruce</Authors>
<Description>Managed, cross-platform live-terminal session, endpoint, mode, input, lifecycle, and terminal-control foundation for .NET.</Description>
<PackageReleaseNotes>Icod.Terminal 1.29.0 adds independent bounded clipboard read/write budgets: 64 KiB defaults with explicit opt-in through 16 MiB. Larger correlated reads use incremental bounded storage; writes encode directly into a complete ordered frame. Includes explicit public samples and package validation while preserving .NET 8/9/10, existing APIs and production dependencies. Local capacity does not negotiate remote acceptance. Release notes: https://github.com/uniblab/Icod.Terminal/blob/main/docs/releases/1.29.0.md. Compatibility policy: https://github.com/uniblab/Icod.Terminal/blob/main/docs/Compatibility-and-Versioning.md. Usage: https://github.com/uniblab/Icod.Terminal/blob/main/docs/Clipboard-Transfers.md.</PackageReleaseNotes>
<PackageReleaseNotes>1.29.1 decodes legacy Escape-prefixed Alt characters, including fragmented Unicode, and preserves Ctrl+Z and Unicode on Windows using direct ReadConsoleW input. Raw sessions expose Ctrl+C and Ctrl+Z as application keys; CBreak retains host control handling. Adds cancellation-safe owned console reads, Rich Input sample checks, Unix PTY coverage, and a native Windows console regression smoke. No public API or dependency changes. Release notes: https://github.com/uniblab/Icod.Terminal/blob/main/docs/releases/1.29.1.md. Compatibility policy: https://github.com/uniblab/Icod.Terminal/blob/main/docs/Compatibility-and-Versioning.md.</PackageReleaseNotes>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageIcon>icon.png</PackageIcon>
<PackageProjectUrl>https://github.com/uniblab/Icod.Terminal</PackageProjectUrl>
Expand Down
22 changes: 17 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,9 @@

## Status

Stable release: **1.29.0**. It adds bounded, opt-in OSC 52 clipboard transfers;
the stable **1.0.0 compatibility floor** remains unchanged.
Stable release: **1.29.1**. The stable **1.0.0 compatibility floor** remains unchanged.

See the [1.29.0 release notes](https://github.com/uniblab/Icod.Terminal/blob/main/docs/releases/1.29.0.md),
See the [1.29.1 release notes](https://github.com/uniblab/Icod.Terminal/blob/main/docs/releases/1.29.1.md),
[changelog](https://github.com/uniblab/Icod.Terminal/blob/main/CHANGELOG.md) for release history,
the [development roadmap](https://github.com/uniblab/Icod.Terminal/blob/main/Icod.Terminal-Development-Roadmap.md)
for current work, and the [compatibility policy](https://github.com/uniblab/Icod.Terminal/blob/main/docs/Compatibility-and-Versioning.md)
Expand Down Expand Up @@ -66,7 +65,7 @@ See [`docs/Architecture.md`](docs/Architecture.md) for the permanent architectur
Install the stable release:

```text
dotnet add package Icod.Terminal --version 1.29.0
dotnet add package Icod.Terminal --version 1.29.1
```

The partial animation-frame API requires `Icod.Terminal 1.23.0` or later. The geometry and planning APIs described below require `Icod.Terminal 1.24.0` or later.
Expand Down Expand Up @@ -235,7 +234,7 @@ The [compatibility sample](samples/Icod.Terminal.Compatibility.Sample/README.md)
The root README describes the current product by capability rather than by the release in which each feature first appeared.

- **Live terminal session and lifecycle** — terminal endpoint observation; native terminal-mode capture/mutation; deterministic session disposal; lifecycle invalidation; scoped/reversible state where exact restoration is supportable.
- **Unified input and events** — one authoritative reader for text, keys, bracketed paste, focus, mouse, lifecycle observations, active query responses, and unsolicited protocol-neutral semantic events.
- **Unified input and events** — one authoritative reader for text, keys, bracketed paste, focus, mouse, lifecycle observations, active query responses, and unsolicited protocol-neutral semantic events, including legacy Escape-prefixed Alt characters and direct Windows console control/Unicode input.
- **Typed terminal queries** — bounded cursor, status, style, color, clipboard, notification, pointer, appearance, and related observations integrated with the same input/query authority.
- **Terminal environment awareness** — opt-in appearance and in-band resize reporting with independent scoped ownership, typed semantic observations, and separate native-geometry provenance.
- **Static screen advertisement** — immutable `Profile.Screen.Advertises...` facts, separate from parameter-specific planning and live evidence; the [capability sample](samples/Icod.Terminal.CapabilityPlanning.Sample/README.md) demonstrates all three.
Expand Down Expand Up @@ -394,6 +393,19 @@ The repository uses C# 13. Release qualification covers Windows, Linux, and macO

`TerminalSession.OpenAsync(options)` reads interactive Unix input through a session-owned byte transport on Linux and macOS. It bypasses .NET console line editing so keys and terminal replies reach the decoder without waiting for Enter or acquiring managed echo. The existing input-mode and echo options still control native terminal behavior. Dispose the session to restore modes and release its input descriptor. The overload accepting explicit byte services continues to borrow them; callers retain their lifetime responsibility.

On Windows, the same overload owns a separate `CONIN$` handle and reads it with
`ReadConsoleW`. This keeps Ctrl+Z in the input stream instead of allowing a
standard byte-stream read to interpret it as end-of-input, and converts console
Unicode to UTF-8 for the common decoder. Cancellation finishes the outstanding
native read before its owned handle is closed. Sessions opened with explicit
byte services retain their existing borrowed-transport behavior.

Applications that bind Ctrl+C or Ctrl+Z as commands should request
`TerminalInputMode.Raw`. Raw mode disables POSIX signal handling and Windows
processed input so both arrive through `ReadEventAsync()` as Character keys with
the Control modifier. `CBreak` deliberately retains host signal/processed-input
handling for applications that want conventional interrupt and suspend behavior.

`Icod.Terminal` is a managed library but intentionally uses native platform facilities where terminal modes or console behavior require them. Protocol and feature availability still depends on the terminal endpoint attached to the process; operating-system identity or terminal brand is not treated as sufficient support proof.

## Design Boundaries and Guarantees
Expand Down
25 changes: 25 additions & 0 deletions docs/Input-and-Events.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ Typed query methods share the same underlying input stream and response router.

`ITerminalInput` remains public so custom transports can be supplied when opening a session. A live session does not expose that borrowed input transport as a public property. Callers must not create a competing reader over the same transport while the session owns it.

The default Windows session owns a separate `CONIN$` handle and reads Unicode
characters through `ReadConsoleW`, then incrementally encodes them as UTF-8 for
the common decoder. This preserves control characters such as Ctrl+Z instead of
giving byte-stream EOF semantics to the Windows console host, and preserves
surrogate pairs across native read boundaries. Cancellation completes the
outstanding native read before closing the owned handle. An explicit
`ITerminalInput` remains borrowed and does not use this Windows-console path.

This rule is normative. It prevents an external read from stealing bytes belonging to fragmented UTF-8, terminal keys, mouse/focus/paste frames, modern keyboard frames, active query responses, or unsolicited semantic reports.

## 2. Unified event model
Expand Down Expand Up @@ -145,6 +153,15 @@ The decoder recognizes terminfo key capabilities for navigation, editing, functi

Traditional keyboard input remains available regardless of whether a modern keyboard protocol is negotiated.

Applications that need Ctrl+C, Ctrl+Z, or another host control character as an
application command must request `TerminalInputMode.Raw`. In raw mode, decoded
Ctrl+C and Ctrl+Z are `TerminalKey.Character` events with the Control modifier
and `Character` equal to `C` or `Z`. `TerminalInputMode.CBreak` intentionally
retains POSIX signal handling and Windows processed-input handling; it is not an
interception mode for those shortcuts. The application chooses the input mode
when opening the session and handles the resulting event through
`ReadEventAsync(...)`; no separate signal-interception API is required.

### Escape ambiguity

An isolated Escape key is ambiguous with the prefix of many terminal sequences. The decoder therefore uses a bounded ambiguity interval.
Expand All @@ -159,6 +176,14 @@ and may be configured per session through `TerminalInputDecoderOptions.EscapeSeq

The timeout distinguishes an isolated Escape from an incomplete escape-prefixed sequence; it is not an application-wide input polling interval.

After reserving known terminfo matches and control-family introducers for their
existing sequence and recovery paths, Escape followed within that interval by
one valid non-control UTF-8 scalar becomes a `TerminalKey.Character` key with
the Alt modifier. The scalar may arrive across multiple native reads. CSI, OSC,
DCS, APC, PM, SOS, SS3, and string-terminator introducers do not become Alt
characters. If no qualifying scalar arrives within the interval, the decoder
returns the isolated Escape key and retains later input for normal decoding.

## 7. Rich input events

### Mouse
Expand Down
51 changes: 51 additions & 0 deletions docs/releases/1.29.1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Icod.Terminal 1.29.1

This patch repairs input problems exposed by the Icod.Widgets menu gallery.
Legacy Alt+F now arrives as one Alt-modified character. The same rule preserves
fragmented non-control Unicode Alt characters. Windows console input
uses `ReadConsoleW` so Ctrl+Z remains a control key on hosts where `ReadFile`
interprets it as end-of-input, even in raw mode.

## Input behavior

Known terminfo keys and control-family introducers retain their existing decoding
and recovery paths. A valid non-control UTF-8 scalar following Escape within the
configured ambiguity interval becomes one `TerminalKey.Character` event with
the Alt modifier. An isolated Escape still resolves after the bounded interval.

The default Windows session opens a separate owned `CONIN$` handle. `ReadConsoleW`
provides UTF-16 input, which the transport incrementally converts to UTF-8 for
the shared decoder. Ctrl+Z therefore remains data rather than end-of-input, and
surrogate pairs remain intact across native reads and small caller buffers.

Applications can intercept both Ctrl+C and Ctrl+Z by opening the session with
`TerminalInputMode.Raw` and handling their Control-modified Character events
through `ReadEventAsync(...)`. `CBreak` continues to preserve host signal and
processed-input behavior. This input-mode contract is unchanged; 1.29.1 repairs
the Windows transport so Ctrl+Z can satisfy it reliably.

## Ownership and compatibility

The reader preserves Unicode across native read boundaries and owns a separate
console handle. Cancellation waits for the native read to finish before closing
the handle. Process input/output encodings are not changed.

No public APIs or production dependencies change. Targets remain `net8.0`, `net9.0`, and `net10.0`.
See [compatibility policy](../Compatibility-and-Versioning.md).
Existing clipboard budgets and keyboard protocol reporting remain available.
Ambiguous control-family introducers retain their existing sequence behavior.

## Validation

Decoder and transport tests cover fragmented Alt input, standalone Escape,
control sequences, Ctrl+Z, small UTF-8 buffers, split surrogate pairs,
cancellation, reuse, and disposal. The PR Windows job runs an isolated native
console smoke with injected key records through the real session reader.

The Rich Input sample now uses raw input and prompts a fallback Alt+F check plus
Ctrl+C/Ctrl+Z-followed-by-input checks. Linux and macOS PTY smokes verify both
control shortcuts in raw mode; the isolated native Windows smoke verifies them
through the real Windows session reader.

After publishing, the Widgets/DCurses dependency chain must consume this patch
before the Widgets gallery's physical Alt+F and Windows Ctrl+Z retest.
11 changes: 10 additions & 1 deletion samples/Icod.Terminal.RichInput.Sample/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ You should have received a copy of the GNU General Public License

await using TerminalSession session = await TerminalSession.OpenAsync(
new TerminalSessionOptions {
InputMode = TerminalInputMode.CBreak,
// Raw lets applications bind control characters such as Ctrl+C and Ctrl+Z.
InputMode = TerminalInputMode.Raw,
EchoInput = false
}
);
Expand Down Expand Up @@ -83,6 +84,14 @@ await WriteLineAsync(
session,
"Kitty-capable terminals may report repeat/release phases, alternate key identities, and associated text."
);
await WriteLineAsync(
session,
"With traditional input, Alt+F should report one Alt-modified Character key."
);
await WriteLineAsync(
session,
"Raw mode lets Ctrl+C and Ctrl+Z report as Control-modified Character keys; input should continue."
);
await WriteLineAsync(
session,
"Mouse coordinates below are zero-based terminal-cell coordinates."
Expand Down
22 changes: 22 additions & 0 deletions samples/Icod.Terminal.RichInput.Sample/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,28 @@ dotnet run --project samples/Icod.Terminal.RichInput.Sample/Icod.Terminal.RichIn

The sample also targets `net8.0` and `net9.0` when the matching runtime is installed. Type text, use navigation/editing keys, paste, click, or change focus. Press **q**, **Q**, or **Escape** to exit; end of input also ends the loop. Terminal support determines which reports actually arrive, so a physical terminal need not emit every event shown in the scripted package witness.

## 1.29.1 input checks

When modern keyboard reporting is unavailable and the terminal uses traditional
Escape-prefixed input, press Alt+F. It should produce one event resembling:

```text
Key key=Character ... character="f" ... modifiers=Alt
```

The exact phase and alternate-character fields depend on the terminal. The
important observations are one Character key, `f`, and the Alt modifier rather
than a standalone Escape followed by text.

The sample deliberately requests `TerminalInputMode.Raw`. Press Ctrl+C, type a
printable key, then press Ctrl+Z and type another printable key. Each control
shortcut should produce one `TerminalKey.Character` event with the Control
modifier (`C` or `Z`), and both following keys must still arrive. Raw mode is
essential here: `CBreak` retains host signal/processed-input handling, where
POSIX normally maps Ctrl+C to `SIGINT` and Ctrl+Z to `SIGTSTP`, and Windows
retains processed console handling. Neither check requires modern keyboard
reporting.

## Reporting and fallback

The sample first requests an input-protocol lease for Kitty `AllKeys` reporting, bracketed paste, focus, and button mouse tracking. If that combined request is unavailable, it tries paste/focus/mouse without modern keyboard reporting. If that is also unavailable, the basic event loop still runs. The lease is disposed on exit; the session remains the sole reader throughout.
Expand Down
2 changes: 1 addition & 1 deletion samples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ Normal execution needs interactive input/output and alternate-screen/cursor/rend

Interactive inspector for text, keys, bracketed paste, focus, mouse, lifecycle, unsolicited semantic events, and negotiated modern keyboard reporting.

On terminals that send Kitty phase-bearing functional CSI frames, navigation, editing, and Menu keys retain repeat/release phases in the existing event model. The example tracks a held Left key and Control+Insert repeats only while a modern keyboard-reporting lease is active. Traditional key decoding remains the fallback; an ordinary traditional press does not imply that a matching release will arrive. The [sample walkthrough](Icod.Terminal.RichInput.Sample/README.md) explains reporting, output, privacy, and fresh-package verification.
On terminals that send Kitty phase-bearing functional CSI frames, navigation, editing, and Menu keys retain repeat/release phases in the existing event model. The example tracks a held Left key and Control+Insert repeats only while a modern keyboard-reporting lease is active. Traditional key decoding remains the fallback; an ordinary traditional press does not imply that a matching release will arrive. In fallback mode, Alt+F demonstrates one Alt-modified character event. The sample uses raw input so Ctrl+C and Ctrl+Z can be application shortcuts rather than host signals or processed-console controls. The [sample walkthrough](Icod.Terminal.RichInput.Sample/README.md) explains reporting, the 1.29.1 checks, output, privacy, and fresh-package verification.

```text
dotnet run --project samples/Icod.Terminal.RichInput.Sample/Icod.Terminal.RichInput.Sample.csproj -f net10.0
Expand Down
Loading
Loading