Leith.Term.Presentation.Transport.Ssh 435.0.0
Leith.Term.Presentation
Leith — pronounced /liːθ/; in Scottish English, like leaf.
Terminal hosting for .NET: open local PTY or SSH sessions, drive input and process APIs, and render the visible terminal grid with framework-neutral presentation contracts or SkiaSharp.
Status: 0.1.0 pre-publication, not yet published — a private prototype under active
development. There is no compatibility promise yet: the public API baselines are a drift
detector, not a freeze, and the surface is expected to be reshaped before the first release.
During the current Leith.Term refactor, baseline snapshot tests are suspended — see
Docs/BASELINE_TESTS_SUSPENDED.md.
Read ../Leith.Workspace/PRE_PUBLICATION.md for which constraints in
this documentation are live and which describe the state at publication; see the
0.1.0 shipping record before publishing.
Packages
NuGet IDs match assembly names (Leith.Term.Presentation.*). The IDs below describe the current release
candidate.
| Package ID | Role |
|---|---|
Leith.Term.Presentation.Abstractions |
Host/session contracts, snapshots, transports, and features |
Leith.Term.Presentation.Integration |
Semantic input contracts and the framework-neutral session input router |
Leith.Term.Presentation.Session |
Host factory and session host over the Leith.Term engine |
Leith.Term.Presentation.Transport.Local |
Cross-platform local PTY through sibling Leith.Pty |
Leith.Term.Presentation.Transport.Ssh |
SSH PTY through sibling Tmds.Ssh |
Leith.Term.Presentation.Host.Platform |
A ready-to-open request for the current OS shell |
Leith.Term.Presentation.Host.Avalonia |
Avalonia composition root for hosts and transports |
Leith.Term.Presentation.Host.ProGpu |
ProGPU composition root for hosts and transports |
Leith.Term.Presentation.Gui.Definitions |
Framework-neutral frames, geometry, presentation, and themes |
Leith.Term.Presentation.Rendering.Skia |
Font metrics and a canvas presenter for SkiaSharp |
Leith.Term.Presentation.Control.Avalonia |
Avalonia terminal control |
All packages target .NET 10.
Engine (Leith.Term)
The VT emulator engine lives in the sibling repo Leith.Term.Engine
(../Leith.Term.Engine/ next to this checkout). Leith.Term.Presentation.Session references the
Leith.Term package from that repo via ProjectReference. Engine tests and benchmarks run in
Leith.Term.Engine, not here.
Current product path: Session owns Leith.Term.Terminal directly. Local transport is sibling Leith.Pty
Dedicated. Performance status:
Benchmarks/results/PERFORMANCE_STATUS.md.
Avalonia quick start
Install the UI package, plus Leith.Term.Presentation.Host.Avalonia for the local shell request:
<PackageReference Include="Leith.Term.Presentation.Control.Avalonia" Version="0.1.0" />
<PackageReference Include="Leith.Term.Presentation.Host.Avalonia" Version="0.1.0" />
Control.Avalonia deliberately does not depend on Host.Avalonia: which shell to launch is an
application decision, and an SSH-only consumer has no use for it. Take Host.Avalonia when you want
the ready-made local shell request below.
Apply the styles once during application initialization, then open and display a session:
using Leith.Term.Presentation.Abstractions;
using Leith.Term.Presentation.Control.Avalonia.Controls;
using Leith.Term.Presentation.Control.Avalonia.Styling;
using Leith.Term.Presentation.Host;
using Leith.Term.Presentation.Host.Avalonia;
TerminalControlStyles.Apply(application);
var host = AvaloniaTerminalHost.CreateLocal();
var open = await host.OpenAsync(LocalTerminalRequestFactory.CreateDefault());
if (open.Status == TerminalOperationStatus.Succeeded && open.Session is { } session)
{
Content = SessionTerminalControlFactory.CreateWithSkiaMetrics(session);
}
// Keep the host for the lifetime of the UI. During application shutdown:
// await host.DisposeAsync();
The complete runnable version is in src/samples/AvaloniaHelloTerminal. For a full capability gallery, see AvaloniaFeatureGallery.
Custom Skia canvas quick start
Other Skia hosts use the same public presenter:
using SkiaSharp;
using Leith.Term.Presentation.Rendering.Skia;
using var presenter = new TerminalSkiaCanvasPresenter(session);
presenter.Invalidated += (_, _) => InvalidateCanvas();
// In the framework's size callback:
await presenter.ResizeAsync(width, height, renderScale);
// In the framework's paint callback:
presenter.Render(canvas, SKRect.Create(width, height), renderScale);
The terminal host must receive a real UI dispatcher. ImmediateGuiDispatcher is intended for
tests and headless tools, not for a live UI session.
Composition and lifetime
Leith.Term.Presentation uses Pure DI by default. The composition root lives in the application
(or a platform package such as Leith.Term.Presentation.Host.Avalonia), not inside GUI controls.
| Component | Lifetime | Notes |
|---|---|---|
ITerminalHost |
Singleton per app | Owns open sessions; dispose on shutdown |
ITerminalSession |
Per open | Created by OpenAsync; dispose with host or CloseAsync |
ITerminalTransportProvider |
Singleton | Registered in the composition root |
IGuiDispatcher |
Singleton | Must match the UI thread |
ITerminalFontMetricsProvider |
Singleton or transient | Injected into controls via SessionTerminalControlFactory |
TerminalHostFactory.Create is the canonical Pure DI entry point. If you already use a
container, every package registers the piece it owns:
using Leith.Term.Presentation.Host.Avalonia; // AddAvaloniaTerminalHost
using Leith.Term.Presentation.Transport.Local; // AddLocalPtyTransport
services.AddAvaloniaTerminalHost() // Avalonia dispatcher + ITerminalHost
.AddLocalPtyTransport(); // the "local" transport
// Shut down asynchronously - the host owns child processes and PTYs:
await serviceProvider.DisposeAsync();
Host.ProGpu exposes the same shape. Add
AddSshTransport() for SSH; transports accumulate rather than replace each other.
Call Add…TerminalHost from the UI thread — the dispatcher is built there — and dispose
the container with DisposeAsync, not Dispose.
GUI packages (Control.Avalonia) depend only on abstractions and rendering contracts — they
do not reference Session or Transport.*. Use Host.Avalonia to wire dispatcher, transports,
and host together.
Headless or custom composition
Packages: Leith.Term.Presentation.Session, Leith.Term.Presentation.Transport.Local, Leith.Term.Presentation.Host.Platform.
using Leith.Term.Presentation.Abstractions;
using Leith.Term.Presentation.Host;
using Leith.Term.Presentation.Session;
using Leith.Term.Presentation.Transport.Local;
ITerminalHost host = TerminalHostFactory.Create(
dispatcher,
[new LocalPtyTerminalTransportProvider()]);
var open = await host.OpenAsync(LocalTerminalRequestFactory.CreateDefault());
if (open.Session is { } session)
{
await session.Input.SendLineAsync("dotnet --info");
var snapshot = session.Screen.GetSnapshot();
}
Runnable samples
| Sample | What it demonstrates | Requirement |
|---|---|---|
| AvaloniaHelloTerminal | Full Avalonia terminal control | Windows, macOS, or Linux |
The canvas samples deliberately use a command box instead of pretending to be complete terminal controls. A production control must additionally handle key chords, IME, paste, selection, mouse protocols, focus, and accessibility.
In-tree backends
Alongside the shipped Avalonia package the repository contains the ProGPU control under
src/Gui/Gui.Control/. It is not packable and is not published; build it from source.
Whether it ships is an open decision — see
0.1.0 shipping record.
To write a backend for another framework, see Docs/BACKEND_AUTHOR_GUIDE.md.
Consumer API
The canonical boundary is ITerminalHost / ITerminalSession:
Screen— read-only size, title, and neutral snapshotsFeatures— optional features discovered throughTryGet<T>()Input— bytes, text, or complete linesProcess— commands, working directory, reconnect, and stopResizeAsync— grid resize propagated to the transport
Shipping assemblies expose no engine types. Expected operational failures return
TerminalOperationResult or TerminalOpenResult; programmer errors throw. Cancellation cancels
the current operation, not the lifetime of the child process.
Build, test, and pack
The repository pins the stable .NET 10 SDK in global.json.
dotnet build Term.Presentation.slnx -c Release
dotnet test Term.Presentation.slnx -c Release --no-build
dotnet pack Term.Presentation.slnx -c Release -o artifacts/nupkg
The normal suite is hermetic. The SSH live smoke is opt-in through
TERMINAL_SHARP_SSH_LIVE=1 and TERMINAL_SHARP_SSH_LIVE_ALIAS=<ssh-config-alias>.
Repository layout
| Path | Role |
|---|---|
src/Abstractions |
Public contracts |
src/Session |
Host and internal engine session |
src/Transport/Transport.Local, Transport.Ssh |
Local PTY and SSH transports |
src/Host/Host.Platform |
OS shell resolvers and the default local-shell request |
src/Host/Host.* |
Per-framework composition roots (dispatcher + transports + host) |
src/Gui/Gui.Definitions |
Framework-neutral presentation, framing, and input decisions |
src/Gui/Gui.Rendering/Rendering.Skia |
Skia glyph shaping and canvas presenter |
src/Gui/Gui.Control/Control.* |
UI backends; only Control.Avalonia ships as a package |
src/samples |
Runnable integration examples |
src/Tests |
Contract, session, architecture, and backend tests |
Docs |
Architecture, adapter, and release notes |
License
Leith.Term.Presentation is MIT-licensed; see LICENSE.
The embedded Symbols Nerd Font Mono carries its own license in
THIRD-PARTY.
Builtin box-drawing tables follow Windows Terminal's MIT-licensed BuiltinGlyphs.cpp; source
provenance is recorded in the relevant files. NuGet dependencies retain their own licenses.
Governance: ../Leith.Workspace/. Build/test: product Docs/ and this README.
Avalonia is a registered trademark of AvaloniaUI OÜ. Leith.Term.Presentation is an independent project and is not affiliated with or endorsed by AvaloniaUI OÜ.
No packages depend on Leith.Term.Presentation.Transport.Ssh.
.NET 10.0
- Leith.Term.Presentation.Abstractions (>= 435.0.0)
- BouncyCastle.Cryptography (>= 2.7.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.11)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.11)
| Version | Downloads | Last updated |
|---|---|---|
| 435.0.0 | 0 | 10/01/2026 |
| 432.0.0 | 0 | 10/01/2026 |
| 430.0.0 | 0 | 10/01/2026 |
| 428.0.0 | 0 | 10/01/2026 |
| 422.0.0 | 0 | 09/30/2026 |
| 420.0.0 | 0 | 09/30/2026 |
| 418.0.0 | 0 | 09/30/2026 |
| 416.0.0 | 0 | 09/30/2026 |
| 414.0.0 | 0 | 09/30/2026 |
| 412.0.0 | 0 | 09/30/2026 |
| 410.0.0 | 0 | 09/30/2026 |
| 409.0.0 | 0 | 09/30/2026 |
| 407.0.0 | 0 | 09/30/2026 |
| 405.0.0 | 0 | 09/30/2026 |
| 403.0.0 | 0 | 09/30/2026 |
| 401.0.0 | 0 | 09/30/2026 |
| 399.0.0 | 0 | 09/30/2026 |
| 387.0.0 | 0 | 09/29/2026 |
| 386.0.0 | 0 | 09/29/2026 |
| 382.0.0 | 0 | 09/28/2026 |
| 381.0.0 | 0 | 09/28/2026 |
| 380.0.0 | 0 | 09/28/2026 |
| 379.0.0 | 0 | 09/28/2026 |
| 378.0.0 | 0 | 09/28/2026 |
| 377.0.0 | 0 | 09/28/2026 |
| 376.0.0 | 0 | 09/28/2026 |
| 375.0.0 | 0 | 09/28/2026 |
| 374.0.0 | 0 | 09/28/2026 |
| 373.0.0 | 0 | 09/28/2026 |