RoyalApps.RoyalTerminal.GhosttySharp.Native.Win64
0.1.25
Prefix Reserved
dotnet add package RoyalApps.RoyalTerminal.GhosttySharp.Native.Win64 --version 0.1.25
NuGet\Install-Package RoyalApps.RoyalTerminal.GhosttySharp.Native.Win64 -Version 0.1.25
<PackageReference Include="RoyalApps.RoyalTerminal.GhosttySharp.Native.Win64" Version="0.1.25" />
<PackageVersion Include="RoyalApps.RoyalTerminal.GhosttySharp.Native.Win64" Version="0.1.25" />
<PackageReference Include="RoyalApps.RoyalTerminal.GhosttySharp.Native.Win64" />
paket add RoyalApps.RoyalTerminal.GhosttySharp.Native.Win64 --version 0.1.25
#r "nuget: RoyalApps.RoyalTerminal.GhosttySharp.Native.Win64, 0.1.25"
#:package RoyalApps.RoyalTerminal.GhosttySharp.Native.Win64@0.1.25
#addin nuget:?package=RoyalApps.RoyalTerminal.GhosttySharp.Native.Win64&version=0.1.25
#tool nuget:?package=RoyalApps.RoyalTerminal.GhosttySharp.Native.Win64&version=0.1.25
RoyalTerminal
<p align="center"> <img src="assets/royalterminal-logo-light.svg#gh-light-mode-only" alt="RoyalTerminal logo" width="128" height="128"> <img src="assets/royalterminal-logo-dark.svg#gh-dark-mode-only" alt="RoyalTerminal logo" width="128" height="128"> </p>
<img width="2690" height="1840" alt="image" src="https://github.com/user-attachments/assets/5ac2f18a-6461-495a-9344-382e25f82c2a" />
High-performance .NET 10 terminal stack with a backend-neutral Avalonia core (RoyalTerminal.Avalonia), framebuffer shader support, official native Ghostty VT integration (libghostty-vt), and a separate fully managed VT implementation.
Documentation
Read the published documentation at royalapplications.github.io/RoyalTerminal. Source lives in docs/ and is published through the VitePress/GitHub Pages workflow in .github/workflows/docs.yml.
NuGet Packages
Primary Packages
Modular Managed Packages (Packable Composition Units)
| Package | Responsibility |
|---|---|
RoyalApps.RoyalTerminal.Avalonia.Settings |
Reusable Avalonia settings panel controls and state for session, connection, terminal, appearance, SSH, logging, and highlighting configuration |
RoyalApps.RoyalTerminal.Terminal |
Core terminal contracts (ITerminalEndpoint, ITerminalInputSink, ITerminalSelectionSource, ITerminalModeSource), SSH bootstrap helpers, and screen model |
RoyalApps.RoyalTerminal.Unicode |
Deterministic Unicode data and terminal width helpers |
RoyalApps.RoyalTerminal.Sixel |
Reusable managed sixel decoder and image payload model |
RoyalApps.RoyalTerminal.Terminal.Vt.Managed |
Managed VT processor (BasicVtProcessor) |
RoyalApps.RoyalTerminal.Terminal.Vt.Ghostty |
Native VT processor (GhosttyVtProcessor over official libghostty-vt terminal/render APIs) + GhosttyVtProcessorProvider |
RoyalApps.RoyalTerminal.Terminal.Vt.Default |
Preference-based VT processor factory (VtProcessorPreference) |
RoyalApps.RoyalTerminal.Terminal.Pty.Unix |
Unix PTY implementation (forkpty) |
RoyalApps.RoyalTerminal.Terminal.Pty.Windows |
Windows PTY implementation (ConPTY) |
RoyalApps.RoyalTerminal.Terminal.Pty.Platform |
Platform PTY factory (DefaultPtyFactory) |
RoyalApps.RoyalTerminal.Terminal.Transport.Pty |
PTY transport provider and wrapper (PtyTerminalTransportProvider) |
RoyalApps.RoyalTerminal.Terminal.Transport.Pipe |
Process pipe transport provider (PipeTerminalTransportProvider) |
RoyalApps.RoyalTerminal.Terminal.Transport.Raw |
Raw TCP terminal transport provider |
RoyalApps.RoyalTerminal.Terminal.Transport.Telnet |
Telnet terminal transport provider with option negotiation |
RoyalApps.RoyalTerminal.Terminal.Transport.Serial |
Serial line terminal transport provider |
RoyalApps.RoyalTerminal.Terminal.Transport.Ssh.Abstractions |
SSH host-key validation contracts |
RoyalApps.RoyalTerminal.Terminal.Transport.Ssh.SshNet |
SSH transport provider (SshNetTerminalTransportProvider) |
RoyalApps.RoyalTerminal.Terminal.Transport.Ssh.SshNet.Agent |
Optional SSH agent auth contributor for SSH.NET |
RoyalApps.RoyalTerminal.Terminal.Services.Contracts |
Terminal session service contracts |
RoyalApps.RoyalTerminal.Terminal.Services |
Terminal session service implementations |
RoyalApps.RoyalTerminal.Rendering.Text |
Reusable text shaping/fallback subsystem (HarfBuzzTextShaper, TerminalFontResolver) |
RoyalApps.RoyalTerminal.Shaders |
Dependency-free shader source models and compatibility translation for Skia Runtime Effect terminal shaders |
RoyalApps.RoyalTerminal.Rendering.Skia |
CPU cell renderer core (SkiaTerminalRenderer, GlyphCache) with HarfBuzz shaping, fallback font resolution, and framebuffer shader post-processing |
RoyalApps.RoyalTerminal.Rendering.Contracts |
Backend-agnostic render contracts (RenderTargetDescriptor, capabilities) |
RoyalApps.RoyalTerminal.Rendering.Interop.Ghostty |
Managed wrapper for ghostty-renderer-capi |
RoyalApps.RoyalTerminal.Rendering.Interop.Ghostty.Skia |
Skia bridge (SkiaInteropRenderer) with CPU fallback |
RoyalApps.RoyalTerminal.Avalonia.Rendering.GhosttyInterop |
Avalonia render-target acquisition and texture interop draw handler |
Features
- Core/native VT split:
RoyalApps.RoyalTerminal.Avalonia: backend-neutral control and services.RoyalApps.RoyalTerminal.Terminal.Vt.Ghostty: official native VT integration over upstreamlibghostty-vt.
- Backend-neutral endpoint contracts (
ITerminalEndpoint,ITerminalInputSink,ITerminalSelectionSource,ITerminalModeSource) for control reuse across backends. - Pluggable transport runtime (
ITerminalTransportFactory) supporting PTY, process pipe, SSH, raw TCP, Telnet, and serial sessions. - Shared SSH bootstrap helper (
SshShellBootstrapCommandBuilder) for consistent POSIXexportcommand composition across SSH backends. - Pluggable SSH secret persistence via
ISshSecretStore+ISshSecretProtectorwith cross-platform secure defaults (SshSecretProtectionFactory). - Session profiles + persistent settings model via
TerminalSessionProfile*contracts,TerminalSessionProfileSerializer, andJsonFileTerminalSessionProfileStore. - Regex text highlighting with one or more user-configurable rules, static cached or realtime matching, optional foreground/background colors, dark-theme overrides, and persisted settings/profile support.
- Thread-safe output ingestion:
TerminalControl.WriteOutput(...)can be called from background SSH/network callbacks (marshaled to UI thread internally). - Preference-based VT selection via
VtProcessorPreference(Auto,Managed,Native). - Three integration modes with explicit trade-offs between fidelity, portability, and native dependencies.
- Split rendering architecture:
- CPU cell rendering path (
RoyalTerminal.Rendering.Skia) - Managed framebuffer shader pipeline with direct Skia Runtime Effect source plus Ghostty/Shadertoy and Windows Terminal HLSL compatibility adapters
- GPU interop path (
RoyalApps.RoyalTerminal.Rendering.*+ghostty-renderer-capi)
- CPU cell rendering path (
- Official native VT engine via
libghostty-vtterminal/render-state APIs on all supported platforms. - Modular PTY and VT packages (
Terminal.Pty.*,Terminal.Vt.*). - HarfBuzz-backed text shaping with grid-safe fallback behavior and optional diagnostics counters.
- Grapheme-aware cell model in managed VT and official native VT render-state paths.
- Terminal session service split (
Terminal.Services.ContractsandTerminal.Services). - Sample applications:
- Avalonia demo (
samples/RoyalTerminal.Demo) with structured settings categories (Session/Connection/Terminal/Appearance/SSH/Logging), transport forms (PTY/Pipe/Raw TCP/Telnet/Serial/SSH), a tabbed Settings flyout with profile CRUD (new/duplicate/delete/set default) and explicit apply/save, session/event logging, shader samples, and terminal behavior toggles (copy-on-select, bell notifications, backspace mode, paste safety, text shaping/ligatures) - Windows Forms host sample (
samples/RoyalTerminal.WinFormsHost) showingAvalonia.Win32.Interoperability, PerMonitorV2 DPI, Win32 focus forwarding, andTerminalControl.Paddingfor embedded margins - macOS SwiftUI native tabbed demo (
samples/RoyalTerminal.MacNativeTabbed) that hosts GhosttyKit directly as a separate native sample, outside the managedRoyalTerminal.GhosttySharpsurface - VT/PTy control catalog CLI (
samples/RoyalTerminal.ControlCatalog) with managed/Ghostty VT probes, ncurses/TUI parity scenarios, and rich visual rendering galleries
- Avalonia demo (
Regex Text Highlighting
TerminalControl can apply ordered regex highlight rules to rendered terminal rows. Each rule can set foreground color, background color, both, or neither color independently. When a color is unset, matching cells keep the color already provided by the terminal application.
Runtime API:
using RoyalTerminal.Avalonia.Controls;
using RoyalTerminal.Avalonia.Rendering;
using RoyalTerminal.Terminal;
TerminalControl terminal = new()
{
TextHighlightingMode = TerminalTextHighlightingMode.Static,
TextHighlightRules =
[
new TerminalTextHighlightRule
{
Name = "Errors",
Pattern = @"\b(ERROR|FAIL|FATAL)\b",
Foreground = 0xFFFFE6E6,
Background = 0xFF7F1D1D,
DarkForeground = 0xFFFFB4B4,
DarkBackground = 0xFF450A0A,
},
new TerminalTextHighlightRule
{
Name = "IPv4 addresses",
Pattern = @"\b(?:25[0-5]|2[0-4]\d|1?\d?\d)(?:\.(?:25[0-5]|2[0-4]\d|1?\d?\d)){3}\b",
Foreground = 0xFF93C5FD,
},
],
};
Modes:
| Mode | Behavior |
|---|---|
Static |
Caches matched cells for unchanged row text, rule revision, and theme state. This is the default for normal terminal use. |
Realtime |
Recomputes matching rows whenever they are rendered. |
Disabled |
Keeps configured rules but skips regex matching and rendering overrides. |
Persisted profiles store the feature on TerminalSessionAppearanceSettings.TextHighlightingMode and TerminalSessionAppearanceSettings.TextHighlightRules. The reusable settings panel exposes it from the Appearance tab under Text Highlighting, including rule add/remove, enable/disable, mode selection, foreground/background checkboxes, and dark-theme color overrides.
The main foreground/background checkboxes control whether a rule changes that color. Dark foreground/background checkboxes are theme-specific overrides; when they are unchecked, the normal color is reused for dark themes.
See Regex Text Highlighting for the full profile JSON format, settings API, renderer behavior, performance details, and limitations.
Transport Session Model
TerminalControl now runs through a transport abstraction, not a PTY-only runtime:
TerminalControl.StartSessionAsync(ITerminalTransportOptions)selects a transport.ITerminalTransportFactoryresolves a provider byTransportId.- The chosen
ITerminalTransport(pty,pipe, orssh) owns I/O and lifecycle. TerminalSessionServiceremains the coordination point for endpoint/input/selection/mode contracts.
Supported transport option models:
| Transport | Options Type | Notes |
|---|---|---|
| PTY | PtyTransportOptions |
Interactive local shell semantics (ConPTY/forkpty) |
| Pipe | PipeTransportOptions |
Non-PTY process streams, useful for command/log scenarios |
| SSH | SshTransportOptions |
Remote terminal sessions with optional PTY request and host-key checks (OpenSSH known_hosts and optional SHA-256 pinning) |
| Raw TCP | RawTcpTransportOptions |
Unframed TCP byte stream sessions |
| Telnet | TelnetTransportOptions |
Telnet remote sessions with option negotiation handling |
| Serial | SerialTransportOptions |
Direct serial line sessions (baud/parity/stop bits/handshake) |
Terminal Capture and Replay
Capture/replay is available for TerminalControl and is designed to be reusable outside the demo app.
- Captured timeline events:
- terminal output bytes
- terminal input bytes sent through session routing
- terminal resize events
- process exit status events
- marker events when loaded from formats that support them
- Persistence:
- native RoyalTerminal JSON via
TerminalCaptureSessionSerializer - asciicast v3 via
TerminalCaptureSessionFormats.AsciicastV3 - pluggable formats via
ITerminalCaptureSessionFormatandTerminalCaptureSessionFormatRegistry - recommended extensions:
.rtcap.jsonand.cast
- native RoyalTerminal JSON via
- Replay controls:
- play, pause, stop, and seek by timeline position
- replay surface reset to captured initial dimensions on load, stop, and reset seeks
- paused or static replay frames rebuild at the current control size after host resize
Demo Integration (samples/RoyalTerminal.Demo)
The demo toolbar includes:
Start Capture/Stop Capture- capture format selector (
RoyalTerminal JSONorAsciicast v3) Save Capture(writes the capture session using the selected format)Load Replay(opens RoyalTerminal JSON or asciicast v3 capture files in a replay tab)Settings(opens tabbed session/profile editor with explicitApplyandSave)
When replay is active, the replay timeline bar is shown with play/pause, stop, slider seek, elapsed/total display, and source label.
Reusable API Surface
RoyalTerminal.Avalonia.Capture.TerminalCaptureRuntime- runtime orchestration for capture + replay against a
TerminalControl
- runtime orchestration for capture + replay against a
RoyalTerminal.Terminal.TerminalCaptureRecorder- event recorder with snapshot/finalize semantics
RoyalTerminal.Terminal.TerminalCaptureSession- serializable capture payload (metadata + ordered events)
RoyalTerminal.Terminal.TerminalCaptureSessionSerializer- stream/file load + save helpers for native JSON plus explicit-format overloads
RoyalTerminal.Terminal.ITerminalCaptureSessionFormat- pluggable recording format contract
RoyalTerminal.Terminal.TerminalCaptureSessionFormatRegistry- format lookup, save by id, and load probing
RoyalTerminal.Terminal.TerminalCaptureSessionFormats- built-in RoyalTerminal JSON and asciicast v3 format instances
using RoyalTerminal.Avalonia.Capture;
using RoyalTerminal.Avalonia.Controls;
using RoyalTerminal.Terminal;
var terminal = new TerminalControl();
var captureRuntime = new TerminalCaptureRuntime(terminal);
captureRuntime.StartCapture();
terminal.SendInput("ls\r");
terminal.WriteOutput("file1\nfile2\n"u8);
TerminalCaptureSession captured = captureRuntime.StopCapture();
await TerminalCaptureSessionSerializer.SaveToFileAsync(captured, "session.rtcap.json");
await TerminalCaptureSessionSerializer.SaveToFileAsync(
captured,
"session.cast",
TerminalCaptureSessionFormats.AsciicastV3);
TerminalCaptureSession loaded =
await TerminalCaptureSessionSerializer.LoadFromFileAsync("session.rtcap.json");
await using FileStream asciicast = File.OpenRead("session.cast");
TerminalCaptureSession loadedAsciicast =
await TerminalCaptureSessionFormats.DefaultRegistry.LoadAsync(asciicast, "session.cast");
captureRuntime.LoadReplay(loaded, "session.rtcap.json");
captureRuntime.PlayReplay();
captureRuntime.SeekReplay(2.5);
captureRuntime.PauseReplay();
captureRuntime.StopReplay();
Integration Modes
| Mode | Control | Package Set | VT Engine | Renderer | PTY | Platform | Best For |
|---|---|---|---|---|---|---|---|
| Native VT | TerminalControl |
RoyalApps.RoyalTerminal.Avalonia + RoyalApps.RoyalTerminal.Terminal.Vt.Ghostty + native assets |
official libghostty-vt terminal/render-state APIs |
Skia cell renderer | Unix PTY / ConPTY | macOS/Linux/Windows | Cross-platform native VT parser on the upstream Ghostty C API |
| Managed VT | TerminalControl |
RoyalApps.RoyalTerminal.Avalonia |
BasicVtProcessor (C#) |
Skia cell renderer | Unix PTY / ConPTY | macOS/Linux/Windows | Explicit managed VT path |
| Rendered (Auto VT) | TerminalControl |
RoyalApps.RoyalTerminal.Avalonia (+ optional native VT provider packages) |
Auto (libghostty-vt when available, otherwise BasicVtProcessor) |
Skia cell renderer | Unix PTY / ConPTY | macOS/Linux/Windows | Default backend-neutral mode |
Mode Availability and Fallback Policy (Demo)
The demo exposes only cross-platform modes. When native VT is unavailable, mode routing falls back deterministically to the next supported mode.
Resolver cycle order:
Native VT -> Managed VT -> Rendered (Auto VT)
Fallback chains when requested mode is unavailable:
| Requested Mode | Fallback Chain (next supported mode in resolver order) |
|---|---|
Native VT |
Managed VT -> Rendered (Auto VT) |
Managed VT |
Rendered (Auto VT) |
Rendered (Auto VT) |
Always supported (no fallback required) |
Mode cycle in the demo also skips unsupported modes and always lands on a runnable mode.
Demo Tab Mode Indicators
The Avalonia demo uses a glyph + color marker in each tab header:
| Mode | Marker | Color |
|---|---|---|
Native VT |
■ |
Green (#6AB04C) |
Managed VT |
▲ |
Teal (#4EC9B0) |
Rendered (Auto VT) |
▼ |
Olive (#6A9955) |
TerminalControl VT Preference Modes
| Demo Mode Label | VtProcessorPreference |
Behavior |
|---|---|---|
| Native VT | Native |
Requires the official native Ghostty VT provider (libghostty-vt), throws when unavailable |
| Managed VT | Managed |
Forces BasicVtProcessor for deterministic pure-managed behavior |
| Rendered (Auto VT) | Auto |
Uses official native VT when available and falls back to managed VT otherwise |
Architecture
flowchart TD
App["Application / Avalonia Host"]
subgraph CoreUI["RoyalTerminal.Avalonia (Core)"]
C1["TerminalControl"]
C2["TerminalDrawHandler / TerminalPresenter"]
end
subgraph Terminal["Terminal Modules"]
T0["RoyalTerminal.Terminal"]
T1["Terminal.Vt.Managed"]
T2["Terminal.Vt.Ghostty"]
T3["Terminal.Vt.Default"]
P1["Terminal.Pty.Unix"]
P2["Terminal.Pty.Windows"]
P3["Terminal.Pty.Platform"]
P4["Terminal.Transport.Pty"]
P5["Terminal.Transport.Pipe"]
P6["Terminal.Transport.Ssh.Abstractions"]
P7["Terminal.Transport.Ssh.SshNet"]
S1["Terminal.Services.Contracts"]
S2["Terminal.Services"]
end
subgraph CoreRender["Rendering Core"]
R4["Rendering.Skia"]
end
subgraph GhosttyRender["Ghostty Rendering Interop (Optional)"]
R0["Rendering.Contracts"]
R1["Rendering.Interop.Ghostty"]
R2["Rendering.Interop.Ghostty.Skia"]
R3["Avalonia.Rendering.GhosttyInterop"]
end
subgraph Native["Native Libraries"]
N1["libghostty-vt"]
N2["libghostty-renderer-capi"]
end
App --> CoreUI
CoreUI --> Terminal
CoreUI --> CoreRender
Terminal --> Native
GhosttyRender --> Native
Usage
1. Backend-Neutral Cross-Platform Control (RoyalTerminal.Avalonia)
using RoyalTerminal.Avalonia.Controls;
using RoyalTerminal.Terminal;
var terminal = new TerminalControl
{
FontFamilyName = "JetBrains Mono",
TerminalFontSize = 14,
Columns = 120,
Rows = 40,
ScrollbackLimit = 10_000,
VtProcessorPreference = VtProcessorPreference.Auto,
};
terminal.StartPty(
workingDirectory: Environment.GetFolderPath(Environment.SpecialFolder.UserProfile));
StartPty(...) remains supported for backwards compatibility. The preferred session API is StartSessionAsync(...) with transport options.
1a. Unified Session Start (StartSessionAsync)
using RoyalTerminal.Avalonia.Controls;
using RoyalTerminal.Terminal;
var terminal = new TerminalControl();
ITerminalTransportOptions options = new PtyTransportOptions(
Command: null,
WorkingDirectory: Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
Environment: null,
Dimensions: new TerminalSessionDimensions(120, 40, 1200, 800));
await terminal.StartSessionAsync(options);
Preserve Session Scrollback
New sessions start with a clean buffer by default. Reconnect flows can keep the previous session history in the same control:
terminal.PreserveScrollbackOnSessionStart = true;
await terminal.StartSessionAsync(options);
await terminal.StartSessionAsync(nextOptions, preserveScrollback: true);
terminal.ClearScrollback();
ClearScrollback() removes history while keeping the active viewport intact. See the Session History And Scrollback guide for managed VT, Ghostty VT, and reference-terminal behavior details.
1b. Start a Pipe Session (PipeTransportOptions)
using RoyalTerminal.Avalonia.Controls;
using RoyalTerminal.Terminal;
var terminal = new TerminalControl();
await terminal.StartPipeAsync(
new PipeTransportOptions(
Command: new TerminalCommandSpec(
FileName: "/bin/sh",
Arguments: new[] { "-lc", "dotnet --info" }),
WorkingDirectory: null,
Environment: null,
MergeStdErrIntoStdOut: true,
Dimensions: new TerminalSessionDimensions(120, 40, 1200, 800)));
1c. Start an SSH Session (SshTransportOptions)
using System;
using System.Collections.Generic;
using RoyalTerminal.Avalonia.Controls;
using RoyalTerminal.Terminal;
var terminal = new TerminalControl();
SshTransportOptions options = new(
Endpoint: new SshEndpointOptions("example.com", 22, "alice"),
RequestPty: true,
TerminalType: "xterm-256color",
InitialCommand: null,
Authentication: new SshAuthenticationOptions(
UsePassword: true,
PasswordSecretId: "demo-password",
PrivateKeySecretIds: Array.Empty<string>(),
UseAgent: false),
Dimensions: new TerminalSessionDimensions(120, 40, 1200, 800))
{
ExpectedHostKeyFingerprintSha256 = "SHA256:BASE64_FINGERPRINT",
EnvironmentVariables = new Dictionary<string, string>(StringComparer.Ordinal)
{
["LANG"] = "en_US.UTF-8",
["LC_CTYPE"] = "en_US.UTF-8",
["TERM"] = "xterm-256color",
},
};
await terminal.StartSshAsync(options);
ExpectedHostKeyFingerprintSha256 is optional. When omitted, host-key trust falls back to KnownHostsSshHostKeyValidator (OpenSSH known_hosts).
EnvironmentVariables is optional. When provided, values are applied through a POSIX-shell bootstrap command (export KEY='value') before InitialCommand.
Environment-variable validation rules:
- Name must match
[A-Za-z_][A-Za-z0-9_]*. - Value must be non-null.
- Value must not contain
CR,LF, orNUL.
1c.0 Advanced SSH configuration surface (proxy, forwarding, policy)
options = options with
{
Proxy = new SshProxyOptions(
Type: SshProxyType.Socks5,
Host: "proxy.example.com",
Port: 1080,
Username: "proxy-user",
Password: "proxy-password"),
PortForwardings =
[
new SshPortForwardOptions(
Mode: SshPortForwardMode.Local,
BindAddress: "127.0.0.1",
SourcePort: 15432,
DestinationHost: "db.internal",
DestinationPort: 5432),
],
Policy = new SshPolicyOptions(
KeepAliveIntervalSeconds: 30,
ConnectTimeoutSeconds: 15),
};
X11 has an options surface (SshX11Options) but is currently not supported by the SSH.NET backend transport and will throw when enabled.
1c.1 Reuse SSH Bootstrap Composition in Custom Backends (Rebex, etc.)
If you run SSH with a custom backend instead of RoyalTerminal.Terminal.Transport.Ssh.SshNet, use the same bootstrap helper for consistent behavior:
using RoyalTerminal.Terminal;
SshTransportOptions options = /* build options */;
string? bootstrap = SshShellBootstrapCommandBuilder.Build(options);
if (!string.IsNullOrWhiteSpace(bootstrap))
{
// WriteLine to your custom shell channel (for example Rebex shell stream).
SendLineToRemoteShell(bootstrap);
}
You can also use the overload:
string? bootstrap = SshShellBootstrapCommandBuilder.Build(
initialCommand: "exec $SHELL -il",
environmentVariables: new Dictionary<string, string>
{
["LANG"] = "en_US.UTF-8",
});
1d. PTY with Custom Shell Profile
using RoyalTerminal.Avalonia.Controls;
using RoyalTerminal.Terminal;
IShellProfileCatalog shellCatalog = new DefaultShellProfileCatalog();
ShellProfile shellProfile = shellCatalog.GetDefaultProfile();
var terminal = new TerminalControl();
await terminal.StartSessionAsync(
new PtyTransportOptions(
Command: shellProfile.Command,
WorkingDirectory: Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
Environment: null,
Dimensions: new TerminalSessionDimensions(120, 40, 1200, 800)));
1e. SSH Credential Provider Injection
using System;
using System.Threading;
using RoyalTerminal.Avalonia.Controls;
using RoyalTerminal.Avalonia.Services;
using RoyalTerminal.Terminal;
using RoyalTerminal.Terminal.Services;
using RoyalTerminal.Terminal.Transport.Ssh;
using RoyalTerminal.Terminal.Transport.Ssh.SshNet;
sealed class RuntimeCredentialProvider : ISshCredentialProvider
{
public ValueTask<SshResolvedCredentials> ResolveAsync(
SshCredentialRequest request,
CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
return ValueTask.FromResult(
new SshResolvedCredentials(
Password: Environment.GetEnvironmentVariable("SSH_PASSWORD"),
PrivateKeyPemOrPath: Array.Empty<string>(),
UseAgent: false));
}
}
var terminal = new TerminalControl(
new TerminalSessionService(),
new DefaultTerminalInputAdapter(),
new DefaultTerminalSelectionService(),
new DefaultTerminalScrollService(),
new DefaultVtProcessorFactory(),
new DefaultPtyFactory(),
new RuntimeCredentialProvider(),
new KnownHostsSshHostKeyValidator(),
transportFactory: null);
1f. Protected SSH Secret Store (Cross-Platform Default)
using System;
using RoyalTerminal.Terminal;
ISshSecretStore secretStore = SshSecretProtectionFactory.CreateDefaultSecretStore();
await secretStore.SaveSecretAsync("ssh/password", "my-password");
await secretStore.SaveSecretAsync("ssh/key/main", "/home/user/.ssh/id_ed25519");
ISshCredentialProvider credentialProvider = new SecretStoreSshCredentialProvider(secretStore);
1g. Session Profiles and Persistent Settings
using RoyalTerminal.Avalonia.Controls;
using RoyalTerminal.Terminal;
TerminalSessionProfilesDocument profiles = new()
{
Profiles =
[
new TerminalSessionProfile
{
Id = "dev-ssh",
DisplayName = "Dev SSH",
Transport = new TerminalSessionTransportProfile
{
TransportId = TerminalTransportIds.Ssh,
Ssh = new TerminalSessionSshSettings
{
Host = "example.com",
Port = 22,
Username = "alice",
Authentication = new TerminalSessionSshAuthenticationSettings
{
UsePassword = true,
PasswordSecretId = "ssh/dev/password",
},
},
},
},
],
};
ITerminalSessionProfileStore store = TerminalSessionProfileStoreFactory.CreateDefault();
await store.SaveAsync(profiles);
TerminalSessionProfilesDocument loaded = await store.LoadAsync();
ITerminalTransportOptions options = TerminalSessionProfileMapper.ToTransportOptions(loaded.Profiles[0]);
var terminal = new TerminalControl();
await terminal.StartSessionAsync(options);
1h. Embed in Windows Forms
Use Avalonia.Win32.Interoperability from a Windows Forms project and start Avalonia once with SetupWithoutStarting() before creating the host control. TerminalControl.Padding is the supported embedded-margin API; it defaults to 0 for existing layouts, and 8 works well when the control is hosted edge-to-edge in a form.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net10.0-windows7.0</TargetFramework>
<UseWindowsForms>true</UseWindowsForms>
<EnableWindowsTargeting>true</EnableWindowsTargeting>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Avalonia.Desktop" />
<PackageReference Include="Avalonia.Themes.Fluent" />
<PackageReference Include="Avalonia.Win32.Interoperability" />
<PackageReference Include="RoyalApps.RoyalTerminal.Avalonia" />
</ItemGroup>
</Project>
using Avalonia;
using Avalonia.Themes.Fluent;
using Avalonia.Win32.Interoperability;
using RoyalTerminal.Avalonia.Controls;
using WinForms = System.Windows.Forms;
WinForms.Application.SetHighDpiMode(WinForms.HighDpiMode.PerMonitorV2);
WinForms.Application.EnableVisualStyles();
WinForms.Application.SetCompatibleTextRenderingDefault(false);
AppBuilder.Configure<App>()
.UsePlatformDetect()
.SetupWithoutStarting();
TerminalControl terminal = new()
{
Padding = new Thickness(8),
};
WinFormsAvaloniaControlHost host = new()
{
Dock = WinForms.DockStyle.Fill,
Content = terminal,
};
For focus interop, handle WM_SETFOCUS on the WinForms host and post the Avalonia focus call at input priority. Avoid focus work from WM_KILLFOCUS; Windows is already moving focus at that point.
private const int WmSetFocus = 0x0007;
protected override void WndProc(ref WinForms.Message m)
{
base.WndProc(ref m);
if (m.Msg == WmSetFocus)
{
Dispatcher.UIThread.Post(
() => terminal.Focus(),
DispatcherPriority.Input);
}
}
For DPI, keep TerminalFontSize in Avalonia device-independent pixels. Avalonia top-level RenderScaling is forwarded automatically by TerminalControl; a WinForms host should also forward DeviceDpi / 96.0 after parent DPI changes so native or external render endpoints implementing ITerminalScaleSink receive physical scale through SetContentScale.
protected override void OnDpiChangedAfterParent(EventArgs e)
{
base.OnDpiChangedAfterParent(e);
double scale = DeviceDpi > 0 ? DeviceDpi / 96d : 1d;
Dispatcher.UIThread.Post(
() => terminal.SetContentScale(scale, scale),
DispatcherPriority.Input);
}
See samples/RoyalTerminal.WinFormsHost for a complete Windows-only sample targeting net10.0-windows7.0.
2. Core Control with Native VT Provider (official libghostty-vt)
using RoyalTerminal.Avalonia.Controls;
using RoyalTerminal.Avalonia.Services;
using RoyalTerminal.Terminal;
using RoyalTerminal.Terminal.Services;
var terminal = new TerminalControl(
new TerminalSessionService(),
new DefaultTerminalInputAdapter(),
new DefaultTerminalSelectionService(),
new DefaultTerminalScrollService(),
new DefaultVtProcessorFactory([new GhosttyVtProcessorProvider()]),
new DefaultPtyFactory())
{
VtProcessorPreference = VtProcessorPreference.Native,
};
terminal.StartPty();
3. Core Control with Explicit Managed VT (BasicVtProcessor)
using RoyalTerminal.Avalonia.Controls;
using RoyalTerminal.Terminal;
var terminal = new TerminalControl
{
VtProcessorPreference = VtProcessorPreference.Managed,
};
terminal.StartPty();
4. Attach a Custom Endpoint (ITerminalEndpoint)
using System;
using System.Text;
using RoyalTerminal.Avalonia.Controls;
using RoyalTerminal.Terminal;
sealed class LoggingEndpoint : ITerminalEndpoint
{
public void SendText(ReadOnlySpan<byte> utf8)
=> Console.WriteLine($"INPUT: {Encoding.UTF8.GetString(utf8)}");
public void SetFocus(bool focused)
=> Console.WriteLine($"FOCUS: {focused}");
public void SetSize(int widthPx, int heightPx)
=> Console.WriteLine($"SIZE: {widthPx}x{heightPx}");
}
var terminal = new TerminalControl();
terminal.AttachEndpoint(new LoggingEndpoint());
terminal.SendInput("echo hello\n");
terminal.DetachEndpoint();
TerminalControl supports two endpoint integration levels:
ITerminalEndpointonly (byte-stream integration): fallback key/text/mouse/focus VT sequences are written throughSendText(...).ITerminalEndpoint+ITerminalInputSink(full-fidelity event integration): structured key/text/pointer events are passed directly to your backend.
4a. Comprehensive Custom SSH Endpoint Setup (Rebex or Other SSH SDKs)
Use this model when you own the SSH channel/session lifecycle and only need TerminalControl for VT parsing, rendering, and input mapping:
using System;
using RoyalTerminal.Avalonia.Controls;
using RoyalTerminal.Terminal;
sealed class ByteStreamSshEndpoint : ITerminalEndpoint
{
private readonly Action<byte[]> _sendBytes;
private readonly Action<bool> _setRemoteFocus;
private readonly Action<int, int> _setRemoteSizePx;
public ByteStreamSshEndpoint(
Action<byte[]> sendBytes,
Action<bool> setRemoteFocus,
Action<int, int> setRemoteSizePx)
{
_sendBytes = sendBytes;
_setRemoteFocus = setRemoteFocus;
_setRemoteSizePx = setRemoteSizePx;
}
public void SendText(ReadOnlySpan<byte> utf8) => _sendBytes(utf8.ToArray());
public void SetFocus(bool focused) => _setRemoteFocus(focused);
public void SetSize(int widthPx, int heightPx) => _setRemoteSizePx(widthPx, heightPx);
}
var terminal = new TerminalControl
{
VtProcessorPreference = VtProcessorPreference.Managed,
};
var endpoint = new ByteStreamSshEndpoint(
sendBytes: bytes => SendBytesToSshShell(bytes), // your SSH write path
setRemoteFocus: focused => NotifySshFocus(focused), // optional backend-specific signal
setRemoteSizePx: (w, h) => NotifySshPixelSize(w, h)); // optional backend-specific signal
terminal.AttachEndpoint(endpoint);
// Route remote output to TerminalControl from any thread (network callback thread is fine).
OnSshData((buffer, length) =>
{
terminal.WriteOutput(buffer.AsSpan(0, length));
});
Behavior when using endpoint-only mode:
- Keyboard and text input are encoded to VT byte sequences and sent through
ITerminalEndpoint.SendText(...). - Mouse reporting sequences are sent through
SendText(...)when mouse modes are enabled (for example DECSET1000/1002/1003with SGR1006). - Focus in/out (
CSI I/CSI O) is sent throughSendText(...)when focus mode DECSET1004is enabled. WriteOutput(...)is safe from background callback threads;TerminalControlmarshals processing to the UI thread internally.- Mode state is normally learned from the stream you feed into
WriteOutput(...); if your backend tracks modes separately, expose them viaITerminalModeSource(and optionallyITerminalFocusEventModeSource).
If you implement ITerminalInputSink, fallback byte-sequence encoding is bypassed and your backend receives structured TerminalKeyEvent / TerminalPointerEvent.
5. Renderer Shaping Controls and Diagnostics
using RoyalTerminal.Avalonia.Controls;
using RoyalTerminal.Avalonia.Rendering;
var terminal = new TerminalControl();
terminal.StartPty();
if (terminal.Renderer is { } renderer)
{
renderer.EnableTextShaping = true;
renderer.TextDirectionMode = TextDirectionMode.Auto;
renderer.EnableLigatures = false;
renderer.EnableTextRenderDiagnostics = true;
TextRenderDiagnostics diagnostics = renderer.GetTextRenderDiagnostics(reset: true);
}
6. Direct Renderer Interop (No Avalonia Adapter)
using RoyalTerminal.Rendering.Contracts;
using RoyalTerminal.Rendering.Interop.Ghostty;
using var context = new GhosttyRenderContext();
using var surface = context.CreateSurface(RenderBackendKind.Software);
surface.SetSize(800, 600);
surface.SetScale(1.0, 1.0);
ulong frameToken = surface.BeginFrame();
byte[] rgba = new byte[800 * 600 * 4];
RenderFrameResult frame = surface.RenderToRgba(rgba, 800, 600, 800 * 4);
surface.EndFrame(frameToken);
Migration Guide
Existing PTY Users
TerminalControl.StartPty(...)is still supported and remains the compatibility API.TerminalControl.PtyandTerminalControl.HasPtystill work for PTY sessions.- VT response writeback now goes through session input routing, so no PTY-specific handling is required in callers.
Preferred New Session API
- New code should use
StartSessionAsync(...),StartPipeAsync(...), orStartSshAsync(...). - Query active session state with:
TerminalControl.HasActiveSessionTerminalControl.ActiveTransportId
API Name and Package Changes
- Core control rename:
GhosttyTerminalControl→TerminalControlGhosttyTerminalPresenter→TerminalPresenter
- VT selection moved from
UseNativeVtProcessortoVtProcessorPreference. - Legacy surface-coupled
ITerminalSurfacecontract was removed.
Installation
Backend-Neutral Setup (No Ghostty Dependency)
# Core Avalonia control + PTY + managed VT defaults
dotnet add package RoyalApps.RoyalTerminal.Avalonia
Optional Native VT for Core Control
# Native VT provider over official libghostty-vt bindings
dotnet add package RoyalApps.RoyalTerminal.Terminal.Vt.Ghostty
# Restore/publish for the RID you target so NuGet selects the matching native package.
dotnet publish -r osx-arm64
RoyalApps.RoyalTerminal.GhosttySharp and RoyalApps.RoyalTerminal.Rendering.Interop.Ghostty ship
runtime.json metadata that maps supported RIDs to the matching
RoyalApps.RoyalTerminal.GhosttySharp.Native.* package. Direct native package references
are only needed when you intentionally want to force a specific native asset
package.
Optional SSH Transport Packages
dotnet add package RoyalApps.RoyalTerminal.Terminal.Transport.Ssh.Abstractions
dotnet add package RoyalApps.RoyalTerminal.Terminal.Transport.Ssh.SshNet
# Optional SSH agent auth adapter
dotnet add package RoyalApps.RoyalTerminal.Terminal.Transport.Ssh.SshNet.Agent
If you integrate a custom SSH SDK (for example Rebex) via AttachEndpoint(...), you can skip SSH.NET packages and use:
dotnet add package RoyalApps.RoyalTerminal.Avalonia
dotnet add package RoyalApps.RoyalTerminal.Terminal
Modular Rendering Interop Setup
Use this when embedding the renderer interop pipeline directly (for TextureInterop or custom integrations):
dotnet add package RoyalApps.RoyalTerminal.Rendering.Contracts
dotnet add package RoyalApps.RoyalTerminal.Rendering.Interop.Ghostty
dotnet add package RoyalApps.RoyalTerminal.Shaders
dotnet add package RoyalApps.RoyalTerminal.Rendering.Skia
dotnet add package RoyalApps.RoyalTerminal.Rendering.Interop.Ghostty.Skia
dotnet add package RoyalApps.RoyalTerminal.Avalonia.Rendering.GhosttyInterop
For source builds or internal feeds, create the same package set with bash scripts/pack-nuget.sh --configuration Release --output artifacts --version <version>.
Codex SKILL
This repository includes a Codex skill:
skills/royalterminal-development
Install to Codex Skills Directory
From the repository root:
SKILL_NAME="royalterminal-development"
CODEX_SKILLS_DIR="${CODEX_HOME:-$HOME/.codex}/skills"
mkdir -p "$CODEX_SKILLS_DIR"
cp -R "skills/$SKILL_NAME" "$CODEX_SKILLS_DIR/$SKILL_NAME"
Install as Symlink (recommended while iterating on the skill)
SKILL_NAME="royalterminal-development"
CODEX_SKILLS_DIR="${CODEX_HOME:-$HOME/.codex}/skills"
mkdir -p "$CODEX_SKILLS_DIR"
ln -sfn "$(pwd)/skills/$SKILL_NAME" "$CODEX_SKILLS_DIR/$SKILL_NAME"
Verify Install
test -f "${CODEX_HOME:-$HOME/.codex}/skills/royalterminal-development/SKILL.md" && echo "skill installed"
Skill Entry Files
- Skill instructions:
skills/royalterminal-development/SKILL.md - Granular references:
skills/royalterminal-development/references/
Feature Comparison
| Capability | Native VT (TerminalControl) |
Managed VT (TerminalControl) |
Rendered (TerminalControl, Auto VT) |
|---|---|---|---|
| Package entry point | RoyalApps.RoyalTerminal.Avalonia + Terminal.Vt.Ghostty |
RoyalApps.RoyalTerminal.Avalonia |
RoyalApps.RoyalTerminal.Avalonia |
| Platform availability | macOS/Linux/Windows | macOS/Linux/Windows | macOS/Linux/Windows |
| VT engine | official libghostty-vt |
BasicVtProcessor |
auto-selects native VT when available, otherwise managed VT |
| Renderer path | Skia cell renderer | Skia cell renderer | Skia cell renderer |
Requires libghostty-vt |
Yes | No | Optional |
| Full Avalonia overlay support | Yes | Yes | Yes |
| Cross-platform mode | Yes | Yes | Yes |
| Demo fallback when unavailable | Routed to Managed VT then Rendered |
Routed to Rendered |
Always supported |
Rendering Interop Contract
RoyalTerminal.Rendering.Contracts defines the backend-neutral model:
RenderBackendKind:Software,Metal,Vulkan,D3D11,D3D12,OpenGLRenderTargetDescriptor: native handle carrier for one render target submissionRenderBackendCapabilities: supported features/sample counts/pixel formatsRenderFeatureFlags:ExternalTextureTargets,ExternalFramebufferTargets,CpuRgbaFallback,ExplicitFrameLifecycle, etc.
RoyalTerminal.Rendering.Interop.Ghostty.Skia (SkiaInteropRenderer) behavior:
- Validate descriptor.
- Attempt direct interop only when surface capabilities advertise the target kind (
ExternalTextureTargetsorExternalFramebufferTargets). - Fall back to CPU RGBA path when direct interop is unavailable or fails and fallback is enabled.
Native Renderer C API (ghostty-renderer-capi)
native/ghostty-renderer-capi exports:
- Context/surface lifecycle
- Surface configuration (
set_size,set_scale,set_focus,set_color_scheme) - Explicit frame lifecycle (
begin_frame/end_frame) - Target validation and rendering (
validate_target,render_to_target) - CPU fallback rendering (
render_to_rgba)
Header: native/ghostty-renderer-capi/include/ghostty_renderer.h
Build directly:
cd native/ghostty-renderer-capi
bash build.sh release
bash build.sh test
Official VT Library (libghostty-vt)
libghostty-vt is now the only native VT engine used by RoyalTerminal. GhosttyVtProcessor
drives the upstream terminal and render-state APIs directly, with no custom standalone wrapper.
Build directly:
cd external/ghostty
zig build -Doptimize=ReleaseFast -Dapp-runtime=none
For distributable Windows x64 artifacts, build a scalar compatibility DLL with an explicit baseline CPU. This avoids AVX/VEX instructions in startup paths on older CPUs, constrained VMs, and Windows ARM64 x64 emulation:
.\scripts\build-native.ps1 -Arch x64 -Release
# or, from external/ghostty:
zig build -Doptimize=ReleaseFast -Dapp-runtime=none -Dtarget=x86_64-windows-msvc -Dcpu=x86_64-vzeroupper -Dsimd=false
CI verifies the Windows x64 native artifacts with scripts/verify-windows-x64-no-avx.ps1.
Install LLVM or pass the verifier an explicit -ObjdumpPath when running the
check locally.
Ghostty Submodule Status
RoyalTerminal now tracks upstream Ghostty directly in external/ghostty; the local
Ghostty fork patches and managed libghostty wrapper layer were removed. The
managed/native integration now targets upstream libghostty-vt directly, with
ghostty-renderer-capi retained only for optional render-target interop.
PTY Layer
| Package | Implementation |
|---|---|
RoyalApps.RoyalTerminal.Terminal.Pty.Unix |
UnixPty (forkpty, TIOCSWINSZ) |
RoyalApps.RoyalTerminal.Terminal.Pty.Windows |
WindowsPty (ConPTY) |
RoyalApps.RoyalTerminal.Terminal.Pty.Platform |
DefaultPtyFactory selector |
Native Library Resolution
Renderer interop (RoyalTerminal.Rendering.Interop.Ghostty) supports:
GHOSTTY_RENDERER_CAPI_LIBRARY_PATH(absolute file path)GHOSTTY_RENDERER_CAPI_LIBRARY_DIR(directory containing the library)
Probe order includes:
- Explicit env vars above
runtimes/<rid>/native/next to app base directoryruntimes/<rid>/native/next to assembly directory- Default OS loader paths
Native Libraries and Placement
| Platform | Files |
|---|---|
| macOS | libghostty-vt.dylib, libghostty-renderer-capi.dylib |
| Linux | libghostty-vt.so, libghostty-renderer-capi.so |
| Windows | ghostty-vt.dll, ghostty-renderer-capi.dll |
Primary runtime package locations:
src/RoyalTerminal.GhosttySharp.Native.OSX/runtimes/<rid>/native/src/RoyalTerminal.GhosttySharp.Native.Linux64/runtimes/<rid>/native/src/RoyalTerminal.GhosttySharp.Native.Win64/runtimes/<rid>/native/
Package consumers normally do not reference those native packages directly.
RID-aware restore/publish resolves them from the runtime.json files in
RoyalApps.RoyalTerminal.GhosttySharp and RoyalApps.RoyalTerminal.Rendering.Interop.Ghostty.
Project Structure
RoyalTerminal/
├── Directory.Build.props
├── Directory.Packages.props
├── RoyalTerminal.sln
├── native/
│ └── ghostty-renderer-capi/
├── src/
│ ├── RoyalTerminal.GhosttySharp/
│ ├── RoyalTerminal.Avalonia/
│ ├── RoyalTerminal.Avalonia.Settings/
│ ├── RoyalTerminal.Avalonia.Rendering.GhosttyInterop/
│ ├── RoyalTerminal.Terminal/
│ ├── RoyalTerminal.Unicode/
│ ├── RoyalTerminal.Sixel/
│ ├── RoyalTerminal.Terminal.Vt.Managed/
│ ├── RoyalTerminal.Terminal.Vt.Ghostty/
│ ├── RoyalTerminal.Terminal.Vt.Default/
│ ├── RoyalTerminal.Terminal.Pty.Unix/
│ ├── RoyalTerminal.Terminal.Pty.Windows/
│ ├── RoyalTerminal.Terminal.Pty.Platform/
│ ├── RoyalTerminal.Terminal.Transport.Pty/
│ ├── RoyalTerminal.Terminal.Transport.Pipe/
│ ├── RoyalTerminal.Terminal.Transport.Raw/
│ ├── RoyalTerminal.Terminal.Transport.Telnet/
│ ├── RoyalTerminal.Terminal.Transport.Serial/
│ ├── RoyalTerminal.Terminal.Transport.Ssh.Abstractions/
│ ├── RoyalTerminal.Terminal.Transport.Ssh.SshNet/
│ ├── RoyalTerminal.Terminal.Transport.Ssh.SshNet.Agent/
│ ├── RoyalTerminal.Terminal.Services.Contracts/
│ ├── RoyalTerminal.Terminal.Services/
│ ├── RoyalTerminal.Rendering.Text/
│ ├── RoyalTerminal.Shaders/
│ ├── RoyalTerminal.Rendering.Contracts/
│ ├── RoyalTerminal.Rendering.Interop.Ghostty/
│ ├── RoyalTerminal.Rendering.Skia/
│ ├── RoyalTerminal.Rendering.Interop.Ghostty.Skia/
│ ├── RoyalTerminal.GhosttySharp.Native.OSX/
│ ├── RoyalTerminal.GhosttySharp.Native.Linux64/
│ └── RoyalTerminal.GhosttySharp.Native.Win64/
├── samples/RoyalTerminal.Demo/
├── samples/RoyalTerminal.WinFormsHost/
├── samples/RoyalTerminal.MacNativeTabbed/
├── samples/RoyalTerminal.ControlCatalog/
├── tests/
│ ├── RoyalTerminal.Benchmarks/
│ ├── RoyalTerminal.Tests/
│ └── RoyalTerminal.IntegrationTests/
└── scripts/
├── build-native.sh
└── build-native.ps1
Building
Prerequisites
- .NET 10 SDK
- Zig 0.15.2+
- Ghostty submodule:
git submodule update --init --recursive
Windows-specific prerequisite:
- Symlink creation must be available for Zig package extraction.
- Enable Developer Mode (
start ms-settings:developers) or run PowerShell as Administrator. scripts/build-native.ps1uses a repository-local Zig global cache at.zig-global-cacheto avoid stale user-level cache state.
Build Native + Managed
# macOS/Linux
bash scripts/build-native.sh --release
# Windows
pwsh scripts/build-native.ps1 -Release
# Managed build
dotnet build RoyalTerminal.sln -c Release
Run Demo
dotnet run --project samples/RoyalTerminal.Demo
Run Control Catalog CLI
dotnet run --project samples/RoyalTerminal.ControlCatalog
Optional demo toggles:
ROYALTERMINAL_DISABLE_TEXT_SHAPING=1 \
ROYALTERMINAL_ENABLE_RENDER_DIAGNOSTICS=1 \
dotnet run --project samples/RoyalTerminal.Demo
Run macOS Native SwiftUI Sample
swift run --package-path samples/RoyalTerminal.MacNativeTabbed
Testing
# Full test pass
dotnet test RoyalTerminal.sln -c Release
# Rendering-focused tests
dotnet test tests/RoyalTerminal.Tests/RoyalTerminal.Tests.csproj -c Release --filter "RenderingInteropTests|RenderingSkiaInteropTests|RenderingAvaloniaAdapterTests|RenderingContractsTests"
# Optional SSH transport integration tests (env-gated)
ROYALTERMINAL_IT_SSH_HOST=127.0.0.1 \
ROYALTERMINAL_IT_SSH_PORT=22 \
ROYALTERMINAL_IT_SSH_USERNAME=test-user \
ROYALTERMINAL_IT_SSH_PASSWORD=secret \
ROYALTERMINAL_IT_SSH_HOST_KEY_SHA256=SHA256:your-host-key-fingerprint \
dotnet test tests/RoyalTerminal.IntegrationTests/RoyalTerminal.IntegrationTests.csproj -c Release --filter "SshTransportIntegrationTests"
# Optional key-based auth for SSH integration tests
# ROYALTERMINAL_IT_SSH_PRIVATE_KEY can contain either a PEM payload or a private-key file path.
Ncurses Harness (Manual Mouse/Keyboard/Resize Validation)
The ncurses harness fixture lives at:
tests/RoyalTerminal.Tests/Fixtures/NcursesHarness.py
Important: it requires RT_HARNESS_LOG. If this variable is missing, the script exits immediately.
Run it manually:
RT_HARNESS_LOG=/tmp/rt-harness.log \
RT_HARNESS_TIMEOUT_SEC=300 \
TERM=xterm-256color \
python3 tests/RoyalTerminal.Tests/Fixtures/NcursesHarness.py
While it is running:
- Press keys (logs
KEY ...) - Click/scroll in the terminal viewport (logs
MOUSE ...) - Resize window (logs
RESIZE ...) - Press
qto quit (logsEXIT quit)
Watch events from another terminal:
tail -f /tmp/rt-harness.log
If a previous app (for example mc) is suspended, resume/terminate it before running the harness:
jobs
fg %1 # or: kill %1
Current baseline in this repository:
- Unit tests: 369 passed
- Integration tests: 47 passed
Performance baseline harness:
dotnet run --project tests/RoyalTerminal.Benchmarks/RoyalTerminal.Benchmarks.csproj -c Release -- --output /tmp/royalterminal-render-baseline.md
API Coverage
libghostty-vt
| Category | Status |
|---|---|
| Terminal lifecycle/process/resize/reset | Implemented |
| Render-state lifecycle and dirty tracking | Implemented |
| Row/cell/grapheme iteration via render state | Implemented |
| Default/palette color APIs | Implemented |
| Mode queries, focus, size-report, formatter, key/mouse helpers | Implemented |
ghostty-renderer-capi
| Category | Status |
|---|---|
| Context/surface lifecycle | Implemented |
| Target validation + render-to-target | Implemented |
| CPU RGBA fallback | Implemented |
| Backend descriptors (Metal/Vulkan/D3D11/D3D12/OpenGL/Software) | Implemented end-to-end (validation + direct-target render dispatch + CPU fallback) |
Notes
ReactiveUI,ReactiveUI.Avalonia, andReactiveUI.SourceGeneratorsare used by the demo app only.- Library packages remain framework/service oriented and avoid app-level ReactiveUI dependencies.
License
Acknowledgements
Learn more about Target Frameworks and .NET Standard.
This package has no dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.