Porta.Pty

February 20, 2026 ยท View on GitHub

A cross-platform pseudoterminal (PTY) library for .NET that enables spawning and interacting with terminal processes on Windows, Linux, and macOS.

Windows Linux macOS NuGet Version License: MIT

Features

  • Cross-Platform Support: Works on Windows (using ConPTY), Linux, and macOS
  • Simple API: Easy-to-use async API for spawning terminal processes
  • Full PTY Control: Read/write streams, resize terminal, handle process exit events
  • Unicode Support: Full UTF-8 support including complex characters
  • Native PTY Shim: Includes a native C library to avoid .NET runtime permissioning issues with fork() on Linux/macOS
  • .NET Standard 2.0: Compatible with .NET Core 2.0+, .NET 5+, and .NET Framework 4.6.1+

Installation

Install via NuGet Package Manager:

dotnet add package Porta.Pty

Or via the Package Manager Console in Visual Studio:

Install-Package Porta.Pty

Usage

Basic Example

using Porta.Pty;
using System.Text;

// Configure the terminal options
var options = new PtyOptions
{
    Name = "MyTerminal",
    Cols = 120,
    Rows = 30,
    Cwd = Environment.CurrentDirectory,
    App = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) 
        ? Path.Combine(Environment.SystemDirectory, "cmd.exe") 
        : "/bin/bash",
    Environment = new Dictionary<string, string>
    {
        { "MY_VAR", "value" }
    }
};

// Spawn the terminal process
using IPtyConnection terminal = await PtyProvider.SpawnAsync(options, CancellationToken.None);

// Handle process exit
terminal.ProcessExited += (sender, e) => 
{
    Console.WriteLine($"Terminal exited with code: {e.ExitCode}");
};

// Write to the terminal
byte[] command = Encoding.UTF8.GetBytes("echo Hello World\r");
await terminal.WriterStream.WriteAsync(command, 0, command.Length);
await terminal.WriterStream.FlushAsync();

// Read from the terminal
byte[] buffer = new byte[4096];
int bytesRead = await terminal.ReaderStream.ReadAsync(buffer, 0, buffer.Length);
string output = Encoding.UTF8.GetString(buffer, 0, bytesRead);
Console.WriteLine(output);

// Resize the terminal
terminal.Resize(80, 24);

PtyOptions Properties

PropertyTypeDescription
Namestring?Optional terminal name
ColsintInitial number of columns
RowsintInitial number of rows
CwdstringWorking directory for the process
AppstringPath to the executable to spawn
CommandLinestring[]Command line arguments
VerbatimCommandLineboolIf true, arguments are not quoted
EnvironmentIDictionary<string, string>Environment variables (empty value removes the variable)

IPtyConnection Interface

MemberDescription
ReaderStreamStream for reading output from the terminal
WriterStreamStream for writing input to the terminal
PidProcess ID of the terminal process
ExitCodeExit code (available after process exits)
ProcessExitedEvent fired when the process exits
Resize(cols, rows)Resize the terminal dimensions
Kill()Immediately terminate the process
WaitForExit(ms)Wait for process exit with timeout

Architecture

flowchart TB
    subgraph Entry["Entry Point"]
        PtyProvider["PtyProvider<br/>(Static class)"]
    end

    subgraph Platform["PlatformServices"]
        direction LR
        WinProvider["Windows<br/>Provider"]
        LinuxProvider["Linux<br/>Provider"]
        MacProvider["macOS<br/>Provider"]
    end

    subgraph Connections["Platform Connections"]
        direction LR
        PseudoConsole["PseudoConsole<br/>Connection<br/>(ConPTY API)"]
        LinuxPty["Unix PTY<br/>Connection<br/>(forkpty)"]
        MacPty["Unix PTY<br/>Connection<br/>(forkpty)"]
    end

    PtyProvider -->|"SpawnAsync(PtyOptions)"| Platform
    WinProvider --> PseudoConsole
    LinuxProvider --> LinuxPty
    MacProvider --> MacPty

    PseudoConsole -->|"implements"| IPtyConnection["IPtyConnection"]
    LinuxPty -->|"implements"| IPtyConnection
    MacPty -->|"implements"| IPtyConnection

Platform Implementations

Windows

  • Uses the Windows ConPTY (Pseudo Console) API introduced in Windows 10 1809
  • Leverages CreatePseudoConsole, ResizePseudoConsole, and ClosePseudoConsole native functions
  • Process isolation via Windows Job Objects for clean process termination
  • Implements proper cleanup order per Microsoft documentation

Linux & macOS

  • Uses POSIX PTY functions (forkpty, openpty) via a native C shim library
  • Sets appropriate terminal environment variables (TERM=xterm-256color)
  • Clears conflicting environment variables (TMUX, screen sessions)

Native PTY Shim

On Linux and macOS, Porta.Pty includes a native C shim library (libporta_pty) that performs the forkpty() + execvp() sequence entirely in native code.

This is necessary because starting with .NET 7, the runtime enables W^X (Write XOR Execute) memory protection by default. W^X prevents memory pages from being both writable and executable simultaneously โ€” a security hardening measure. This conflicts with fork() when running managed code in the forked child process, since the .NET runtime's JIT-compiled code and memory layout can violate the W^X invariant in the child.

By delegating the fork+exec to native C code, Porta.Pty avoids running any managed .NET code in the forked child process, completely eliminating the W^X conflict. The native shim libraries are bundled in the NuGet package for each supported platform (linux-x64, linux-arm64, osx-x64, osx-arm64) and are loaded automatically at runtime.

Key Components

ComponentDescription
PtyProviderStatic class providing the SpawnAsync entry point
PtyOptionsConfiguration class for terminal spawning
IPtyConnectionInterface representing an active terminal connection
IPtyProviderInternal interface implemented by platform-specific providers
PlatformServicesPlatform detection and provider selection logic

Dependencies

  • Vanara.PInvoke.Kernel32: Windows API P/Invoke bindings
  • Mono.Posix.NETStandard: POSIX API bindings for Unix platforms

License

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

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.