crosspty

package module
v0.0.0-...-3f7b12a Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 28, 2026 License: BSD-3-Clause Imports: 11 Imported by: 0

README

CrossPTY

CrossPTY is a Go library providing a cross-platform pseudo-terminal (PTY) interface with built-in process lifetime management.

Usage

This library unifies PTY interactions across Unix and Windows into a single interface.

Documentation: The core API definitions, configuration options, and detailed behaviors are documented in the comments within pty.go.

Simple Example
func main() {
	// Configure the command
	// See pty.go for details on Env, Dir, and Size configuration.
	cmd := crosspty.CommandConfig{
		Argv: []string{"/bin/bash"}, // Use "cmd.exe" or "powershell.exe" on Windows
	}
	// Start the PTY
	p, err := crosspty.Start(cmd)
	if err != nil {
		panic(err)
	}
	// Close ensures the process is terminated (gracefully first, then forcefully)
	defer p.Close()
	// Pty implements io.ReadWriter
	go io.Copy(p, os.Stdin)
	io.Copy(os.Stdout, p)
}

Compatibility

Unix-like Systems

CrossPTY uses creack/pty for its Unix implementation. It supports any Linux kernel configured with UNIX 98 pseudo-terminal support (CONFIG_UNIX98_PTYS=y) and the /dev/ptmx device.

Windows

CrossPTY uses ConPTY API, which requires Windows 10 October 2018 Update (version 1809) or Windows Server 2019 or later.

Credit

Unix implementation uses creack/pty.

Windows implementation is refactored and derived from:

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrUnacceptableTimeout = errors.New("crosspty: unacceptable timeout or delay")
	ErrKillTimeout         = errors.New("crosspty: kill process timeout")

	// Your Windows version does not support the ConPTY feature
	ErrConPTYNotSupported = errors.New("crosspty: ConPTY not supported at this OS")
)

Functions

func ApplyEnvFallbackAndInject

func ApplyEnvFallbackAndInject(Env []string, Fallback, Inject map[string]string) (New []string)

func Oneshot

func Oneshot(cc CommandConfig) (buf []byte, err error)

A simple helper that runs the command once and collects all output. Note: Close errors are ignored.

Types

type CloseConfig

type CloseConfig struct {
	// Total timeout for Close().
	// Must be at least 1 second longer than ForceKillDelay.
	// default: 10s
	CloseTimeout time.Duration

	// Delay before attempting to force kill the process.
	// default: 5s
	ForceKillDelay time.Duration

	// Unix only.
	// default: SIGKILL
	ForceKillSignal syscall.Signal
}

type CommandConfig

type CommandConfig struct {
	// e.g. []string{"/usr/bin/bash", "-i"}
	//  - Recommend using an absolute full path as argv0.
	//  - If argv0 is relative, it is resolved relative to Dir.
	//  - If and only if argv0 contains no path separators, a exec.LookPath() is performed.
	//  - (Windows only) If exec.LookPath() performed, it will try to add a missing file
	//    extension. This is only recommended for .exe files. If you want to use
	//    .cmd/.bat, please use []string{"cmd.exe", "/C", "C:\\full\\foo.bat"}.
	Argv []string

	// default: os.Getwd()
	// Working dir. Recommend using an absolute full path. If it is relative,
	// it is resolved relative to Dir.
	// Will also generate a `PWD` EnvInject if no such one unless EnvInject is explicit empty.
	Dir string

	// default: os.Environ()
	// If you want an empty environment (not default), use []string{}.
	Env []string

	// default: {"TERM": "vt100"}
	// Will be insert to Env if not set.
	// Windows also have a `SYSTEMROOT` fallback by default.
	EnvFallback map[string]string

	// default: PWD (Unix) or Empty (Windows)
	// Overwrite Env.
	// Use {"A": ""} to delete key "A".
	EnvInject map[string]string

	// default: 24x80
	Size TermSize
}

func NormalizeCommandConfig

func NormalizeCommandConfig(cc_ CommandConfig) (cc CommandConfig, err error)

Safe for repeated calls with the same value

type Pty

type Pty interface {
	// After the process dies, Write may or may not return an error, but it will not panic.
	// After the process dies, Write generally will not block, but MIGHT BLOCK when write too much.
	// You MUST NOT write after Close().
	// Thread-safe.
	//
	// For Windows:
	// Your sub-process may have an ENABLE_LINE_INPUT by default. Which means
	// They need a "\r\n" to read what you write.
	Write(d []byte) (n int, err error)

	// If there is nothing to read and the fd is closed (usually, process dead), return io.EOF.
	// After the fd is closed, you can still read remaining data (if any).
	// You MUST NOT read after Close().
	// Thread-safe.
	Read(d []byte) (n int, err error)

	// Not thread-safe. Can be called multiple times.
	// Safe to call concurrently with other funcs except Close().
	// You should call this before Close().
	SetCloseConfig(CloseConfig) error

	// Kill sub-process and wait for it to die (with timeout), freeing resources.
	// Will attempt graceful termination first (SIGHUP, CTRL_CLOSE_EVENT).
	// Close() will not wait for Read/Write to finish; it may interrupt ongoing r/w.
	// Thread-safe. Can be called multiple times.
	Close() error

	// Wait for the sub-process to exit.
	//  - If you do not read, the process may not exit (buffer full).
	//  - A successful Close() will stop the wait.
	//  - Wait() will exit immediately if a previous Wait() has already exited.
	//  - It is idempotent and will return the same exit code.
	//  - You still need to call Close() after Wait() to free resources.
	//  - You do not have to call Wait() if you do not care about the exit code or process state.
	//  - Thread-safe. Can be called multiple times from multiple goroutines.
	//  - Returns the sub-process exit code (-1 means N/A, e.g., killed by signal).
	//
	// For Windows:
	//  - Wait() may also return -1 when exit code could not be retrieved or unable to wait
	//    (permission issues, etc.), in that case, sub-process may still running.
	Wait() int

	// Thread-safe.
	Pid() int

	// Best-effort thread-safe. you MUST NOT setSize after Close().
	// Windows will resend the whole screen when resize.
	SetSize(sz TermSize) error
}

Pty represents a pseudo-terminal session. It also manages the lifetime of a process attached to a pseudo-terminal. Remember to handle escape sequences in the output.

R/W concurrently: Read and Write can safely occur concurrently. Write concurrently will same as Write concurrently an os.File, should safe. Read too.

Encoding: In Unix, depends on you Env and config. At most case it's UTF-8. In Windows, ConPTY will speak UTF-8 with you in theory.

func Start

func Start(cc CommandConfig) (Pty, error)

func StartExecCmd

func StartExecCmd(cmd *exec.Cmd, sz TermSize) (Pty, error)

Unix only. You do not need to, and MUST NOT, set setpgid. Use this function only if you know exactly what you are doing.

type TermSize

type TermSize struct {
	Rows uint16 // Number of rows (in cells).
	Cols uint16 // Number of columns (in cells).
	X    uint16 // Width in pixels (optional, unix only).
	Y    uint16 // Height in pixels (optional, unix only).
}

Directories

Path Synopsis
example
passthrough command
internal

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL