creack/pty Go Package

repository·master·Indexed 24 days ago

https://github.com/creack/pty

A Go package for managing Unix pseudo-terminals (PTY), enabling developers to run commands within a terminal environment and interact with them via standard I/O. It provides functionality to start commands with PTYs using Start, StartWithSize, and StartWithAttrs, as well as tools for managing terminal dimensions through Winsize, Setsize, Getsize, and InheritSize.

Tokens
2.4K
Snippets
7
Records
18
Agent score
79%

What's inside creack/pty

  1. Implement an interactive shell with PTY

    master

    To create a fully interactive shell experience, you must handle three key aspects:

    1. PTY Lifecycle: Start the command with pty.Start(c) and ensure the PTY is closed when finished.
    2. Terminal Resizing: Listen for syscall.SIGWINCH signals and use pty.InheritSize(os.Stdin, ptmx) to synchronize the PTY size with the user's terminal window.
    3. Raw Mode: Put the local terminal (os.Stdin) into raw mode using golang.org/x/term.MakeRaw so that keystrokes are passed directly to the PTY without being processed by the local terminal's line discipline. Remember to restore the terminal state using term.Restore when finished.

    Finally, bridge the input and output by copying os.Stdin to the PTY and the PTY to os.Stdout.

    package main
    
    import (
            "io"
            "log"
            "os"
            "os/exec"
            "os/signal"
            "syscall"
    
            "github.com/creack/pty"
            "golang.org/x/term"
    )
    
    func test() error {
            // Create arbitrary command.
            c := exec.Command("bash")
    
            // Start the command with a pty.
            ptmx, err := pty.Start(c)
            if err != nil {
                    return err
            }
            // Make sure to close the pty at the end.
            defer func() { _ = ptmx.Close() }() // Best effort.
    
            // Handle pty size.
            ch := make(chan os.Signal, 1)
            signal.Notify(ch, syscall.SIGWINCH)
            go func() {
                    for range ch {
                            if err := pty.InheritSize(os.Stdin, ptmx); err != nil {
                                    log.Printf("error resizing pty: %s", err)
                            }
                    }
            }()
            ch <- syscall.SIGWINCH // Initial resize.
            defer func() { signal.Stop(ch); close(ch) }() // Cleanup signals when done.
    
            // Set stdin in raw mode.
            oldState, err := term.MakeRaw(int(os.Stdin.Fd()))
            if err != nil {
                    panic(err)
            }
            defer func() { _ = term.Restore(int(os.Stdin.Fd()), oldState) }() // Best effort.
    
            // Copy stdin to the pty and the pty to stdout.
            // NOTE: The goroutine will keep reading until the next keystroke before returning.
            go func() { _, _ = io.Copy(ptmx, os.Stdin) }()
            _, _ = io.Copy(os.Stdout, ptmx)
    
            return nil
    }
    
    func main() {
            if err := test(); err != nil {
                    log.Fatal(err)
            }
    }
  2. Start a command with a pseudo-terminal

    master

    Use pty.Start(c) to start an exec.Command within a new pseudo-terminal. This returns an io.ReadWriteCloser that represents the PTY. You can write input to this object to interact with the command and read its output from it.

    package main
    
    import (
    	"io"
    	"os"
    	"os/exec"
    
    	"github.com/creack/pty"
    )
    
    func main() {
    	c := exec.Command("grep", "--color=auto", "bar")
    	f, err := pty.Start(c)
    	if err != nil {
    		panic(err)
    	}
    
    	go func() {
    		f.Write([]byte("foo\n"))
    		f.Write([]byte("bar\n"))
    		f.Write([]byte("baz\n"))
    		f.Write([]byte{4}) // EOT
    	}()
    	io.Copy(os.Stdout, f)
    }
  3. Synchronize PTY size with terminal window

    master
    Use pty.InheritSize(os.Stdin, ptmx) to resize the pseudo-terminal (ptmx) to match the dimensions of the controlling terminal (os.Stdin). This is typically triggered by listening for syscall.SIGWINCH signals.
  4. Synchronize terminal window sizes with InheritSize()

    master

    Use InheritSize(pty, tty) to apply the window size of a pseudo-terminal (pty) to a controlling terminal (tty).

    To ensure the tty automatically resizes whenever the pty receives a window size change notification, you should call InheritSize within a signal handler for syscall.SIGWINCH.

  5. Start a command with a pseudo-terminal using Start()

    master
    The Start function starts a given *exec.Cmd in a new session and assigns a pseudo-terminal (pty) to the command's Stdin, Stdout, and Stderr. It returns the *os.File representing the master side of the pty. This is the standard way to run a process that requires a terminal environment.
  6. Terminal window size functions on Windows

    master
    On Windows platforms, the pty package provides dummy implementations of terminal window size functions. These functions are included to ensure the package compiles, but they do not perform actual resizing or size retrieval. They will always return ErrUnsupported or nil with ErrUnsupported.
  7. Start a command with custom terminal size or attributes using StartWithAttrs()

    master

    The StartWithAttrs function allows for fine-grained control when starting a command. It can:

    1. Set a specific terminal size using a *Winsize pointer before starting the command.
    2. Override the command's SysProcAttr with a custom *syscall.SysProcAttr.

    This is useful for edge cases, such as creating a pty without a controlling terminal. If sz is provided, the pty is resized via Setsize before the command starts. If attrs is provided, it is assigned to c.SysProcAttr.

  8. Start a command with a specific pseudo-terminal size using StartWithSize()

    master

    The StartWithSize function initializes a pseudo-terminal (pty) and assigns it to the command's Stdin, Stdout, and Stderr. It resizes the pty to the dimensions provided in the Winsize pointer before starting the command.

    Crucially, this function automatically configures the command's SysProcAttr to start the process in a new session (Setsid = true) and set the controlling terminal (Setctty = true), which is standard behavior for terminal applications. It returns the *os.File representing the master side of the pty.

  9. Open a new PTY and TTY

    master
    Use Open() to create a new pseudo-terminal (pty) and its corresponding controlling terminal (tty). This function returns two *os.File pointers: one representing the PTY (which you can write to and read from to interact with a process) and one representing the TTY (the controlling terminal).