Skip to content

Embedding In Avalonia

RoyalTerminal is easiest to understand from the top down: drop TerminalControl into an Avalonia view, start a session, and only then decide whether you need custom settings UI, capture tooling, or GPU interop. That is also how the public API is structured. The default path is small, and the lower layers only come into view when you need to customize behavior.

The default host story

Most applications only need RoyalApps.RoyalTerminal.Avalonia, a TerminalControl, and a session start call:

xml
<Window
    xmlns="https://github.com/avaloniaui"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:rt="clr-namespace:RoyalTerminal.Avalonia.Controls;assembly=RoyalTerminal.Avalonia">
  <rt:TerminalControl
      x:Name="Terminal"
      Columns="120"
      Rows="36"
      ScrollbackLimit="10000"
      TerminalFontSize="14" />
</Window>
csharp
PtyTransportOptions options = new(
    Command: null,
    WorkingDirectory: Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
    Environment: null,
    Dimensions: new TerminalSessionDimensions(120, 36, 1200, 800));

await Terminal.StartSessionAsync(options);

That one control already owns the default session service, the active VT processor, the terminal renderer, selection and scrolling behavior, and the mode-aware input path.

Preserving session history

Session history is clean by default: starting a new session clears the previous buffer. Hosts that keep the same control alive across reconnects can opt in to preserving scrollback:

csharp
Terminal.PreserveScrollbackOnSessionStart = true;
await Terminal.StartSessionAsync(options);

For per-session control, pass the explicit overload:

csharp
await Terminal.StartSessionAsync(options, preserveScrollback: true);

Use ClearScrollback() when the host wants to discard history while keeping the active viewport and session alive:

csharp
Terminal.ClearScrollback();

See Session History And Scrollback for the host API and Session Restart Semantics for the managed VT, Ghostty VT, xterm.js, Windows Terminal, and RoyalTerminal state-reset comparison.

What the control actually owns

TerminalControl is the public host boundary. It is where Avalonia-facing configuration lives: font family, font size, grid size, scrollback size, colors, active theme, regex text highlighting, framebuffer shaders, and VtProcessorPreference. It also exposes the events most hosts care about, such as DataReceived, TitleChanged, Bell, ProcessExited, CloseRequested, and TerminalResized.

The rendering tree around the control is intentionally exposed instead of hidden:

TypeWhy it exists
TerminalControlMain terminal host control and session entry point.
TerminalPresenterThe rendering host inside the control template.
TerminalScrollDataShared scroll extent and viewport state.
VirtualizedTerminalScrollViewerILogicalScrollable implementation for long terminal histories.
TerminalDrawHandlerComposition visual handler for the default Skia render path.
TerminalDrawHandler.UpdateMessageSwaps the active renderer and screen state source.
TerminalDrawHandler.InvalidateMessageRequests a fresh frame.
TerminalDrawHandler.ResizeMessageForces redraw after size changes.
TerminalDataEventArgsCarries terminal output bytes to the host.
TerminalSizeEventArgsCarries resize notifications to the host.

This is an important difference between RoyalTerminal and a monolithic widget: the control is high level, but it still exposes the moving pieces you need if you are building a specialized host.

Applying framebuffer shaders

TerminalControl can apply one or more post-process shaders to the completed terminal frame:

csharp
using RoyalTerminal.Shaders;

Terminal.ShaderSources =
[
    new TerminalShaderSource(
        "Scanline",
        shaderSource,
        TerminalShaderLanguage.SkiaRuntimeEffect,
        requiresContinuousAnimation: true)
];

Use ShaderAnimationEnabled to control whether animated shaders keep rendering between terminal output events. It defaults to true.

RoyalTerminal supports direct Skia Runtime Effect source plus compatibility adapters for Ghostty/Shadertoy-style mainImage GLSL and common Windows Terminal-style HLSL pixel shaders. See Shader Support and Applying Shaders for the complete API and compatibility matrix.

Applying regex text highlights

TerminalControl.TextHighlightRules applies ordered regex rules to rendered terminal rows. The feature is useful for log levels, hostnames, IP addresses, ticket ids, prompts, and other row-local tokens that should stand out without changing terminal output itself.

csharp
using RoyalTerminal.Avalonia.Rendering;
using RoyalTerminal.Terminal;

Terminal.TextHighlightingMode = TerminalTextHighlightingMode.Static;
Terminal.TextHighlightRules =
[
    new TerminalTextHighlightRule
    {
        Name = "Warnings",
        Pattern = @"\b(WARN|WARNING)\b",
        Foreground = 0xFFFFF3C4,
        Background = 0xFF78350F,
    },
];

Foreground and background are optional independently. Unset colors preserve the cell's current foreground or background. See Regex Text Highlighting for mode selection, persisted profile settings, dark-theme overrides, regex behavior, and performance notes.

Input, selection, paste, and shortcuts

The control routes all user input through public service abstractions rather than hard-wiring it in code-behind. That is what lets the demo, tests, and custom hosts all exercise the same input pipeline.

Input and viewport services

TypePurpose
ITerminalInputAdapterConverts Avalonia key, text, pointer, and wheel input into terminal protocol sequences.
DefaultTerminalInputAdapterDefault implementation used by TerminalControl.
ITerminalScrollServiceScroll contract for viewport movement and bottom-follow behavior.
DefaultTerminalScrollServiceDefault viewport scroll implementation.
ITerminalSelectionServiceSelection, clipboard export, and paste contract.
DefaultTerminalSelectionServiceDefault selection and clipboard implementation.

Paste safety

RoyalTerminal treats paste as a terminal concern rather than a plain text-box concern. The paste safety surface exists so hosts can choose whether multiline content or control characters should flow straight through, be sanitized, or require confirmation.

TypePurpose
TerminalPasteSafetyPolicyHost policy: allow, confirm, sanitize, or block.
TerminalPasteRiskRisk flags detected in the paste payload.
TerminalPasteSafetyDecisionResult returned by the host when confirmation is required.
TerminalPasteContextPaste payload plus the detected risk flags.
TerminalUnsafePasteHandlerAsync callback for confirmation flows.
TerminalPasteRequestFull paste request: bracketed paste plus safety behavior.

Shortcut dispatch

RoyalTerminal keeps common clipboard-style shortcuts configurable instead of hard-coded.

TypePurpose
TerminalShortcutGestureOne key and modifier combination.
TerminalShortcutConfigurationCopy, paste, cut, and select-all gesture sets.
TerminalShortcutDispatcherStatic helper that dispatches common terminal shortcuts using a configuration.

Capture and replay in the UI layer

Capture and replay are designed like Ghostty's higher-level feature docs: the public surface is small, but it sits on top of a more detailed runtime. In Avalonia, the entry point is TerminalCaptureRuntime. It subscribes to TerminalControl events and the session service, records output, input, resizes, and process exits, and can play the session back into the same control on a timeline.

If you are building a debugging, support, or teaching workflow, this is the feature to start with. The recorder types are covered in Sessions, Profiles, And Settings, and the RoyalTerminal JSON/asciicast v3 persistence layer is covered in Capture Formats.

Adding a settings surface

The RoyalApps.RoyalTerminal.Avalonia.Settings package is not just a demo convenience. It is the reusable configuration UI for editing the same session document model used by the runtime.

Settings controls

TypePurpose
TerminalSettingsPanelRoot settings host control.
TerminalSettingsSessionPanelSession identity and transport selection UI.
TerminalSettingsConnectionPanelConnection and endpoint details UI.
TerminalSettingsTerminalPanelTerminal behavior UI.
TerminalSettingsAppearancePanelFont, scroll, opacity, and regex text highlighting UI.
TerminalSettingsSshPanelSSH auth, proxy, forwarding, and X11 UI.
TerminalSettingsLoggingPanelSession and event logging UI.

Settings state

The state model is intentionally explicit: one owner object plus typed slices per category.

TypePurpose
TerminalSettingsCategoryStateBaseObservable base type for a category slice.
TerminalSettingsPanelStateCentral state owner for profile CRUD, property editing, and dirty tracking.
TerminalSettingsProfileItemLightweight profile list item.
TerminalSettingsTransportModeOptionLightweight transport picker option.
TerminalSettingsSshAuthModeOptionSSH auth mode option with stable built-in ids.
TerminalSettingsSessionStateSession name and transport mode slice.
TerminalSettingsConnectionStateWorking directory, shell, raw TCP, Telnet, serial, and base SSH endpoint slice.
TerminalSettingsTerminalBehaviorStateCopy, bell, shaping, ligature, paste, and terminal-type slice.
TerminalSettingsAppearanceStateFont, opacity, and regex text highlighting appearance slice.
TerminalSettingsSshStateSSH auth, trust, proxy, forwarding, and timeout slice.
TerminalSettingsLoggingStateLog file, log format, flush, and event-log slice.

The important idea is that the settings package edits durable session documents instead of directly mutating transports or controls. That keeps the UI reusable and testable.

When to step into Avalonia GPU interop

Most hosts should stay on the default Skia path. The Avalonia Ghostty interop package exists for applications that explicitly want Ghostty renderer surfaces and need to bridge Avalonia's graphics context into that renderer.

High-level interop types

TypePurpose
AvaloniaRenderBackendPreferenceBackend selection policy for GPU interop.
IAvaloniaSkiaRenderTargetProviderCreates render requests from Avalonia Skia leases.
AvaloniaSkiaRenderTargetProviderDefault backend and handle resolver with CPU fallback diagnostics.
TerminalTextureInteropDrawHandlerComposition visual handler for Ghostty renderer interop.
TerminalTextureInteropDrawHandler.UpdateMessageSwaps renderer, provider, and optional overlay renderer/screen.
TerminalTextureInteropDrawHandler.ResizeMessageUpdates only the target size.
TerminalTextureInteropDrawHandler.InvalidateMessageRequests another render pass.

Platform handle providers

The package separates handle acquisition by backend so hosts can replace only the pieces they need:

BackendContractDefaultNull/fallback
D3D11IAvaloniaD3D11TextureHandleProviderDefaultAvaloniaD3D11TextureHandleProviderNullAvaloniaD3D11TextureHandleProvider
D3D12IAvaloniaD3D12TextureHandleProviderDefaultAvaloniaD3D12TextureHandleProviderNullAvaloniaD3D12TextureHandleProvider
MetalIAvaloniaMetalTextureHandleProviderDefaultAvaloniaMetalTextureHandleProviderNullAvaloniaMetalTextureHandleProvider
VulkanIAvaloniaVulkanTextureHandleProviderDefaultAvaloniaVulkanTextureHandleProviderNullAvaloniaVulkanTextureHandleProvider
OpenGLIAvaloniaOpenGlRenderTargetHandleProviderDefaultAvaloniaOpenGlRenderTargetHandleProviderNullAvaloniaOpenGlRenderTargetHandleProvider

Choosing the right level

If you are embedding RoyalTerminal into an Avalonia application, the normal progression is:

  1. Start with TerminalControl.
  2. Add TerminalCaptureRuntime if you need capture or replay.
  3. Add RoyalApps.RoyalTerminal.Avalonia.Settings if your application edits saved session documents.
  4. Add RoyalApps.RoyalTerminal.Avalonia.Rendering.GhosttyInterop only when your host architecture specifically needs Ghostty renderer interop.

MIT Licensed