Skip to main content

Avalonia terminal controls (TerminalControl/TerminalWindow) that provide an XTerm console for hosting console applications in Avalonia, including Sixel and Kitty graphics.

Total Downloads
13.5K
13,545
Published
Oct 14, 2026
5d ago

Get Started

dotnet add package Iciclecreek.Avalonia.Terminal --version 4.1.0
ProductVersions
.NET
net10.0

README

Icon

Iciclecreek.Avalonia.Terminal For Avalonia 12.x

Terminal Demo

A cross-platform XTerm terminal emulator control for Avalonia UI applications.

Build Status NuGet License

Introduction

Iciclecreek.Avalonia.Terminal provides Avalonia controls for embedding a fully-featured terminal emulator in your cross-platform desktop applications. Built on top of XTerm.NET for terminal emulation and Porta.Pty for pseudo-terminal support, it offers:

  • Full XTerm-compatible terminal emulation
  • Cross-platform support (Windows, Linux, macOS)
  • Scrollback buffer with configurable size
  • Text selection and clipboard support
  • Terminal window manipulation commands (resize, move, minimize, maximize, etc.)
  • Dynamic title updates from terminal escape sequences
  • Customizable fonts, colors, and styling
  • Graphics

Installation

Install via NuGet Package Manager:

dotnet add package Iciclecreek.Avalonia.Terminal

Or via the Package Manager Console in Visual Studio:

Install-Package Iciclecreek.Avalonia.Terminal

Compliance

Compliance

Usage

There are 2 Avalonia Controls:

  • TerminalControl - allows you to embed a terminal anywhere in xaml markup
  • TerminalWindow - a popup window with a terminal control in it.

TerminalControl

TerminalControl is a templated control that provides a terminal view with an integrated scrollbar. Use this when you want to embed a terminal within your own window or layout.

XAML:

<Window xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:terminal="using:Iciclecreek.Terminal"
        x:Class="MyApp.MainWindow">
    
    <terminal:TerminalControl x:Name="Terminal"
                              FontFamily="Cascadia Mono"
                              FontSize="14"
                              BufferSize="1000"
                              ProcessExited="OnProcessExited"/>
</Window>

Code-behind:

using Avalonia.Controls;
using Iciclecreek.Terminal;

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void OnProcessExited(object? sender, ProcessExitedEventArgs e)
    {
        // Handle process exit (e.g., close window)
        Close();
    }
}

Properties:

Property Type Default Description
Process string cmd.exe (Windows) / bash (Unix) The shell or process to launch
ProcessArgs IList<string> Empty Command-line arguments for the process
StartingDirectory string? Current working directory The initial working directory used when launching the PTY process
CurrentDirectory string? Read-only The current working directory reported by the running terminal session via OSC 7
ExitCode int Read-only The exit code of the launched process after it has terminated
Pid int Read-only The operating system process identifier of the launched terminal process
BufferSize int 1000 Scrollback buffer size (number of lines)
VerbatimCommandLine bool false When false, each entry in ProcessArgs reaches the process as a distinct argument, quoted so it arrives exactly as written. Set true to join the entries into one command line and let the process parse it itself. Windows only β€” Unix passes an argument vector to exec, so there is nothing to build
EnvironmentVariables IDictionary<string,string>? null Extra environment variables for the launched process. Merged into the environment it would otherwise inherit, not substituted for it, so setting one variable does not cost you PATH. TERM is already set to xterm-256color by the PTY layer
FontFamily FontFamily Monospace stack Terminal font family. Defaults to Cascadia Mono and friends, falling back to the platform's generic monospace β€” a terminal must not inherit a proportional UI font. Set it to override
FontSize double Inherited Terminal font size
Foreground IBrush White Default text color
Background IBrush Black Terminal background color
SelectionBrush IBrush Semi-transparent blue Text selection highlight color
TextDecorations TextDecorationLocation? null Text decorations applied to terminal text
Ligatures bool false Draw the font's programming ligatures (->, =>, != joining, in Fira Code, Cascadia Code and friends). Opt-in; fonts without ligatures are unaffected either way
CursorColor Color White Cursor color
CursorStyle CursorStyle Bar Cursor shape β€” Bar, Block or Underline
CursorBlink bool true Whether the cursor blinks
CursorBlinkRate int 530 Blink interval in milliseconds
ViewportY int Live Absolute line index of the top of the viewport. Settable, to drive your own scrollbar
MaxScrollback int Read-only Largest valid ViewportY β€” total buffer lines minus visible lines
ViewportLines int Read-only Number of lines visible
IsAlternateBuffer bool Read-only True while a full-screen application (vim, htop, less) is using the alternate screen buffer
IsLive bool Read-only True while a PTY is attached and its process has not exited

Methods:

Method Description
LaunchProcess() Launches the configured Process with the current ProcessArgs and StartingDirectory
LaunchProcess(string? startingDirectory, string process, params string[] args) Convenience overload that sets StartingDirectory, Process, and ProcessArgs, then launches the process
Kill() Terminates the running terminal process
SendInputAsync(string text, CancellationToken) Sends text to the running process as if typed. Sent verbatim, so append \r to submit a command: SendInputAsync("ls -la\r"). Does nothing when no process is running
CopyAsync() Copies the selection to the clipboard. Returns false when nothing was selected
PasteAsync() Pastes clipboard text into the terminal
AttachConnection(IPtyConnection) Drives the terminal from a PTY the caller owns. Throws if the template has not been applied
DetachConnection() Stops following the current connection and hands it back, without stopping the process
WaitForExit(int ms) Waits for the terminal process to exit or for the specified timeout to elapse

Events:

Event Description
ProcessExited Raised when the PTY process exits. The handler receives ProcessExitedEventArgs with the process ExitCode.

TerminalWindow

TerminalWindow is a complete window implementation that automatically handles terminal events like title changes and window manipulation commands. Use this when you want a standalone terminal window.

XAML:

<terminal:TerminalWindow xmlns="https://github.com/avaloniaui"
                         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                         xmlns:terminal="using:Iciclecreek.Terminal"
                         x:Class="MyApp.TerminalWindow"
                         Title="Terminal"
                         Width="800"
                         Height="600"
                         FontFamily="Consolas"
                         FontSize="12"
                         Background="Black"
                         Foreground="White"
                         CloseOnProcessExit="True"
                         UpdateTitleFromTerminal="True"/>

Or create programmatically:

using Iciclecreek.Terminal;

var terminalWindow = new TerminalWindow
{
    Title = "My Terminal",
    Width = 800,
    Height = 600,
    FontFamily = new FontFamily("Cascadia Mono"),
    FontSize = 14,
    StartingDirectory = Environment.CurrentDirectory,
    Process = "pwsh.exe",  // PowerShell Core
    ProcessArgs = new[] { "-NoLogo" },
    CloseOnProcessExit = true
};

terminalWindow.Show();

Methods:

Method Description
LaunchProcess() Launches the configured Process with the current ProcessArgs and StartingDirectory
LaunchProcess(string? startingDirectory, string process, params string[] args) Convenience overload that sets StartingDirectory, Process, and ProcessArgs, then launches the process
Kill() Terminates the running terminal process
SendInputAsync(string text, CancellationToken) Sends text to the running process as if typed. Sent verbatim, so append \r to submit a command: SendInputAsync("ls -la\r"). Does nothing when no process is running
CopyAsync() Copies the selection to the clipboard. Returns false when nothing was selected
PasteAsync() Pastes clipboard text into the terminal
AttachConnection(IPtyConnection) Drives the terminal from a PTY the caller owns. Throws if the template has not been applied
DetachConnection() Stops following the current connection and hands it back, without stopping the process
WaitForExit(int ms) Waits for the terminal process to exit or for the specified timeout to elapse

Additional Properties (beyond TerminalControl):

Property Type Default Description
ExitCode int Read-only The exit code of the launched process after it has terminated
Pid int Read-only The operating system process identifier of the launched terminal process
CloseOnProcessExit bool true Automatically close the window when the process exits
UpdateTitleFromTerminal bool true Update window title from terminal escape sequences

Window manipulation commands from the terminal (resize, move, minimize, maximize, raise, lower, fullscreen) are always handled. Handle the corresponding bubbling event from TerminalView and mark it Handled to suppress the default behaviour for any of them.

TerminalWindow exposes everything TerminalControl does, forwarding each member to the control it hosts, and TerminalControl in turn exposes everything TerminalView does. A test walks all three surfaces by reflection and fails if they drift apart, so a member added to the view without a forwarder breaks the build rather than going unnoticed.

Events:

Event Description
ProcessExited Raised when the PTY process exits. The handler receives ProcessExitedEventArgs with the process ExitCode. If CloseOnProcessExit is true, the event is raised before the window closes.

Render frame rate

Terminals repaint on a shared, throttled frame rather than on every chunk of output, so a window hosting several of them does one invalidation pass instead of one per terminal per chunk. The rate defaults to 30 FPS and is settable:

using Iciclecreek.Terminal;

TerminalRenderThrottle.TargetFrameRate = 60;   // valid range 1–1000

Raise it for a smoother repaint on a high-refresh display, or lower it to hand UI-thread time back to the rest of the app β€” a terminal streaming build output at 10 FPS is still perfectly readable and costs a third of the invalidations. Because the frame is shared, the setting is global rather than per-terminal: it applies to every terminal already open, from the next frame scheduled. Safe to set from any thread.

Graphics

Two image protocols are decoded by XTerm.NET and drawn by the terminal β€” Sixel (ESC P … q … ESC \) and Kitty (ESC _ G … ESC \). img2sixel, chafa, lsix, timg, icat, yazi and image.nvim work with no configuration. An image behaves like terminal content rather than an overlay: typing over it replaces that part of the picture, clear removes it, and it scrolls with the text around it and is freed when it falls out of the scrollback.

Animated Kitty images play on their own. The terminal runs a clock only while something is actually animating, advances by elapsed time rather than one frame per tick β€” so a late repaint stutters an animation rather than slowing it down β€” and re-uploads a picture's texture only when its frame actually changes.

Two things are handled for you and are worth knowing about:

  • The cell size measured from your font is published to the emulator and is the answer given to a CSI 16 t query. That is what applications use to size their images, so a picture lines up with the grid it was drawn for β€” including after a font change or a move to a display with different scaling.
  • A picture is uploaded once and re-blitted on every frame after that, not re-decoded; the upload is released when the terminal drops the picture. Wherever Avalonia runs on Skia, the pictures in front of the text are drawn by a small picture layer from GPU textures made once per picture, so a screen full of pictures costs draws, not bandwidth. Text is always drawn through DrawingContext.

Sixel is drawn as one image per line, however many Sixels landed on it. XTerm.NET keeps the pixels each line shows rather than every picture that painted them, so a program that repaints small regions over and over β€” a cursor, a hover, a diffing framebuffer such as SixelBuffer β€” costs at most one draw per row. The strip is drawn over the text and under any Kitty picture in front of it, and is uploaded again only when its pixels change.

A Kitty picture is held as a run per line. Each run carries the source rectangle and the columns it covers; the picture layer gathers the rows of one placement and draws the picture once, clipped to them. On a backend with no raster surface β€” Consolonia, for instance β€” images are skipped and the text still renders.

Pictures may overlap, and a row is drawn one run at a time from the back forwards, with the text going down between the ones behind it and the ones in front. That ordering is the whole of the compositing: a translucent picture blends over whatever was drawn under it because it is drawn after it. A cell's own background is painted by the bottom-most picture covering it and by no other, since painting it again nearer the front would erase what is behind rather than let it show through.

A run keeps its natural width even when the window is too narrow to show it, so the renderer draws as much as fits and narrows the source by the same proportion. Narrowing the window shows less of a picture and widening it shows more; nothing is destroyed in between.

Either protocol can be turned off. Switching Sixel off also stops the terminal advertising it, so applications send text instead of pictures rather than pictures that get dropped:

terminalControl.Terminal.Options.SixelEnabled = false;
terminalControl.Terminal.Options.KittyGraphicsEnabled = false;

Kitty's file, temp-file and shared-memory transmission media (t=f, t=t, t=s) are deliberately refused β€” they would have the terminal open a path chosen by the process it is hosting. Clients are told so and fall back to sending the pixels directly.

Kitty

Links

Authors

License

This project is licensed under the MIT License - see the LICENSE file for details.

Downloads by Version

Depended On By

Find packages that declare this package as a dependency. NuGet does not provide a reverse-dependency endpoint, so candidates are checked on demand.

Adoption Assessment

Assess this package against its supported frameworks, license, maintenance signals and dependency graph before making it a project standard.

Compatibility metadata lists 1 target framework; confirm the exact target used by your application.

Related Guides & Workflows

Learn how to evaluate, integrate, or audit this package in your .NET project.