Skip to content

Use pty with no command? #153

@robinovitch61

Description

@robinovitch61

Thanks @creack for the great library and community support!

I have a question that I hope makes sense.

I am using a websocket api that sends stdin messages and receives stdout and stderr output from the command as well as exit codes.

For example, this API is used in a web UI, with the following string of messages sent and received
2022-06-25_09-35-14

This web UI uses xterm.js to provide a terminal-like input ui and to interpret the responses, including ansi escape sequences, into terminal output.

I am building a terminal application that would like to leverage this same API, so a "terminal-in-a-terminal" like thing, where stdin is sent to the API and responses received are rendered in my application.

I would like to use creack/pty as the response interpreter, handling ansi escape sequences and the like, and holding a view of the terminal session that I can read into a string and render to the screen of my application.

So the flow is roughly like:

  • stdin sent to websocket connection
  • response received
  • write response to pty
  • read entire pty to string
  • render string to screen
  • repeat

If I use creack/pty this way, I don't actually have a command to start - it's just a nice box that interprets ansi escape sequences for me and allows me to retrieve the current "string view" of the terminal.

Here is my attempt to get a command-less pty, write to it, and read from it:

package main

import (
	"bytes"
	"fmt"
	"github.com/creack/pty"
	"io"
	"os"
)

func getPtyWithoutCommand() (*os.File, error) {
	// this function just pty.StartWithAttrs with command-specific stuff commented out
	pty, tty, err := pty.Open()
	if err != nil {
		return nil, err
	}
	defer func() { _ = tty.Close() }() // Best effort.

	// if sz != nil {
	// 	if err := Setsize(pty, sz); err != nil {
	// 		_ = pty.Close() // Best effort.
	// 		return nil, err
	// 	}
	// }
	// if c.Stdout == nil {
	// 	c.Stdout = tty
	// }
	// if c.Stderr == nil {
	// 	c.Stderr = tty
	// }
	// if c.Stdin == nil {
	// 	c.Stdin = tty
	// }
	//
	// c.SysProcAttr = attrs
	//
	// if err := c.Start(); err != nil {
	// 	_ = pty.Close() // Best effort.
	// 	return nil, err
	// }
	return pty, err
}

func main() {
	myPty, err := getPtyWithoutCommand()
	if err != nil {
		panic(err)
	}

	_, err = myPty.Write([]byte("test\n"))
	if err != nil {
		panic(err)
	}
	_, err = myPty.Write([]byte{4}) // EOT
	if err != nil {
		panic(err)
	}

	buf := new(bytes.Buffer)
	_, err = io.Copy(buf, myPty)
	if err != nil {
		panic(err)
	}
	fmt.Println(buf.String())
}

I get the following error

❯ go run test.go
panic: write /dev/ptmx: input/output error

goroutine 1 [running]:
main.main()
        test.go:52 +0x19c
exit status 2

Is what I'm trying to do sane at all? Is there a better way to achieve my goal here?

Thank you!
Leo

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions