VirtualTerminal.Avalonia
1.8.1
dotnet add package VirtualTerminal.Avalonia --version 1.8.1
NuGet\Install-Package VirtualTerminal.Avalonia -Version 1.8.1
<PackageReference Include="VirtualTerminal.Avalonia" Version="1.8.1" />
<PackageVersion Include="VirtualTerminal.Avalonia" Version="1.8.1" />
<PackageReference Include="VirtualTerminal.Avalonia" />
paket add VirtualTerminal.Avalonia --version 1.8.1
#r "nuget: VirtualTerminal.Avalonia, 1.8.1"
#:package VirtualTerminal.Avalonia@1.8.1
#addin nuget:?package=VirtualTerminal.Avalonia&version=1.8.1
#tool nuget:?package=VirtualTerminal.Avalonia&version=1.8.1
VirtualTerminal
VirtualTerminal is a small, UI-agnostic VT/ANSI terminal engine for .NET with WPF and Avalonia controls.
It parses terminal escape sequences into a cross-platform screen buffer and lets you plug in different backends (local ConPTY shell, SSH, or custom sessions) through a single ITerminalSession abstraction.
This repository contains:
- VirtualTerminal – the core engine:
TerminalDecoder,TerminalScreenBuffer,TerminalState, options and session abstractions. - VirtualTerminal.WPF –
TerminalControlfor WPF (System.Windows.Controls.Control). - VirtualTerminal.Avalonia –
TerminalControlfor Avalonia (Avalonia.Controls.TemplatedControl). - VirtualTerminal.CommandLine –
CommandLineSession, a ConPTY-backed local shell. (Windows only) - VirtualTerminal.SecureShell –
SecureShellSession, an SSH.NET-backed remote shell. - VirtualTerminal.TestApp – a small WPF demo application
Getting started
Install / reference projects
- Add the
VirtualTerminalproject to your solution (or reference the compiled assembly). - Add
VirtualTerminal.WPForVirtualTerminal.Avaloniadepending on your UI stack. - (Optional) Add
VirtualTerminal.CommandLineand/orVirtualTerminal.SecureShellfor those backends. - Target
net10.0(Avalonia / engine) ornet10.0-windows(WPF backends and test apps).
Basic XAML setup
WPF
<Window x:Class="MyApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vt="clr-namespace:VirtualTerminal;assembly=VirtualTerminal.WPF">
<Grid>
<vt:TerminalControl
x:Name="Terminal"
AllowDirectInput="True"
CursorBlinking="True"
ScrollDownVisible="True"
Background="Black"
FontFamily="Cascadia Mono"
FontSize="14" />
</Grid>
</Window>
Avalonia
<Window x:Class="MyApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vt="clr-namespace:VirtualTerminal;assembly=VirtualTerminal.Avalonia">
<vt:TerminalControl
CurrentSession="{Binding Session}"
AllowDirectInput="True"
CursorBlinking="True"
Background="Black"
FontFamily="Cascadia Mono"
TerminalFontSize="14" />
</Window>
Basic code-behind: attach a session
Create a session instance and assign it to the control:
using VirtualTerminal;
public partial class MainWindow : Window
{
private CommandLineSession? _session;
public MainWindow()
{
InitializeComponent();
_session = new CommandLineSession(); // defaults to cmd.exe
Terminal.Session = _session; // WPF
// Terminal.CurrentSession = _session; // Avalonia
}
protected override void OnClosed(EventArgs e)
{
base.OnClosed(e);
_session?.Dispose();
}
}
Important: the control does not own or dispose the session. Dispose the session yourself when the window/control is closed.
Core concepts and public API
TerminalControl
The WPF and Avalonia controls render the active ITerminalSession.Buffer and forward keyboard input into the session.
Key properties
Session/CurrentSession(DependencyProperty/StyledProperty)- The active
ITerminalSession.
- The active
AllowDirectInput(defaulttrue)- If
true, keyboard input is encoded as VT sequences and sent to the session.
- If
CursorBlinking,CursorVisible,CursorColor,CursorShape- Cursor appearance.
ScrollDownVisible(defaulttrue)- Shows a floating indicator when scrolled back in history.
Background/Foreground- Default terminal colors.
FontFamily/FontSize(WPF) orTerminalFontFamily/TerminalFontSize(Avalonia)- Rendering font.
LineHeight- Line height multiplier.
ScrollbackLines- Maximum number of scrollback lines to retain.
AutoScrolling(read-only)truewhen the viewport is pinned to the live bottom.
Key behavior
- Renders the active screen buffer using glyph-based text rendering.
- Handles
PreviewKeyDown/PreviewTextInputwhenAllowDirectInput == true. - Mouse wheel scrolls through scrollback.
Ctrl + Mouse Wheelzooms the terminal font.
ITerminalSession
The minimal interface any backend must implement.
Members
event EventHandler? BufferUpdated– raise when the buffer changes and the UI should repaint.TerminalScreenBuffer Buffer { get; }– the screen buffer.ITerminalDecoder Decoder { get; }– the VT decoder that writes intoBuffer.Encoding InputEncoding { get; }/Encoding OutputEncoding { get; }.string Title { get; }– logical title of the session.void Resize(ushort columns, ushort rows)– called when the control is resized.void Write(ReadOnlySpan<byte> data)– writes input bytes to the backend.int Read(Span<byte> buffer)/byte[] ReadAll()– reads response data from the backend (e.g. replies to DA/DSR queries).
TerminalSession
Recommended abstract base class in VirtualTerminal.Session.
It owns a TerminalDecoder and a TerminalScreenBuffer, wires BufferUpdated, and provides:
void Resize(ushort columns, ushort rows)– resizes the local decoder buffer.void Write(ReadOnlySpan<byte> data)– writes to the local decoder and notifies the UI.protected void NotifyBufferUpdated()– raisesBufferUpdated.IDisposablepattern.
Derived sessions override Write and Resize to talk to the actual backend.
TerminalDecoder
Parses incoming VT/ANSI byte streams and applies them to a TerminalScreenBuffer.
Key method: void Write(ReadOnlySpan<byte> data).
TerminalScreenBuffer
The engine screen buffer. It owns:
- the primary and alternate screen grids,
- the scrollback ring,
- tab stops,
- the scroll region,
- dirty-row tracking.
Public API includes Rows, Columns, GetRow(int), GetScrollback(), Resize(...), ScrollbackCount, HasDirtyRows, MarkRowClean, SyncRoot, etc.
Addons
- VirtualTerminal.CommandLine –
CommandLineSessionfor local ConPTY shells (cmd.exe, PowerShell, etc.). - VirtualTerminal.SecureShell –
SecureShellSessionfor SSH.NET remote shells.
Each addon has its own README with setup and usage:
VirtualTerminal.CommandLine/README.mdVirtualTerminal.SecureShell/README.md
Small features & UX details
- Clear screen:
TerminalControl.Clear()sendsESC[2J ESC[H. - Font zoom:
Ctrl + Mouse Wheelchanges the render font size. - Scroll-to-bottom indicator: appears when scrolled up; click or press a key to return to live output.
- Auto-scrolling: when at the bottom, new output keeps the view pinned.
- Resize debounce: both controls debounce rapid window resize events and suppress intermediate renders so backends like ConPTY have time to produce a clean reflow frame.
Context menu and built-in commands
Both controls expose static commands for common terminal operations. If no ContextMenu is assigned, the right mouse button behaves in terminal style: copy when text is selected, paste otherwise. If you assign a ContextMenu, the control lets the framework open it and the commands can be used from XAML.
WPF
<vt:TerminalControl x:Name="Terminal" ...>
<vt:TerminalControl.ContextMenu>
<ContextMenu>
<MenuItem Command="{x:Static vt:TerminalControl.CopyCommand}"
CommandTarget="{Binding PlacementTarget, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
<MenuItem Command="{x:Static vt:TerminalControl.PasteCommand}"
CommandTarget="{Binding PlacementTarget, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
<Separator />
<MenuItem Command="{x:Static vt:TerminalControl.SelectAllCommand}"
CommandTarget="{Binding PlacementTarget, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
<MenuItem Command="{x:Static vt:TerminalControl.ClearCommand}"
CommandTarget="{Binding PlacementTarget, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
</ContextMenu>
</vt:TerminalControl.ContextMenu>
</vt:TerminalControl>
Avalonia
<vt:TerminalControl x:Name="Terminal" ...>
<vt:TerminalControl.ContextMenu>
<ContextMenu>
<MenuItem Command="{x:Static vt:TerminalControl.CopyCommand}" CommandParameter="{Binding #Terminal}" />
<MenuItem Command="{x:Static vt:TerminalControl.PasteCommand}" CommandParameter="{Binding #Terminal}" />
<Separator />
<MenuItem Command="{x:Static vt:TerminalControl.SelectAllCommand}" CommandParameter="{Binding #Terminal}" />
<MenuItem Command="{x:Static vt:TerminalControl.ClearCommand}" CommandParameter="{Binding #Terminal}" />
</ContextMenu>
</vt:TerminalControl.ContextMenu>
</vt:TerminalControl>
| Command | Action |
|---|---|
CopyCommand |
Copies the current selection to the clipboard. |
PasteCommand |
Pastes the clipboard content into the session. |
SelectAllCommand |
Selects the entire visible screen. |
ClearCommand |
Sends ESC[2J ESC[H to clear the screen. |
Ctrl+C/Ctrl+V and Ctrl+Shift+C keep working as before.
Notes & limitations
- ConPTY resize: ConPTY handles its own reflow. The engine resizes the local geometry and lets ConPTY repaint/overwrite the visible area. A minimum grid size (10×3) is enforced to avoid shell resets at extremely small sizes.
- Windows-only backends:
VirtualTerminal.CommandLineandVirtualTerminal.SecureShelltargetnet10.0-windows. The core engine and Avalonia control targetnet10.0.
Changelog
Unreleased
WPF TerminalControl
- xterm mouse tracking forwarding. When the running app has enabled mouse reporting (
DECSET 1000/1002/1003), clicks, drags and wheel are forwarded to it as xterm sequences (SGR 1006 when enabled, legacy otherwise) instead of being used for local selection and scrollback. This lets full-screen TUIs (e.g.claudein fullscreen mode) receive mouse events.Shiftbypasses forwarding so the user can still copy text from a mouse-owning app. New internal helpers:IsMouseReporting,ShouldForwardToApp,MapMouseButton,ShouldReportMotion; state_trackedButtontracks the held button for button-event/any-event motion. - Meta (Alt) key sequences. WPF does not fire
TextInputwhile Alt is held, so character keys with Alt were dropped. The control now composes the xterm meta sequenceESC + <base char>for left Alt + printable, resolvinge.SystemKey(WPF reportsKey.Systemunder Alt). AltGr (right Alt) is excluded: it composes alternate characters (e.g.AltGr+2 = @) and already arrives throughOnPreviewTextInput.Ctrl+Altis also excluded (not pure meta). NewKeyHelper.GetCharFromKey(Key, bool ignoreAlt)neutralizes Alt in the keyboard state beforeToUnicode, andKeyHelper.IsRightAltPressed()distinguishes AltGr. - Lifecycle fix.
Unloadedno longer disposes the control: WPF raisesUnloadedwhenever the control leaves the visual tree (reparenting, template re-application, content swap) but the same instance can be reattached and must keep rendering.OnControlLoaded/OnControlUnloadednow only start/stop the dispatcher timers; tearing down the renderer is the owner's job viaDispose. Previously, disposing onUnloadedleft_rendererConfiguredtrue with a releasedGlyphCacheand every laterOnRenderthrew "GlyphCache not configured".Disposeis now idempotent and gates renders via_disposed/_rendererConfigured. - Host screen scan. New
GetVisibleScreenText()returns the visible screen (primary or alternate buffer) one row per line, read under the buffer lock, and theScreenUpdatedevent is raised on the UI thread (atDispatcherPriority.Background, after the visual is invalidated) whenever the session buffer updates. Lets the host scan the visible screen for sentinels (e.g. a pending-question marker) without polling.
VirtualTerminal.Input.MouseEncoder
- New
EncodeMotion(TerminalMouseButton?, int x, int y, TerminalModifier, bool sgrEncoding)for pointer-motion (drag/hover) reports. Bit 5 (value 32) of the code marks a motion event;button == nullreports motion with no button down (any-event mode).
VirtualTerminal.CommandLine (Win32ProcessFactory)
CreateProcessnow receivesinfo.Environmentandinfo.CurrentDirectoryinstead ofnull/null. Previously the configured working directory for the ConPTY session (ProcessCreationInfo.CurrentDirectory) was ignored and the shell started in the host's cwd.
| 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.0.5)
- VirtualTerminal (>= 1.8.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.