Devolutions.Terminal.Control
2026.3.6
Prefix Reserved
dotnet add package Devolutions.Terminal.Control --version 2026.3.6
NuGet\Install-Package Devolutions.Terminal.Control -Version 2026.3.6
<PackageReference Include="Devolutions.Terminal.Control" Version="2026.3.6" />
<PackageVersion Include="Devolutions.Terminal.Control" Version="2026.3.6" />
<PackageReference Include="Devolutions.Terminal.Control" />
paket add Devolutions.Terminal.Control --version 2026.3.6
#r "nuget: Devolutions.Terminal.Control, 2026.3.6"
#:package Devolutions.Terminal.Control@2026.3.6
#addin nuget:?package=Devolutions.Terminal.Control&version=2026.3.6
#tool nuget:?package=Devolutions.Terminal.Control&version=2026.3.6
Devolutions.Terminal.Control
A self-contained, NativeAOT-friendly Avalonia terminal control (TermControl)
extracted from Devolutions Terminal.
This package bundles the VT engine, renderer, connection layer, and Windows
Terminal-compatible settings parser that TermControl depends on, plus the
native process hosts that local shells need, so a single package reference is
enough to embed a fully functional terminal in any Avalonia application on
Windows, Linux, and macOS.
Getting started
<ItemGroup>
<PackageReference Include="Devolutions.Terminal.Control" Version="*" />
</ItemGroup>
TermControl has no parameterless constructor usable from XAML (its
constructor takes an optional ITerminalEngine), so create and start it in
code-behind instead of declaring it as a XAML element:
using Devolutions.Terminal;
using Devolutions.Terminal.Settings;
var terminal = new TermControl();
Content = terminal;
var profile = OperatingSystem.IsWindows()
? new ProfileSettings()
: new ProfileSettings { Commandline = Environment.GetEnvironmentVariable("SHELL") ?? "/bin/sh" };
await terminal.StartAsync(profile, columns: 120, rows: 30);
ProfileSettings defaults to Windows PowerShell, so set Commandline to a
Unix shell on Linux and macOS.
The columns and rows passed to StartAsync set both the engine and PTY
initially, even if the control was arranged at a different size before startup.
Subsequent layout passes resize both to fit the available space. You can use
TermControl.MeasureCell(profile, scale) to calculate an initial grid
for the profile and display; pending layout changes made while the connection
starts are applied once it is ready.
Local shells and native hosts
| Platform | Transport | Native host in the package |
|---|---|---|
| Windows x86/x64/ARM64 | ConPTY | OpenConsole.exe under tools/conpty, copied by the package's build targets |
| Linux x64/ARM64 | forkpty relay |
dt-pty-host under runtimes/linux-<arch>/native |
| macOS x64/ARM64 (13.0+) | forkpty relay |
dt-pty-host under runtimes/osx-<arch>/native |
On Linux and macOS, a RID-specific build or publish (-r linux-x64, -r osx-arm64,
and so on) places dt-pty-host next to your executable. A RID-less build keeps it
under runtimes/<rid>/native/, and the control finds it there. NuGet does not
preserve Unix file modes, so the package's build targets mark the host executable
when you build on Linux or macOS, and the control sets the execute bit at startup
if the host was deployed without it. If your app is installed somewhere the
process cannot write, make sure your packaging keeps dt-pty-host executable
(mode 0755). The Linux host requires glibc 2.31 or later.
Stream an asciicast recording
The control can stream a recording to a caller-supplied WebSocket push URI while retaining the recording locally for save or replay:
// The application owns endpoint discovery and authentication. For a DVLS
// recording push URL, the signed token is already present in the query string.
var recordingUri = new Uri(
"wss://gateway.example/jet/jrec/push/session-id?token=signed-token");
await terminal.StartRecordingAsync(recordingUri);
// Terminal output is now sent as asciicast v2 JSONL text frames.
await terminal.StopRecordingAsync();
StartRecordingAsync preserves existing query parameters and adds
fileType=asciicast. It opens a plain WebSocket without a subprotocol, sends
the v2 header first, then streams output events with elapsed timestamps and
CRLF-normalized text. The terminal's visible screen is included as the initial
frame. Pass the optional path argument to save the same recording to disk
when it stops.
For an official asciinema server, create a live stream through its HTTP API and
pass the returned ws_producer_url with the desired format:
var producerUri = new Uri(streamResponse.WsProducerUrl);
await terminal.StartRecordingAsync(producerUri, AsciicastFormat.V3);
// Terminal output is sent using the v3.asciicast WebSocket subprotocol.
await terminal.StopRecordingAsync();
This overload supports both AsciicastFormat.V2 and AsciicastFormat.V3,
negotiates the corresponding v2.asciicast or v3.asciicast subprotocol, and
leaves the producer URI unchanged. V2 streams use elapsed timestamps; v3
streams use per-event time deltas as required by the asciicast v3 format.
Endpoint construction, token acquisition, and authentication remain the host
application's responsibility. RecordingStreamCompletion can be awaited to
confirm that queued frames were flushed, and RecordingStreamError exposes the
last transport failure.
See the samples/Devolutions.Terminal.Control.Sample
project in the Devolutions Terminal repository
for a full working app, plus the full source and documentation.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | 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
- Avalonia (>= 12.1.1)
- Avalonia.Skia (>= 12.1.1)
- HarfBuzzSharp.NativeAssets.Linux (>= 8.3.1.5)
- Microsoft.Windows.Console.ConPTY (>= 1.24.260710001)
- SkiaSharp (>= 3.119.4)
- SkiaSharp.HarfBuzz (>= 3.119.4)
- SkiaSharp.NativeAssets.Linux (>= 3.119.4)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.